Simple History - Version 3.0.0

Version Description

(January 2022) =

  • Fixed: Used wrong text domain for some strings in Limit Login Attempts logger.
  • Fixed: Post logger now ignores changes to the _encloseme meta key.
  • Fixed: Readme text loaded from GitHub repo is now filtered using wp_kses().
  • Fixed: Links in readme text loaded from GitHub repo now opens in new window/tab by default (instead of loading in the modal/thickbox iframe).
  • Added: Logger messages is shown when clicking number of message strings in settings debug tab.
  • Added: Num occasions in RSS feed is now wrapped in a <p> tag.
  • Removed: "Simple Legacy Logger" is removed because it has not been used for a very long time.
  • Removed: "GitHub Plugin URI" header removed from index file, so installs of Simple History from Github using Git Updater are not supported from now on.
  • Removed: Box with translations notice removed from sidebar because it did not work properly when using different languages as site language and user language.
  • Internal: Code formatting to better match the WordPress coding standards, code cleanup, text escaping. (#243)
Download this release

Release Info

Developer eskapism
Plugin Icon 128x128 Simple History
Version 3.0.0
Comparing to
See all releases

Code changes from version 2.43.0 to 3.0.0

Files changed (45) hide show
  1. CHANGELOG.md +196 -10
  2. code.md +8 -0
  3. composer.json +16 -6
  4. composer.lock +340 -1768
  5. css/styles.css +0 -32
  6. dropins/SimpleHistoryDebugDropin.php +49 -51
  7. dropins/SimpleHistoryDonateDropin.php +53 -69
  8. dropins/SimpleHistoryExportDropin.php +215 -232
  9. dropins/SimpleHistoryFilterDropin.php +607 -567
  10. dropins/SimpleHistoryIpInfoDropin.php +195 -201
  11. dropins/SimpleHistoryNewRowsNotifier.php +67 -64
  12. dropins/SimpleHistoryPluginPatchesDropin.php +153 -161
  13. dropins/SimpleHistoryRSSDropin.php +426 -397
  14. dropins/SimpleHistorySettingsDebugDropin.php +16 -31
  15. dropins/SimpleHistorySettingsLogtestDropin.php +238 -282
  16. dropins/SimpleHistorySettingsStatsDropin.php +66 -78
  17. dropins/SimpleHistorySidebarDropin.php +89 -191
  18. dropins/SimpleHistorySidebarSettings.php +78 -81
  19. dropins/SimpleHistorySidebarStats.php +195 -194
  20. dropins/SimpleHistoryWPCLIDropin.php +144 -155
  21. examples/example-dropin.php +40 -47
  22. examples/example-logger.php +77 -77
  23. examples/examples.php +285 -211
  24. inc/SimpleHistory.php +3095 -3147
  25. inc/SimpleHistoryLogQuery.php +616 -683
  26. inc/helpers.php +138 -134
  27. inc/oldversions.php +37 -37
  28. index.php +27 -27
  29. loggers/AvailableUpdatesLogger.php +302 -299
  30. loggers/FileEditsLogger.php +252 -248
  31. loggers/PluginEnableMediaReplaceLogger.php +96 -94
  32. loggers/PluginUserSwitchingLogger.php +125 -130
  33. loggers/PluginWPCrontrolLogger.php +399 -406
  34. loggers/Plugin_ACF.php +1043 -1045
  35. loggers/Plugin_BeaverBuilder.php +96 -99
  36. loggers/Plugin_DuplicatePost.php +138 -142
  37. loggers/Plugin_LimitLoginAttempts.php +220 -223
  38. loggers/Plugin_Redirection.php +376 -388
  39. loggers/Plugin_UltimateMembers_Logger.php +45 -45
  40. loggers/SimpleCategoriesLogger.php +307 -316
  41. loggers/SimpleCommentsLogger.php +730 -736
  42. loggers/SimpleCoreUpdatesLogger.php +104 -109
  43. loggers/SimpleExportLogger.php +46 -49
  44. loggers/SimpleLegacyLogger.php +0 -104
  45. loggers/SimpleLogger.php +970 -1708
CHANGELOG.md CHANGED
@@ -1,6 +1,146 @@
1
-
2
  # Changelog for 2015 an earlier
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  = 2.5 (December 2015) =
5
 
6
  - Added: Category edits are now logged, so now you can see terms, categories and taxonomies that are added, changed, and deleted. Fixes for example https://wordpress.org/support/topic/view-changes-to-categories and https://twitter.com/hmarafi/status/655994402037362688.
@@ -192,7 +332,7 @@
192
  - Fixed: Plugin installs from uploaded ZIP files are now logged correctly. Fixes https://github.com/bonny/WordPress-Simple-History/issues/59.
193
  - Fixed: Check that JavaScript variables it set and that the object have properties set. Fixes https://wordpress.org/support/topic/firefox-37-js-error-generated-by-simplehistoryipinfodropinjs.
194
  - Updated: German translation updated.
195
- - Changed: Loading of loggers, dropins, and so one are moved from action `plugins_loaded` to `after_setup_theme` so themes can actually use for example the load_dropin_*-filters...
196
  - Changed: Misc small design fixes.
197
 
198
  = 2.0.23 (March 2015) =
@@ -314,7 +454,7 @@
314
  - Added: [WordPress 4.1 added the feature to log out a user from all their sessions](http://codex.wordpress.org/Version_4.1#Users). Simple History now logs when a user is logged out from all their sessions except the current browser, or if an admin destroys all sessions for a user. [View screenshot of new session logout log item](https://dl.dropboxusercontent.com/s/k4cmfmncekmfiib/2014-12-simple-history-changelog-user-sessions.png)
315
 
316
  - Added: filter to shortcut loading of a dropin. Example that completely skips loading the RSS-feed-dropin:
317
- `add_filter("simple_history/dropin/load_dropin_SimpleHistoryRSSDropin", "__return_false");`
318
 
319
  = 2.0.5 (November 2014) =
320
 
@@ -356,30 +496,37 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
356
  - Much much more.
357
 
358
  = 1.3.11 =
 
359
  - Don't use deprecated function get_commentdata(). Fixes https://wordpress.org/support/topic/get_commentdata-function-is-deprecated.
360
  - Don't use mysql_query() directly. Fixes https://wordpress.org/support/topic/deprecated-mysql-warning.
361
  - Beta testers wanted! I'm working on the next version of Simple History and now I need some beta testers. If you want to try out the shiny new and cool version please download the [v2 branch](https://github.com/bonny/WordPress-Simple-History/tree/v2) over at GitHub. Thanks!
362
 
363
  = 1.3.10 =
 
364
  - Fix: correct usage of "its"
365
  - Fix: removed serif font in log. Fixes https://wordpress.org/support/topic/two-irritations-and-pleas-for-change.
366
 
367
  = 1.3.9 =
 
368
  - Fixed strict standards warning
369
  - Tested on WordPress 4.0
370
 
371
  = 1.3.8 =
 
372
  - Added filter for rss feed: `simple_history/rss_feed_show`. Fixes more things in this thread: http://wordpress.org/support/topic/more-rss-feed-items.
373
 
374
  = 1.3.7 =
 
375
  - Added filter for rss feed: `simple_history/rss_feed_args`. Fixes http://wordpress.org/support/topic/more-rss-feed-items.
376
 
377
  = 1.3.6 =
 
378
  - Added Polish translation
379
  - Added correct XML encoding and header
380
  - Fixed notice warnings when media did not exist on file system
381
 
382
  = 1.3.5 =
 
383
  - Added a reload-button at top. Click it to reload the history. No need to refresh page no more!
384
  - Fixed items being reloaded when just clicking the dropdown (not having selected anything yet)
385
  - Fixed bug with keyboard navigation
@@ -387,28 +534,34 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
387
  - Use less SQL queries
388
 
389
  = 1.3.4 =
 
390
  - Changed the way post types show in the dropdown. Now uses plural names + not prefixed with main post type. Looks better I think. Thank to Hassan for the suggestion!
391
  - Added "bytes" to size units that an attachment can have. Also fixes undefined notice warning when attachment had a size less that 1 KB.
392
 
393
  = 1.3.3 =
 
394
  - Capability for viewing settings changed from edit_pages to the more correct [manage_options](http://codex.wordpress.org/Roles_and_Capabilities#manage_options)
395
 
396
  = 1.3.2 =
 
397
  - Could get php notice warning if rss secret was not set. Also: make sure both public and private secret exists.
398
 
399
  = 1.3.1 =
 
400
  - Improved contrast for details view
401
  - Fix sql error on installation due to missing column
402
  - Remove options and database table during removal of plugin
403
  - Added: German translation for extender module
404
 
405
  = 1.3 =
 
406
  - Added: history events can store text description with a more detailed explanation of the history item
407
  - Added: now logs failed login attempts for existing username. Uses the new text description to store more info, for example user agent and remote ip address (REMOTE_ADDR)
408
  - Fixed: box did not change height when clicking on occasions
409
  - Fixed: use on() instead of live() in JavaScript
410
 
411
  = 1.2 =
 
412
  - Fixed: Plugin name is included when plugins is activated or deactivated. Previosuly only folder name and name of php file was included.
413
  - Added: Attachment thumbnails are now visible if history item is an attachment. Also includes some metadata.
414
  - Changed: Filters now use dropdowns for type and user. When a site had lots of users and lots of post types, the filter section could be way to big.
@@ -419,6 +572,7 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
419
  - POT-file updated
420
 
421
  = 1.1 =
 
422
  - Added the Simple History Extender-module/plugin. With this great addon to Simple History it is very easy for other developers to add their own actions to simple history, including a settings panel to check actions on/off. All work on this module was made by Laurens Offereins (lmoffereins@gmail.com). Super thanks!
423
  - With the help of Simple History Extender this plugin also tracks changes made in bbPress, Gravity Forms and in Widges. Awesome!
424
  - Added user email to RSS feed + some other small changed to make it compatible with IFTTT.com. Thanks to phoenixMagoo for the code changes. Fixes http://wordpress.org/support/topic/suggestions-a-couple-of-tweaks-to-the-rss-feed.
@@ -429,23 +583,29 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
429
  - Added new filter: simple_history_db_purge_days_interval. Hook it to change default clear interval of 60 days.
430
 
431
  = 1.0.9 =
 
432
  - Added French translation
433
 
434
  = 1.0.8 =
 
435
  - Added: filter simple_history_allow_db_purge that is used to determine if the history should be purged/cleaned after 60 days or not. Return false and it will never be cleaned.
436
  - Fixed: fixed a security issue with the RSS feed. User who should not be able to view the feed could get access to it. Please update to this version to keep your change log private!
437
 
438
  = 1.0.7 =
 
439
  - Fixed: Used a PHP shorthand opening tag at a place. Sorry!
440
  - Fixed: Now loads scripts and styles over HTTPS, if that's being used. Thanks to "llch" for the patch.
441
 
442
  = 1.0.6 =
 
443
  - Added: option to set number of items to show, per page. Default i 5 history log items.
444
 
445
  = 1.0.5 =
 
446
  - Fixed: some translation issues, including updated POT-file for translators.
447
 
448
  = 1.0.4 =
 
449
  - You may want to clear the history database after this update because the items in the log will have mixed translate/untranslated status and it may look/work a bit strange.
450
  - Added: Option to clear the database of log items.
451
  - Changed: No longer stored translated history items in the log. This makes the history work even if/when you switch langauge of WordPress.
@@ -453,14 +613,17 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
453
  - Some more items are translateable
454
 
455
  = 1.0.3 =
 
456
  - Updated German translation
457
  - Some translation fixes
458
 
459
  = 1.0.2 =
 
460
  - Fixed a translation bug
461
  - Added updated German translation
462
 
463
  = 1.0.1 =
 
464
  - The pagination no longer disappear after clickin "occasions"
465
  - Fixed: AJAX loading of new history items didn't work.
466
  - New filter: simple_history_view_history_capability. Default is "edit_pages". Modify this to change what cabability is required to view the history.
@@ -468,13 +631,16 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
468
  - Updated: new POT file. So translators my want to update their translations...
469
 
470
  = 1.0 =
 
471
  - Added: pagination. Gives you more information, for example the number of items, and quicker access to older history items. Also looks more like the rest of the WordPress GUI.
472
  - Modified: search now searches type of action (added, modified, deleted, etc.).
473
 
474
  = 0.8.1 =
 
475
  - Fixed some annoying errors that slipt through testing.
476
 
477
  = 0.8 =
 
478
  - Added: now also logs when a user saves any of the built in settings page (general, writing, reading, discussion, media, privacy, and permalinks. What more things do you want to see in the history? Let me know in the [support forum](http://wordpress.org/support/plugin/simple-history).
479
  - Added: gravatar of user performing action is always shown
480
  - Fixed: history items that was posts/pages/custom post types now get linked again
@@ -484,18 +650,22 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
484
  - Also added donate-links. Tried to keep them discrete. Anyway: please [donate](http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=changelog&utm_campaign=simplehistory) if you use this plugin regularly.
485
 
486
  = 0.7.2 =
 
487
  - Default settings should be to show on page, missed that one. Sorry!
488
 
489
  = 0.7.1 =
 
490
  - Fixed a PHP shorttag
491
 
492
  = 0.7 =
 
493
  - Do not show on dashboard by default to avoid clutter. Can be enabled in settings.
494
  - Add link to settings from plugin list
495
  - Settings are now available as it's own page under Settings -> Simple Fields. It was previosly on the General settings page and some people had difficulties finding it there.
496
  - Added filters: simple_history_show_settings_page, simple_history_show_on_dashboard, simple_history_show_as_page
497
 
498
  = 0.6 =
 
499
  - Changed widget name to just "History" instead of "Simple History". Keep it simple. Previous name implied there also was an "Advanced History" somewhere.
500
  - Made the widget look a bit WordPress-ish by borrwing some of the looks from the comments widget.
501
  - Fix for database that didn't use UTF-8 (sorry international users!)
@@ -503,61 +673,75 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
503
  - Updated POT-file
504
 
505
  = 0.5 =
 
506
  - Added author to RSS
507
  - Added german translation, thanks http://www.fuerther-freiheit.info/
508
  - Added swedish translation, thanks http://jockegustin.se
509
  - Better support for translation
510
 
511
  = 0.4 =
 
512
  - Added: Now you can search the history
513
  - Added: Choose if you wan't to load/show more than just 5 rows from the history
514
 
515
  = 0.3.11 =
 
516
  - Fixed: titles are now escaped
517
 
518
  = 0.3.10 =
 
519
  - Added chinese translation
520
  - Fixed a variable notice
521
  - More visible ok-message after setting a new RSS secret
522
 
523
  = 0.3.9 =
 
524
  - Attachment names were urlencoded and looked wierd. Now they're not.
525
  - Started to store plugin version number
526
 
527
  = 0.3.8 =
 
528
  - Added chinese translation
529
  - Uses WordPress own human_time_diff() instead of own version
530
  - Fix for time zones
531
 
532
  = 0.3.7 =
 
533
  - Directly after installation of Simple History you could view the history RSS feed without using any secret. Now a secret is automatically set during installation.
534
 
535
  = 0.3.6 =
 
536
  - Made the RSS-feature a bit easier to find: added a RSS-icon to the dashboard window - it's very discrete, you can find it at the bottom right corner. On the Simple History page it's a bit more clear, at the bottom, with text and all. Enjoy!
537
  - Added POT-file
538
 
539
  = 0.3.5 =
 
540
  - using get_the_title instead of fetching the title directly from the post object. should make plugins like qtranslate work a bit better.
541
- - preparing for translation by using __() and _e() functions. POT-file will be available shortly.
542
  - Could get cryptic "simpleHistoryNoMoreItems"-text when loading a type with no items.
543
 
544
  = 0.3.4 =
 
545
  - RSS-feed is now valid, and should work at more places (could be broken because of html entities and stuff)
546
 
547
  = 0.3.3 =
 
548
  - Moved JavaScript to own file
549
  - Added comments to the history, so now you can see who approved a comment (or unapproved, or marked as spam, or moved to trash, or restored from the trash)
550
 
551
  = 0.3.2 =
 
552
  - fixed some php notice messages + some other small things I don't remember..
553
 
554
  = 0.3.1 =
 
555
  - forgot to escape html for posts
556
  - reduced memory usage... I think/hope...
557
  - changes internal verbs for actions. some old history items may look a bit weird.
558
  - added RSS feed for recent changes - keep track of changes via your favorite RSS-reader
559
 
560
  = 0.3 =
 
561
  - page is now added under dashboard (was previously under tools). just feel better.
562
  - mouse over on date now display detailed date a bit faster
563
  - layout fixes to make it cooler, better, faster, stronger
@@ -565,14 +749,16 @@ I've spend hundreds of hours making this update, so if you use it and like it pl
565
  - the name of deleted items now show up, instead of "Unknown name" or similar
566
  - added support for plugins (who activated/deactivated what plugin)
567
  - support for third party history items. Use like this:
568
- simple_history_add("action=repaired&object_type=starship&object_name=USS Enterprise");
569
- this would result in somehting like this:
570
- Starship "USS Enterprise" repaired
571
- by admin (John Doe), just now
572
  - capability edit_pages needed to show history. Is this an appropriate capability do you think?
573
 
574
  = 0.2 =
575
- * Compatible with 2.9.2
 
576
 
577
  = 0.1 =
578
- * First public version. It works!
 
 
1
  # Changelog for 2015 an earlier
2
 
3
+ = 2.13 (November 2016) =
4
+
5
+ - Added filter `simple_history_log` that is a simplified way to add message to the log, without the need to check for the existance of Simple History or its SimpleLogger function. Use it like this: `apply_filters("simple_history_log", "This is a logged message");` See the [examples file](https://github.com/bonny/WordPress-Simple-History/blob/master/examples/examples.php) for more examples.
6
+ - IP info now displays a popup with map + geolocation info for users using HTTPS again. Thanks to the great https://twitter.com/ipinfoio for letting all users use their service :)
7
+ - Fix notice warning for missing `$data_parent_row`
8
+
9
+ = 2.12 (September 2016) =
10
+
11
+ - You can show a different number of log items in the log on the dashboard and on the dedicated history page. By default the dashboard will show 5 items and the page will show 30.
12
+ - On multisites the user search filter now only search users in the current site.
13
+ - The statistics chart using Chart.js now uses the namespace window.Simple_History_Chart instead of window.Chart, decreasing the risk that two versions of the Chart.js library overwriting each others. Fixes https://wordpress.org/support/topic/comet-cache-breaks-simple-history/. (Note to future me: this was fixed by renaming the `window.chart` variable to `window.chart.Simple_history_chart` in the line `window.Chart = module.exports = Chart;`)
14
+ - If spam comments are logged they are now included in the log. Change made to make sql query shorter and easier. Should not actually show any spam comments anyway because we don't log them since version 2.5.5 anyway. If you want to revert this behavior for some reason you can use the filter `simple_history/comments_logger/include_spam`.
15
+
16
+ = 2.11 (September 2016) =
17
+
18
+ - Added support for plugin [Redirection](https://wordpress.org/plugins/redirection/).
19
+ Redirects and groups that are created, changed, enabled and disabled will be logged. Also when the plugin global settings are changed that will be logged.
20
+ - Fix possible notice error from User logger.
21
+ - "View changelog" link now works on multisite.
22
+
23
+ = 2.10 (September 2016) =
24
+
25
+ - Available updates to plugins, themes, and WordPress itself is now logged.
26
+ Pretty great if you subscribe to the RSS feed to get the changes on a site. No need to manually check the updates-page to see if there are any updates.
27
+ - Changed to logic used to determine if a post edit should be logged or not. Version 2.9 used a version that started to log a bit to much for some plugins. This should fix the problems with the Nextgen Gallery, All-In-One Events Calendar, and Membership 2 plugins. If you still have problems with a plugin that is causing to many events to be logged, please let me know!
28
+
29
+ = 2.9.1 (August 2016) =
30
+
31
+ - Fixed an issue where the logged time was off by some hours, due to timezone being manually set elsewhere.
32
+ Should fix https://wordpress.org/support/topic/logged-time-off-by-2-hours and https://wordpress.org/support/topic/different-time-between-dashboard-and-logger.
33
+ - Fixed Nextgen Gallery and Nextgen Gallery Plus logging lots and lots of event when viewing posts with galleries. The posts was actually updated, so this plugin did nothing wrong. But it was indeed a bit annoying and most likely something you didn't want in your log. Fixes https://wordpress.org/support/topic/non-stop-logging-nextgen-gallery-items.
34
+
35
+ = 2.9 (August 2016) =
36
+
37
+ - Added custom date ranges to the dates filter. Just select "Custom date range..." in the dates dropdown and you can choose to see the log between any two exact dates.
38
+ - The values in the statistics graph can now be clicked and when clicked the log is filtered to only show logged events from that day. Very convenient if you have a larger number of events logged for one day and quickly want to find out what exactly was logged that day.
39
+ - Dates filter no longer accepts multi values. It was indeed a bit confusing that you could select both "Last 7 days" and "Last 3 days".
40
+ - Fix for empty previous plugin version (the `{plugin_prev_version}` placeholder) when updating plugins.
41
+ - Post and pages updates done in the WordPress apps for Ios and Android should be logged again.
42
+
43
+ = 2.8 (August 2016) =
44
+
45
+ - Theme installs are now logged
46
+ - ...and so are theme updates
47
+ - ...and theme deletions. Awesome!
48
+ - Support for plugin [Limit Login Attempts](https://wordpress.org/plugins/limit-login-attempts/).
49
+ Failed login attempts, lockouts and configuration changes will be logged.
50
+ - Correct message is now used when a plugin update fails, i.e. the message for key `plugin_update_failed`.
51
+ - The original untranslated strings for plugin name and so on are stored when storing info for plugin installs and updates and similar.
52
+ - Default number of events to show is now 10 instead of 5.
53
+
54
+ = 2.7.5 (August 2016) =
55
+
56
+ - User logins using e-mail are now logged correctly. Previously the user would be logged in successfully but the log said that they failed.
57
+ - Security fix: only users with [`list_users`](https://codex.wordpress.org/Roles_and_Capabilities#list_users) capability can view the users filter and use the autocomplete api for users.
58
+ Previously the autocomplete function could be used by all logged in users.
59
+ - Add labels to search filters. (I do really hate label-less forms so it's kinda very strange that this was not in place before.)
60
+ - Misc other internal fixes
61
+
62
+ = 2.7.4 (July 2016) =
63
+
64
+ - Log a warning message if a plugin gets disabled automatically by WordPress because of any of these errors: "Plugin file does not exist.", "Invalid plugin path.", "The plugin does not have a valid header."
65
+ - Fix warning error if `on_wp_login()` was called without second argument.
66
+ - Fix options diff not being shown correctly.
67
+ - Fix notice if no message key did exist for a log message.
68
+
69
+ = 2.7.3 (June 2016) =
70
+
71
+ - Removed the usage of the mb\_\* functions and mbstring is no longer a requirement.
72
+ - Added a new debug tab to the settings page. On the debug page you can see stuff like how large your database is and how many rows that are stored in the database. Also, a list of all loggers are listed there together with some useful (for developers anyway) information.
73
+
74
+ = 2.7.2 (June 2016) =
75
+
76
+ - Fixed message about mbstring required not being echo'ed.
77
+ - Fixed notice errors for users not allowed to view the log.
78
+
79
+ = 2.7.1 (June 2016) =
80
+
81
+ - Added: Add shortcut to history in Admin bar for current site and in Network Admin Bar for each site where plugin is installed. Can be disabled using filters `simple_history/add_admin_bar_menu_item` and `simple_history/add_admin_bar_network_menu_item`.
82
+ - Added: Add check that [´mbstring´](http://php.net/manual/en/book.mbstring.php) is enabled and show a warning if it's not.
83
+ - Changed: Changes to "Front Page Displays" in "Reading Settings" now show the name of the old and new page (before only id was logged).
84
+ - Changed: Changes to "Default Post Category" and "Default Mail Category" in "Writing Settings" now show the name of the old and new category (before only id was logged).
85
+ - Fixed: When changing "Front Page Displays" in "Reading Settings" the option "rewrite_rules" also got logged.
86
+ - Fixed: Changes in Permalink Settings were not logged correctly.
87
+ - Fixed: Actions done with [WP-CLI](https://wp-cli.org/) was not correctly attributed. Now the log should say "WP-CLI" intead of "Other" for actions done in WP CLI.
88
+
89
+ = 2.7 (May 2016) =
90
+
91
+ - Added: When a user is created or edited the log now shows what fields have changed and from what old value to what new value. A much requested feature!
92
+ - Fixed: If you edited your own profile the log would say that you edited "their profile". Now it says that you edited "your profile" instead.
93
+ - Changed: Post diffs could get very tall. Now they are max approx 8 rows by default, but if you hover the diff (or give it focus with your keyboard) you get a scrollbar and can scroll the contents. Fixes https://wordpress.org/support/topic/dashboard-max-length-of-content and https://wordpress.org/support/topic/feature-request-make-content-diff-report-expandable-and-closed-by-default.
94
+ - Fixed: Maybe fix a notice varning if a transient was missing a name or value.
95
+
96
+ = 2.6 (May 2016) =
97
+
98
+ - Added: A nice little graph in the sidebar that displays the number of logged events per day the last 28 days. Graph is powered by [Chart.js](http://www.chartjs.org/).
99
+ - Added: Function `get_num_events_last_n_days()`
100
+ - Added: Function `get_num_events_per_day_last_n_days()`
101
+ - Changed: Switched to transients from cache at some places, because more people will benefit from transients instead of cache (that requires object cache to be installed).
102
+ - Changed: New constant `SETTINGS_GENERAL_OPTION_GROUP`. Fixes https://wordpress.org/support/topic/constant-for-settings-option-group-name-option_group.
103
+ - Fixed: Long log messages with no spaces would get cut of. Now all the message is shown, but with one or several line breaks. Fixes https://github.com/bonny/WordPress-Simple-History/pull/112.
104
+ - Fixed: Some small CSS modification to make the page less "jumpy" while loading (for example setting a default height to the select2 input box).
105
+
106
+ = 2.5.5 (April 2016) =
107
+
108
+ - Changed: The logger for Enable Media Replace required the capability `edit_files` to view the logged events, but since this also made it impossible to view events if the constant `DISALLOW_FILE_EDIT` was true. Now Enable Media Replace requires the capability `upload_files` instead. Makes more sense. Fixes https://wordpress.org/support/topic/simple-history-and-disallow_file_edit.
109
+ - Changed: No longer log spam trackbacks or comments. Before this version these where logged, but not shown.
110
+ - Fixed: Translations was not loaded for Select2. Fixes https://wordpress.org/support/topic/found-a-string-thats-not-translatable-v-254.
111
+ - Fixed: LogQuery `date_to`-argument was using `date_from`.
112
+ - Changed: The changelog for 2015 and earlier are now moved to [CHANGELOG.md](https://github.com/bonny/WordPress-Simple-History/blob/master/CHANGELOG.md).
113
+
114
+ = 2.5.4 (March 2016) =
115
+
116
+ - Added: Support for new key in info array from logger: "name_via". Set this value in a logger and the string will be shown next to the date of the logged event. Useful when logging actions from third party plugins, or any kind of other logging that is not from WordPress core.
117
+ - Added: Method `getInfoValueByKey` added to the SimpleLogger class, for easier retrieval of values from the info array of a logger.
118
+ - Fixed: Themes could no be deleted. Fixes https://github.com/bonny/WordPress-Simple-History/issues/98 and https://wordpress.org/support/topic/deleting-theme-1.
119
+ - Fixed: Notice error when generating permalink for event.
120
+ - Fixed: Removed a `console.log()`.
121
+ - Changed: Check that array key is integer or string. Hopefully fixes https://wordpress.org/support/topic/error-in-wp-adminerror_log.
122
+
123
+ = 2.5.3 (February 2016) =
124
+
125
+ - Fixed: Old entries was not correctly removed. Fixes https://github.com/bonny/WordPress-Simple-History/issues/108.
126
+
127
+ = 2.5.2 (February 2016) =
128
+
129
+ - Added: The GUI log now updates the relative "fuzzy" timestamps in real time. This means that if you keep the log opened, the relative date for each event, for example "2 minutes ago" or "2 hours ago", will always be up to date (hah!). Keep the log opened for 5 minutes and you will see that the event that previously said "2 minutes ago" now says "7 minutes ago". Fixes https://github.com/bonny/WordPress-Simple-History/issues/88 and is implemented using the great [timeago jquery plugin](http://timeago.yarp.com/).
130
+ - Added: Filter `simple_history/user_logger/plain_text_output_use_you`. Works the same way as the `simple_history/header_initiator_use_you` filter, but for the rich text part when a user has edited their profile.
131
+ - Fixed: Logger slugs that contained for example backslashes (becuase they where namespaced) would not show up in the log. Now logger slugs are escaped. Fixes https://github.com/bonny/WordPress-Simple-History/issues/103.
132
+ - Changed: Actions and things that only is needed in admin area are now only called if `is_admin()`. Fixes https://github.com/bonny/WordPress-Simple-History/issues/105.
133
+
134
+ = 2.5.1 (February 2016) =
135
+
136
+ - Fixed: No longer assume that the ajaxurl don't already contains query params. Should fix problems with third party plugins like [WPML](https://wpml.org/).
137
+ - Fixed: Notice if context key did not exist. Should fix https://github.com/bonny/WordPress-Simple-History/issues/100.
138
+ - Fixed: Name and title on dashboard and settings page were not translateable. Fixes https://wordpress.org/support/topic/dashboard-max-length-of-content.
139
+ - Fixed: Typo when user resets password.
140
+ - Added: Filter `simple_history/row_header_date_output`.
141
+ - Added: Filter `simple_history/log/inserted`.
142
+ - Added: Filter `simple_history/row_header_date_output`.
143
+
144
  = 2.5 (December 2015) =
145
 
146
  - Added: Category edits are now logged, so now you can see terms, categories and taxonomies that are added, changed, and deleted. Fixes for example https://wordpress.org/support/topic/view-changes-to-categories and https://twitter.com/hmarafi/status/655994402037362688.
332
  - Fixed: Plugin installs from uploaded ZIP files are now logged correctly. Fixes https://github.com/bonny/WordPress-Simple-History/issues/59.
333
  - Fixed: Check that JavaScript variables it set and that the object have properties set. Fixes https://wordpress.org/support/topic/firefox-37-js-error-generated-by-simplehistoryipinfodropinjs.
334
  - Updated: German translation updated.
335
+ - Changed: Loading of loggers, dropins, and so one are moved from action `plugins_loaded` to `after_setup_theme` so themes can actually use for example the load*dropin*\*-filters...
336
  - Changed: Misc small design fixes.
337
 
338
  = 2.0.23 (March 2015) =
454
  - Added: [WordPress 4.1 added the feature to log out a user from all their sessions](http://codex.wordpress.org/Version_4.1#Users). Simple History now logs when a user is logged out from all their sessions except the current browser, or if an admin destroys all sessions for a user. [View screenshot of new session logout log item](https://dl.dropboxusercontent.com/s/k4cmfmncekmfiib/2014-12-simple-history-changelog-user-sessions.png)
455
 
456
  - Added: filter to shortcut loading of a dropin. Example that completely skips loading the RSS-feed-dropin:
457
+ `add_filter("simple_history/dropin/load_dropin_SimpleHistoryRSSDropin", "__return_false");`
458
 
459
  = 2.0.5 (November 2014) =
460
 
496
  - Much much more.
497
 
498
  = 1.3.11 =
499
+
500
  - Don't use deprecated function get_commentdata(). Fixes https://wordpress.org/support/topic/get_commentdata-function-is-deprecated.
501
  - Don't use mysql_query() directly. Fixes https://wordpress.org/support/topic/deprecated-mysql-warning.
502
  - Beta testers wanted! I'm working on the next version of Simple History and now I need some beta testers. If you want to try out the shiny new and cool version please download the [v2 branch](https://github.com/bonny/WordPress-Simple-History/tree/v2) over at GitHub. Thanks!
503
 
504
  = 1.3.10 =
505
+
506
  - Fix: correct usage of "its"
507
  - Fix: removed serif font in log. Fixes https://wordpress.org/support/topic/two-irritations-and-pleas-for-change.
508
 
509
  = 1.3.9 =
510
+
511
  - Fixed strict standards warning
512
  - Tested on WordPress 4.0
513
 
514
  = 1.3.8 =
515
+
516
  - Added filter for rss feed: `simple_history/rss_feed_show`. Fixes more things in this thread: http://wordpress.org/support/topic/more-rss-feed-items.
517
 
518
  = 1.3.7 =
519
+
520
  - Added filter for rss feed: `simple_history/rss_feed_args`. Fixes http://wordpress.org/support/topic/more-rss-feed-items.
521
 
522
  = 1.3.6 =
523
+
524
  - Added Polish translation
525
  - Added correct XML encoding and header
526
  - Fixed notice warnings when media did not exist on file system
527
 
528
  = 1.3.5 =
529
+
530
  - Added a reload-button at top. Click it to reload the history. No need to refresh page no more!
531
  - Fixed items being reloaded when just clicking the dropdown (not having selected anything yet)
532
  - Fixed bug with keyboard navigation
534
  - Use less SQL queries
535
 
536
  = 1.3.4 =
537
+
538
  - Changed the way post types show in the dropdown. Now uses plural names + not prefixed with main post type. Looks better I think. Thank to Hassan for the suggestion!
539
  - Added "bytes" to size units that an attachment can have. Also fixes undefined notice warning when attachment had a size less that 1 KB.
540
 
541
  = 1.3.3 =
542
+
543
  - Capability for viewing settings changed from edit_pages to the more correct [manage_options](http://codex.wordpress.org/Roles_and_Capabilities#manage_options)
544
 
545
  = 1.3.2 =
546
+
547
  - Could get php notice warning if rss secret was not set. Also: make sure both public and private secret exists.
548
 
549
  = 1.3.1 =
550
+
551
  - Improved contrast for details view
552
  - Fix sql error on installation due to missing column
553
  - Remove options and database table during removal of plugin
554
  - Added: German translation for extender module
555
 
556
  = 1.3 =
557
+
558
  - Added: history events can store text description with a more detailed explanation of the history item
559
  - Added: now logs failed login attempts for existing username. Uses the new text description to store more info, for example user agent and remote ip address (REMOTE_ADDR)
560
  - Fixed: box did not change height when clicking on occasions
561
  - Fixed: use on() instead of live() in JavaScript
562
 
563
  = 1.2 =
564
+
565
  - Fixed: Plugin name is included when plugins is activated or deactivated. Previosuly only folder name and name of php file was included.
566
  - Added: Attachment thumbnails are now visible if history item is an attachment. Also includes some metadata.
567
  - Changed: Filters now use dropdowns for type and user. When a site had lots of users and lots of post types, the filter section could be way to big.
572
  - POT-file updated
573
 
574
  = 1.1 =
575
+
576
  - Added the Simple History Extender-module/plugin. With this great addon to Simple History it is very easy for other developers to add their own actions to simple history, including a settings panel to check actions on/off. All work on this module was made by Laurens Offereins (lmoffereins@gmail.com). Super thanks!
577
  - With the help of Simple History Extender this plugin also tracks changes made in bbPress, Gravity Forms and in Widges. Awesome!
578
  - Added user email to RSS feed + some other small changed to make it compatible with IFTTT.com. Thanks to phoenixMagoo for the code changes. Fixes http://wordpress.org/support/topic/suggestions-a-couple-of-tweaks-to-the-rss-feed.
583
  - Added new filter: simple_history_db_purge_days_interval. Hook it to change default clear interval of 60 days.
584
 
585
  = 1.0.9 =
586
+
587
  - Added French translation
588
 
589
  = 1.0.8 =
590
+
591
  - Added: filter simple_history_allow_db_purge that is used to determine if the history should be purged/cleaned after 60 days or not. Return false and it will never be cleaned.
592
  - Fixed: fixed a security issue with the RSS feed. User who should not be able to view the feed could get access to it. Please update to this version to keep your change log private!
593
 
594
  = 1.0.7 =
595
+
596
  - Fixed: Used a PHP shorthand opening tag at a place. Sorry!
597
  - Fixed: Now loads scripts and styles over HTTPS, if that's being used. Thanks to "llch" for the patch.
598
 
599
  = 1.0.6 =
600
+
601
  - Added: option to set number of items to show, per page. Default i 5 history log items.
602
 
603
  = 1.0.5 =
604
+
605
  - Fixed: some translation issues, including updated POT-file for translators.
606
 
607
  = 1.0.4 =
608
+
609
  - You may want to clear the history database after this update because the items in the log will have mixed translate/untranslated status and it may look/work a bit strange.
610
  - Added: Option to clear the database of log items.
611
  - Changed: No longer stored translated history items in the log. This makes the history work even if/when you switch langauge of WordPress.
613
  - Some more items are translateable
614
 
615
  = 1.0.3 =
616
+
617
  - Updated German translation
618
  - Some translation fixes
619
 
620
  = 1.0.2 =
621
+
622
  - Fixed a translation bug
623
  - Added updated German translation
624
 
625
  = 1.0.1 =
626
+
627
  - The pagination no longer disappear after clickin "occasions"
628
  - Fixed: AJAX loading of new history items didn't work.
629
  - New filter: simple_history_view_history_capability. Default is "edit_pages". Modify this to change what cabability is required to view the history.
631
  - Updated: new POT file. So translators my want to update their translations...
632
 
633
  = 1.0 =
634
+
635
  - Added: pagination. Gives you more information, for example the number of items, and quicker access to older history items. Also looks more like the rest of the WordPress GUI.
636
  - Modified: search now searches type of action (added, modified, deleted, etc.).
637
 
638
  = 0.8.1 =
639
+
640
  - Fixed some annoying errors that slipt through testing.
641
 
642
  = 0.8 =
643
+
644
  - Added: now also logs when a user saves any of the built in settings page (general, writing, reading, discussion, media, privacy, and permalinks. What more things do you want to see in the history? Let me know in the [support forum](http://wordpress.org/support/plugin/simple-history).
645
  - Added: gravatar of user performing action is always shown
646
  - Fixed: history items that was posts/pages/custom post types now get linked again
650
  - Also added donate-links. Tried to keep them discrete. Anyway: please [donate](http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=changelog&utm_campaign=simplehistory) if you use this plugin regularly.
651
 
652
  = 0.7.2 =
653
+
654
  - Default settings should be to show on page, missed that one. Sorry!
655
 
656
  = 0.7.1 =
657
+
658
  - Fixed a PHP shorttag
659
 
660
  = 0.7 =
661
+
662
  - Do not show on dashboard by default to avoid clutter. Can be enabled in settings.
663
  - Add link to settings from plugin list
664
  - Settings are now available as it's own page under Settings -> Simple Fields. It was previosly on the General settings page and some people had difficulties finding it there.
665
  - Added filters: simple_history_show_settings_page, simple_history_show_on_dashboard, simple_history_show_as_page
666
 
667
  = 0.6 =
668
+
669
  - Changed widget name to just "History" instead of "Simple History". Keep it simple. Previous name implied there also was an "Advanced History" somewhere.
670
  - Made the widget look a bit WordPress-ish by borrwing some of the looks from the comments widget.
671
  - Fix for database that didn't use UTF-8 (sorry international users!)
673
  - Updated POT-file
674
 
675
  = 0.5 =
676
+
677
  - Added author to RSS
678
  - Added german translation, thanks http://www.fuerther-freiheit.info/
679
  - Added swedish translation, thanks http://jockegustin.se
680
  - Better support for translation
681
 
682
  = 0.4 =
683
+
684
  - Added: Now you can search the history
685
  - Added: Choose if you wan't to load/show more than just 5 rows from the history
686
 
687
  = 0.3.11 =
688
+
689
  - Fixed: titles are now escaped
690
 
691
  = 0.3.10 =
692
+
693
  - Added chinese translation
694
  - Fixed a variable notice
695
  - More visible ok-message after setting a new RSS secret
696
 
697
  = 0.3.9 =
698
+
699
  - Attachment names were urlencoded and looked wierd. Now they're not.
700
  - Started to store plugin version number
701
 
702
  = 0.3.8 =
703
+
704
  - Added chinese translation
705
  - Uses WordPress own human_time_diff() instead of own version
706
  - Fix for time zones
707
 
708
  = 0.3.7 =
709
+
710
  - Directly after installation of Simple History you could view the history RSS feed without using any secret. Now a secret is automatically set during installation.
711
 
712
  = 0.3.6 =
713
+
714
  - Made the RSS-feature a bit easier to find: added a RSS-icon to the dashboard window - it's very discrete, you can find it at the bottom right corner. On the Simple History page it's a bit more clear, at the bottom, with text and all. Enjoy!
715
  - Added POT-file
716
 
717
  = 0.3.5 =
718
+
719
  - using get_the_title instead of fetching the title directly from the post object. should make plugins like qtranslate work a bit better.
720
+ - preparing for translation by using \_\_() and \_e() functions. POT-file will be available shortly.
721
  - Could get cryptic "simpleHistoryNoMoreItems"-text when loading a type with no items.
722
 
723
  = 0.3.4 =
724
+
725
  - RSS-feed is now valid, and should work at more places (could be broken because of html entities and stuff)
726
 
727
  = 0.3.3 =
728
+
729
  - Moved JavaScript to own file
730
  - Added comments to the history, so now you can see who approved a comment (or unapproved, or marked as spam, or moved to trash, or restored from the trash)
731
 
732
  = 0.3.2 =
733
+
734
  - fixed some php notice messages + some other small things I don't remember..
735
 
736
  = 0.3.1 =
737
+
738
  - forgot to escape html for posts
739
  - reduced memory usage... I think/hope...
740
  - changes internal verbs for actions. some old history items may look a bit weird.
741
  - added RSS feed for recent changes - keep track of changes via your favorite RSS-reader
742
 
743
  = 0.3 =
744
+
745
  - page is now added under dashboard (was previously under tools). just feel better.
746
  - mouse over on date now display detailed date a bit faster
747
  - layout fixes to make it cooler, better, faster, stronger
749
  - the name of deleted items now show up, instead of "Unknown name" or similar
750
  - added support for plugins (who activated/deactivated what plugin)
751
  - support for third party history items. Use like this:
752
+ simple_history_add("action=repaired&object_type=starship&object_name=USS Enterprise");
753
+ this would result in somehting like this:
754
+ Starship "USS Enterprise" repaired
755
+ by admin (John Doe), just now
756
  - capability edit_pages needed to show history. Is this an appropriate capability do you think?
757
 
758
  = 0.2 =
759
+
760
+ - Compatible with 2.9.2
761
 
762
  = 0.1 =
763
+
764
+ - First public version. It works!
code.md CHANGED
@@ -35,3 +35,11 @@ Fix things:
35
 
36
  - Will try to follow OneFlow:
37
  https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow
 
 
 
 
 
 
 
 
35
 
36
  - Will try to follow OneFlow:
37
  https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow
38
+
39
+ ## Update match 2021
40
+
41
+ Code standards is updated to WordPress own.
42
+
43
+ Uses composer package `dealerdirect/phpcodesniffer-composer-installer` to find PHP_CodeSniffer rules automagically. Just run `composer install` and then `vendor/bin/phpcs`.
44
+
45
+ Use PHP 7 for now, the WordPress rules crashes on PHP 8 so far (bug fixed but no version with fix released).
composer.json CHANGED
@@ -7,11 +7,13 @@
7
  "log",
8
  "history"
9
  ],
10
- "homepage": "http://simple-history.com/",
11
  "minimum-stability": "dev",
12
  "require-dev": {
13
- "squizlabs/php_codesniffer": "^3.5",
14
- "phpunit/phpunit": "^7"
 
 
15
  },
16
  "require": {
17
  "php": ">=5.2.0"
@@ -19,11 +21,14 @@
19
  "authors": [
20
  {
21
  "name": "Pär Thernström",
22
- "email": "par.thernstrom@gmail.com",
23
- "homepage": "https://eskapism.se/",
24
- "role": "Developer"
25
  }
26
  ],
 
 
 
 
 
27
  "dist": {
28
  "url": "https://downloads.wordpress.org/plugin/simple-history.zip",
29
  "type": "zip"
@@ -32,5 +37,10 @@
32
  "lint": "./vendor/bin/phpcs .",
33
  "lint-fix": "./vendor/bin/phpcbf .",
34
  "test": "./vendor/bin/phpunit"
 
 
 
 
 
35
  }
36
  }
7
  "log",
8
  "history"
9
  ],
10
+ "homepage": "https://simple-history.com/",
11
  "minimum-stability": "dev",
12
  "require-dev": {
13
+ "dealerdirect/phpcodesniffer-composer-installer": "*",
14
+ "phpcompatibility/php-compatibility": "*",
15
+ "wp-coding-standards/wpcs": "*",
16
+ "phpcompatibility/phpcompatibility-wp": "*"
17
  },
18
  "require": {
19
  "php": ">=5.2.0"
21
  "authors": [
22
  {
23
  "name": "Pär Thernström",
24
+ "homepage": "https://eskapism.se/"
 
 
25
  }
26
  ],
27
+ "support": {
28
+ "issues": "https://github.com/bonny/WordPress-Simple-History/issues",
29
+ "forum": "https://wordpress.org/support/plugin/simple-history/",
30
+ "source": "https://github.com/bonny/WordPress-Simple-History"
31
+ },
32
  "dist": {
33
  "url": "https://downloads.wordpress.org/plugin/simple-history.zip",
34
  "type": "zip"
37
  "lint": "./vendor/bin/phpcs .",
38
  "lint-fix": "./vendor/bin/phpcbf .",
39
  "test": "./vendor/bin/phpunit"
40
+ },
41
+ "config": {
42
+ "allow-plugins": {
43
+ "dealerdirect/phpcodesniffer-composer-installer": true
44
+ }
45
  }
46
  }
composer.lock CHANGED
@@ -1,1770 +1,342 @@
1
  {
2
- "_readme": [
3
- "This file locks the dependencies of your project to a known state",
4
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
- "This file is @generated automatically"
6
- ],
7
- "content-hash": "3eb8f2dd7c7507bb8a21f6bb503f457f",
8
- "packages": [],
9
- "packages-dev": [
10
- {
11
- "name": "doctrine/instantiator",
12
- "version": "1.5.x-dev",
13
- "source": {
14
- "type": "git",
15
- "url": "https://github.com/doctrine/instantiator.git",
16
- "reference": "6410c4b8352cb64218641457cef64997e6b784fb"
17
- },
18
- "dist": {
19
- "type": "zip",
20
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/6410c4b8352cb64218641457cef64997e6b784fb",
21
- "reference": "6410c4b8352cb64218641457cef64997e6b784fb",
22
- "shasum": ""
23
- },
24
- "require": {
25
- "php": "^7.1 || ^8.0"
26
- },
27
- "require-dev": {
28
- "doctrine/coding-standard": "^8.0",
29
- "ext-pdo": "*",
30
- "ext-phar": "*",
31
- "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
32
- "phpstan/phpstan": "^0.12",
33
- "phpstan/phpstan-phpunit": "^0.12",
34
- "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
35
- },
36
- "type": "library",
37
- "autoload": {
38
- "psr-4": {
39
- "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
40
- }
41
- },
42
- "notification-url": "https://packagist.org/downloads/",
43
- "license": [
44
- "MIT"
45
- ],
46
- "authors": [
47
- {
48
- "name": "Marco Pivetta",
49
- "email": "ocramius@gmail.com",
50
- "homepage": "https://ocramius.github.io/"
51
- }
52
- ],
53
- "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
54
- "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
55
- "keywords": [
56
- "constructor",
57
- "instantiate"
58
- ],
59
- "support": {
60
- "issues": "https://github.com/doctrine/instantiator/issues",
61
- "source": "https://github.com/doctrine/instantiator/tree/1.4.x"
62
- },
63
- "funding": [
64
- {
65
- "url": "https://www.doctrine-project.org/sponsorship.html",
66
- "type": "custom"
67
- },
68
- {
69
- "url": "https://www.patreon.com/phpdoctrine",
70
- "type": "patreon"
71
- },
72
- {
73
- "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
74
- "type": "tidelift"
75
- }
76
- ],
77
- "time": "2020-11-10T19:05:51+00:00"
78
- },
79
- {
80
- "name": "myclabs/deep-copy",
81
- "version": "1.x-dev",
82
- "source": {
83
- "type": "git",
84
- "url": "https://github.com/myclabs/DeepCopy.git",
85
- "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
86
- },
87
- "dist": {
88
- "type": "zip",
89
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
90
- "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
91
- "shasum": ""
92
- },
93
- "require": {
94
- "php": "^7.1 || ^8.0"
95
- },
96
- "replace": {
97
- "myclabs/deep-copy": "self.version"
98
- },
99
- "require-dev": {
100
- "doctrine/collections": "^1.0",
101
- "doctrine/common": "^2.6",
102
- "phpunit/phpunit": "^7.1"
103
- },
104
- "default-branch": true,
105
- "type": "library",
106
- "autoload": {
107
- "psr-4": {
108
- "DeepCopy\\": "src/DeepCopy/"
109
- },
110
- "files": [
111
- "src/DeepCopy/deep_copy.php"
112
- ]
113
- },
114
- "notification-url": "https://packagist.org/downloads/",
115
- "license": [
116
- "MIT"
117
- ],
118
- "description": "Create deep copies (clones) of your objects",
119
- "keywords": [
120
- "clone",
121
- "copy",
122
- "duplicate",
123
- "object",
124
- "object graph"
125
- ],
126
- "support": {
127
- "issues": "https://github.com/myclabs/DeepCopy/issues",
128
- "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
129
- },
130
- "funding": [
131
- {
132
- "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
133
- "type": "tidelift"
134
- }
135
- ],
136
- "time": "2020-11-13T09:40:50+00:00"
137
- },
138
- {
139
- "name": "phar-io/manifest",
140
- "version": "1.0.3",
141
- "source": {
142
- "type": "git",
143
- "url": "https://github.com/phar-io/manifest.git",
144
- "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
145
- },
146
- "dist": {
147
- "type": "zip",
148
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
149
- "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
150
- "shasum": ""
151
- },
152
- "require": {
153
- "ext-dom": "*",
154
- "ext-phar": "*",
155
- "phar-io/version": "^2.0",
156
- "php": "^5.6 || ^7.0"
157
- },
158
- "type": "library",
159
- "extra": {
160
- "branch-alias": {
161
- "dev-master": "1.0.x-dev"
162
- }
163
- },
164
- "autoload": {
165
- "classmap": [
166
- "src/"
167
- ]
168
- },
169
- "notification-url": "https://packagist.org/downloads/",
170
- "license": [
171
- "BSD-3-Clause"
172
- ],
173
- "authors": [
174
- {
175
- "name": "Arne Blankerts",
176
- "email": "arne@blankerts.de",
177
- "role": "Developer"
178
- },
179
- {
180
- "name": "Sebastian Heuer",
181
- "email": "sebastian@phpeople.de",
182
- "role": "Developer"
183
- },
184
- {
185
- "name": "Sebastian Bergmann",
186
- "email": "sebastian@phpunit.de",
187
- "role": "Developer"
188
- }
189
- ],
190
- "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
191
- "support": {
192
- "issues": "https://github.com/phar-io/manifest/issues",
193
- "source": "https://github.com/phar-io/manifest/tree/master"
194
- },
195
- "time": "2018-07-08T19:23:20+00:00"
196
- },
197
- {
198
- "name": "phar-io/version",
199
- "version": "2.0.1",
200
- "source": {
201
- "type": "git",
202
- "url": "https://github.com/phar-io/version.git",
203
- "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
204
- },
205
- "dist": {
206
- "type": "zip",
207
- "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
208
- "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
209
- "shasum": ""
210
- },
211
- "require": {
212
- "php": "^5.6 || ^7.0"
213
- },
214
- "type": "library",
215
- "autoload": {
216
- "classmap": [
217
- "src/"
218
- ]
219
- },
220
- "notification-url": "https://packagist.org/downloads/",
221
- "license": [
222
- "BSD-3-Clause"
223
- ],
224
- "authors": [
225
- {
226
- "name": "Arne Blankerts",
227
- "email": "arne@blankerts.de",
228
- "role": "Developer"
229
- },
230
- {
231
- "name": "Sebastian Heuer",
232
- "email": "sebastian@phpeople.de",
233
- "role": "Developer"
234
- },
235
- {
236
- "name": "Sebastian Bergmann",
237
- "email": "sebastian@phpunit.de",
238
- "role": "Developer"
239
- }
240
- ],
241
- "description": "Library for handling version information and constraints",
242
- "support": {
243
- "issues": "https://github.com/phar-io/version/issues",
244
- "source": "https://github.com/phar-io/version/tree/master"
245
- },
246
- "time": "2018-07-08T19:19:57+00:00"
247
- },
248
- {
249
- "name": "phpdocumentor/reflection-common",
250
- "version": "dev-master",
251
- "source": {
252
- "type": "git",
253
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
254
- "reference": "cf8df60735d98fd18070b7cab0019ba0831e219c"
255
- },
256
- "dist": {
257
- "type": "zip",
258
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/cf8df60735d98fd18070b7cab0019ba0831e219c",
259
- "reference": "cf8df60735d98fd18070b7cab0019ba0831e219c",
260
- "shasum": ""
261
- },
262
- "require": {
263
- "php": ">=7.1"
264
- },
265
- "default-branch": true,
266
- "type": "library",
267
- "extra": {
268
- "branch-alias": {
269
- "dev-master": "2.x-dev"
270
- }
271
- },
272
- "autoload": {
273
- "psr-4": {
274
- "phpDocumentor\\Reflection\\": "src/"
275
- }
276
- },
277
- "notification-url": "https://packagist.org/downloads/",
278
- "license": [
279
- "MIT"
280
- ],
281
- "authors": [
282
- {
283
- "name": "Jaap van Otterdijk",
284
- "email": "opensource@ijaap.nl"
285
- }
286
- ],
287
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
288
- "homepage": "http://www.phpdoc.org",
289
- "keywords": [
290
- "FQSEN",
291
- "phpDocumentor",
292
- "phpdoc",
293
- "reflection",
294
- "static analysis"
295
- ],
296
- "support": {
297
- "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
298
- "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master"
299
- },
300
- "time": "2020-06-19T17:42:03+00:00"
301
- },
302
- {
303
- "name": "phpdocumentor/reflection-docblock",
304
- "version": "dev-master",
305
- "source": {
306
- "type": "git",
307
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
308
- "reference": "e3324ecbde7319b0bbcf0fd7ca4af19469c38da9"
309
- },
310
- "dist": {
311
- "type": "zip",
312
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e3324ecbde7319b0bbcf0fd7ca4af19469c38da9",
313
- "reference": "e3324ecbde7319b0bbcf0fd7ca4af19469c38da9",
314
- "shasum": ""
315
- },
316
- "require": {
317
- "ext-filter": "*",
318
- "php": "^7.2 || ^8.0",
319
- "phpdocumentor/reflection-common": "^2.2",
320
- "phpdocumentor/type-resolver": "^1.3",
321
- "webmozart/assert": "^1.9.1"
322
- },
323
- "require-dev": {
324
- "mockery/mockery": "~1.3.2"
325
- },
326
- "default-branch": true,
327
- "type": "library",
328
- "extra": {
329
- "branch-alias": {
330
- "dev-master": "5.x-dev"
331
- }
332
- },
333
- "autoload": {
334
- "psr-4": {
335
- "phpDocumentor\\Reflection\\": "src"
336
- }
337
- },
338
- "notification-url": "https://packagist.org/downloads/",
339
- "license": [
340
- "MIT"
341
- ],
342
- "authors": [
343
- {
344
- "name": "Mike van Riel",
345
- "email": "me@mikevanriel.com"
346
- },
347
- {
348
- "name": "Jaap van Otterdijk",
349
- "email": "account@ijaap.nl"
350
- }
351
- ],
352
- "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
353
- "support": {
354
- "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
355
- "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
356
- },
357
- "time": "2020-11-18T14:27:38+00:00"
358
- },
359
- {
360
- "name": "phpdocumentor/type-resolver",
361
- "version": "1.x-dev",
362
- "source": {
363
- "type": "git",
364
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
365
- "reference": "6759f2268deb9f329812679e9dcb2d0083b2a30b"
366
- },
367
- "dist": {
368
- "type": "zip",
369
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6759f2268deb9f329812679e9dcb2d0083b2a30b",
370
- "reference": "6759f2268deb9f329812679e9dcb2d0083b2a30b",
371
- "shasum": ""
372
- },
373
- "require": {
374
- "php": "^7.2 || ^8.0",
375
- "phpdocumentor/reflection-common": "^2.0"
376
- },
377
- "require-dev": {
378
- "ext-tokenizer": "*"
379
- },
380
- "default-branch": true,
381
- "type": "library",
382
- "extra": {
383
- "branch-alias": {
384
- "dev-1.x": "1.x-dev"
385
- }
386
- },
387
- "autoload": {
388
- "psr-4": {
389
- "phpDocumentor\\Reflection\\": "src"
390
- }
391
- },
392
- "notification-url": "https://packagist.org/downloads/",
393
- "license": [
394
- "MIT"
395
- ],
396
- "authors": [
397
- {
398
- "name": "Mike van Riel",
399
- "email": "me@mikevanriel.com"
400
- }
401
- ],
402
- "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
403
- "support": {
404
- "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
405
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.x"
406
- },
407
- "time": "2021-02-02T21:09:27+00:00"
408
- },
409
- {
410
- "name": "phpspec/prophecy",
411
- "version": "1.12.2",
412
- "source": {
413
- "type": "git",
414
- "url": "https://github.com/phpspec/prophecy.git",
415
- "reference": "245710e971a030f42e08f4912863805570f23d39"
416
- },
417
- "dist": {
418
- "type": "zip",
419
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39",
420
- "reference": "245710e971a030f42e08f4912863805570f23d39",
421
- "shasum": ""
422
- },
423
- "require": {
424
- "doctrine/instantiator": "^1.2",
425
- "php": "^7.2 || ~8.0, <8.1",
426
- "phpdocumentor/reflection-docblock": "^5.2",
427
- "sebastian/comparator": "^3.0 || ^4.0",
428
- "sebastian/recursion-context": "^3.0 || ^4.0"
429
- },
430
- "require-dev": {
431
- "phpspec/phpspec": "^6.0",
432
- "phpunit/phpunit": "^8.0 || ^9.0"
433
- },
434
- "type": "library",
435
- "extra": {
436
- "branch-alias": {
437
- "dev-master": "1.11.x-dev"
438
- }
439
- },
440
- "autoload": {
441
- "psr-4": {
442
- "Prophecy\\": "src/Prophecy"
443
- }
444
- },
445
- "notification-url": "https://packagist.org/downloads/",
446
- "license": [
447
- "MIT"
448
- ],
449
- "authors": [
450
- {
451
- "name": "Konstantin Kudryashov",
452
- "email": "ever.zet@gmail.com",
453
- "homepage": "http://everzet.com"
454
- },
455
- {
456
- "name": "Marcello Duarte",
457
- "email": "marcello.duarte@gmail.com"
458
- }
459
- ],
460
- "description": "Highly opinionated mocking framework for PHP 5.3+",
461
- "homepage": "https://github.com/phpspec/prophecy",
462
- "keywords": [
463
- "Double",
464
- "Dummy",
465
- "fake",
466
- "mock",
467
- "spy",
468
- "stub"
469
- ],
470
- "support": {
471
- "issues": "https://github.com/phpspec/prophecy/issues",
472
- "source": "https://github.com/phpspec/prophecy/tree/1.12.2"
473
- },
474
- "time": "2020-12-19T10:15:11+00:00"
475
- },
476
- {
477
- "name": "phpunit/php-code-coverage",
478
- "version": "6.1.4",
479
- "source": {
480
- "type": "git",
481
- "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
482
- "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
483
- },
484
- "dist": {
485
- "type": "zip",
486
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
487
- "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
488
- "shasum": ""
489
- },
490
- "require": {
491
- "ext-dom": "*",
492
- "ext-xmlwriter": "*",
493
- "php": "^7.1",
494
- "phpunit/php-file-iterator": "^2.0",
495
- "phpunit/php-text-template": "^1.2.1",
496
- "phpunit/php-token-stream": "^3.0",
497
- "sebastian/code-unit-reverse-lookup": "^1.0.1",
498
- "sebastian/environment": "^3.1 || ^4.0",
499
- "sebastian/version": "^2.0.1",
500
- "theseer/tokenizer": "^1.1"
501
- },
502
- "require-dev": {
503
- "phpunit/phpunit": "^7.0"
504
- },
505
- "suggest": {
506
- "ext-xdebug": "^2.6.0"
507
- },
508
- "type": "library",
509
- "extra": {
510
- "branch-alias": {
511
- "dev-master": "6.1-dev"
512
- }
513
- },
514
- "autoload": {
515
- "classmap": [
516
- "src/"
517
- ]
518
- },
519
- "notification-url": "https://packagist.org/downloads/",
520
- "license": [
521
- "BSD-3-Clause"
522
- ],
523
- "authors": [
524
- {
525
- "name": "Sebastian Bergmann",
526
- "email": "sebastian@phpunit.de",
527
- "role": "lead"
528
- }
529
- ],
530
- "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
531
- "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
532
- "keywords": [
533
- "coverage",
534
- "testing",
535
- "xunit"
536
- ],
537
- "support": {
538
- "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
539
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master"
540
- },
541
- "time": "2018-10-31T16:06:48+00:00"
542
- },
543
- {
544
- "name": "phpunit/php-file-iterator",
545
- "version": "2.0.x-dev",
546
- "source": {
547
- "type": "git",
548
- "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
549
- "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
550
- },
551
- "dist": {
552
- "type": "zip",
553
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
554
- "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
555
- "shasum": ""
556
- },
557
- "require": {
558
- "php": ">=7.1"
559
- },
560
- "require-dev": {
561
- "phpunit/phpunit": "^8.5"
562
- },
563
- "type": "library",
564
- "extra": {
565
- "branch-alias": {
566
- "dev-master": "2.0.x-dev"
567
- }
568
- },
569
- "autoload": {
570
- "classmap": [
571
- "src/"
572
- ]
573
- },
574
- "notification-url": "https://packagist.org/downloads/",
575
- "license": [
576
- "BSD-3-Clause"
577
- ],
578
- "authors": [
579
- {
580
- "name": "Sebastian Bergmann",
581
- "email": "sebastian@phpunit.de",
582
- "role": "lead"
583
- }
584
- ],
585
- "description": "FilterIterator implementation that filters files based on a list of suffixes.",
586
- "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
587
- "keywords": [
588
- "filesystem",
589
- "iterator"
590
- ],
591
- "support": {
592
- "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
593
- "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0"
594
- },
595
- "funding": [
596
- {
597
- "url": "https://github.com/sebastianbergmann",
598
- "type": "github"
599
- }
600
- ],
601
- "time": "2020-11-30T08:25:21+00:00"
602
- },
603
- {
604
- "name": "phpunit/php-text-template",
605
- "version": "1.2.1",
606
- "source": {
607
- "type": "git",
608
- "url": "https://github.com/sebastianbergmann/php-text-template.git",
609
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
610
- },
611
- "dist": {
612
- "type": "zip",
613
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
614
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
615
- "shasum": ""
616
- },
617
- "require": {
618
- "php": ">=5.3.3"
619
- },
620
- "type": "library",
621
- "autoload": {
622
- "classmap": [
623
- "src/"
624
- ]
625
- },
626
- "notification-url": "https://packagist.org/downloads/",
627
- "license": [
628
- "BSD-3-Clause"
629
- ],
630
- "authors": [
631
- {
632
- "name": "Sebastian Bergmann",
633
- "email": "sebastian@phpunit.de",
634
- "role": "lead"
635
- }
636
- ],
637
- "description": "Simple template engine.",
638
- "homepage": "https://github.com/sebastianbergmann/php-text-template/",
639
- "keywords": [
640
- "template"
641
- ],
642
- "support": {
643
- "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
644
- "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
645
- },
646
- "time": "2015-06-21T13:50:34+00:00"
647
- },
648
- {
649
- "name": "phpunit/php-timer",
650
- "version": "2.1.x-dev",
651
- "source": {
652
- "type": "git",
653
- "url": "https://github.com/sebastianbergmann/php-timer.git",
654
- "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
655
- },
656
- "dist": {
657
- "type": "zip",
658
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
659
- "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
660
- "shasum": ""
661
- },
662
- "require": {
663
- "php": ">=7.1"
664
- },
665
- "require-dev": {
666
- "phpunit/phpunit": "^8.5"
667
- },
668
- "type": "library",
669
- "extra": {
670
- "branch-alias": {
671
- "dev-master": "2.1-dev"
672
- }
673
- },
674
- "autoload": {
675
- "classmap": [
676
- "src/"
677
- ]
678
- },
679
- "notification-url": "https://packagist.org/downloads/",
680
- "license": [
681
- "BSD-3-Clause"
682
- ],
683
- "authors": [
684
- {
685
- "name": "Sebastian Bergmann",
686
- "email": "sebastian@phpunit.de",
687
- "role": "lead"
688
- }
689
- ],
690
- "description": "Utility class for timing",
691
- "homepage": "https://github.com/sebastianbergmann/php-timer/",
692
- "keywords": [
693
- "timer"
694
- ],
695
- "support": {
696
- "issues": "https://github.com/sebastianbergmann/php-timer/issues",
697
- "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1"
698
- },
699
- "funding": [
700
- {
701
- "url": "https://github.com/sebastianbergmann",
702
- "type": "github"
703
- }
704
- ],
705
- "time": "2020-11-30T08:20:02+00:00"
706
- },
707
- {
708
- "name": "phpunit/php-token-stream",
709
- "version": "3.1.x-dev",
710
- "source": {
711
- "type": "git",
712
- "url": "https://github.com/sebastianbergmann/php-token-stream.git",
713
- "reference": "472b687829041c24b25f475e14c2f38a09edf1c2"
714
- },
715
- "dist": {
716
- "type": "zip",
717
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2",
718
- "reference": "472b687829041c24b25f475e14c2f38a09edf1c2",
719
- "shasum": ""
720
- },
721
- "require": {
722
- "ext-tokenizer": "*",
723
- "php": ">=7.1"
724
- },
725
- "require-dev": {
726
- "phpunit/phpunit": "^7.0"
727
- },
728
- "type": "library",
729
- "extra": {
730
- "branch-alias": {
731
- "dev-master": "3.1-dev"
732
- }
733
- },
734
- "autoload": {
735
- "classmap": [
736
- "src/"
737
- ]
738
- },
739
- "notification-url": "https://packagist.org/downloads/",
740
- "license": [
741
- "BSD-3-Clause"
742
- ],
743
- "authors": [
744
- {
745
- "name": "Sebastian Bergmann",
746
- "email": "sebastian@phpunit.de"
747
- }
748
- ],
749
- "description": "Wrapper around PHP's tokenizer extension.",
750
- "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
751
- "keywords": [
752
- "tokenizer"
753
- ],
754
- "support": {
755
- "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
756
- "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1"
757
- },
758
- "funding": [
759
- {
760
- "url": "https://github.com/sebastianbergmann",
761
- "type": "github"
762
- }
763
- ],
764
- "abandoned": true,
765
- "time": "2020-11-30T08:38:46+00:00"
766
- },
767
- {
768
- "name": "phpunit/phpunit",
769
- "version": "7.5.20",
770
- "source": {
771
- "type": "git",
772
- "url": "https://github.com/sebastianbergmann/phpunit.git",
773
- "reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
774
- },
775
- "dist": {
776
- "type": "zip",
777
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
778
- "reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
779
- "shasum": ""
780
- },
781
- "require": {
782
- "doctrine/instantiator": "^1.1",
783
- "ext-dom": "*",
784
- "ext-json": "*",
785
- "ext-libxml": "*",
786
- "ext-mbstring": "*",
787
- "ext-xml": "*",
788
- "myclabs/deep-copy": "^1.7",
789
- "phar-io/manifest": "^1.0.2",
790
- "phar-io/version": "^2.0",
791
- "php": "^7.1",
792
- "phpspec/prophecy": "^1.7",
793
- "phpunit/php-code-coverage": "^6.0.7",
794
- "phpunit/php-file-iterator": "^2.0.1",
795
- "phpunit/php-text-template": "^1.2.1",
796
- "phpunit/php-timer": "^2.1",
797
- "sebastian/comparator": "^3.0",
798
- "sebastian/diff": "^3.0",
799
- "sebastian/environment": "^4.0",
800
- "sebastian/exporter": "^3.1",
801
- "sebastian/global-state": "^2.0",
802
- "sebastian/object-enumerator": "^3.0.3",
803
- "sebastian/resource-operations": "^2.0",
804
- "sebastian/version": "^2.0.1"
805
- },
806
- "conflict": {
807
- "phpunit/phpunit-mock-objects": "*"
808
- },
809
- "require-dev": {
810
- "ext-pdo": "*"
811
- },
812
- "suggest": {
813
- "ext-soap": "*",
814
- "ext-xdebug": "*",
815
- "phpunit/php-invoker": "^2.0"
816
- },
817
- "bin": [
818
- "phpunit"
819
- ],
820
- "type": "library",
821
- "extra": {
822
- "branch-alias": {
823
- "dev-master": "7.5-dev"
824
- }
825
- },
826
- "autoload": {
827
- "classmap": [
828
- "src/"
829
- ]
830
- },
831
- "notification-url": "https://packagist.org/downloads/",
832
- "license": [
833
- "BSD-3-Clause"
834
- ],
835
- "authors": [
836
- {
837
- "name": "Sebastian Bergmann",
838
- "email": "sebastian@phpunit.de",
839
- "role": "lead"
840
- }
841
- ],
842
- "description": "The PHP Unit Testing framework.",
843
- "homepage": "https://phpunit.de/",
844
- "keywords": [
845
- "phpunit",
846
- "testing",
847
- "xunit"
848
- ],
849
- "support": {
850
- "issues": "https://github.com/sebastianbergmann/phpunit/issues",
851
- "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20"
852
- },
853
- "time": "2020-01-08T08:45:45+00:00"
854
- },
855
- {
856
- "name": "sebastian/code-unit-reverse-lookup",
857
- "version": "1.0.x-dev",
858
- "source": {
859
- "type": "git",
860
- "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
861
- "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
862
- },
863
- "dist": {
864
- "type": "zip",
865
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
866
- "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
867
- "shasum": ""
868
- },
869
- "require": {
870
- "php": ">=5.6"
871
- },
872
- "require-dev": {
873
- "phpunit/phpunit": "^8.5"
874
- },
875
- "type": "library",
876
- "extra": {
877
- "branch-alias": {
878
- "dev-master": "1.0.x-dev"
879
- }
880
- },
881
- "autoload": {
882
- "classmap": [
883
- "src/"
884
- ]
885
- },
886
- "notification-url": "https://packagist.org/downloads/",
887
- "license": [
888
- "BSD-3-Clause"
889
- ],
890
- "authors": [
891
- {
892
- "name": "Sebastian Bergmann",
893
- "email": "sebastian@phpunit.de"
894
- }
895
- ],
896
- "description": "Looks up which function or method a line of code belongs to",
897
- "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
898
- "support": {
899
- "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
900
- "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0"
901
- },
902
- "funding": [
903
- {
904
- "url": "https://github.com/sebastianbergmann",
905
- "type": "github"
906
- }
907
- ],
908
- "time": "2020-11-30T08:15:22+00:00"
909
- },
910
- {
911
- "name": "sebastian/comparator",
912
- "version": "3.0.x-dev",
913
- "source": {
914
- "type": "git",
915
- "url": "https://github.com/sebastianbergmann/comparator.git",
916
- "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
917
- },
918
- "dist": {
919
- "type": "zip",
920
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
921
- "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
922
- "shasum": ""
923
- },
924
- "require": {
925
- "php": ">=7.1",
926
- "sebastian/diff": "^3.0",
927
- "sebastian/exporter": "^3.1"
928
- },
929
- "require-dev": {
930
- "phpunit/phpunit": "^8.5"
931
- },
932
- "type": "library",
933
- "extra": {
934
- "branch-alias": {
935
- "dev-master": "3.0-dev"
936
- }
937
- },
938
- "autoload": {
939
- "classmap": [
940
- "src/"
941
- ]
942
- },
943
- "notification-url": "https://packagist.org/downloads/",
944
- "license": [
945
- "BSD-3-Clause"
946
- ],
947
- "authors": [
948
- {
949
- "name": "Sebastian Bergmann",
950
- "email": "sebastian@phpunit.de"
951
- },
952
- {
953
- "name": "Jeff Welch",
954
- "email": "whatthejeff@gmail.com"
955
- },
956
- {
957
- "name": "Volker Dusch",
958
- "email": "github@wallbash.com"
959
- },
960
- {
961
- "name": "Bernhard Schussek",
962
- "email": "bschussek@2bepublished.at"
963
- }
964
- ],
965
- "description": "Provides the functionality to compare PHP values for equality",
966
- "homepage": "https://github.com/sebastianbergmann/comparator",
967
- "keywords": [
968
- "comparator",
969
- "compare",
970
- "equality"
971
- ],
972
- "support": {
973
- "issues": "https://github.com/sebastianbergmann/comparator/issues",
974
- "source": "https://github.com/sebastianbergmann/comparator/tree/3.0"
975
- },
976
- "funding": [
977
- {
978
- "url": "https://github.com/sebastianbergmann",
979
- "type": "github"
980
- }
981
- ],
982
- "time": "2020-11-30T08:04:30+00:00"
983
- },
984
- {
985
- "name": "sebastian/diff",
986
- "version": "3.0.x-dev",
987
- "source": {
988
- "type": "git",
989
- "url": "https://github.com/sebastianbergmann/diff.git",
990
- "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
991
- },
992
- "dist": {
993
- "type": "zip",
994
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
995
- "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
996
- "shasum": ""
997
- },
998
- "require": {
999
- "php": ">=7.1"
1000
- },
1001
- "require-dev": {
1002
- "phpunit/phpunit": "^7.5 || ^8.0",
1003
- "symfony/process": "^2 || ^3.3 || ^4"
1004
- },
1005
- "type": "library",
1006
- "extra": {
1007
- "branch-alias": {
1008
- "dev-master": "3.0-dev"
1009
- }
1010
- },
1011
- "autoload": {
1012
- "classmap": [
1013
- "src/"
1014
- ]
1015
- },
1016
- "notification-url": "https://packagist.org/downloads/",
1017
- "license": [
1018
- "BSD-3-Clause"
1019
- ],
1020
- "authors": [
1021
- {
1022
- "name": "Sebastian Bergmann",
1023
- "email": "sebastian@phpunit.de"
1024
- },
1025
- {
1026
- "name": "Kore Nordmann",
1027
- "email": "mail@kore-nordmann.de"
1028
- }
1029
- ],
1030
- "description": "Diff implementation",
1031
- "homepage": "https://github.com/sebastianbergmann/diff",
1032
- "keywords": [
1033
- "diff",
1034
- "udiff",
1035
- "unidiff",
1036
- "unified diff"
1037
- ],
1038
- "support": {
1039
- "issues": "https://github.com/sebastianbergmann/diff/issues",
1040
- "source": "https://github.com/sebastianbergmann/diff/tree/3.0"
1041
- },
1042
- "funding": [
1043
- {
1044
- "url": "https://github.com/sebastianbergmann",
1045
- "type": "github"
1046
- }
1047
- ],
1048
- "time": "2020-11-30T07:59:04+00:00"
1049
- },
1050
- {
1051
- "name": "sebastian/environment",
1052
- "version": "4.2.x-dev",
1053
- "source": {
1054
- "type": "git",
1055
- "url": "https://github.com/sebastianbergmann/environment.git",
1056
- "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
1057
- },
1058
- "dist": {
1059
- "type": "zip",
1060
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
1061
- "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
1062
- "shasum": ""
1063
- },
1064
- "require": {
1065
- "php": ">=7.1"
1066
- },
1067
- "require-dev": {
1068
- "phpunit/phpunit": "^7.5"
1069
- },
1070
- "suggest": {
1071
- "ext-posix": "*"
1072
- },
1073
- "type": "library",
1074
- "extra": {
1075
- "branch-alias": {
1076
- "dev-master": "4.2-dev"
1077
- }
1078
- },
1079
- "autoload": {
1080
- "classmap": [
1081
- "src/"
1082
- ]
1083
- },
1084
- "notification-url": "https://packagist.org/downloads/",
1085
- "license": [
1086
- "BSD-3-Clause"
1087
- ],
1088
- "authors": [
1089
- {
1090
- "name": "Sebastian Bergmann",
1091
- "email": "sebastian@phpunit.de"
1092
- }
1093
- ],
1094
- "description": "Provides functionality to handle HHVM/PHP environments",
1095
- "homepage": "http://www.github.com/sebastianbergmann/environment",
1096
- "keywords": [
1097
- "Xdebug",
1098
- "environment",
1099
- "hhvm"
1100
- ],
1101
- "support": {
1102
- "issues": "https://github.com/sebastianbergmann/environment/issues",
1103
- "source": "https://github.com/sebastianbergmann/environment/tree/4.2"
1104
- },
1105
- "funding": [
1106
- {
1107
- "url": "https://github.com/sebastianbergmann",
1108
- "type": "github"
1109
- }
1110
- ],
1111
- "time": "2020-11-30T07:53:42+00:00"
1112
- },
1113
- {
1114
- "name": "sebastian/exporter",
1115
- "version": "3.1.x-dev",
1116
- "source": {
1117
- "type": "git",
1118
- "url": "https://github.com/sebastianbergmann/exporter.git",
1119
- "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
1120
- },
1121
- "dist": {
1122
- "type": "zip",
1123
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
1124
- "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
1125
- "shasum": ""
1126
- },
1127
- "require": {
1128
- "php": ">=7.0",
1129
- "sebastian/recursion-context": "^3.0"
1130
- },
1131
- "require-dev": {
1132
- "ext-mbstring": "*",
1133
- "phpunit/phpunit": "^6.0"
1134
- },
1135
- "type": "library",
1136
- "extra": {
1137
- "branch-alias": {
1138
- "dev-master": "3.1.x-dev"
1139
- }
1140
- },
1141
- "autoload": {
1142
- "classmap": [
1143
- "src/"
1144
- ]
1145
- },
1146
- "notification-url": "https://packagist.org/downloads/",
1147
- "license": [
1148
- "BSD-3-Clause"
1149
- ],
1150
- "authors": [
1151
- {
1152
- "name": "Sebastian Bergmann",
1153
- "email": "sebastian@phpunit.de"
1154
- },
1155
- {
1156
- "name": "Jeff Welch",
1157
- "email": "whatthejeff@gmail.com"
1158
- },
1159
- {
1160
- "name": "Volker Dusch",
1161
- "email": "github@wallbash.com"
1162
- },
1163
- {
1164
- "name": "Adam Harvey",
1165
- "email": "aharvey@php.net"
1166
- },
1167
- {
1168
- "name": "Bernhard Schussek",
1169
- "email": "bschussek@gmail.com"
1170
- }
1171
- ],
1172
- "description": "Provides the functionality to export PHP variables for visualization",
1173
- "homepage": "http://www.github.com/sebastianbergmann/exporter",
1174
- "keywords": [
1175
- "export",
1176
- "exporter"
1177
- ],
1178
- "support": {
1179
- "issues": "https://github.com/sebastianbergmann/exporter/issues",
1180
- "source": "https://github.com/sebastianbergmann/exporter/tree/3.1"
1181
- },
1182
- "funding": [
1183
- {
1184
- "url": "https://github.com/sebastianbergmann",
1185
- "type": "github"
1186
- }
1187
- ],
1188
- "time": "2020-11-30T07:47:53+00:00"
1189
- },
1190
- {
1191
- "name": "sebastian/global-state",
1192
- "version": "2.0.0",
1193
- "source": {
1194
- "type": "git",
1195
- "url": "https://github.com/sebastianbergmann/global-state.git",
1196
- "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
1197
- },
1198
- "dist": {
1199
- "type": "zip",
1200
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
1201
- "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
1202
- "shasum": ""
1203
- },
1204
- "require": {
1205
- "php": "^7.0"
1206
- },
1207
- "require-dev": {
1208
- "phpunit/phpunit": "^6.0"
1209
- },
1210
- "suggest": {
1211
- "ext-uopz": "*"
1212
- },
1213
- "type": "library",
1214
- "extra": {
1215
- "branch-alias": {
1216
- "dev-master": "2.0-dev"
1217
- }
1218
- },
1219
- "autoload": {
1220
- "classmap": [
1221
- "src/"
1222
- ]
1223
- },
1224
- "notification-url": "https://packagist.org/downloads/",
1225
- "license": [
1226
- "BSD-3-Clause"
1227
- ],
1228
- "authors": [
1229
- {
1230
- "name": "Sebastian Bergmann",
1231
- "email": "sebastian@phpunit.de"
1232
- }
1233
- ],
1234
- "description": "Snapshotting of global state",
1235
- "homepage": "http://www.github.com/sebastianbergmann/global-state",
1236
- "keywords": [
1237
- "global state"
1238
- ],
1239
- "support": {
1240
- "issues": "https://github.com/sebastianbergmann/global-state/issues",
1241
- "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0"
1242
- },
1243
- "time": "2017-04-27T15:39:26+00:00"
1244
- },
1245
- {
1246
- "name": "sebastian/object-enumerator",
1247
- "version": "3.0.x-dev",
1248
- "source": {
1249
- "type": "git",
1250
- "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1251
- "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
1252
- },
1253
- "dist": {
1254
- "type": "zip",
1255
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
1256
- "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
1257
- "shasum": ""
1258
- },
1259
- "require": {
1260
- "php": ">=7.0",
1261
- "sebastian/object-reflector": "^1.1.1",
1262
- "sebastian/recursion-context": "^3.0"
1263
- },
1264
- "require-dev": {
1265
- "phpunit/phpunit": "^6.0"
1266
- },
1267
- "type": "library",
1268
- "extra": {
1269
- "branch-alias": {
1270
- "dev-master": "3.0.x-dev"
1271
- }
1272
- },
1273
- "autoload": {
1274
- "classmap": [
1275
- "src/"
1276
- ]
1277
- },
1278
- "notification-url": "https://packagist.org/downloads/",
1279
- "license": [
1280
- "BSD-3-Clause"
1281
- ],
1282
- "authors": [
1283
- {
1284
- "name": "Sebastian Bergmann",
1285
- "email": "sebastian@phpunit.de"
1286
- }
1287
- ],
1288
- "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1289
- "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1290
- "support": {
1291
- "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
1292
- "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0"
1293
- },
1294
- "funding": [
1295
- {
1296
- "url": "https://github.com/sebastianbergmann",
1297
- "type": "github"
1298
- }
1299
- ],
1300
- "time": "2020-11-30T07:40:27+00:00"
1301
- },
1302
- {
1303
- "name": "sebastian/object-reflector",
1304
- "version": "1.1.x-dev",
1305
- "source": {
1306
- "type": "git",
1307
- "url": "https://github.com/sebastianbergmann/object-reflector.git",
1308
- "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
1309
- },
1310
- "dist": {
1311
- "type": "zip",
1312
- "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
1313
- "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
1314
- "shasum": ""
1315
- },
1316
- "require": {
1317
- "php": ">=7.0"
1318
- },
1319
- "require-dev": {
1320
- "phpunit/phpunit": "^6.0"
1321
- },
1322
- "type": "library",
1323
- "extra": {
1324
- "branch-alias": {
1325
- "dev-master": "1.1-dev"
1326
- }
1327
- },
1328
- "autoload": {
1329
- "classmap": [
1330
- "src/"
1331
- ]
1332
- },
1333
- "notification-url": "https://packagist.org/downloads/",
1334
- "license": [
1335
- "BSD-3-Clause"
1336
- ],
1337
- "authors": [
1338
- {
1339
- "name": "Sebastian Bergmann",
1340
- "email": "sebastian@phpunit.de"
1341
- }
1342
- ],
1343
- "description": "Allows reflection of object attributes, including inherited and non-public ones",
1344
- "homepage": "https://github.com/sebastianbergmann/object-reflector/",
1345
- "support": {
1346
- "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
1347
- "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1"
1348
- },
1349
- "funding": [
1350
- {
1351
- "url": "https://github.com/sebastianbergmann",
1352
- "type": "github"
1353
- }
1354
- ],
1355
- "time": "2020-11-30T07:37:18+00:00"
1356
- },
1357
- {
1358
- "name": "sebastian/recursion-context",
1359
- "version": "3.0.x-dev",
1360
- "source": {
1361
- "type": "git",
1362
- "url": "https://github.com/sebastianbergmann/recursion-context.git",
1363
- "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
1364
- },
1365
- "dist": {
1366
- "type": "zip",
1367
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
1368
- "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
1369
- "shasum": ""
1370
- },
1371
- "require": {
1372
- "php": ">=7.0"
1373
- },
1374
- "require-dev": {
1375
- "phpunit/phpunit": "^6.0"
1376
- },
1377
- "type": "library",
1378
- "extra": {
1379
- "branch-alias": {
1380
- "dev-master": "3.0.x-dev"
1381
- }
1382
- },
1383
- "autoload": {
1384
- "classmap": [
1385
- "src/"
1386
- ]
1387
- },
1388
- "notification-url": "https://packagist.org/downloads/",
1389
- "license": [
1390
- "BSD-3-Clause"
1391
- ],
1392
- "authors": [
1393
- {
1394
- "name": "Sebastian Bergmann",
1395
- "email": "sebastian@phpunit.de"
1396
- },
1397
- {
1398
- "name": "Jeff Welch",
1399
- "email": "whatthejeff@gmail.com"
1400
- },
1401
- {
1402
- "name": "Adam Harvey",
1403
- "email": "aharvey@php.net"
1404
- }
1405
- ],
1406
- "description": "Provides functionality to recursively process PHP variables",
1407
- "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1408
- "support": {
1409
- "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
1410
- "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0"
1411
- },
1412
- "funding": [
1413
- {
1414
- "url": "https://github.com/sebastianbergmann",
1415
- "type": "github"
1416
- }
1417
- ],
1418
- "time": "2020-11-30T07:34:24+00:00"
1419
- },
1420
- {
1421
- "name": "sebastian/resource-operations",
1422
- "version": "2.0.x-dev",
1423
- "source": {
1424
- "type": "git",
1425
- "url": "https://github.com/sebastianbergmann/resource-operations.git",
1426
- "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
1427
- },
1428
- "dist": {
1429
- "type": "zip",
1430
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
1431
- "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
1432
- "shasum": ""
1433
- },
1434
- "require": {
1435
- "php": ">=7.1"
1436
- },
1437
- "type": "library",
1438
- "extra": {
1439
- "branch-alias": {
1440
- "dev-master": "2.0-dev"
1441
- }
1442
- },
1443
- "autoload": {
1444
- "classmap": [
1445
- "src/"
1446
- ]
1447
- },
1448
- "notification-url": "https://packagist.org/downloads/",
1449
- "license": [
1450
- "BSD-3-Clause"
1451
- ],
1452
- "authors": [
1453
- {
1454
- "name": "Sebastian Bergmann",
1455
- "email": "sebastian@phpunit.de"
1456
- }
1457
- ],
1458
- "description": "Provides a list of PHP built-in functions that operate on resources",
1459
- "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
1460
- "support": {
1461
- "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
1462
- "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0"
1463
- },
1464
- "funding": [
1465
- {
1466
- "url": "https://github.com/sebastianbergmann",
1467
- "type": "github"
1468
- }
1469
- ],
1470
- "time": "2020-11-30T07:30:19+00:00"
1471
- },
1472
- {
1473
- "name": "sebastian/version",
1474
- "version": "2.0.1",
1475
- "source": {
1476
- "type": "git",
1477
- "url": "https://github.com/sebastianbergmann/version.git",
1478
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
1479
- },
1480
- "dist": {
1481
- "type": "zip",
1482
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
1483
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
1484
- "shasum": ""
1485
- },
1486
- "require": {
1487
- "php": ">=5.6"
1488
- },
1489
- "type": "library",
1490
- "extra": {
1491
- "branch-alias": {
1492
- "dev-master": "2.0.x-dev"
1493
- }
1494
- },
1495
- "autoload": {
1496
- "classmap": [
1497
- "src/"
1498
- ]
1499
- },
1500
- "notification-url": "https://packagist.org/downloads/",
1501
- "license": [
1502
- "BSD-3-Clause"
1503
- ],
1504
- "authors": [
1505
- {
1506
- "name": "Sebastian Bergmann",
1507
- "email": "sebastian@phpunit.de",
1508
- "role": "lead"
1509
- }
1510
- ],
1511
- "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1512
- "homepage": "https://github.com/sebastianbergmann/version",
1513
- "support": {
1514
- "issues": "https://github.com/sebastianbergmann/version/issues",
1515
- "source": "https://github.com/sebastianbergmann/version/tree/master"
1516
- },
1517
- "time": "2016-10-03T07:35:21+00:00"
1518
- },
1519
- {
1520
- "name": "squizlabs/php_codesniffer",
1521
- "version": "dev-master",
1522
- "source": {
1523
- "type": "git",
1524
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
1525
- "reference": "75ff420b23c0866c99c350099381a93b8602ebe6"
1526
- },
1527
- "dist": {
1528
- "type": "zip",
1529
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/75ff420b23c0866c99c350099381a93b8602ebe6",
1530
- "reference": "75ff420b23c0866c99c350099381a93b8602ebe6",
1531
- "shasum": ""
1532
- },
1533
- "require": {
1534
- "ext-simplexml": "*",
1535
- "ext-tokenizer": "*",
1536
- "ext-xmlwriter": "*",
1537
- "php": ">=5.4.0"
1538
- },
1539
- "require-dev": {
1540
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
1541
- },
1542
- "bin": [
1543
- "bin/phpcs",
1544
- "bin/phpcbf"
1545
- ],
1546
- "type": "library",
1547
- "extra": {
1548
- "branch-alias": {
1549
- "dev-master": "3.x-dev"
1550
- }
1551
- },
1552
- "notification-url": "https://packagist.org/downloads/",
1553
- "license": [
1554
- "BSD-3-Clause"
1555
- ],
1556
- "authors": [
1557
- {
1558
- "name": "Greg Sherwood",
1559
- "role": "lead"
1560
- }
1561
- ],
1562
- "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
1563
- "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
1564
- "keywords": [
1565
- "phpcs",
1566
- "standards"
1567
- ],
1568
- "time": "2020-05-21T06:41:29+00:00"
1569
- },
1570
- {
1571
- "name": "symfony/polyfill-ctype",
1572
- "version": "dev-main",
1573
- "source": {
1574
- "type": "git",
1575
- "url": "https://github.com/symfony/polyfill-ctype.git",
1576
- "reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
1577
- },
1578
- "dist": {
1579
- "type": "zip",
1580
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
1581
- "reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
1582
- "shasum": ""
1583
- },
1584
- "require": {
1585
- "php": ">=7.1"
1586
- },
1587
- "suggest": {
1588
- "ext-ctype": "For best performance"
1589
- },
1590
- "default-branch": true,
1591
- "type": "library",
1592
- "extra": {
1593
- "branch-alias": {
1594
- "dev-main": "1.22-dev"
1595
- },
1596
- "thanks": {
1597
- "name": "symfony/polyfill",
1598
- "url": "https://github.com/symfony/polyfill"
1599
- }
1600
- },
1601
- "autoload": {
1602
- "psr-4": {
1603
- "Symfony\\Polyfill\\Ctype\\": ""
1604
- },
1605
- "files": [
1606
- "bootstrap.php"
1607
- ]
1608
- },
1609
- "notification-url": "https://packagist.org/downloads/",
1610
- "license": [
1611
- "MIT"
1612
- ],
1613
- "authors": [
1614
- {
1615
- "name": "Gert de Pagter",
1616
- "email": "BackEndTea@gmail.com"
1617
- },
1618
- {
1619
- "name": "Symfony Community",
1620
- "homepage": "https://symfony.com/contributors"
1621
- }
1622
- ],
1623
- "description": "Symfony polyfill for ctype functions",
1624
- "homepage": "https://symfony.com",
1625
- "keywords": [
1626
- "compatibility",
1627
- "ctype",
1628
- "polyfill",
1629
- "portable"
1630
- ],
1631
- "support": {
1632
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0"
1633
- },
1634
- "funding": [
1635
- {
1636
- "url": "https://symfony.com/sponsor",
1637
- "type": "custom"
1638
- },
1639
- {
1640
- "url": "https://github.com/fabpot",
1641
- "type": "github"
1642
- },
1643
- {
1644
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
1645
- "type": "tidelift"
1646
- }
1647
- ],
1648
- "time": "2021-01-07T16:49:33+00:00"
1649
- },
1650
- {
1651
- "name": "theseer/tokenizer",
1652
- "version": "1.2.0",
1653
- "source": {
1654
- "type": "git",
1655
- "url": "https://github.com/theseer/tokenizer.git",
1656
- "reference": "75a63c33a8577608444246075ea0af0d052e452a"
1657
- },
1658
- "dist": {
1659
- "type": "zip",
1660
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
1661
- "reference": "75a63c33a8577608444246075ea0af0d052e452a",
1662
- "shasum": ""
1663
- },
1664
- "require": {
1665
- "ext-dom": "*",
1666
- "ext-tokenizer": "*",
1667
- "ext-xmlwriter": "*",
1668
- "php": "^7.2 || ^8.0"
1669
- },
1670
- "type": "library",
1671
- "autoload": {
1672
- "classmap": [
1673
- "src/"
1674
- ]
1675
- },
1676
- "notification-url": "https://packagist.org/downloads/",
1677
- "license": [
1678
- "BSD-3-Clause"
1679
- ],
1680
- "authors": [
1681
- {
1682
- "name": "Arne Blankerts",
1683
- "email": "arne@blankerts.de",
1684
- "role": "Developer"
1685
- }
1686
- ],
1687
- "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
1688
- "support": {
1689
- "issues": "https://github.com/theseer/tokenizer/issues",
1690
- "source": "https://github.com/theseer/tokenizer/tree/master"
1691
- },
1692
- "funding": [
1693
- {
1694
- "url": "https://github.com/theseer",
1695
- "type": "github"
1696
- }
1697
- ],
1698
- "time": "2020-07-12T23:59:07+00:00"
1699
- },
1700
- {
1701
- "name": "webmozart/assert",
1702
- "version": "dev-master",
1703
- "source": {
1704
- "type": "git",
1705
- "url": "https://github.com/webmozarts/assert.git",
1706
- "reference": "9c89b265ccc4092d58e66d72af5d343ee77a41ae"
1707
- },
1708
- "dist": {
1709
- "type": "zip",
1710
- "url": "https://api.github.com/repos/webmozarts/assert/zipball/9c89b265ccc4092d58e66d72af5d343ee77a41ae",
1711
- "reference": "9c89b265ccc4092d58e66d72af5d343ee77a41ae",
1712
- "shasum": ""
1713
- },
1714
- "require": {
1715
- "php": "^7.2 || ^8.0",
1716
- "symfony/polyfill-ctype": "^1.8"
1717
- },
1718
- "conflict": {
1719
- "phpstan/phpstan": "<0.12.20",
1720
- "vimeo/psalm": "<3.9.1"
1721
- },
1722
- "require-dev": {
1723
- "phpunit/phpunit": "^8.5.13"
1724
- },
1725
- "default-branch": true,
1726
- "type": "library",
1727
- "extra": {
1728
- "branch-alias": {
1729
- "dev-master": "1.10-dev"
1730
- }
1731
- },
1732
- "autoload": {
1733
- "psr-4": {
1734
- "Webmozart\\Assert\\": "src/"
1735
- }
1736
- },
1737
- "notification-url": "https://packagist.org/downloads/",
1738
- "license": [
1739
- "MIT"
1740
- ],
1741
- "authors": [
1742
- {
1743
- "name": "Bernhard Schussek",
1744
- "email": "bschussek@gmail.com"
1745
- }
1746
- ],
1747
- "description": "Assertions to validate method input/output with nice error messages.",
1748
- "keywords": [
1749
- "assert",
1750
- "check",
1751
- "validate"
1752
- ],
1753
- "support": {
1754
- "issues": "https://github.com/webmozarts/assert/issues",
1755
- "source": "https://github.com/webmozarts/assert/tree/master"
1756
- },
1757
- "time": "2021-01-18T12:52:36+00:00"
1758
- }
1759
- ],
1760
- "aliases": [],
1761
- "minimum-stability": "dev",
1762
- "stability-flags": [],
1763
- "prefer-stable": false,
1764
- "prefer-lowest": false,
1765
- "platform": {
1766
- "php": ">=5.2.0"
1767
- },
1768
- "platform-dev": [],
1769
- "plugin-api-version": "2.0.0"
1770
  }
1
  {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "9f6bb942a3e83517b7d1712f1e1e49df",
8
+ "packages": [],
9
+ "packages-dev": [
10
+ {
11
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
12
+ "version": "dev-master",
13
+ "source": {
14
+ "type": "git",
15
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
16
+ "reference": "858566c0b7fe3798f91f1918e1b455636cbf57af"
17
+ },
18
+ "dist": {
19
+ "type": "zip",
20
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/858566c0b7fe3798f91f1918e1b455636cbf57af",
21
+ "reference": "858566c0b7fe3798f91f1918e1b455636cbf57af",
22
+ "shasum": ""
23
+ },
24
+ "require": {
25
+ "composer-plugin-api": "^1.0 || ^2.0",
26
+ "php": ">=5.3",
27
+ "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0"
28
+ },
29
+ "require-dev": {
30
+ "composer/composer": "*",
31
+ "php-parallel-lint/php-parallel-lint": "^1.3.1",
32
+ "phpcompatibility/php-compatibility": "^9.0"
33
+ },
34
+ "default-branch": true,
35
+ "type": "composer-plugin",
36
+ "extra": {
37
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
38
+ },
39
+ "autoload": {
40
+ "psr-4": {
41
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
42
+ }
43
+ },
44
+ "notification-url": "https://packagist.org/downloads/",
45
+ "license": ["MIT"],
46
+ "authors": [
47
+ {
48
+ "name": "Franck Nijhof",
49
+ "email": "franck.nijhof@dealerdirect.com",
50
+ "homepage": "http://www.frenck.nl",
51
+ "role": "Developer / IT Manager"
52
+ }
53
+ ],
54
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
55
+ "homepage": "http://www.dealerdirect.com",
56
+ "keywords": [
57
+ "PHPCodeSniffer",
58
+ "PHP_CodeSniffer",
59
+ "code quality",
60
+ "codesniffer",
61
+ "composer",
62
+ "installer",
63
+ "phpcs",
64
+ "plugin",
65
+ "qa",
66
+ "quality",
67
+ "standard",
68
+ "standards",
69
+ "style guide",
70
+ "stylecheck",
71
+ "tests"
72
+ ],
73
+ "support": {
74
+ "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
75
+ "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
76
+ },
77
+ "time": "2021-11-30T15:06:39+00:00"
78
+ },
79
+ {
80
+ "name": "phpcompatibility/php-compatibility",
81
+ "version": "9.3.5",
82
+ "source": {
83
+ "type": "git",
84
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
85
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
86
+ },
87
+ "dist": {
88
+ "type": "zip",
89
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
90
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
91
+ "shasum": ""
92
+ },
93
+ "require": {
94
+ "php": ">=5.3",
95
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
96
+ },
97
+ "conflict": {
98
+ "squizlabs/php_codesniffer": "2.6.2"
99
+ },
100
+ "require-dev": {
101
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
102
+ },
103
+ "suggest": {
104
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
105
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
106
+ },
107
+ "type": "phpcodesniffer-standard",
108
+ "notification-url": "https://packagist.org/downloads/",
109
+ "license": ["LGPL-3.0-or-later"],
110
+ "authors": [
111
+ {
112
+ "name": "Wim Godden",
113
+ "homepage": "https://github.com/wimg",
114
+ "role": "lead"
115
+ },
116
+ {
117
+ "name": "Juliette Reinders Folmer",
118
+ "homepage": "https://github.com/jrfnl",
119
+ "role": "lead"
120
+ },
121
+ {
122
+ "name": "Contributors",
123
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
124
+ }
125
+ ],
126
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
127
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
128
+ "keywords": ["compatibility", "phpcs", "standards"],
129
+ "support": {
130
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
131
+ "source": "https://github.com/PHPCompatibility/PHPCompatibility"
132
+ },
133
+ "time": "2019-12-27T09:44:58+00:00"
134
+ },
135
+ {
136
+ "name": "phpcompatibility/phpcompatibility-paragonie",
137
+ "version": "1.3.1",
138
+ "source": {
139
+ "type": "git",
140
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
141
+ "reference": "ddabec839cc003651f2ce695c938686d1086cf43"
142
+ },
143
+ "dist": {
144
+ "type": "zip",
145
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/ddabec839cc003651f2ce695c938686d1086cf43",
146
+ "reference": "ddabec839cc003651f2ce695c938686d1086cf43",
147
+ "shasum": ""
148
+ },
149
+ "require": {
150
+ "phpcompatibility/php-compatibility": "^9.0"
151
+ },
152
+ "require-dev": {
153
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7",
154
+ "paragonie/random_compat": "dev-master",
155
+ "paragonie/sodium_compat": "dev-master"
156
+ },
157
+ "suggest": {
158
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
159
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
160
+ },
161
+ "type": "phpcodesniffer-standard",
162
+ "notification-url": "https://packagist.org/downloads/",
163
+ "license": ["LGPL-3.0-or-later"],
164
+ "authors": [
165
+ {
166
+ "name": "Wim Godden",
167
+ "role": "lead"
168
+ },
169
+ {
170
+ "name": "Juliette Reinders Folmer",
171
+ "role": "lead"
172
+ }
173
+ ],
174
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
175
+ "homepage": "http://phpcompatibility.com/",
176
+ "keywords": [
177
+ "compatibility",
178
+ "paragonie",
179
+ "phpcs",
180
+ "polyfill",
181
+ "standards"
182
+ ],
183
+ "support": {
184
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
185
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
186
+ },
187
+ "time": "2021-02-15T10:24:51+00:00"
188
+ },
189
+ {
190
+ "name": "phpcompatibility/phpcompatibility-wp",
191
+ "version": "dev-master",
192
+ "source": {
193
+ "type": "git",
194
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
195
+ "reference": "d55de55f88697b9cdb94bccf04f14eb3b11cf308"
196
+ },
197
+ "dist": {
198
+ "type": "zip",
199
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/d55de55f88697b9cdb94bccf04f14eb3b11cf308",
200
+ "reference": "d55de55f88697b9cdb94bccf04f14eb3b11cf308",
201
+ "shasum": ""
202
+ },
203
+ "require": {
204
+ "phpcompatibility/php-compatibility": "^9.0",
205
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0"
206
+ },
207
+ "require-dev": {
208
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7"
209
+ },
210
+ "suggest": {
211
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
212
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
213
+ },
214
+ "default-branch": true,
215
+ "type": "phpcodesniffer-standard",
216
+ "notification-url": "https://packagist.org/downloads/",
217
+ "license": ["LGPL-3.0-or-later"],
218
+ "authors": [
219
+ {
220
+ "name": "Wim Godden",
221
+ "role": "lead"
222
+ },
223
+ {
224
+ "name": "Juliette Reinders Folmer",
225
+ "role": "lead"
226
+ }
227
+ ],
228
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
229
+ "homepage": "http://phpcompatibility.com/",
230
+ "keywords": ["compatibility", "phpcs", "standards", "wordpress"],
231
+ "support": {
232
+ "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
233
+ "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
234
+ },
235
+ "time": "2021-12-30T16:37:40+00:00"
236
+ },
237
+ {
238
+ "name": "squizlabs/php_codesniffer",
239
+ "version": "dev-master",
240
+ "source": {
241
+ "type": "git",
242
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
243
+ "reference": "67c82d975fc15b67725c7e4a0312bf8f28354c0d"
244
+ },
245
+ "dist": {
246
+ "type": "zip",
247
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/67c82d975fc15b67725c7e4a0312bf8f28354c0d",
248
+ "reference": "67c82d975fc15b67725c7e4a0312bf8f28354c0d",
249
+ "shasum": ""
250
+ },
251
+ "require": {
252
+ "ext-simplexml": "*",
253
+ "ext-tokenizer": "*",
254
+ "ext-xmlwriter": "*",
255
+ "php": ">=5.4.0"
256
+ },
257
+ "require-dev": {
258
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
259
+ },
260
+ "default-branch": true,
261
+ "bin": ["bin/phpcs", "bin/phpcbf"],
262
+ "type": "library",
263
+ "extra": {
264
+ "branch-alias": {
265
+ "dev-master": "3.x-dev"
266
+ }
267
+ },
268
+ "notification-url": "https://packagist.org/downloads/",
269
+ "license": ["BSD-3-Clause"],
270
+ "authors": [
271
+ {
272
+ "name": "Greg Sherwood",
273
+ "role": "lead"
274
+ }
275
+ ],
276
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
277
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
278
+ "keywords": ["phpcs", "standards"],
279
+ "support": {
280
+ "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
281
+ "source": "https://github.com/squizlabs/PHP_CodeSniffer",
282
+ "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
283
+ },
284
+ "time": "2021-12-20T06:20:45+00:00"
285
+ },
286
+ {
287
+ "name": "wp-coding-standards/wpcs",
288
+ "version": "2.3.0",
289
+ "source": {
290
+ "type": "git",
291
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
292
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62"
293
+ },
294
+ "dist": {
295
+ "type": "zip",
296
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
297
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62",
298
+ "shasum": ""
299
+ },
300
+ "require": {
301
+ "php": ">=5.4",
302
+ "squizlabs/php_codesniffer": "^3.3.1"
303
+ },
304
+ "require-dev": {
305
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
306
+ "phpcompatibility/php-compatibility": "^9.0",
307
+ "phpcsstandards/phpcsdevtools": "^1.0",
308
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
309
+ },
310
+ "suggest": {
311
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
312
+ },
313
+ "type": "phpcodesniffer-standard",
314
+ "notification-url": "https://packagist.org/downloads/",
315
+ "license": ["MIT"],
316
+ "authors": [
317
+ {
318
+ "name": "Contributors",
319
+ "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
320
+ }
321
+ ],
322
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
323
+ "keywords": ["phpcs", "standards", "wordpress"],
324
+ "support": {
325
+ "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
326
+ "source": "https://github.com/WordPress/WordPress-Coding-Standards",
327
+ "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
328
+ },
329
+ "time": "2020-05-13T23:57:56+00:00"
330
+ }
331
+ ],
332
+ "aliases": [],
333
+ "minimum-stability": "dev",
334
+ "stability-flags": [],
335
+ "prefer-stable": false,
336
+ "prefer-lowest": false,
337
+ "platform": {
338
+ "php": ">=5.2.0"
339
+ },
340
+ "platform-dev": [],
341
+ "plugin-api-version": "2.2.0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
css/styles.css CHANGED
@@ -231,10 +231,6 @@ Style different log levels.
231
  margin-top: 5px;
232
  }
233
 
234
- .SimpleHistoryLogitem:hover .SimpleHistoryLogitem__senderImage {
235
- /*opacity: 1;*/
236
- }
237
-
238
  .SimpleHistoryLogitem__senderImage img {
239
  display: block;
240
  max-width: 100%;
@@ -259,7 +255,6 @@ Style different log levels.
259
  .SimpleHistoryLogitem__text,
260
  .SimpleHistoryLogitem__details,
261
  .SimpleHistoryLogitem__details p {
262
- xfont-size: 16px;
263
  line-height: 1.4;
264
  }
265
 
@@ -378,14 +373,6 @@ Images/thumbs can be styles nicely
378
  height: auto;
379
  }
380
 
381
- /*
382
- when occasions are added
383
- */
384
- .SimpleHistoryLogitem--occasionsOpened {
385
- /*box-shadow: 0px 10px 10px -6px rgba(0, 0, 0, 0.3);
386
- z-index: 1;*/
387
- }
388
-
389
  /* remove border below */
390
  .SimpleHistoryLogitem--occasionsOpened::before {
391
  opacity: 0;
@@ -471,9 +458,6 @@ when occasions are added
471
  customizations for the dashboard
472
  i.e. the log is inside a .postbox element
473
  */
474
-
475
- .postbox {
476
- }
477
  #simple_history_dashboard_widget .inside {
478
  padding: 0;
479
  margin-top: 0;
@@ -497,10 +481,6 @@ i.e. the log is inside a .postbox element
497
  padding: 12px 5px 12px 16px;
498
  }
499
 
500
- .postbox .SimpleHistoryLogitem:first-child {
501
- /*padding-top: 0;*/
502
- }
503
-
504
  .postbox .SimpleHistoryLogitem::before {
505
  left: 55px;
506
  }
@@ -571,10 +551,6 @@ Styles for filter
571
  /*
572
  Pagination, below logRows
573
  */
574
-
575
- .SimpleHistoryLogitems__pagination {
576
- }
577
-
578
  .SimpleHistoryPaginationPages {
579
  text-align: center;
580
  padding-top: 20px;
@@ -625,9 +601,6 @@ animations/effects
625
  /*
626
  Modal window with detailss
627
  */
628
- .SimpleHistory-modal {
629
- }
630
-
631
  .SimpleHistory-modal__background {
632
  position: fixed;
633
  top: 0;
@@ -751,9 +724,6 @@ Modal window with detailss
751
  background: inherit;
752
  }
753
 
754
- .SimpleHistory-modal .SimpleHistoryLogitem__senderImage {
755
- /*opacity: 1;*/
756
- }
757
 
758
  .SimpleHistoryLogitem__moreDetails {
759
  border-top: 1px solid rgb(229, 229, 229);
@@ -888,8 +858,6 @@ Modal window with detailss
888
  }
889
 
890
  /* if not hits when using filter function + if ajax error */
891
- .SimpleHistory--hasNoHits {
892
- }
893
 
894
  .SimpleHistory--hasNoHits .SimpleHistoryLogitems__pagination {
895
  display: none;
231
  margin-top: 5px;
232
  }
233
 
 
 
 
 
234
  .SimpleHistoryLogitem__senderImage img {
235
  display: block;
236
  max-width: 100%;
255
  .SimpleHistoryLogitem__text,
256
  .SimpleHistoryLogitem__details,
257
  .SimpleHistoryLogitem__details p {
 
258
  line-height: 1.4;
259
  }
260
 
373
  height: auto;
374
  }
375
 
 
 
 
 
 
 
 
 
376
  /* remove border below */
377
  .SimpleHistoryLogitem--occasionsOpened::before {
378
  opacity: 0;
458
  customizations for the dashboard
459
  i.e. the log is inside a .postbox element
460
  */
 
 
 
461
  #simple_history_dashboard_widget .inside {
462
  padding: 0;
463
  margin-top: 0;
481
  padding: 12px 5px 12px 16px;
482
  }
483
 
 
 
 
 
484
  .postbox .SimpleHistoryLogitem::before {
485
  left: 55px;
486
  }
551
  /*
552
  Pagination, below logRows
553
  */
 
 
 
 
554
  .SimpleHistoryPaginationPages {
555
  text-align: center;
556
  padding-top: 20px;
601
  /*
602
  Modal window with detailss
603
  */
 
 
 
604
  .SimpleHistory-modal__background {
605
  position: fixed;
606
  top: 0;
724
  background: inherit;
725
  }
726
 
 
 
 
727
 
728
  .SimpleHistoryLogitem__moreDetails {
729
  border-top: 1px solid rgb(229, 229, 229);
858
  }
859
 
860
  /* if not hits when using filter function + if ajax error */
 
 
861
 
862
  .SimpleHistory--hasNoHits .SimpleHistoryLogitems__pagination {
863
  display: none;
dropins/SimpleHistoryDebugDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Dropin Name: Debug
@@ -8,54 +8,52 @@ defined('ABSPATH') or die();
8
  * Dropin URI: http://simple-history.com/
9
  * Author: Pär Thernström
10
  */
11
- class SimpleHistoryDebugDropin
12
- {
13
- public function __construct($sh)
14
- {
15
- // Bail if Simple History debug mode is not active.
16
- if (!defined('SIMPLE_HISTORY_LOG_DEBUG') || !SIMPLE_HISTORY_LOG_DEBUG) {
17
- return;
18
- }
19
-
20
- add_action('simple_history/log_argument/context', [$this, 'onLogArgumentContext'], 10, 4);
21
- }
22
-
23
- /**
24
- * Modify the context to add debug information.
25
- *
26
- * @param array $context
27
- * @param string $level
28
- * @param string $message
29
- * @param SimpleLogger $logger
30
- */
31
- public function onLogArgumentContext(array $context, string $level, string $message, SimpleLogger $logger)
32
- {
33
- $sh = SimpleHistory::get_instance();
34
- $context['_debug_get'] = $sh->json_encode($_GET);
35
- $context['_debug_post'] = $sh->json_encode($_POST);
36
- $context['_debug_server'] = $sh->json_encode($_SERVER);
37
- $context['_debug_files'] = $sh->json_encode($_FILES);
38
- $context['_debug_php_sapi_name'] = php_sapi_name();
39
-
40
- global $argv;
41
- $context['_debug_argv'] = $sh->json_encode($argv);
42
-
43
- $consts = get_defined_constants(true);
44
- $consts = $consts['user'];
45
- $context['_debug_user_constants'] = $sh->json_encode($consts);
46
-
47
- $postdata = file_get_contents('php://input');
48
- $context['_debug_http_raw_post_data'] = $sh->json_encode($postdata);
49
-
50
- $context['_debug_wp_debug_backtrace_summary'] = wp_debug_backtrace_summary();
51
- $context['_debug_is_admin'] = json_encode(is_admin());
52
- $context['_debug_is_ajax'] = json_encode(defined('DOING_AJAX') && DOING_AJAX);
53
- $context['_debug_is_doing_cron'] = json_encode(defined('DOING_CRON') && DOING_CRON);
54
-
55
- global $wp_current_filter;
56
- $context['_debug_current_filter_array'] = $wp_current_filter;
57
- $context['_debug_current_filter'] = current_filter();
58
-
59
- return $context;
60
- }
61
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Dropin Name: Debug
8
  * Dropin URI: http://simple-history.com/
9
  * Author: Pär Thernström
10
  */
11
+ class SimpleHistoryDebugDropin {
12
+
13
+ public function __construct( $sh ) {
14
+ // Bail if Simple History debug mode is not active.
15
+ if ( ! defined( 'SIMPLE_HISTORY_LOG_DEBUG' ) || ! SIMPLE_HISTORY_LOG_DEBUG ) {
16
+ return;
17
+ }
18
+
19
+ add_action( 'simple_history/log_argument/context', array( $this, 'onLogArgumentContext' ), 10, 4 );
20
+ }
21
+
22
+ /**
23
+ * Modify the context to add debug information.
24
+ *
25
+ * @param array $context
26
+ * @param string $level
27
+ * @param string $message
28
+ * @param SimpleLogger $logger
29
+ */
30
+ public function onLogArgumentContext( $context, $level, $message, $logger ) {
31
+ $sh = SimpleHistory::get_instance();
32
+ $context['_debug_get'] = $sh->json_encode( $_GET );
33
+ $context['_debug_post'] = $sh->json_encode( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
34
+ $context['_debug_server'] = $sh->json_encode( $_SERVER );
35
+ $context['_debug_files'] = $sh->json_encode( $_FILES );
36
+ $context['_debug_php_sapi_name'] = php_sapi_name();
37
+
38
+ global $argv;
39
+ $context['_debug_argv'] = $sh->json_encode( $argv );
40
+
41
+ $consts = get_defined_constants( true );
42
+ $consts = $consts['user'];
43
+ $context['_debug_user_constants'] = $sh->json_encode( $consts );
44
+
45
+ $postdata = file_get_contents( 'php://input' );
46
+ $context['_debug_http_raw_post_data'] = $sh->json_encode( $postdata );
47
+
48
+ $context['_debug_wp_debug_backtrace_summary'] = wp_debug_backtrace_summary();
49
+ $context['_debug_is_admin'] = json_encode( is_admin() );
50
+ $context['_debug_is_ajax'] = json_encode( defined( 'DOING_AJAX' ) && DOING_AJAX );
51
+ $context['_debug_is_doing_cron'] = json_encode( defined( 'DOING_CRON' ) && DOING_CRON );
52
+
53
+ global $wp_current_filter;
54
+ $context['_debug_current_filter_array'] = $wp_current_filter;
55
+ $context['_debug_current_filter'] = current_filter();
56
+
57
+ return $context;
58
+ }
 
 
59
  }
dropins/SimpleHistoryDonateDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Donate things
@@ -12,72 +12,56 @@ Author: Pär Thernström
12
  * Simple History Donate dropin
13
  * Put some donate messages here and there
14
  */
15
- class SimpleHistoryDonateDropin
16
- {
17
-
18
- // Simple History instance
19
- private $sh;
20
-
21
- public function __construct($sh)
22
- {
23
-
24
- $this->sh = $sh;
25
- add_action('admin_menu', array( $this, 'add_settings' ), 50);
26
- add_action('plugin_row_meta', array( $this, 'action_plugin_row_meta' ), 10, 2);
27
- }
28
-
29
- /**
30
- * Add link to the donate page in the Plugins » Installed plugins screen
31
- * Called from filter 'plugin_row_meta'
32
- */
33
- public function action_plugin_row_meta($links, $file)
34
- {
35
-
36
- if ($file == $this->sh->plugin_basename) {
37
- $links = array_merge(
38
- $links,
39
- array( sprintf('<a href="https://www.paypal.me/eskapism">%1$s</a>', __('Donate', "simple-history")) )
40
- );
41
- }
42
-
43
- return $links;
44
- }
45
-
46
- public function add_settings()
47
- {
48
-
49
- $settings_section_id = 'simple_history_settings_section_donate';
50
-
51
- add_settings_section(
52
- $settings_section_id,
53
- _x('Donate', 'donate settings headline', 'simple-history'), // No title __("General", "simple-history"),
54
- array( $this, 'settings_section_output' ),
55
- SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
56
- );
57
-
58
- // Empty section to make more room below
59
- /*
60
- add_settings_field(
61
- "simple_history_settings_donate",
62
- "", // __("Donate", "simple-history"),
63
- array($this, "settings_field_donate"),
64
- SimpleHistory::SETTINGS_MENU_SLUG,
65
- $settings_section_id
66
- );
67
- */
68
- }
69
-
70
- public function settings_section_output()
71
- {
72
-
73
- printf(
74
- __('If you find Simple History useful please <a href="%1$s">donate</a>.', "simple-history"),
75
- 'https://www.paypal.me/eskapism'
76
- );
77
- }
78
-
79
-
80
- public function settings_field_donate()
81
- {
82
- }
83
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Donate things
12
  * Simple History Donate dropin
13
  * Put some donate messages here and there
14
  */
15
+ class SimpleHistoryDonateDropin {
16
+ private $simple_history;
17
+
18
+ public function __construct( $simple_history ) {
19
+ $this->simple_history = $simple_history;
20
+
21
+ add_action( 'admin_menu', array( $this, 'add_settings' ), 50 );
22
+ add_action( 'plugin_row_meta', array( $this, 'action_plugin_row_meta' ), 10, 2 );
23
+ }
24
+
25
+ /**
26
+ * Add link to the donate page in the Plugins » Installed plugins screen
27
+ * Called from filter 'plugin_row_meta'
28
+ */
29
+ public function action_plugin_row_meta( $links, $file ) {
30
+
31
+ if ( $file == $this->simple_history->plugin_basename ) {
32
+ $links = array_merge(
33
+ $links,
34
+ array( sprintf( '<a href="https://www.paypal.me/eskapism">%1$s</a>', __( 'Donate', 'simple-history' ) ) )
35
+ );
36
+ }
37
+
38
+ return $links;
39
+ }
40
+
41
+ public function add_settings() {
42
+
43
+ $settings_section_id = 'simple_history_settings_section_donate';
44
+
45
+ add_settings_section(
46
+ $settings_section_id,
47
+ _x( 'Donate', 'donate settings headline', 'simple-history' ), // No title __("General", "simple-history"),
48
+ array( $this, 'settings_section_output' ),
49
+ SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
50
+ );
51
+ }
52
+
53
+ public function settings_section_output() {
54
+
55
+ printf(
56
+ wp_kses(
57
+ __( 'If you find Simple History useful please <a href="%1$s">donate</a>.', 'simple-history' ),
58
+ array(
59
+ 'a' => array(
60
+ 'href' => array(),
61
+ ),
62
+ )
63
+ ),
64
+ 'https://www.paypal.me/eskapism'
65
+ );
66
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
dropins/SimpleHistoryExportDropin.php CHANGED
@@ -6,247 +6,230 @@
6
  * Dropin URI: http://simple-history.com/
7
  * Author: Pär Thernström
8
  */
9
- class SimpleHistoryExportDropin
10
- {
11
-
12
- /**
13
- * Simple History instance.
14
- *
15
- * @var $sh
16
- */
17
- private $sh;
18
-
19
- /**
20
- * Constructor.
21
- *
22
- * @param instance $sh Simple History instance.
23
- */
24
- public function __construct($sh)
25
- {
26
-
27
- $this->sh = $sh;
28
-
29
- // Add tab to settings page.
30
- $sh->registerSettingsTab(array(
31
- 'slug' => 'export',
32
- 'name' => _x('Export', 'Export dropin: Tab name on settings page', 'simple-history'),
33
- 'function' => array( $this, 'output' ),
34
- ));
35
-
36
- add_action('init', array( $this, 'downloadExport' ));
37
- }
38
-
39
- public function downloadExport()
40
- {
41
-
42
- global $wpdb;
43
-
44
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
45
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
46
-
47
- if (isset($_POST['simple-history-action']) && $_POST['simple-history-action'] === 'export-history') {
48
- // Will die if nonce not valid
49
- check_admin_referer(__CLASS__ . '-action-export');
50
-
51
- $export_format = isset($_POST['format']) ? $_POST['format'] : 'json';
52
-
53
- // Disable relative time output in header
54
- add_filter('simple_history/header_time_ago_max_time', '__return_zero');
55
- add_filter('simple_history/header_just_now_max_time', '__return_zero');
56
-
57
- // Don't use "You" if event is initiated by the same user that does the export
58
- add_filter('simple_history/header_initiator_use_you', '__return_false');
59
-
60
- $query = new SimpleHistoryLogQuery();
61
-
62
- $query_args = array(
63
- 'paged' => 1,
64
- 'posts_per_page' => 3000,
65
- );
66
-
67
- $events = $query->query($query_args);
68
-
69
- // $events->total_row_count;
70
- $pages_count = $events['pages_count'];
71
- $page_current = $events['page_current'];
72
-
73
- $fp = fopen('php://output', 'w');
74
-
75
- $attachment_header_template = 'Content-Disposition: attachment; filename="%1$s"';
76
-
77
- if ('csv' == $export_format) {
78
- $filename = 'simple-history-export-' . time() . '.csv';
79
- header('Content-Type: text/plain');
80
- header(sprintf($attachment_header_template, $filename));
81
- } elseif ('json' == $export_format) {
82
- $filename = 'simple-history-export-' . time() . '.json';
83
- header('Content-Type: application/json');
84
- header(sprintf($attachment_header_template, $filename));
85
- } elseif ('html' == $export_format) {
86
- $filename = 'simple-history-export-' . time() . '.html';
87
- header('Content-Type: text/html');
88
- // header("Content-Disposition: attachment; filename='{$filename}'");
89
- }
90
-
91
- // Some formats need to output some stuff before the actual loops
92
- if ('json' == $export_format) {
93
- $json_row = '[';
94
- fwrite($fp, $json_row);
95
- } elseif ('html' == $export_format) {
96
- $html = sprintf(
97
- '
98
  <!doctype html>
99
  <meta charset="utf-8">
100
  <title>Simple History export</title>
101
  <ul>
102
  '
103
- );
104
- fwrite($fp, $html);
105
- }
106
-
107
- // Paginate through all pages and all their rows.
108
- $row_loop = 0;
109
- while ($page_current <= $pages_count + 1) {
110
- // if ($page_current > 1) { break; } # To debug/test
111
- foreach ($events['log_rows'] as $one_row) {
112
- // if ( $row_loop > 10) { break; } # To debug/test
113
- set_time_limit(30);
114
-
115
- if ('csv' == $export_format) {
116
- $header_output = strip_tags(html_entity_decode($this->sh->getLogRowHeaderOutput($one_row), ENT_QUOTES, 'UTF-8'));
117
- $header_output = trim(preg_replace('/\s\s+/', ' ', $header_output));
118
-
119
- $message_output = strip_tags(html_entity_decode($this->sh->getLogRowPlainTextOutput($one_row), ENT_QUOTES, 'UTF-8'));
120
-
121
- $user_email = empty($one_row->context['_user_email']) ? null : $one_row->context['_user_email'];
122
- $user_login = empty($one_row->context['_user_login']) ? null : $one_row->context['_user_login'];
123
-
124
- fputcsv($fp, array(
125
- $one_row->date,
126
- $one_row->logger,
127
- $one_row->level,
128
- $one_row->initiator,
129
- $one_row->context_message_key,
130
- $user_email,
131
- $user_login,
132
- $header_output,
133
- $message_output,
134
- $one_row->subsequentOccasions,
135
- ));
136
- } elseif ('json' == $export_format) {
137
- // If not first loop then add a comma between all json objects.
138
- if ($row_loop == 0) {
139
- $comma = "\n";
140
- } else {
141
- $comma = ",\n";
142
- }
143
-
144
- $json_row = $comma . $this->sh->json_encode($one_row);
145
- fwrite($fp, $json_row);
146
- } elseif ('html' == $export_format) {
147
- $html = sprintf(
148
- '
 
 
 
149
  <li>
150
  <div>%1$s</div>
151
  <div>%2$s</div>
152
  <div>%3$s</div>
153
  </li>
154
  ',
155
- $this->sh->getLogRowHeaderOutput($one_row),
156
- $this->sh->getLogRowPlainTextOutput($one_row),
157
- $this->sh->getLogRowDetailsOutput($one_row)
158
- );
159
-
160
- fwrite($fp, $html);
161
- }// End if().
162
-
163
- $row_loop++;
164
- }// End foreach().
165
-
166
- // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
167
- // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
168
- // echo "<br>fetch next page";
169
- flush();
170
-
171
- // Fetch next page
172
- // @TODO: must take into consideration that new items can be added while we do the fetch
173
- $page_current++;
174
- $query_args['paged'] = $page_current;
175
- $events = $query->query($query_args);
176
-
177
- // echo "<br>did fetch next page";
178
- // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
179
- // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
180
- }// End while().
181
-
182
- if ('json' == $export_format) {
183
- $json_row = ']';
184
- fwrite($fp, $json_row);
185
- } elseif ('html' == $export_format) {
186
- $html = sprintf('</ul>');
187
- fwrite($fp, $html);
188
- }
189
-
190
- fclose($fp);
191
- flush();
192
-
193
- exit;
194
-
195
- // echo "<br>done";
196
- }// End if().
197
- }
198
-
199
-
200
- public function output()
201
- {
202
-
203
- ?>
204
- <!-- <h2>Export</h2> -->
205
-
206
- <p><?php _ex('The export function will export the full history.', 'Export dropin: introtext', 'simple-history') ?></p>
207
-
208
- <form method="post">
209
-
210
- <h3><?php _ex('Choose format to export to', 'Export dropin: format', 'simple-history') ?></h3>
211
-
212
- <p>
213
- <label>
214
- <input type="radio" name="format" value="json" checked>
215
- <?php _ex('JSON', 'Export dropin: export format', 'simple-history') ?>
216
- </label>
217
- </p>
218
-
219
- <p>
220
- <label>
221
- <input type="radio" name="format" value="csv">
222
- <?php _ex('CSV', 'Export dropin: export format', 'simple-history') ?>
223
- </label>
224
- </p>
225
-
226
- <!-- <br> -->
227
-
228
- <!--<label>
229
- <input type="radio" name="format" value="html">
230
- HTML
231
- </label>
232
- <br> -->
233
-
234
- <!-- <label>
235
- <input type="radio" name="format" value="xml">
236
- XML
237
- </label> -->
238
-
239
- <p>
240
- <button type="submit" class="button button-primary"><?php _ex('Download Export File', 'Export dropin: submit button', 'simple-history') ?></button>
241
- <input type="hidden" name="simple-history-action" value="export-history">
242
- </p>
243
-
244
- <?php
245
- wp_nonce_field(__CLASS__ . '-action-export');
246
- ?>
247
-
248
- </form>
249
-
250
- <?php
251
- }
252
  }
6
  * Dropin URI: http://simple-history.com/
7
  * Author: Pär Thernström
8
  */
9
+ class SimpleHistoryExportDropin {
10
+ /**
11
+ * Simple History instance.
12
+ *
13
+ * @var $sh
14
+ */
15
+ private $sh;
16
+
17
+ /**
18
+ * Constructor.
19
+ *
20
+ * @param instance $sh Simple History instance.
21
+ */
22
+ public function __construct( $sh ) {
23
+
24
+ $this->sh = $sh;
25
+
26
+ // Add tab to settings page.
27
+ $sh->registerSettingsTab(
28
+ array(
29
+ 'slug' => 'export',
30
+ 'name' => _x( 'Export', 'Export dropin: Tab name on settings page', 'simple-history' ),
31
+ 'function' => array( $this, 'output' ),
32
+ )
33
+ );
34
+
35
+ add_action( 'init', array( $this, 'downloadExport' ) );
36
+ }
37
+
38
+ public function downloadExport() {
39
+
40
+ global $wpdb;
41
+
42
+ if ( isset( $_POST['simple-history-action'] ) && $_POST['simple-history-action'] === 'export-history' ) {
43
+ // Will die if nonce not valid.
44
+ check_admin_referer( __CLASS__ . '-action-export' );
45
+
46
+ $export_format = isset( $_POST['format'] ) ? $_POST['format'] : 'json';
47
+
48
+ // Disable relative time output in header.
49
+ add_filter( 'simple_history/header_time_ago_max_time', '__return_zero' );
50
+ add_filter( 'simple_history/header_just_now_max_time', '__return_zero' );
51
+
52
+ // Don't use "You" if event is initiated by the same user that does the export
53
+ add_filter( 'simple_history/header_initiator_use_you', '__return_false' );
54
+
55
+ $query = new SimpleHistoryLogQuery();
56
+
57
+ $query_args = array(
58
+ 'paged' => 1,
59
+ 'posts_per_page' => 3000,
60
+ );
61
+
62
+ $events = $query->query( $query_args );
63
+
64
+ // $events->total_row_count;
65
+ $pages_count = $events['pages_count'];
66
+ $page_current = $events['page_current'];
67
+
68
+ $fp = fopen( 'php://output', 'w' );
69
+
70
+ $attachment_header_template = 'Content-Disposition: attachment; filename="%1$s"';
71
+
72
+ if ( 'csv' == $export_format ) {
73
+ $filename = 'simple-history-export-' . time() . '.csv';
74
+ header( 'Content-Type: text/plain' );
75
+ header( sprintf( $attachment_header_template, $filename ) );
76
+ } elseif ( 'json' == $export_format ) {
77
+ $filename = 'simple-history-export-' . time() . '.json';
78
+ header( 'Content-Type: application/json' );
79
+ header( sprintf( $attachment_header_template, $filename ) );
80
+ } elseif ( 'html' == $export_format ) {
81
+ $filename = 'simple-history-export-' . time() . '.html';
82
+ header( 'Content-Type: text/html' );
83
+ // header("Content-Disposition: attachment; filename='{$filename}'");
84
+ }
85
+
86
+ // Some formats need to output some stuff before the actual loops
87
+ if ( 'json' == $export_format ) {
88
+ $json_row = '[';
89
+ fwrite( $fp, $json_row );
90
+ } elseif ( 'html' == $export_format ) {
91
+ $html = sprintf(
92
+ '
 
 
 
 
 
93
  <!doctype html>
94
  <meta charset="utf-8">
95
  <title>Simple History export</title>
96
  <ul>
97
  '
98
+ );
99
+ fwrite( $fp, $html );
100
+ }
101
+
102
+ // Paginate through all pages and all their rows.
103
+ $row_loop = 0;
104
+ while ( $page_current <= $pages_count + 1 ) {
105
+ // if ($page_current > 1) { break; } # To debug/test
106
+ foreach ( $events['log_rows'] as $one_row ) {
107
+ // if ( $row_loop > 10) { break; } # To debug/test
108
+ set_time_limit( 30 );
109
+
110
+ if ( 'csv' == $export_format ) {
111
+ $header_output = strip_tags( html_entity_decode( $this->sh->getLogRowHeaderOutput( $one_row ), ENT_QUOTES, 'UTF-8' ) );
112
+ $header_output = trim( preg_replace( '/\s\s+/', ' ', $header_output ) );
113
+
114
+ $message_output = strip_tags( html_entity_decode( $this->sh->getLogRowPlainTextOutput( $one_row ), ENT_QUOTES, 'UTF-8' ) );
115
+
116
+ $user_email = empty( $one_row->context['_user_email'] ) ? null : $one_row->context['_user_email'];
117
+ $user_login = empty( $one_row->context['_user_login'] ) ? null : $one_row->context['_user_login'];
118
+
119
+ fputcsv(
120
+ $fp,
121
+ array(
122
+ $one_row->date,
123
+ $one_row->logger,
124
+ $one_row->level,
125
+ $one_row->initiator,
126
+ $one_row->context_message_key,
127
+ $user_email,
128
+ $user_login,
129
+ $header_output,
130
+ $message_output,
131
+ $one_row->subsequentOccasions,
132
+ )
133
+ );
134
+ } elseif ( 'json' == $export_format ) {
135
+ // If not first loop then add a comma between all json objects.
136
+ if ( $row_loop == 0 ) {
137
+ $comma = "\n";
138
+ } else {
139
+ $comma = ",\n";
140
+ }
141
+
142
+ $json_row = $comma . $this->sh->json_encode( $one_row );
143
+ fwrite( $fp, $json_row );
144
+ } elseif ( 'html' == $export_format ) {
145
+ $html = sprintf(
146
+ '
147
  <li>
148
  <div>%1$s</div>
149
  <div>%2$s</div>
150
  <div>%3$s</div>
151
  </li>
152
  ',
153
+ $this->sh->getLogRowHeaderOutput( $one_row ),
154
+ $this->sh->getLogRowPlainTextOutput( $one_row ),
155
+ $this->sh->getLogRowDetailsOutput( $one_row )
156
+ );
157
+
158
+ fwrite( $fp, $html );
159
+ }// End if().
160
+
161
+ $row_loop++;
162
+ }// End foreach().
163
+
164
+ // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
165
+ // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
166
+ // echo "<br>fetch next page";
167
+ flush();
168
+
169
+ // Fetch next page
170
+ // @TODO: must take into consideration that new items can be added while we do the fetch
171
+ $page_current++;
172
+ $query_args['paged'] = $page_current;
173
+ $events = $query->query( $query_args );
174
+
175
+ // echo "<br>did fetch next page";
176
+ // echo "<br>memory_get_usage:<br>"; print_r(memory_get_usage());
177
+ // echo "<br>memory_get_peak_usage:<br>"; print_r(memory_get_peak_usage());
178
+ }// End while().
179
+
180
+ if ( 'json' == $export_format ) {
181
+ $json_row = ']';
182
+ fwrite( $fp, $json_row );
183
+ } elseif ( 'html' == $export_format ) {
184
+ $html = sprintf( '</ul>' );
185
+ fwrite( $fp, $html );
186
+ }
187
+
188
+ fclose( $fp );
189
+ flush();
190
+
191
+ exit;
192
+
193
+ // echo "<br>done";
194
+ }// End if().
195
+ }
196
+
197
+
198
+ public function output() {
199
+ ?>
200
+ <p><?php echo esc_html_x( 'The export function will export the full history.', 'Export dropin: introtext', 'simple-history' ); ?></p>
201
+
202
+ <form method="post">
203
+
204
+ <h3><?php echo esc_html_x( 'Choose format to export to', 'Export dropin: format', 'simple-history' ); ?></h3>
205
+
206
+ <p>
207
+ <label>
208
+ <input type="radio" name="format" value="json" checked>
209
+ <?php echo esc_html_x( 'JSON', 'Export dropin: export format', 'simple-history' ); ?>
210
+ </label>
211
+ </p>
212
+
213
+ <p>
214
+ <label>
215
+ <input type="radio" name="format" value="csv">
216
+ <?php echo esc_html_x( 'CSV', 'Export dropin: export format', 'simple-history' ); ?>
217
+ </label>
218
+ </p>
219
+
220
+ <p>
221
+ <button type="submit" class="button button-primary">
222
+ <?php echo esc_html_x( 'Download Export File', 'Export dropin: submit button', 'simple-history' ); ?>
223
+ </button>
224
+ <input type="hidden" name="simple-history-action" value="export-history">
225
+ </p>
226
+
227
+ <?php
228
+ wp_nonce_field( __CLASS__ . '-action-export' );
229
+ ?>
230
+
231
+ </form>
232
+
233
+ <?php
234
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
dropins/SimpleHistoryFilterDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Filter GUI
@@ -8,593 +8,633 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryFilterDropin
12
- {
13
 
14
- // Simple History instance
15
- private $sh;
16
 
17
- public function __construct($sh)
18
- {
19
 
20
- $this->sh = $sh;
21
 
22
- add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
23
- add_action('simple_history/history_page/before_gui', array( $this, 'gui_page_filters' ));
24
- add_action('simple_history/dashboard/before_gui', array( $this, 'gui_page_filters' ));
25
- add_action('wp_ajax_simple_history_filters_search_user', array( $this, 'ajax_simple_history_filters_search_user' ));
26
- }
27
 
28
- public function enqueue_admin_scripts()
29
- {
 
 
 
30
 
31
- $file_url = plugin_dir_url(__FILE__);
32
 
33
- wp_enqueue_script('simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
34
 
35
- wp_enqueue_style('simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.css', null, SIMPLE_HISTORY_VERSION);
36
- }
37
 
 
 
38
 
39
- public function gui_page_filters()
40
- {
41
 
42
- $loggers_user_can_read = $this->sh->getLoggersThatUserCanRead();
43
 
44
- /**
45
- * Filter that determines if search filters should be visible directly on page load
46
- *
47
- * Set to true to show the filters when page is loaded
48
- * If false then user must click "Show options"
49
- *
50
- * @since 2.1.2
51
- *
52
- * @param bool $show_more_filters_on_load Default false
53
- */
54
- $show_more_filters_on_load = false;
55
- $show_more_filters_on_load = apply_filters('SimpleHistoryFilterDropin/show_more_filters_on_load', $show_more_filters_on_load);
56
 
57
- ?>
58
- <div class="SimpleHistory__filters <?php echo $show_more_filters_on_load ? 'is-showingMoreFilters' : '' ?>">
 
 
 
 
 
 
 
 
 
 
59
 
60
- <form class="SimpleHistory__filters__form js-SimpleHistory__filters__form">
 
61
 
62
- <!-- <h3><?php _e('Filter history', 'simple-history') ?></h3> -->
 
63
 
64
- <?php
 
 
 
65
 
66
- // Start months filter
67
- global $wpdb;
68
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
69
- $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead(null, 'sql');
70
 
71
- // Get unique months
72
- $cache_key = 'sh_filter_unique_months';
73
- $result_months = get_transient($cache_key);
74
-
75
- if (false === $result_months) {
76
- $sql_dates = sprintf(
77
- '
78
  SELECT DISTINCT ( date_format(DATE, "%%Y-%%m") ) AS yearMonth
79
  FROM %s
80
  WHERE logger IN %s
81
  ORDER BY yearMonth DESC
82
  ',
83
- $table_name, // 1
84
- $loggers_user_can_read_sql_in // 2
85
- );
86
-
87
- $result_months = $wpdb->get_results($sql_dates);
88
-
89
- set_transient($cache_key, $result_months, HOUR_IN_SECONDS);
90
- }
91
-
92
- $arr_days_and_pages = array();
93
-
94
- // Default month = current month
95
- // Mainly for performance reasons, since often
96
- // it's not the users intention to view all events,
97
- // but just the latest
98
- $this_month = date('Y-m');
99
-
100
- // Determine if we limit the date range by default
101
- $daysToShow = 1;
102
-
103
- // Start with the latest day
104
- $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
105
- $numPages = $numEvents / $this->sh->get_pager_size();
106
-
107
- $arr_days_and_pages[] = array(
108
- 'daysToShow' => $daysToShow,
109
- 'numPages' => $numPages,
110
- );
111
-
112
- // Example on my server with lots of brute force attacks (causing log to not load)
113
- // 166434 / 15 = 11 000 pages for last 7 days
114
- // 1 day = 3051 / 15 = 203 pages = still much but better than 11000 pages!
115
- if ($numPages < 20) {
116
- // Not that many things the last day. Let's try to expand to 7 days instead.
117
- $daysToShow = 7;
118
- $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
119
- $numPages = $numEvents / $this->sh->get_pager_size();
120
-
121
- $arr_days_and_pages[] = array(
122
- 'daysToShow' => $daysToShow,
123
- 'numPages' => $numPages,
124
- );
125
-
126
- if ($numPages < 20) {
127
- // Not that many things the last 7 days. Let's try to expand to 14 days instead.
128
- $daysToShow = 14;
129
- $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
130
- $numPages = $numEvents / $this->sh->get_pager_size();
131
-
132
- $arr_days_and_pages[] = array(
133
- 'daysToShow' => $daysToShow,
134
- 'numPages' => $numPages,
135
- );
136
-
137
- if ($numPages < 20) {
138
- // Not many things the last 14 days either. Let try with 30 days.
139
- $daysToShow = 30;
140
- $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
141
- $numPages = $numEvents / $this->sh->get_pager_size();
142
-
143
- $arr_days_and_pages[] = array(
144
- 'daysToShow' => $daysToShow,
145
- 'numPages' => $numPages,
146
- );
147
-
148
- // If 30 days gives a big amount of pages, go back to 14 days
149
- if ($numPages > 1000) {
150
- $daysToShow = 14;
151
- }
152
- }
153
- }
154
- }// End if().
155
-
156
- ?>
157
-
158
- <p class="SimpleHistory__filters__filterRow SimpleHistory__filters__filterRow--date" data-debug-daysAndPages='<?php echo json_encode($arr_days_and_pages) ?>'>
159
-
160
- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Dates:', 'Filter label', 'simple-history') ?></label>
161
-
162
- <select class="SimpleHistory__filters__filter SimpleHistory__filters__filter--date"
163
- name="dates"
164
- placeholder="<?php echo _e('All dates', 'simple-history') ?>"
165
- NOTmultiple
166
- >
167
- <?php
168
-
169
- // custom date range
170
- // since 2.8.1
171
- printf(
172
- '<option value="%1$s" %3$s>%2$s</option>',
173
- 'customRange', // 1 - value
174
- _x('Custom date range...', 'Filter dropin: filter custom range', 'simple-history'), // 2 text
175
- selected($daysToShow, 'customRange', 0)
176
- );
177
-
178
- // One day+ Last week + two weeks back + 30 days back
179
- printf(
180
- '<option value="%1$s" %3$s>%2$s</option>',
181
- 'lastdays:1', // 1 - value
182
- _x('Last day', 'Filter dropin: filter week', 'simple-history'), // 2 text
183
- selected($daysToShow, 1, 0)
184
- );
185
-
186
- printf(
187
- '<option value="%1$s" %3$s>%2$s</option>',
188
- 'lastdays:7', // 1 - value
189
- _x('Last 7 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
190
- selected($daysToShow, 7, 0)
191
- );
192
-
193
- printf(
194
- '<option value="%1$s" %3$s>%2$s</option>',
195
- 'lastdays:14', // 1 - value
196
- _x('Last 14 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
197
- selected($daysToShow, 14, 0)
198
- );
199
-
200
- printf(
201
- '<option value="%1$s" %3$s>%2$s</option>',
202
- 'lastdays:30', // 1 - value
203
- _x('Last 30 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
204
- selected($daysToShow, 30, 0)
205
- );
206
-
207
- printf(
208
- '<option value="%1$s" %3$s>%2$s</option>',
209
- 'lastdays:60', // 1 - value
210
- _x('Last 60 days', 'Filter dropin: filter week', 'simple-history'), // 2 text
211
- selected($daysToShow, 60, 0)
212
- );
213
-
214
- // Months
215
- foreach ($result_months as $row) {
216
- printf(
217
- '<option value="%1$s" %3$s>%2$s</option>',
218
- 'month:' . $row->yearMonth,
219
- date_i18n('F Y', strtotime($row->yearMonth)),
220
- '' // selected( $this_month, $row->yearMonth, false )
221
- );
222
- }
223
-
224
- ?>
225
- </select>
226
-
227
- <!-- <p> -->
228
- <!-- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Between dates:', 'Filter label', 'simple-history') ?></label> -->
229
- <span class="SimpleHistory__filters__filter--dayValuesWrap">
230
- <?php
231
- $this->touch_time('from');
232
- $this->touch_time('to');
233
- ?>
234
- </span>
235
- <!-- </p> -->
236
-
237
- </p><!-- end months -->
238
-
239
- <?php
240
- /**
241
- * Filter to control what the default search string is. Default to empty string.
242
- *
243
- * @since 2.1.2
244
- *
245
- * @param string Default search string
246
- */
247
- $default_search_string = apply_filters('SimpleHistoryFilterDropin/filter_default_search_string', '');
248
- ?>
249
-
250
- <p>
251
-
252
- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Containing words:', 'Filter label', 'simple-history') ?></label>
253
-
254
- <input
255
- type="search"
256
- class="SimpleHistoryFilterDropin-searchInput"
257
- placeholder="<?php /* _e("Containing words", "simple-history"); */ ?>"
258
- name="search"
259
- value="<?php echo esc_attr($default_search_string); ?>"
260
- >
261
-
262
- </p>
263
-
264
- <p class="SimpleHistory__filters__filterSubmitWrap">
265
- <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--first js-SimpleHistoryFilterDropin-doFilter">
266
- <?php _e('Search events', 'simple-history') ?>
267
- </button>
268
- <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--first js-SimpleHistoryFilterDropin-showMoreFilters">
269
- <?php _ex('Show search options', 'Filter dropin: button to show more search options', 'simple-history') ?>
270
- </button>
271
- </p>
272
-
273
- <?php
274
- /**
275
- * Filter to control what the default loglevels are.
276
- *
277
- * @since 2.1.2
278
- *
279
- * @param array Array with loglevel sugs. Default empty = show all.
280
- */
281
- $arr_default_loglevels = apply_filters('SimpleHistoryFilterDropin/filter_default_loglevel', array());
282
- ?>
283
- <div class="SimpleHistory__filters__moreFilters js-SimpleHistory__filters__moreFilters">
284
-
285
- <p>
286
-
287
- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Log levels:', 'Filter label', 'simple-history') ?></label>
288
-
289
- <select
290
- name="loglevels"
291
- class="SimpleHistory__filters__filter SimpleHistory__filters__filter--loglevel"
292
- placeholder="<?php _e('All log levels', 'simple-history') ?>"
293
- multiple
294
- >
295
- <option <?php selected(in_array('debug', $arr_default_loglevels)) ?> value="debug" data-color="#CEF6D8"><?php echo $this->sh->getLogLevelTranslated('Debug') ?></option>
296
- <option <?php selected(in_array('info', $arr_default_loglevels)) ?> value="info" data-color="white"><?php echo $this->sh->getLogLevelTranslated('Info') ?></option>
297
- <option <?php selected(in_array('notice', $arr_default_loglevels)) ?> value="notice" data-color="rgb(219, 219, 183)"><?php echo $this->sh->getLogLevelTranslated('Notice') ?></option>
298
- <option <?php selected(in_array('warning', $arr_default_loglevels)) ?> value="warning" data-color="#F7D358"><?php echo $this->sh->getLogLevelTranslated('Warning') ?></option>
299
- <option <?php selected(in_array('error', $arr_default_loglevels)) ?> value="error" data-color="#F79F81"><?php echo $this->sh->getLogLevelTranslated('Error') ?></option>
300
- <option <?php selected(in_array('critical', $arr_default_loglevels)) ?> value="critical" data-color="#FA5858"><?php echo $this->sh->getLogLevelTranslated('Critical') ?></option>
301
- <option <?php selected(in_array('alert', $arr_default_loglevels)) ?> value="alert" data-color="rgb(199, 69, 69)"><?php echo $this->sh->getLogLevelTranslated('Alert') ?></option>
302
- <option <?php selected(in_array('emergency', $arr_default_loglevels)) ?> value="emergency" data-color="#DF0101"><?php echo $this->sh->getLogLevelTranslated('Emergency') ?></option>
303
- </select>
304
-
305
- </p>
306
-
307
- <?php
308
-
309
- /**
310
- * Todo: Filter to control what the default messages to filter/search.
311
- * Message in in format: LoggerSlug:MessageKey
312
- * For example:
313
- * - SimplePluginLogger:plugin_activated
314
- * - SimpleCommentsLogger:user_comment_added
315
- *
316
- * @since 2.1.2
317
- *
318
- * @param array Array with loglevel sugs. Default empty = show all.
319
- */
320
- // $arr_default_messages = apply_filters("SimpleHistoryFilterDropin/filter_default_messages", array());
321
- ?>
322
- <p>
323
-
324
- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Message types:', 'Filter label', 'simple-history') ?></label>
325
-
326
- <select
327
- name="messages"
328
- class="SimpleHistory__filters__filter SimpleHistory__filters__filter--logger"
329
- placeholder="<?php _e('All messages', 'simple-history') ?>"
330
- multiple
331
- >
332
- <?php
333
- foreach ($loggers_user_can_read as $logger) {
334
- $logger_info = $logger['instance']->getInfo();
335
- $logger_slug = $logger['instance']->slug;
336
-
337
- // Get labels for logger
338
- if (isset($logger_info['labels']['search'])) {
339
- printf('<optgroup label="%1$s">', esc_attr($logger_info['labels']['search']['label']));
340
-
341
- // If all activity
342
- if (! empty($logger_info['labels']['search']['label_all'])) {
343
- $arr_all_search_messages = array();
344
- foreach ($logger_info['labels']['search']['options'] as $option_key => $option_messages) {
345
- $arr_all_search_messages = array_merge($arr_all_search_messages, $option_messages);
346
- }
347
-
348
- foreach ($arr_all_search_messages as $key => $val) {
349
- $arr_all_search_messages[ $key ] = $logger_slug . ':' . $val;
350
- }
351
-
352
- printf('<option value="%2$s">%1$s</option>', esc_attr($logger_info['labels']['search']['label_all']), esc_attr(implode(',', $arr_all_search_messages)));
353
- }
354
-
355
- // For each specific search option
356
- foreach ($logger_info['labels']['search']['options'] as $option_key => $option_messages) {
357
- foreach ($option_messages as $key => $val) {
358
- $option_messages[ $key ] = $logger_slug . ':' . $val;
359
- }
360
-
361
- $str_option_messages = implode(',', $option_messages);
362
- printf(
363
- '<option value="%2$s">%1$s</option>',
364
- esc_attr($option_key), // 1
365
- esc_attr($str_option_messages) // 2
366
- );
367
- }
368
-
369
- printf('</optgroup>');
370
- }// End if().
371
- }// End foreach().
372
- ?>
373
- </select>
374
- </p>
375
-
376
- <?php
377
-
378
- /**
379
- * Filter what users to search for by default
380
- *
381
- * Example:
382
- *
383
- * add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function() { return array(1,4); });
384
- *
385
- * @since 2.1.2
386
- *
387
- * @param array Array with user ids. Default is an empty array = show all users.
388
- */
389
-
390
- /*
391
- add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function($arr) {
392
- $arr = array(
393
- 1,
394
- 4
395
- );
396
- return $arr;
397
- });
398
- //*/
399
-
400
- $default_user_ids = apply_filters('SimpleHistoryFilterDropin/filter_default_user_ids', array());
401
- $arr_default_user_data = array();
402
-
403
- foreach ($default_user_ids as $user_id) {
404
- $arr_default_user_data[] = $this->get_data_for_user($user_id);
405
- }
406
-
407
- if (current_user_can('list_users')) {
408
- ?>
409
- <p>
410
- <label class="SimpleHistory__filters__filterLabel"><?php _ex('Users:', 'Filter label', 'simple-history') ?></label>
411
- <select
412
- name="users"
413
- class="SimpleHistory__filters__filter SimpleHistory__filters__filter--user"
414
- data-placeholder="<?php _e('All users', 'simple-history') ?>"
415
- value="<?php echo esc_attr(implode(',', $default_user_ids)) ?>"
416
- data-default-user-data="<?php echo esc_attr(json_encode($arr_default_user_data)) ?>"
417
- >
418
- </select>
419
- </p>
420
- <?php
421
- }
422
-
423
- ?>
424
-
425
- <p class="SimpleHistory__filters__filterSubmitWrap">
426
- <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--second js-SimpleHistoryFilterDropin-doFilter">
427
- <?php _e('Search events', 'simple-history') ?>
428
- </button>
429
- <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--second js-SimpleHistoryFilterDropin-showMoreFilters">
430
- <?php _ex('Hide search options', 'Filter dropin: button to hide more search options', 'simple-history') ?>
431
- </button>
432
- </p>
433
-
434
- </div><!-- // more filters -->
435
-
436
- <!--
437
- <p>
438
- <button class="button js-SimpleHistoryFilterDropin-doFilter"><?php _e('Search', 'simple-history') ?></button>
439
- </p>
440
- -->
441
-
442
- </form>
443
-
444
- </div>
445
- <?php
446
- }
447
-
448
- /**
449
- * Return format used for select2 for a single user id
450
- *
451
- * @param int $userID
452
- * @return array Array with each user as an object
453
- */
454
- public function get_data_for_user($userID)
455
- {
456
- if (! $userID || ! is_numeric($userID)) {
457
- return false;
458
- }
459
-
460
- $user = get_user_by('id', $userID);
461
-
462
- if (false == $user) {
463
- return false;
464
- }
465
-
466
- $userdata = (object) array(
467
- 'id' => $user->ID,
468
- 'user_email' => $user->user_email,
469
- 'user_login' => $user->user_login,
470
- 'user_nicename' => $user->user_nicename,
471
- );
472
-
473
- $this->add_gravatar_to_user_array($userdata);
474
-
475
- return $userdata;
476
- }
477
-
478
- /**
479
- * Return users
480
- */
481
- public function ajax_simple_history_filters_search_user()
482
- {
483
-
484
- $q = isset($_GET['q']) ? $_GET['q'] : '';
485
- $page_limit = isset($_GET['page_limit']) ? (int) $_GET['page_limit'] : '';
486
-
487
- // query and page limit must be set
488
- if (! $q || ! $page_limit) {
489
- wp_send_json_error();
490
- }
491
-
492
- // user must have list_users capability (default super admin + administrators have this)
493
- if (! current_user_can('list_users')) {
494
- wp_send_json_error();
495
- }
496
-
497
- // Search both current users and all logged rows,
498
- // because a user can change email
499
- // search in context: user_id, user_email, user_login
500
- // search in wp_users: login, nicename, user_email
501
- // search and get users. make sure to use "fields" and "number" or we can get timeout/use lots of memory if we have a large amount of users
502
- $results_user = get_users(array(
503
- 'search' => "*{$q}*",
504
- 'fields' => array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' ),
505
- 'number' => 20,
506
- ));
507
-
508
- // add lower case id to user array
509
- array_walk($results_user, function ($val) {
510
- $val->id = $val->ID;
511
- });
512
-
513
- // add gravatars to user array
514
- array_walk($results_user, array( $this, 'add_gravatar_to_user_array' ));
515
-
516
- $data = array(
517
- 'results' => array(),
518
- 'more' => false,
519
- 'context' => array(),
520
- 'count' => sizeof($results_user),
521
- );
522
-
523
- $data['results'] = array_merge($data['results'], $results_user);
524
-
525
- wp_send_json_success($data);
526
- }
527
-
528
- public function add_gravatar_to_user_array(&$val, $index = null)
529
- {
530
- $val->text = sprintf(
531
- '%1$s - %2$s',
532
- $val->user_login,
533
- $val->user_email
534
- );
535
-
536
- $val->gravatar = $this->sh->get_avatar($val->user_email, '18', 'mm');
537
- }
538
-
539
-
540
- /**
541
- * Print out HTML form date elements for editing post or comment publish date.
542
- *
543
- * Based on the wordpress function touch_time();
544
- *
545
- * @global WP_Locale $wp_locale
546
- *
547
- * @param int|bool $edit Accepts 1|true for editing the date, 0|false for adding the date.
548
- * @param int|bool $for_post Accepts 1|true for applying the date to a post, 0|false for a comment.
549
- * @param int $tab_index The tabindex attribute to add. Default 0.
550
- * @param int|bool $multi Optional. Whether the additional fields and buttons should be added.
551
- * Default 0|false.
552
- */
553
- public function touch_time($from_or_to, $edit = 1)
554
- {
555
-
556
- global $wp_locale;
557
-
558
- // Prefix = text before the inputs
559
- $prefix = '';
560
- $input_prefix = '';
561
- if ('from' == $from_or_to) {
562
- $prefix = _x('From', 'Filter dropin, custom date range', 'simple-history');
563
- $input_prefix = 'from_';
564
- } elseif ('to' == $from_or_to) {
565
- $prefix = _x('To', 'Filter dropin, custom date range', 'simple-history');
566
- $input_prefix = 'to_';
567
- }
568
-
569
- // The default date to show in the inputs
570
- $date = date('Y-m-d');
571
-
572
- $jj = mysql2date('d', $date, false);
573
- $mm = mysql2date('m', $date, false);
574
- $aa = mysql2date('Y', $date, false);
575
-
576
- $month = "<select name='{$input_prefix}mm'>";
577
-
578
- for ($i = 1; $i < 13; $i = $i + 1) {
579
- $monthnum = zeroise($i, 2);
580
- $monthtext = $wp_locale->get_month_abbrev($wp_locale->get_month($i));
581
- $month .= "\t\t\t" . '<option value="' . $monthnum . '" data-text="' . $monthtext . '" ' . selected($monthnum, $mm, false) . '>';
582
- /* translators: 1: month number (01, 02, etc.), 2: month abbreviation */
583
- $month .= sprintf(__('%1$s-%2$s'), $monthnum, $monthtext) . "</option>\n";
584
- }
585
- $month .= '</select>';
586
- $month .= '</label>';
587
-
588
- $day = '<label><span class="screen-reader-text">' . __('Day') . '</span><input type="text" name="' . $input_prefix . 'jj" value="' . $jj . '" size="2" maxlength="2" autocomplete="off" /></label>';
589
- $year = '<label><span class="screen-reader-text">' . __('Year') . '</span><input type="text" name="' . $input_prefix . 'aa" value="' . $aa . '" size="4" maxlength="4" autocomplete="off" /></label>';
590
-
591
- echo '<span class="SimpleHistory__filters__filter SimpleHistory__filters__filter--day">';
592
-
593
- echo $prefix . '<br>';
594
-
595
- /* translators: 1: month, 2: day, 3: year */
596
- printf(__('%1$s %2$s, %3$s'), $month, $day, $year);
597
-
598
- echo '</span>';
599
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Filter GUI
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryFilterDropin {
 
12
 
 
 
13
 
14
+ // Simple History instance
15
+ private $sh;
16
 
17
+ public function __construct( $sh ) {
18
 
19
+ $this->sh = $sh;
 
 
 
 
20
 
21
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
22
+ add_action( 'simple_history/history_page/before_gui', array( $this, 'gui_page_filters' ) );
23
+ add_action( 'simple_history/dashboard/before_gui', array( $this, 'gui_page_filters' ) );
24
+ add_action( 'wp_ajax_simple_history_filters_search_user', array( $this, 'ajax_simple_history_filters_search_user' ) );
25
+ }
26
 
27
+ public function enqueue_admin_scripts() {
28
 
29
+ $file_url = plugin_dir_url( __FILE__ );
30
 
31
+ wp_enqueue_script( 'simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
 
32
 
33
+ wp_enqueue_style( 'simple_history_FilterDropin', $file_url . 'SimpleHistoryFilterDropin.css', null, SIMPLE_HISTORY_VERSION );
34
+ }
35
 
 
 
36
 
37
+ public function gui_page_filters() {
38
 
39
+ $loggers_user_can_read = $this->sh->getLoggersThatUserCanRead();
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ /**
42
+ * Filter that determines if search filters should be visible directly on page load
43
+ *
44
+ * Set to true to show the filters when page is loaded
45
+ * If false then user must click "Show options"
46
+ *
47
+ * @since 2.1.2
48
+ *
49
+ * @param bool $show_more_filters_on_load Default false
50
+ */
51
+ $show_more_filters_on_load = false;
52
+ $show_more_filters_on_load = apply_filters( 'SimpleHistoryFilterDropin/show_more_filters_on_load', $show_more_filters_on_load );
53
 
54
+ ?>
55
+ <div class="SimpleHistory__filters <?php echo $show_more_filters_on_load ? 'is-showingMoreFilters' : ''; ?>">
56
 
57
+ <form class="SimpleHistory__filters__form js-SimpleHistory__filters__form">
58
+ <?php
59
 
60
+ // Start months filter
61
+ global $wpdb;
62
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
63
+ $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead( null, 'sql' );
64
 
65
+ // Get unique months
66
+ $cache_key = 'sh_filter_unique_months';
67
+ $result_months = get_transient( $cache_key );
 
68
 
69
+ if ( false === $result_months ) {
70
+ $sql_dates = sprintf(
71
+ '
 
 
 
 
72
  SELECT DISTINCT ( date_format(DATE, "%%Y-%%m") ) AS yearMonth
73
  FROM %s
74
  WHERE logger IN %s
75
  ORDER BY yearMonth DESC
76
  ',
77
+ $table_name, // 1
78
+ $loggers_user_can_read_sql_in // 2
79
+ );
80
+
81
+ $result_months = $wpdb->get_results( $sql_dates ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
82
+
83
+ set_transient( $cache_key, $result_months, HOUR_IN_SECONDS );
84
+ }
85
+
86
+ $arr_days_and_pages = array();
87
+
88
+ // Default month = current month
89
+ // Mainly for performance reasons, since often
90
+ // it's not the users intention to view all events,
91
+ // but just the latest
92
+
93
+ // Determine if we limit the date range by default
94
+ $daysToShow = 1;
95
+
96
+ // Start with the latest day
97
+ $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
98
+ $numPages = $numEvents / $this->sh->get_pager_size();
99
+
100
+ $arr_days_and_pages[] = array(
101
+ 'daysToShow' => $daysToShow,
102
+ 'numPages' => $numPages,
103
+ );
104
+
105
+ // Example on my server with lots of brute force attacks (causing log to not load)
106
+ // 166434 / 15 = 11 000 pages for last 7 days
107
+ // 1 day = 3051 / 15 = 203 pages = still much but better than 11000 pages!
108
+ if ( $numPages < 20 ) {
109
+ // Not that many things the last day. Let's try to expand to 7 days instead.
110
+ $daysToShow = 7;
111
+ $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
112
+ $numPages = $numEvents / $this->sh->get_pager_size();
113
+
114
+ $arr_days_and_pages[] = array(
115
+ 'daysToShow' => $daysToShow,
116
+ 'numPages' => $numPages,
117
+ );
118
+
119
+ if ( $numPages < 20 ) {
120
+ // Not that many things the last 7 days. Let's try to expand to 14 days instead.
121
+ $daysToShow = 14;
122
+ $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
123
+ $numPages = $numEvents / $this->sh->get_pager_size();
124
+
125
+ $arr_days_and_pages[] = array(
126
+ 'daysToShow' => $daysToShow,
127
+ 'numPages' => $numPages,
128
+ );
129
+
130
+ if ( $numPages < 20 ) {
131
+ // Not many things the last 14 days either. Let try with 30 days.
132
+ $daysToShow = 30;
133
+ $numEvents = $this->sh->get_unique_events_for_days( $daysToShow );
134
+ $numPages = $numEvents / $this->sh->get_pager_size();
135
+
136
+ $arr_days_and_pages[] = array(
137
+ 'daysToShow' => $daysToShow,
138
+ 'numPages' => $numPages,
139
+ );
140
+
141
+ // If 30 days gives a big amount of pages, go back to 14 days
142
+ if ( $numPages > 1000 ) {
143
+ $daysToShow = 14;
144
+ }
145
+ }
146
+ }
147
+ }// End if().
148
+
149
+ ?>
150
+
151
+ <p class="SimpleHistory__filters__filterRow SimpleHistory__filters__filterRow--date" data-debug-daysAndPages='<?php echo json_encode( $arr_days_and_pages ); ?>'>
152
+
153
+ <label class="SimpleHistory__filters__filterLabel"><?php echo esc_html_x( 'Dates:', 'Filter label', 'simple-history' ); ?></label>
154
+
155
+ <select class="SimpleHistory__filters__filter SimpleHistory__filters__filter--date"
156
+ name="dates"
157
+ placeholder="<?php esc_attr_e( 'All dates', 'simple-history' ); ?>"
158
+ NOTmultiple
159
+ >
160
+ <?php
161
+
162
+ // custom date range
163
+ // since 2.8.1
164
+ printf(
165
+ '<option value="%1$s" %3$s>%2$s</option>',
166
+ 'customRange', // 1 - value
167
+ esc_attr_x( 'Custom date range...', 'Filter dropin: filter custom range', 'simple-history' ), // 2 text
168
+ selected( $daysToShow, 'customRange', 0 )
169
+ );
170
+
171
+ // One day+ Last week + two weeks back + 30 days back
172
+ printf(
173
+ '<option value="%1$s" %3$s>%2$s</option>',
174
+ 'lastdays:1', // 1 - value
175
+ esc_attr_x( 'Last day', 'Filter dropin: filter week', 'simple-history' ), // 2 text
176
+ selected( $daysToShow, 1, 0 )
177
+ );
178
+
179
+ printf(
180
+ '<option value="%1$s" %3$s>%2$s</option>',
181
+ 'lastdays:7', // 1 - value
182
+ esc_attr_x( 'Last 7 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
183
+ selected( $daysToShow, 7, 0 )
184
+ );
185
+
186
+ printf(
187
+ '<option value="%1$s" %3$s>%2$s</option>',
188
+ 'lastdays:14', // 1 - value
189
+ esc_attr_x( 'Last 14 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
190
+ selected( $daysToShow, 14, 0 )
191
+ );
192
+
193
+ printf(
194
+ '<option value="%1$s" %3$s>%2$s</option>',
195
+ 'lastdays:30', // 1 - value
196
+ esc_attr_x( 'Last 30 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
197
+ selected( $daysToShow, 30, 0 )
198
+ );
199
+
200
+ printf(
201
+ '<option value="%1$s" %3$s>%2$s</option>',
202
+ 'lastdays:60', // 1 - value
203
+ esc_attr_x( 'Last 60 days', 'Filter dropin: filter week', 'simple-history' ), // 2 text
204
+ selected( $daysToShow, 60, 0 )
205
+ );
206
+
207
+ // Months
208
+ foreach ( $result_months as $row ) {
209
+ printf(
210
+ '<option value="%1$s">%2$s</option>',
211
+ 'month:' . esc_attr( $row->yearMonth ),
212
+ esc_attr(
213
+ date_i18n(
214
+ 'F Y',
215
+ strtotime( $row->yearMonth )
216
+ )
217
+ )
218
+ );
219
+ }
220
+
221
+ ?>
222
+ </select>
223
+
224
+ <span class="SimpleHistory__filters__filter--dayValuesWrap">
225
+ <?php
226
+ $this->touch_time( 'from' );
227
+ $this->touch_time( 'to' );
228
+ ?>
229
+ </span>
230
+
231
+ </p><!-- end months -->
232
+
233
+ <?php
234
+ /**
235
+ * Filter to control what the default search string is. Default to empty string.
236
+ *
237
+ * @since 2.1.2
238
+ *
239
+ * @param string Default search string
240
+ */
241
+ $default_search_string = apply_filters( 'SimpleHistoryFilterDropin/filter_default_search_string', '' );
242
+ ?>
243
+
244
+ <p>
245
+ <label class="SimpleHistory__filters__filterLabel">
246
+ <?php echo esc_html_x( 'Containing words:', 'Filter label', 'simple-history' ); ?>
247
+ </label>
248
+
249
+ <input
250
+ type="search"
251
+ class="SimpleHistoryFilterDropin-searchInput"
252
+ placeholder=""
253
+ name="search"
254
+ value="<?php echo esc_attr( $default_search_string ); ?>"
255
+ >
256
+ </p>
257
+
258
+ <p class="SimpleHistory__filters__filterSubmitWrap">
259
+ <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--first js-SimpleHistoryFilterDropin-doFilter">
260
+ <?php esc_html_e( 'Search events', 'simple-history' ); ?>
261
+ </button>
262
+ <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--first js-SimpleHistoryFilterDropin-showMoreFilters">
263
+ <?php echo esc_html_x( 'Show search options', 'Filter dropin: button to show more search options', 'simple-history' ); ?>
264
+ </button>
265
+ </p>
266
+
267
+ <?php
268
+ /**
269
+ * Filter to control what the default loglevels are.
270
+ *
271
+ * @since 2.1.2
272
+ *
273
+ * @param array Array with loglevel sugs. Default empty = show all.
274
+ */
275
+ $arr_default_loglevels = apply_filters( 'SimpleHistoryFilterDropin/filter_default_loglevel', array() );
276
+ ?>
277
+ <div class="SimpleHistory__filters__moreFilters js-SimpleHistory__filters__moreFilters">
278
+
279
+ <p>
280
+ <label class="SimpleHistory__filters__filterLabel">
281
+ <?php echo esc_html_x( 'Log levels:', 'Filter label', 'simple-history' ); ?>
282
+ </label>
283
+
284
+ <select
285
+ name="loglevels"
286
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--loglevel"
287
+ placeholder="<?php esc_attr_e( 'All log levels', 'simple-history' ); ?>"
288
+ multiple
289
+ >
290
+ <option <?php selected( in_array( 'debug', $arr_default_loglevels ) ); ?> value="debug" data-color="#CEF6D8">
291
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Debug' ) ); ?>
292
+ </option>
293
+ <option <?php selected( in_array( 'info', $arr_default_loglevels ) ); ?> value="info" data-color="white">
294
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Info' ) ); ?>
295
+ </option>
296
+ <option <?php selected( in_array( 'notice', $arr_default_loglevels ) ); ?> value="notice" data-color="rgb(219, 219, 183)">
297
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Notice' ) ); ?>
298
+ </option>
299
+ <option <?php selected( in_array( 'warning', $arr_default_loglevels ) ); ?> value="warning" data-color="#F7D358">
300
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Warning' ) ); ?>
301
+ </option>
302
+ <option <?php selected( in_array( 'error', $arr_default_loglevels ) ); ?> value="error" data-color="#F79F81">
303
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Error' ) ); ?>
304
+ </option>
305
+ <option <?php selected( in_array( 'critical', $arr_default_loglevels ) ); ?> value="critical" data-color="#FA5858">
306
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Critical' ) ); ?>
307
+ </option>
308
+ <option <?php selected( in_array( 'alert', $arr_default_loglevels ) ); ?> value="alert" data-color="rgb(199, 69, 69)">
309
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Alert' ) ); ?>
310
+ </option>
311
+ <option <?php selected( in_array( 'emergency', $arr_default_loglevels ) ); ?> value="emergency" data-color="#DF0101">
312
+ <?php echo esc_html( $this->sh->getLogLevelTranslated( 'Emergency' ) ); ?>
313
+ </option>
314
+ </select>
315
+
316
+ </p>
317
+
318
+ <?php
319
+
320
+ /**
321
+ * Todo: Filter to control what the default messages to filter/search.
322
+ * Message in in format: LoggerSlug:MessageKey
323
+ * For example:
324
+ * - SimplePluginLogger:plugin_activated
325
+ * - SimpleCommentsLogger:user_comment_added
326
+ *
327
+ * @since 2.1.2
328
+ *
329
+ * @param array Array with loglevel sugs. Default empty = show all.
330
+ */
331
+ // $arr_default_messages = apply_filters("SimpleHistoryFilterDropin/filter_default_messages", array());
332
+ ?>
333
+ <p>
334
+ <label class="SimpleHistory__filters__filterLabel">
335
+ <?php echo esc_html_x( 'Message types:', 'Filter label', 'simple-history' ); ?>
336
+ </label>
337
+
338
+ <select
339
+ name="messages"
340
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--logger"
341
+ placeholder="<?php esc_attr_e( 'All messages', 'simple-history' ); ?>"
342
+ multiple
343
+ >
344
+ <?php
345
+ foreach ( $loggers_user_can_read as $logger ) {
346
+ $logger_info = $logger['instance']->getInfo();
347
+ $logger_slug = $logger['instance']->slug;
348
+
349
+ // Get labels for logger
350
+ if ( isset( $logger_info['labels']['search'] ) ) {
351
+ printf( '<optgroup label="%1$s">', esc_attr( $logger_info['labels']['search']['label'] ) );
352
+
353
+ // If all activity
354
+ if ( ! empty( $logger_info['labels']['search']['label_all'] ) ) {
355
+ $arr_all_search_messages = array();
356
+ foreach ( $logger_info['labels']['search']['options'] as $option_key => $option_messages ) {
357
+ $arr_all_search_messages = array_merge( $arr_all_search_messages, $option_messages );
358
+ }
359
+
360
+ foreach ( $arr_all_search_messages as $key => $val ) {
361
+ $arr_all_search_messages[ $key ] = $logger_slug . ':' . $val;
362
+ }
363
+
364
+ printf( '<option value="%2$s">%1$s</option>', esc_attr( $logger_info['labels']['search']['label_all'] ), esc_attr( implode( ',', $arr_all_search_messages ) ) );
365
+ }
366
+
367
+ // For each specific search option
368
+ foreach ( $logger_info['labels']['search']['options'] as $option_key => $option_messages ) {
369
+ foreach ( $option_messages as $key => $val ) {
370
+ $option_messages[ $key ] = $logger_slug . ':' . $val;
371
+ }
372
+
373
+ $str_option_messages = implode( ',', $option_messages );
374
+ printf(
375
+ '<option value="%2$s">%1$s</option>',
376
+ esc_attr( $option_key ), // 1
377
+ esc_attr( $str_option_messages ) // 2
378
+ );
379
+ }
380
+
381
+ printf( '</optgroup>' );
382
+ }// End if().
383
+ }// End foreach().
384
+ ?>
385
+ </select>
386
+ </p>
387
+
388
+ <?php
389
+
390
+ /**
391
+ * Filter what users to search for by default
392
+ *
393
+ * Example:
394
+ *
395
+ * add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function() { return array(1,4); });
396
+ *
397
+ * @since 2.1.2
398
+ *
399
+ * @param array Array with user ids. Default is an empty array = show all users.
400
+ */
401
+
402
+ /*
403
+ add_filter("SimpleHistoryFilterDropin/filter_default_user_ids", function($arr) {
404
+ $arr = array(
405
+ 1,
406
+ 4
407
+ );
408
+ return $arr;
409
+ });
410
+ //*/
411
+
412
+ $default_user_ids = apply_filters( 'SimpleHistoryFilterDropin/filter_default_user_ids', array() ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
413
+ $arr_default_user_data = array();
414
+
415
+ foreach ( $default_user_ids as $user_id ) {
416
+ $arr_default_user_data[] = $this->get_data_for_user( $user_id );
417
+ }
418
+
419
+ if ( current_user_can( 'list_users' ) ) {
420
+ ?>
421
+ <p>
422
+ <label class="SimpleHistory__filters__filterLabel">
423
+ <?php echo esc_html_x( 'Users:', 'Filter label', 'simple-history' ); ?>
424
+ </label>
425
+ <select
426
+ name="users"
427
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--user"
428
+ data-placeholder="<?php esc_html_e( 'All users', 'simple-history' ); ?>"
429
+ value="<?php echo esc_attr( implode( ',', $default_user_ids ) ); ?>"
430
+ data-default-user-data="<?php echo esc_attr( json_encode( $arr_default_user_data ) ); ?>"
431
+ >
432
+ </select>
433
+ </p>
434
+ <?php
435
+ }
436
+
437
+ ?>
438
+
439
+ <p class="SimpleHistory__filters__filterSubmitWrap">
440
+ <button class="button SimpleHistoryFilterDropin-doFilterButton SimpleHistoryFilterDropin-doFilterButton--second js-SimpleHistoryFilterDropin-doFilter">
441
+ <?php esc_html_e( 'Search events', 'simple-history' ); ?>
442
+ </button>
443
+ <button type="button" class="SimpleHistoryFilterDropin-showMoreFilters SimpleHistoryFilterDropin-showMoreFilters--second js-SimpleHistoryFilterDropin-showMoreFilters">
444
+ <?php esc_html_x( 'Hide search options', 'Filter dropin: button to hide more search options', 'simple-history' ); ?>
445
+ </button>
446
+ </p>
447
+
448
+ </div><!-- // more filters -->
449
+
450
+ </form>
451
+
452
+ </div>
453
+ <?php
454
+ }
455
+
456
+ /**
457
+ * Return format used for select2 for a single user id.
458
+ *
459
+ * @param int $userID
460
+ * @return array Array with each user as an object
461
+ */
462
+ public function get_data_for_user( $userID ) {
463
+ if ( ! $userID || ! is_numeric( $userID ) ) {
464
+ return false;
465
+ }
466
+
467
+ $user = get_user_by( 'id', $userID );
468
+
469
+ if ( false == $user ) {
470
+ return false;
471
+ }
472
+
473
+ $userdata = (object) array(
474
+ 'id' => $user->ID,
475
+ 'user_email' => $user->user_email,
476
+ 'user_login' => $user->user_login,
477
+ 'user_nicename' => $user->user_nicename,
478
+ );
479
+
480
+ $this->add_gravatar_to_user_array( $userdata );
481
+
482
+ return $userdata;
483
+ }
484
+
485
+ /**
486
+ * Return users
487
+ */
488
+ public function ajax_simple_history_filters_search_user() {
489
+
490
+ $q = isset( $_GET['q'] ) ? $_GET['q'] : '';
491
+ $page_limit = isset( $_GET['page_limit'] ) ? (int) $_GET['page_limit'] : '';
492
+
493
+ // query and page limit must be set
494
+ if ( ! $q || ! $page_limit ) {
495
+ wp_send_json_error();
496
+ }
497
+
498
+ // user must have list_users capability (default super admin + administrators have this)
499
+ if ( ! current_user_can( 'list_users' ) ) {
500
+ wp_send_json_error();
501
+ }
502
+
503
+ // Search both current users and all logged rows,
504
+ // because a user can change email
505
+ // search in context: user_id, user_email, user_login
506
+ // search in wp_users: login, nicename, user_email
507
+ // search and get users. make sure to use "fields" and "number" or we can get timeout/use lots of memory if we have a large amount of users
508
+ $results_user = get_users(
509
+ array(
510
+ 'search' => "*{$q}*",
511
+ 'fields' => array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' ),
512
+ 'number' => 20,
513
+ )
514
+ );
515
+
516
+ // add lower case id to user array
517
+ array_walk(
518
+ $results_user,
519
+ function ( $val ) {
520
+ $val->id = $val->ID;
521
+ }
522
+ );
523
+
524
+ // add gravatars to user array
525
+ array_walk( $results_user, array( $this, 'add_gravatar_to_user_array' ) );
526
+
527
+ $data = array(
528
+ 'results' => array(),
529
+ 'more' => false,
530
+ 'context' => array(),
531
+ 'count' => count( $results_user ),
532
+ );
533
+
534
+ $data['results'] = array_merge( $data['results'], $results_user );
535
+
536
+ wp_send_json_success( $data );
537
+ }
538
+
539
+ public function add_gravatar_to_user_array( &$val, $index = null ) {
540
+ $val->text = sprintf(
541
+ '%1$s - %2$s',
542
+ $val->user_login,
543
+ $val->user_email
544
+ );
545
+
546
+ $val->gravatar = $this->sh->get_avatar( $val->user_email, '18', 'mm' );
547
+ }
548
+
549
+
550
+ /**
551
+ * Print out HTML form date elements for editing post or comment publish date.
552
+ *
553
+ * Based on the wordpress function touch_time();
554
+ *
555
+ * @global WP_Locale $wp_locale
556
+ *
557
+ * @param int|bool $edit Accepts 1|true for editing the date, 0|false for adding the date.
558
+ * @param int|bool $for_post Accepts 1|true for applying the date to a post, 0|false for a comment.
559
+ * @param int $tab_index The tabindex attribute to add. Default 0.
560
+ * @param int|bool $multi Optional. Whether the additional fields and buttons should be added.
561
+ * Default 0|false.
562
+ */
563
+ public function touch_time( $from_or_to, $edit = 1 ) {
564
+ global $wp_locale;
565
+
566
+ // Prefix = text before the inputs
567
+ $prefix = '';
568
+ $input_prefix = '';
569
+ if ( 'from' == $from_or_to ) {
570
+ $prefix = _x( 'From', 'Filter dropin, custom date range', 'simple-history' );
571
+ $input_prefix = 'from_';
572
+ } elseif ( 'to' == $from_or_to ) {
573
+ $prefix = _x( 'To', 'Filter dropin, custom date range', 'simple-history' );
574
+ $input_prefix = 'to_';
575
+ }
576
+
577
+ // The default date to show in the inputs
578
+ $date = gmdate( 'Y-m-d' );
579
+
580
+ $jj = mysql2date( 'd', $date, false );
581
+ $mm = mysql2date( 'm', $date, false );
582
+ $aa = mysql2date( 'Y', $date, false );
583
+
584
+ $month = "<select name='{$input_prefix}mm'>";
585
+
586
+ for ( $i = 1; $i < 13; ++$i ) {
587
+ $monthnum = zeroise( $i, 2 );
588
+ $monthtext = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) );
589
+ $month .= "\t\t\t" . '<option value="' . $monthnum . '" data-text="' . $monthtext . '" ' . selected( $monthnum, $mm, false ) . '>';
590
+ /* translators: 1: month number (01, 02, etc.), 2: month abbreviation */
591
+ $month .= sprintf( __( '%1$s-%2$s', 'simple-history' ), $monthnum, $monthtext ) . "</option>\n";
592
+ }
593
+ $month .= '</select>';
594
+ $month .= '</label>';
595
+
596
+ $day = '<label><span class="screen-reader-text">' . __( 'Day', 'simple-history' ) . '</span><input type="text" name="' . $input_prefix . 'jj" value="' . $jj . '" size="2" maxlength="2" autocomplete="off" /></label>';
597
+ $year = '<label><span class="screen-reader-text">' . __( 'Year', 'simple-history' ) . '</span><input type="text" name="' . $input_prefix . 'aa" value="' . $aa . '" size="4" maxlength="4" autocomplete="off" /></label>';
598
+
599
+ echo '<span class="SimpleHistory__filters__filter SimpleHistory__filters__filter--day">';
600
+
601
+ echo esc_html( $prefix ) . '<br>';
602
+
603
+ $wp_kses_args = array(
604
+ 'select' => array(
605
+ 'name' => array(),
606
+ ),
607
+ 'label' => array(),
608
+ 'option' => array(
609
+ 'value' => array(),
610
+ 'data-text' => array(),
611
+ 'selected' => array(),
612
+
613
+ ),
614
+ 'span' => array(
615
+ 'class' => array(),
616
+ ),
617
+ 'input' => array(
618
+ 'type' => array(),
619
+ 'name' => array(),
620
+ 'value' => array(),
621
+ 'size' => array(),
622
+ 'maxlength' => array(),
623
+ 'autocomplete' => array(),
624
+ ),
625
+ );
626
+
627
+ /* translators: 1: month, 2: day, 3: year */
628
+ echo wp_kses(
629
+ sprintf(
630
+ __( '%1$s %2$s, %3$s', 'simple-history' ),
631
+ $month,
632
+ $day,
633
+ $year
634
+ ),
635
+ $wp_kses_args
636
+ );
637
+
638
+ echo '</span>';
639
+ }
640
  }
dropins/SimpleHistoryIpInfoDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: IP Info
@@ -8,204 +8,198 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryIpInfoDropin
12
- {
13
-
14
- private $sh;
15
-
16
- public function __construct($sh)
17
- {
18
-
19
- $this->sh = $sh;
20
-
21
- add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
22
- add_action('simple_history/admin_footer', array( $this, 'add_js_template' ));
23
-
24
- add_filter(
25
- 'simple_history/row_header_output/display_ip_address',
26
- [ $this, 'row_header_display_ip_address_filter'],
27
- 10,
28
- 2
29
- );
30
- }
31
-
32
- /**
33
- * Display IP Addressses for login related messages.
34
- *
35
- * @param bool $bool
36
- * @param object $row
37
- * @return bool
38
- */
39
- public function row_header_display_ip_address_filter($bool, $row)
40
- {
41
- // Bail if log row in not from our logger.
42
- if ('SimpleUserLogger' !== $row->logger) {
43
- return $bool;
44
- }
45
-
46
- // Message keys to show IP Addresses for.
47
- $arr_keys_to_log = [
48
- 'user_logged_in',
49
- 'user_login_failed',
50
- 'user_unknown_login_failed',
51
- 'user_unknown_logged_in',
52
- ];
53
-
54
- // Bail if not correct message key.
55
- if (!in_array($row->context_message_key, $arr_keys_to_log)) {
56
- return $bool;
57
- }
58
-
59
- return true;
60
- }
61
-
62
- public function enqueue_admin_scripts()
63
- {
64
-
65
- $file_url = plugin_dir_url(__FILE__);
66
-
67
- wp_enqueue_script('simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
68
-
69
- wp_enqueue_style('simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.css', null, SIMPLE_HISTORY_VERSION);
70
- }
71
-
72
- public function add_js_template()
73
- {
74
- ?>
75
-
76
- <div class="SimpleHistoryIpInfoDropin__popup">
77
- <div class="SimpleHistoryIpInfoDropin__popupArrow"></div>
78
- <div class="SimpleHistoryIpInfoDropin__popupClose"><button class="SimpleHistoryIpInfoDropin__popupCloseButton">×</button></div>
79
- <div class="SimpleHistoryIpInfoDropin__popupContent"></div>
80
- </div>
81
-
82
- <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loading">
83
- <p><?php _ex('Getting IP info ...', 'IP Info Dropin', 'simple-history'); ?></p>
84
- </script>
85
-
86
- <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-error">
87
- <p><?php _ex('Could not get info about IP address.', 'IP Info Dropin', 'simple-history'); ?></p>
88
- </script>
89
-
90
- <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loaded">
91
- <!--
92
- {
93
- "ip": "8.8.8.8",
94
- "hostname": "google-public-dns-a.google.com",
95
- "city": "Mountain View",
96
- "region": "California",
97
- "country": "US",
98
- "loc": "37.3860,-122.0838",
99
- "org": "AS15169 Google Inc.",
100
- "postal": "94035"
101
- }
102
- -->
103
- <# if ( typeof(data.bogon) != "undefined" ) { #>
104
-
105
- <p><?php _ex('That IP address does not seem like a public one.', 'IP Info Dropin', 'simple-history'); ?></p>
106
-
107
- <# } else { #>
108
-
109
- <table class="SimpleHistoryIpInfoDropin__ipInfoTable">
110
-
111
- <tr class="SimpleHistoryIpInfoDropin__ipInfoTable__mapRow">
112
- <td colspan="2">
113
- <!--
114
- <# if ( typeof(data.loc) != "undefined" && data.loc ) { #>
115
- <a href="https://www.google.com/maps/place/{{ data.loc }}/@{{ data.loc }},6z" target="_blank">
116
- <img src="https://maps.googleapis.com/maps/api/staticmap?center={{ data.loc }}&zoom=7&size=350x100&sensor=false" width="350" height="100" alt="Google Map">
117
- </a>
118
- <# } #>
119
- -->
120
- </td>
121
- </tr>
122
-
123
- <# if ( typeof(data.ip) != "undefined" && data.ip ) { #>
124
- <tr>
125
- <td>
126
- <?php _ex('IP address', 'IP Info Dropin', 'simple-history'); ?>
127
- </td>
128
- <td>
129
- {{ data.ip }}
130
- </td>
131
- </tr>
132
- <# } #>
133
-
134
- <# if ( typeof(data.hostname) != "undefined" && data.hostname ) { #>
135
- <tr>
136
- <td>
137
- <?php _ex('Hostname', 'IP Info Dropin', 'simple-history'); ?>
138
- </td>
139
- <td>
140
- {{ data.hostname }}
141
- </td>
142
- </tr>
143
- <# } #>
144
-
145
- <# if ( typeof(data.org) != "undefined" && data.org ) { #>
146
- <tr>
147
- <td>
148
- <?php _ex('Network', 'IP Info Dropin', 'simple-history'); ?>
149
- </td>
150
- <td>
151
- {{ data.org }}
152
- </td>
153
- </tr>
154
- <# } #>
155
-
156
- <# if ( typeof(data.network) != "undefined" && data.network ) { #>
157
- <tr>
158
- <td>
159
- <?php _ex('Network', 'IP Info Dropin', 'simple-history'); ?>
160
- </td>
161
- <td>
162
- {{ data.network }}
163
- </td>
164
- </tr>
165
- <# } #>
166
-
167
- <# if ( typeof(data.city) != "undefined" && data.city ) { #>
168
- <tr>
169
- <td>
170
- <?php _ex('City', 'IP Info Dropin', 'simple-history'); ?>
171
- </td>
172
- <td>
173
- {{ data.city }}
174
- </td>
175
- </tr>
176
- <# } #>
177
-
178
- <# if ( typeof(data.region) != "undefined" && data.region ) { #>
179
- <tr>
180
- <td>
181
- <?php _ex('Region', 'IP Info Dropin', 'simple-history'); ?>
182
- </td>
183
- <td>
184
- {{ data.region }}
185
- </td>
186
- </tr>
187
- <# } #>
188
-
189
- <# if ( typeof(data.country) != "undefined" && data.country ) { #>
190
- <tr>
191
- <td>
192
- <?php _ex('Country', 'IP Info Dropin', 'simple-history'); ?>
193
- </td>
194
- <td>
195
- {{ data.country }}
196
- </td>
197
- </tr>
198
- <# } #>
199
-
200
- </table>
201
-
202
- <p class="SimpleHistoryIpInfoDropin__provider">
203
- <?php printf(_x('IP info provided by %1$s ipinfo.io %2$s', 'IP Info Dropin', 'simple-history'), "<a href='https://ipinfo.io/{{ data.ip }}' target='_blank'>", '</a>'); ?>
204
- </p>
205
-
206
- <# } #>
207
-
208
- </script>
209
- <?php
210
- }
211
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: IP Info
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryIpInfoDropin {
12
+ private $sh;
13
+
14
+ public function __construct( $sh ) {
15
+
16
+ $this->sh = $sh;
17
+
18
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
19
+ add_action( 'simple_history/admin_footer', array( $this, 'add_js_template' ) );
20
+
21
+ add_filter(
22
+ 'simple_history/row_header_output/display_ip_address',
23
+ array( $this, 'row_header_display_ip_address_filter' ),
24
+ 10,
25
+ 2
26
+ );
27
+ }
28
+
29
+ /**
30
+ * Display IP Addressses for login related messages.
31
+ *
32
+ * @param bool $bool
33
+ * @param object $row
34
+ * @return bool
35
+ */
36
+ public function row_header_display_ip_address_filter( $bool, $row ) {
37
+ // Bail if log row in not from our logger.
38
+ if ( 'SimpleUserLogger' !== $row->logger ) {
39
+ return $bool;
40
+ }
41
+
42
+ // Message keys to show IP Addresses for.
43
+ $arr_keys_to_log = array(
44
+ 'user_logged_in',
45
+ 'user_login_failed',
46
+ 'user_unknown_login_failed',
47
+ 'user_unknown_logged_in',
48
+ );
49
+
50
+ // Bail if not correct message key.
51
+ if ( ! in_array( $row->context_message_key, $arr_keys_to_log ) ) {
52
+ return $bool;
53
+ }
54
+
55
+ return true;
56
+ }
57
+
58
+ public function enqueue_admin_scripts() {
59
+
60
+ $file_url = plugin_dir_url( __FILE__ );
61
+
62
+ wp_enqueue_script( 'simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
63
+
64
+ wp_enqueue_style( 'simple_history_IpInfoDropin', $file_url . 'SimpleHistoryIpInfoDropin.css', null, SIMPLE_HISTORY_VERSION );
65
+ }
66
+
67
+ public function add_js_template() {
68
+ ?>
69
+
70
+ <div class="SimpleHistoryIpInfoDropin__popup">
71
+ <div class="SimpleHistoryIpInfoDropin__popupArrow"></div>
72
+ <div class="SimpleHistoryIpInfoDropin__popupClose"><button class="SimpleHistoryIpInfoDropin__popupCloseButton">×</button></div>
73
+ <div class="SimpleHistoryIpInfoDropin__popupContent"></div>
74
+ </div>
75
+
76
+ <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loading">
77
+ <p><?php echo esc_html_x( 'Getting IP info ...', 'IP Info Dropin', 'simple-history' ); ?></p>
78
+ </script>
79
+
80
+ <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-error">
81
+ <p><?php echo esc_html_x( 'Could not get info about IP address.', 'IP Info Dropin', 'simple-history' ); ?></p>
82
+ </script>
83
+
84
+ <script type="text/html" id="tmpl-simple-history-ipinfodropin-popup-loaded">
85
+ <!--
86
+ {
87
+ "ip": "8.8.8.8",
88
+ "hostname": "google-public-dns-a.google.com",
89
+ "city": "Mountain View",
90
+ "region": "California",
91
+ "country": "US",
92
+ "loc": "37.3860,-122.0838",
93
+ "org": "AS15169 Google Inc.",
94
+ "postal": "94035"
95
+ }
96
+ -->
97
+ <# if ( typeof(data.bogon) != "undefined" ) { #>
98
+
99
+ <p><?php echo esc_html_x( 'That IP address does not seem like a public one.', 'IP Info Dropin', 'simple-history' ); ?></p>
100
+
101
+ <# } else { #>
102
+
103
+ <table class="SimpleHistoryIpInfoDropin__ipInfoTable">
104
+
105
+ <tr class="SimpleHistoryIpInfoDropin__ipInfoTable__mapRow">
106
+ <td colspan="2">
107
+ <!--
108
+ <# if ( typeof(data.loc) != "undefined" && data.loc ) { #>
109
+ <a href="https://www.google.com/maps/place/{{ data.loc }}/@{{ data.loc }},6z" target="_blank">
110
+ <img src="https://maps.googleapis.com/maps/api/staticmap?center={{ data.loc }}&zoom=7&size=350x100&sensor=false" width="350" height="100" alt="Google Map">
111
+ </a>
112
+ <# } #>
113
+ -->
114
+ </td>
115
+ </tr>
116
+
117
+ <# if ( typeof(data.ip) != "undefined" && data.ip ) { #>
118
+ <tr>
119
+ <td>
120
+ <?php echo esc_html_x( 'IP address', 'IP Info Dropin', 'simple-history' ); ?>
121
+ </td>
122
+ <td>
123
+ {{ data.ip }}
124
+ </td>
125
+ </tr>
126
+ <# } #>
127
+
128
+ <# if ( typeof(data.hostname) != "undefined" && data.hostname ) { #>
129
+ <tr>
130
+ <td>
131
+ <?php echo esc_html_x( 'Hostname', 'IP Info Dropin', 'simple-history' ); ?>
132
+ </td>
133
+ <td>
134
+ {{ data.hostname }}
135
+ </td>
136
+ </tr>
137
+ <# } #>
138
+
139
+ <# if ( typeof(data.org) != "undefined" && data.org ) { #>
140
+ <tr>
141
+ <td>
142
+ <?php echo esc_html_x( 'Network', 'IP Info Dropin', 'simple-history' ); ?>
143
+ </td>
144
+ <td>
145
+ {{ data.org }}
146
+ </td>
147
+ </tr>
148
+ <# } #>
149
+
150
+ <# if ( typeof(data.network) != "undefined" && data.network ) { #>
151
+ <tr>
152
+ <td>
153
+ <?php echo esc_html_x( 'Network', 'IP Info Dropin', 'simple-history' ); ?>
154
+ </td>
155
+ <td>
156
+ {{ data.network }}
157
+ </td>
158
+ </tr>
159
+ <# } #>
160
+
161
+ <# if ( typeof(data.city) != "undefined" && data.city ) { #>
162
+ <tr>
163
+ <td>
164
+ <?php echo esc_html_x( 'City', 'IP Info Dropin', 'simple-history' ); ?>
165
+ </td>
166
+ <td>
167
+ {{ data.city }}
168
+ </td>
169
+ </tr>
170
+ <# } #>
171
+
172
+ <# if ( typeof(data.region) != "undefined" && data.region ) { #>
173
+ <tr>
174
+ <td>
175
+ <?php echo esc_html_x( 'Region', 'IP Info Dropin', 'simple-history' ); ?>
176
+ </td>
177
+ <td>
178
+ {{ data.region }}
179
+ </td>
180
+ </tr>
181
+ <# } #>
182
+
183
+ <# if ( typeof(data.country) != "undefined" && data.country ) { #>
184
+ <tr>
185
+ <td>
186
+ <?php echo esc_html_x( 'Country', 'IP Info Dropin', 'simple-history' ); ?>
187
+ </td>
188
+ <td>
189
+ {{ data.country }}
190
+ </td>
191
+ </tr>
192
+ <# } #>
193
+
194
+ </table>
195
+
196
+ <p class="SimpleHistoryIpInfoDropin__provider">
197
+ <?php printf( esc_html_x( 'IP info provided by %1$s ipinfo.io %2$s', 'IP Info Dropin', 'simple-history' ), "<a href='https://ipinfo.io/{{ data.ip }}' target='_blank'>", '</a>' ); ?>
198
+ </p>
199
+
200
+ <# } #>
201
+
202
+ </script>
203
+ <?php
204
+ }
 
 
 
 
 
 
205
  }
dropins/SimpleHistoryNewRowsNotifier.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Dropin Name: New Items Notifier
@@ -9,90 +9,93 @@ defined('ABSPATH') or die();
9
  * Author: Pär Thernström
10
  */
11
 
12
- class SimpleHistoryNewRowsNotifier
13
- {
14
 
15
- // Simple History instance
16
- private $sh;
17
 
18
- // How often we should check for new rows, in ms
19
- private $interval = 10000;
20
 
21
- public function __construct($sh)
22
- {
23
 
24
- $this->sh = $sh;
25
 
26
- // How often the script checks for new rows
27
- $this->interval = (int) apply_filters('SimpleHistoryNewRowsNotifier/interval', $this->interval);
28
 
29
- add_action('wp_ajax_SimpleHistoryNewRowsNotifier', array( $this, 'ajax' ));
30
- add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
31
- }
32
 
33
- public function enqueue_admin_scripts()
34
- {
 
35
 
36
- $file_url = plugin_dir_url(__FILE__);
37
 
38
- wp_enqueue_script('simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
39
 
40
- $arr_localize_data = array(
41
- 'interval' => $this->interval,
42
- 'errorCheck' => _x('An error occured while checking for new events', 'New rows notifier: error while checking for new rows', 'simple-history'),
43
- );
44
 
45
- wp_localize_script('simple_history_NewRowsNotifierDropin', 'simple_history_NewRowsNotifierDropin', $arr_localize_data);
 
 
 
46
 
47
- wp_enqueue_style('simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.css', null, SIMPLE_HISTORY_VERSION);
48
- }
49
 
50
- public function ajax()
51
- {
52
 
53
- $apiArgs = isset($_GET['apiArgs']) ? $_GET['apiArgs'] : array();
54
 
55
- if (! $apiArgs) {
56
- wp_send_json_error(array(
57
- 'error' => 'MISSING_APIARGS',
58
- ));
59
- }
60
 
61
- if (empty($apiArgs['since_id']) || ! is_numeric($apiArgs['since_id'])) {
62
- wp_send_json_error(array(
63
- 'error' => 'MISSING_SINCE_ID',
64
- ));
65
- }
 
 
66
 
67
- // User must have capability to view the history page
68
- if (! current_user_can($this->sh->get_view_history_capability())) {
69
- wp_send_json_error(array(
70
- 'error' => 'CAPABILITY_ERROR',
71
- ));
72
- }
 
73
 
74
- // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null;
75
- $logQueryArgs = $apiArgs;
 
 
 
 
 
 
76
 
77
- $logQuery = new SimpleHistoryLogQuery();
78
- $answer = $logQuery->query($logQueryArgs);
79
 
80
- // Use our own response array instead of $answer to keep size down
81
- $json_data = array();
82
 
83
- $numNewRows = isset($answer['total_row_count']) ? $answer['total_row_count'] : 0;
84
- $json_data['num_new_rows'] = $numNewRows;
85
- $json_data['num_mysql_queries'] = get_num_queries();
86
 
87
- if ($numNewRows) {
88
- // We have new rows
89
- // Append strings
90
- $textRowsFound = sprintf(_n('1 new event', '%d new events', $numNewRows, 'simple-history'), $numNewRows);
91
- $json_data['strings'] = array(
92
- 'newRowsFound' => $textRowsFound,
93
- );
94
- }
95
 
96
- wp_send_json_success($json_data);
97
- }
 
 
 
 
 
 
 
 
 
98
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Dropin Name: New Items Notifier
9
  * Author: Pär Thernström
10
  */
11
 
12
+ class SimpleHistoryNewRowsNotifier {
 
13
 
 
 
14
 
15
+ // Simple History instance
16
+ private $sh;
17
 
18
+ // How often we should check for new rows, in ms
19
+ private $interval = 10000;
20
 
21
+ public function __construct( $sh ) {
22
 
23
+ $this->sh = $sh;
 
24
 
25
+ // How often the script checks for new rows
26
+ $this->interval = (int) apply_filters( 'SimpleHistoryNewRowsNotifier/interval', $this->interval );
 
27
 
28
+ add_action( 'wp_ajax_SimpleHistoryNewRowsNotifier', array( $this, 'ajax' ) );
29
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
30
+ }
31
 
32
+ public function enqueue_admin_scripts() {
33
 
34
+ $file_url = plugin_dir_url( __FILE__ );
35
 
36
+ wp_enqueue_script( 'simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
 
 
 
37
 
38
+ $arr_localize_data = array(
39
+ 'interval' => $this->interval,
40
+ 'errorCheck' => _x( 'An error occured while checking for new events', 'New rows notifier: error while checking for new rows', 'simple-history' ),
41
+ );
42
 
43
+ wp_localize_script( 'simple_history_NewRowsNotifierDropin', 'simple_history_NewRowsNotifierDropin', $arr_localize_data );
 
44
 
45
+ wp_enqueue_style( 'simple_history_NewRowsNotifierDropin', $file_url . 'SimpleHistoryNewRowsNotifierDropin.css', null, SIMPLE_HISTORY_VERSION );
46
+ }
47
 
48
+ public function ajax() {
49
 
50
+ $apiArgs = isset( $_GET['apiArgs'] ) ? $_GET['apiArgs'] : array();
 
 
 
 
51
 
52
+ if ( ! $apiArgs ) {
53
+ wp_send_json_error(
54
+ array(
55
+ 'error' => 'MISSING_APIARGS',
56
+ )
57
+ );
58
+ }
59
 
60
+ if ( empty( $apiArgs['since_id'] ) || ! is_numeric( $apiArgs['since_id'] ) ) {
61
+ wp_send_json_error(
62
+ array(
63
+ 'error' => 'MISSING_SINCE_ID',
64
+ )
65
+ );
66
+ }
67
 
68
+ // User must have capability to view the history page
69
+ if ( ! current_user_can( $this->sh->get_view_history_capability() ) ) {
70
+ wp_send_json_error(
71
+ array(
72
+ 'error' => 'CAPABILITY_ERROR',
73
+ )
74
+ );
75
+ }
76
 
77
+ // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null;
78
+ $logQueryArgs = $apiArgs;
79
 
80
+ $logQuery = new SimpleHistoryLogQuery();
81
+ $answer = $logQuery->query( $logQueryArgs );
82
 
83
+ // Use our own response array instead of $answer to keep size down
84
+ $json_data = array();
 
85
 
86
+ $numNewRows = isset( $answer['total_row_count'] ) ? $answer['total_row_count'] : 0;
87
+ $json_data['num_new_rows'] = $numNewRows;
88
+ $json_data['num_mysql_queries'] = get_num_queries();
 
 
 
 
 
89
 
90
+ if ( $numNewRows ) {
91
+ // We have new rows
92
+ // Append strings
93
+ $textRowsFound = sprintf( _n( '%s new event', '%s new events', $numNewRows, 'simple-history' ), $numNewRows );
94
+ $json_data['strings'] = array(
95
+ 'newRowsFound' => $textRowsFound,
96
+ );
97
+ }
98
+
99
+ wp_send_json_success( $json_data );
100
+ }
101
  }
dropins/SimpleHistoryPluginPatchesDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Plugin Patches
@@ -8,164 +8,156 @@ Dropin Description: Used to patch plugins that behave wierd
8
  Dropin URI: http://simple-history.com/
9
  Author: Pär Thernström
10
  */
11
-
12
- class SimpleHistoryPluginPatchesDropin
13
- {
14
-
15
- private $sh;
16
-
17
- public function __construct($sh)
18
- {
19
-
20
- $this->sh = $sh;
21
-
22
- $this->patch_captcha_on_login();
23
-
24
- add_filter(
25
- 'simple_history/post_logger/skip_posttypes',
26
- array( $this, 'woocommerce_skip_scheduled_actions_posttype' )
27
- );
28
- }
29
-
30
- /**
31
- * Skip logging of WooCommerce scheduled actions/cron related things,
32
- * stored in the scheduled-action"post type. If not disabled the log can be filled with
33
- * a large amount of actions for this postype.
34
- *
35
- * @since 2.3
36
- */
37
- public function woocommerce_skip_scheduled_actions_posttype($skip_posttypes)
38
- {
39
- $skip_posttypes[] = 'scheduled-action';
40
- return $skip_posttypes;
41
- }
42
-
43
- /**
44
- * Captcha on Login
45
- *
46
- * Calls wp_logut() wrongly when
47
- * - a user IP is blocked
48
- * - when max num of tries is reached
49
- * - or when the capcha is not correct
50
- *
51
- * So the event logged will be logged_out but should be user_login_failed or user_unknown_login_failed.
52
- * Wrong events logged reported here:
53
- * https://wordpress.org/support/topic/many-unknown-logged-out-entries
54
- *
55
- * Plugin also gives lots of errors, reported by me here:
56
- * https://wordpress.org/support/topic/errors-has_cap-deprecated-strict-standards-warning
57
- */
58
- public function patch_captcha_on_login()
59
- {
60
-
61
- add_action('simple_history/log/do_log', array( $this, 'patch_captcha_on_login_on_log' ), 10, 5);
62
- }
63
-
64
- // Detect that this log message is being called from Captha on login
65
- // and that the message is "user_logged_out"
66
- public function patch_captcha_on_login_on_log($doLog, $level = null, $message = null, $context = null, $loggerInstance = null)
67
- {
68
-
69
- if (empty($context) || ! isset($context['_message_key']) || 'user_logged_out' != $context['_message_key']) {
70
- // Message key did not exist or was not "user_logged_out"
71
- return $doLog;
72
- }
73
-
74
- // 22 nov 2015: disabled this check beacuse for example robots/scripts don't pass all args
75
- // instead they only post "log" and "pwd"
76
- // codiga is the input with the captcha
77
- /*
78
- if ( ! isset( $_POST["log"], $_POST["pwd"], $_POST["wp-submit"], $_POST["codigo"] ) ) {
79
- // All needed post variables was not set
80
- return $doLog;
81
- }
82
- */
83
-
84
- // The Captcha on login uses a class called 'Anderson_Makiyama_Captcha_On_Login'
85
- // and also a global variable called $global $anderson_makiyama
86
- global $anderson_makiyama;
87
- if (! class_exists('Anderson_Makiyama_Captcha_On_Login') || ! isset($anderson_makiyama)) {
88
- return $doLog;
89
- }
90
-
91
- // We must come from wp-login
92
- // Disabled 22 nov 2015 because robots/scripts dont send referer
93
- /*
94
- $wp_referer = wp_get_referer();
95
- if ( ! $wp_referer || ! "wp-login.php" == basename( $wp_referer ) ) {
96
- return $doLog;
97
- }
98
- */
99
-
100
- if (! isset($_SERVER['REQUEST_URI'])) {
101
- return $doLog;
102
- }
103
-
104
- // File must be wp-login.php (can it even be another?)
105
- $request_uri = basename(wp_unslash($_SERVER['REQUEST_URI']));
106
- if ('wp-login.php' !== $request_uri) {
107
- return $doLog;
108
- }
109
-
110
- $anderson_makiyama_indice = Anderson_Makiyama_Captcha_On_Login::PLUGIN_ID;
111
- $capcha_on_login_class_name = $anderson_makiyama[ $anderson_makiyama_indice ]::CLASS_NAME;
112
-
113
- $capcha_on_login_options = (array) get_option($capcha_on_login_class_name . '_options', array());
114
- $last_100_logins = isset($capcha_on_login_options['last_100_logins']) ? (array) $capcha_on_login_options['last_100_logins'] : array();
115
- $last_100_logins = array_reverse($last_100_logins);
116
-
117
- // Possible messages
118
- // - Failed: IP already blocked
119
- // - Failed: exceeded max number of tries
120
- // - Failed: image code did not match
121
- // - Failed: Login or Password did not match
122
- // - Success
123
- $last_login_status = isset($last_100_logins[0][2]) ? $last_100_logins[0][2] : '';
124
-
125
- // If we get here we're pretty sure we come from Captcha on login
126
- // and that we should cancel the wp_logout message and log an failed login instead
127
- // Get the user logger
128
- $userLogger = $this->sh->getInstantiatedLoggerBySlug('SimpleUserLogger');
129
-
130
- if (! $userLogger) {
131
- return $doLog;
132
- }
133
-
134
- // $userLogger->warningMessage("user_unknown_login_failed", $context);
135
- // Same context as in SimpleUserLogger
136
- $context = array(
137
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
138
- 'server_http_user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null,
139
- '_occasionsID' => 'SimpleUserLogger' . '/failed_user_login',
140
- 'patch_using_patch' => true,
141
- 'patch_name' => 'captcha_on_login',
142
- );
143
-
144
- // Append capcha message
145
- if ($last_login_status) {
146
- $context['patch_last_login_status'] = $last_login_status;
147
- }
148
-
149
- // Get user id and email and login
150
- // Not passed to filter, but we have it in $_POST
151
- $login_username = isset($_POST['log']) ? $_POST['log'] : null;
152
-
153
- if ($login_username) {
154
- $context['login_user_login'] = $login_username;
155
-
156
- $user = get_user_by('login', $login_username);
157
-
158
- if (is_a($user, 'WP_User')) {
159
- $context['login_user_id'] = $user->ID;
160
- $context['login_user_email'] = $user->user_email;
161
- }
162
- }
163
-
164
- $userLogger->warningMessage('user_login_failed', $context);
165
-
166
- // Cancel original log event
167
- $doLog = false;
168
-
169
- return $doLog;
170
- }
171
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Plugin Patches
8
  Dropin URI: http://simple-history.com/
9
  Author: Pär Thernström
10
  */
11
+ class SimpleHistoryPluginPatchesDropin {
12
+ private $sh;
13
+
14
+ public function __construct( $sh ) {
15
+ $this->sh = $sh;
16
+
17
+ $this->patch_captcha_on_login();
18
+
19
+ add_filter(
20
+ 'simple_history/post_logger/skip_posttypes',
21
+ array( $this, 'woocommerce_skip_scheduled_actions_posttype' )
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Skip logging of WooCommerce scheduled actions/cron related things,
27
+ * stored in the scheduled-action"post type. If not disabled the log can be filled with
28
+ * a large amount of actions for this postype.
29
+ *
30
+ * @since 2.3
31
+ */
32
+ public function woocommerce_skip_scheduled_actions_posttype( $skip_posttypes ) {
33
+ $skip_posttypes[] = 'scheduled-action';
34
+ return $skip_posttypes;
35
+ }
36
+
37
+ /**
38
+ * Captcha on Login
39
+ *
40
+ * Calls wp_logut() wrongly when
41
+ * - a user IP is blocked
42
+ * - when max num of tries is reached
43
+ * - or when the capcha is not correct
44
+ *
45
+ * So the event logged will be logged_out but should be user_login_failed or user_unknown_login_failed.
46
+ * Wrong events logged reported here:
47
+ * https://wordpress.org/support/topic/many-unknown-logged-out-entries
48
+ *
49
+ * Plugin also gives lots of errors, reported by me here:
50
+ * https://wordpress.org/support/topic/errors-has_cap-deprecated-strict-standards-warning
51
+ */
52
+ public function patch_captcha_on_login() {
53
+
54
+ add_action( 'simple_history/log/do_log', array( $this, 'patch_captcha_on_login_on_log' ), 10, 5 );
55
+ }
56
+
57
+ // Detect that this log message is being called from Captha on login
58
+ // and that the message is "user_logged_out"
59
+ public function patch_captcha_on_login_on_log( $doLog, $level = null, $message = null, $context = null, $loggerInstance = null ) {
60
+
61
+ if ( empty( $context ) || ! isset( $context['_message_key'] ) || 'user_logged_out' != $context['_message_key'] ) {
62
+ // Message key did not exist or was not "user_logged_out"
63
+ return $doLog;
64
+ }
65
+
66
+ // 22 nov 2015: disabled this check beacuse for example robots/scripts don't pass all args
67
+ // instead they only post "log" and "pwd"
68
+ // codiga is the input with the captcha
69
+ /*
70
+ if ( ! isset( $_POST["log"], $_POST["pwd"], $_POST["wp-submit"], $_POST["codigo"] ) ) {
71
+ // All needed post variables was not set
72
+ return $doLog;
73
+ }
74
+ */
75
+
76
+ // The Captcha on login uses a class called 'Anderson_Makiyama_Captcha_On_Login'
77
+ // and also a global variable called $global $anderson_makiyama
78
+ global $anderson_makiyama;
79
+ if ( ! class_exists( 'Anderson_Makiyama_Captcha_On_Login' ) || ! isset( $anderson_makiyama ) ) {
80
+ return $doLog;
81
+ }
82
+
83
+ // We must come from wp-login
84
+ // Disabled 22 nov 2015 because robots/scripts dont send referer
85
+ /*
86
+ $wp_referer = wp_get_referer();
87
+ if ( ! $wp_referer || ! "wp-login.php" == basename( $wp_referer ) ) {
88
+ return $doLog;
89
+ }
90
+ */
91
+
92
+ if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
93
+ return $doLog;
94
+ }
95
+
96
+ // File must be wp-login.php (can it even be another?)
97
+ $request_uri = basename( wp_unslash( $_SERVER['REQUEST_URI'] ) );
98
+ if ( 'wp-login.php' !== $request_uri ) {
99
+ return $doLog;
100
+ }
101
+
102
+ $anderson_makiyama_indice = Anderson_Makiyama_Captcha_On_Login::PLUGIN_ID;
103
+ $capcha_on_login_class_name = $anderson_makiyama[ $anderson_makiyama_indice ]::CLASS_NAME;
104
+
105
+ $capcha_on_login_options = (array) get_option( $capcha_on_login_class_name . '_options', array() );
106
+ $last_100_logins = isset( $capcha_on_login_options['last_100_logins'] ) ? (array) $capcha_on_login_options['last_100_logins'] : array();
107
+ $last_100_logins = array_reverse( $last_100_logins );
108
+
109
+ // Possible messages
110
+ // - Failed: IP already blocked
111
+ // - Failed: exceeded max number of tries
112
+ // - Failed: image code did not match
113
+ // - Failed: Login or Password did not match
114
+ // - Success
115
+ $last_login_status = isset( $last_100_logins[0][2] ) ? $last_100_logins[0][2] : '';
116
+
117
+ // If we get here we're pretty sure we come from Captcha on login
118
+ // and that we should cancel the wp_logout message and log an failed login instead
119
+ // Get the user logger
120
+ $userLogger = $this->sh->getInstantiatedLoggerBySlug( 'SimpleUserLogger' );
121
+
122
+ if ( ! $userLogger ) {
123
+ return $doLog;
124
+ }
125
+
126
+ // $userLogger->warningMessage("user_unknown_login_failed", $context);
127
+ // Same context as in SimpleUserLogger
128
+ $context = array(
129
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
130
+ 'server_http_user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null,
131
+ '_occasionsID' => 'SimpleUserLogger/failed_user_login',
132
+ 'patch_using_patch' => true,
133
+ 'patch_name' => 'captcha_on_login',
134
+ );
135
+
136
+ // Append capcha message
137
+ if ( $last_login_status ) {
138
+ $context['patch_last_login_status'] = $last_login_status;
139
+ }
140
+
141
+ // Get user id and email and login
142
+ // Not passed to filter, but we have it in $_POST
143
+ $login_username = isset( $_POST['log'] ) ? sanitize_user( $_POST['log'] ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing
144
+
145
+ if ( $login_username ) {
146
+ $context['login_user_login'] = $login_username;
147
+
148
+ $user = get_user_by( 'login', $login_username );
149
+
150
+ if ( is_a( $user, 'WP_User' ) ) {
151
+ $context['login_user_id'] = $user->ID;
152
+ $context['login_user_email'] = $user->user_email;
153
+ }
154
+ }
155
+
156
+ $userLogger->warningMessage( 'user_login_failed', $context );
157
+
158
+ // Cancel original log event
159
+ $doLog = false;
160
+
161
+ return $doLog;
162
+ }
 
 
 
 
 
 
 
 
163
  }
dropins/SimpleHistoryRSSDropin.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
 
3
- // defined('ABSPATH') or die();
 
4
  /*
5
  Dropin Name: Global RSS Feed
6
  Dropin URI: http://simple-history.com/
@@ -10,400 +11,428 @@ Author: Pär Thernström
10
  /**
11
  * Simple History RSS Feed drop-in
12
  */
13
- class SimpleHistoryRSSDropin
14
- {
15
- public function __construct($sh)
16
- {
17
-
18
- $this->sh = $sh;
19
-
20
- if (! function_exists('get_editable_roles')) {
21
- require_once(ABSPATH . '/wp-admin/includes/user.php');
22
- }
23
-
24
- // Check the status of the RSS feed
25
- $this->isRssEnabled();
26
-
27
- // Generate a rss secret, if it does not exist
28
- if (! get_option('simple_history_rss_secret')) {
29
- $this->updateRssSecret();
30
- }
31
-
32
- add_action('init', array( $this, 'checkForRssFeedRequest' ));
33
-
34
- // Add settings with prio 11 so it' added after the main Simple History settings
35
- add_action('admin_menu', array( $this, 'addSettings' ), 11);
36
- }
37
-
38
- /**
39
- * Add settings for the RSS feed
40
- * + also regenerates the secret if requested
41
- */
42
- public function addSettings()
43
- {
44
-
45
- // we register a setting to keep track of the RSS feed status (enabled/disabled)
46
- register_setting(
47
- SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
48
- 'simple_history_enable_rss_feed',
49
- array(
50
- $this,
51
- 'public updateRssStatus',
52
- )
53
- );
54
- /**
55
- * Start new section for RSS feed
56
- */
57
- $settings_section_rss_id = 'simple_history_settings_section_rss';
58
-
59
- add_settings_section(
60
- $settings_section_rss_id,
61
- _x('RSS feed', 'rss settings headline', 'simple-history'), // No title __("General", "simple-history"),
62
- array( $this, 'settingsSectionOutput' ),
63
- SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
64
- );
65
-
66
- // Enable/Disabled RSS feed
67
- add_settings_field(
68
- 'simple_history_enable_rss_feed',
69
- __('Enable', 'simple-history'),
70
- array( $this, 'settingsFieldRssEnable' ),
71
- SimpleHistory::SETTINGS_MENU_SLUG,
72
- $settings_section_rss_id
73
- );
74
-
75
- // if RSS is activated we display other fields
76
- if ($this->isRssEnabled()) {
77
- // RSS address
78
- add_settings_field(
79
- 'simple_history_rss_feed',
80
- __('Address', 'simple-history'),
81
- array( $this, 'settingsFieldRss' ),
82
- SimpleHistory::SETTINGS_MENU_SLUG,
83
- $settings_section_rss_id
84
- );
85
-
86
- // Regnerate address
87
- add_settings_field(
88
- 'simple_history_rss_feed_regenerate_secret',
89
- __('Regenerate', 'simple-history'),
90
- array( $this, 'settingsFieldRssRegenerate' ),
91
- SimpleHistory::SETTINGS_MENU_SLUG,
92
- $settings_section_rss_id
93
- );
94
- }
95
-
96
- // Create new RSS secret
97
- $create_new_secret = false;
98
- $create_secret_nonce_name = 'simple_history_rss_secret_regenerate_nonce';
99
- $createNonceOk = isset($_GET[ $create_secret_nonce_name ]) && wp_verify_nonce($_GET[ $create_secret_nonce_name ], 'simple_history_rss_update_secret');
100
-
101
- if ($createNonceOk) {
102
- $create_new_secret = true;
103
- $this->updateRssSecret();
104
-
105
- // Add updated-message and store in transient and then redirect
106
- // This is the way options.php does it.
107
- $msg = __('Created new secret RSS address', 'simple-history');
108
- add_settings_error('simple_history_rss_feed_regenerate_secret', 'simple_history_rss_feed_regenerate_secret', $msg, 'updated');
109
- set_transient('settings_errors', get_settings_errors(), 30);
110
-
111
- $goback = esc_url_raw(add_query_arg('settings-updated', 'true', wp_get_referer()));
112
- wp_redirect($goback);
113
- exit;
114
- }
115
- }
116
-
117
- /**
118
- * Check if RSS feed is enabled or disabled
119
- */
120
- public function isRssEnabled()
121
- {
122
-
123
- // User has never used the plugin we disable RSS feed
124
- if (get_option('simple_history_rss_secret') === false && get_option('simple_history_enable_rss_feed') === false) {
125
- // We disable RSS by default, we use 0/1 to prevent fake disabled with bools from functions returning false for unset
126
- update_option('simple_history_enable_rss_feed', '0');
127
- } elseif (get_option('simple_history_enable_rss_feed') === false) {
128
- // User was using the plugin before RSS feed became disabled by default
129
- // We activate RSS to prevent a "breaking change"
130
- update_option('simple_history_enable_rss_feed', '1');
131
- return true;
132
- } elseif (get_option('simple_history_enable_rss_feed') === '1') {
133
- return true;
134
- }
135
-
136
- return false;
137
- }
138
-
139
- /**
140
- * Output for settings field that show current RSS address
141
- */
142
- public function settingsFieldRssEnable()
143
- {
144
- ?>
145
- <input value="1" type="checkbox" id="simple_history_enable_rss_feed" name="simple_history_enable_rss_feed" <?php checked($this->isRssEnabled(), 1); ?> />
146
- <label for="simple_history_enable_rss_feed"><?php _e('Enable RSS feed', 'simple-history') ?></label>
147
- <?php
148
- }
149
-
150
- /**
151
- * Sanitize RSS enabled/disabled status on update settings
152
- */
153
- public function updateRssStatus($field)
154
- {
155
-
156
- if ($field === '1') {
157
- return '1';
158
- }
159
-
160
- return '0';
161
- }
162
-
163
-
164
- /**
165
- * Check if current request is a request for the RSS feed
166
- */
167
- public function checkForRssFeedRequest()
168
- {
169
- // check for RSS
170
- // don't know if this is the right way to do this, but it seems to work!
171
- if (isset($_GET['simple_history_get_rss'])) {
172
- $this->outputRss();
173
- exit;
174
- }
175
- }
176
-
177
- /**
178
- * Modify capability check so all users reading rss feed (logged in or not) can read all loggers
179
- */
180
- public function onCanReadSingleLogger($user_can_read_logger, $logger_instance, $user_id)
181
- {
182
- $user_can_read_logger = true;
183
-
184
- return $user_can_read_logger;
185
- }
186
-
187
- /**
188
- * Output RSS
189
- */
190
- public function outputRss()
191
- {
192
-
193
- $rss_secret_option = get_option('simple_history_rss_secret');
194
- $rss_secret_get = isset($_GET['rss_secret']) ? $_GET['rss_secret'] : '';
195
-
196
- if (empty($rss_secret_option) || empty($rss_secret_get)) {
197
- die();
198
- }
199
-
200
- $rss_show = true;
201
- $rss_show = apply_filters('simple_history/rss_feed_show', $rss_show);
202
- if (! $rss_show || ! $this->isRssEnabled()) {
203
- wp_die('Nothing here.');
204
- }
205
-
206
- header('Content-Type: text/xml; charset=utf-8');
207
- echo '<?xml version="1.0" encoding="UTF-8"?>';
208
- $self_link = $this->getRssAddress();
209
-
210
- if ($rss_secret_option === $rss_secret_get) {
211
- ?>
212
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
213
- <channel>
214
- <title><![CDATA[<?php printf(__('History for %s', 'simple-history'), get_bloginfo('name')) ?>]]></title>
215
- <description><![CDATA[<?php printf(__('WordPress History for %s', 'simple-history'), get_bloginfo('name')) ?>]]></description>
216
- <link><?php echo get_bloginfo('url') ?></link>
217
- <atom:link href="<?php echo $self_link; ?>" rel="self" type="application/atom+xml" />
218
- <?php
219
-
220
- // Override capability check: if you have a valid rss_secret_key you can read it all
221
- $action_tag = 'simple_history/loggers_user_can_read/can_read_single_logger';
222
- add_action($action_tag, array( $this, 'onCanReadSingleLogger' ), 10, 3);
223
-
224
- // Modify header time output so it does not show relative date or time ago-format
225
- // Because we don't know when a user reads the RSS feed, time ago format may be very inaccurate
226
- add_action('simple_history/header_just_now_max_time', '__return_zero');
227
- add_action('simple_history/header_time_ago_max_time', '__return_zero');
228
-
229
- // Get log rows
230
- $args = array(
231
- 'posts_per_page' => 10,
232
- );
233
-
234
- $args = apply_filters('simple_history/rss_feed_args', $args);
235
-
236
- $logQuery = new SimpleHistoryLogQuery();
237
- $queryResults = $logQuery->query($args);
238
-
239
- // Remove capability override after query is done
240
- // remove_action( $action_tag, array($this, "onCanReadSingleLogger") );
241
- foreach ($queryResults['log_rows'] as $row) {
242
- $header_output = $this->sh->getLogRowHeaderOutput($row);
243
- $text_output = $this->sh->getLogRowPlainTextOutput($row);
244
- $details_output = $this->sh->getLogRowDetailsOutput($row);
245
-
246
- // http://cyber.law.harvard.edu/rss/rss.html#ltguidgtSubelementOfLtitemgt
247
- // $item_guid = home_url() . "?SimpleHistoryGuid=" . $row->id;
248
- $item_guid = esc_url(add_query_arg('SimpleHistoryGuid', $row->id, home_url()));
249
- $item_link = esc_url(add_query_arg('SimpleHistoryGuid', $row->id, home_url()));
250
-
251
- /**
252
- * Filter the guid/link URL used in RSS feed.
253
- * Link will be esc_url'ed by simple history, so no need to do that in your filter
254
- *
255
- * @since 2.0.23
256
- *
257
- * @param string $item_guid link.
258
- * @param array $row
259
- */
260
- $item_link = apply_filters('simple_history/rss_item_link', $item_link, $row);
261
- $item_link = esc_url($item_link);
262
-
263
- $item_title = sprintf(
264
- '%2$s',
265
- $this->sh->getLogLevelTranslated($row->level),
266
- wp_kses($text_output, array())
267
- );
268
-
269
- $level_output = sprintf(__('Severity level: %1$s'), $this->sh->getLogLevelTranslated($row->level));
270
-
271
- ?>
272
- <item>
273
- <title><![CDATA[<?php echo $item_title; ?>]]></title>
274
- <description><![CDATA[
275
- <p><?php echo $header_output ?></p>
276
- <p><?php echo $text_output ?></p>
277
- <div><?php echo $details_output ?></div>
278
- <p><?php echo $level_output ?></p>
279
- <?php
280
- $occasions = $row->subsequentOccasions - 1;
281
- if ($occasions) {
282
- printf(
283
- _n('+%1$s occasion', '+%1$s occasions', $occasions, 'simple-history'),
284
- $occasions
285
- );
286
- }
287
- ?>
288
- ]]></description>
289
- <?php
290
- // author must be email to validate, but the field is optional, so we skip it
291
- /* <author><?php echo $row->initiator ?></author> */
292
- ?>
293
- <pubDate><?php echo date('D, d M Y H:i:s', strtotime($row->date)) ?> GMT</pubDate>
294
- <guid isPermaLink="false"><![CDATA[<?php echo $item_guid ?>]]></guid>
295
- <link><![CDATA[<?php echo $item_link ?>]]></link>
296
- </item>
297
- <?php
298
- } // End foreach().
299
-
300
- ?>
301
- </channel>
302
- </rss>
303
- <?php
304
- } else {
305
- // RSS secret was not ok
306
- ?>
307
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
308
- <channel>
309
- <title><?php printf(__('History for %s', 'simple-history'), get_bloginfo('name')) ?></title>
310
- <description><?php printf(__('WordPress History for %s', 'simple-history'), get_bloginfo('name')) ?></description>
311
- <link><?php echo home_url() ?></link>
312
- <item>
313
- <title><?php _e('Wrong RSS secret', 'simple-history')?></title>
314
- <description><?php _e('Your RSS secret for Simple History RSS feed is wrong. Please see WordPress settings for current link to the RSS feed.', 'simple-history')?></description>
315
- <pubDate><?php echo date('D, d M Y H:i:s', time()) ?> GMT</pubDate>
316
- <guid><?php echo home_url() . '?SimpleHistoryGuid=wrong-secret' ?></guid>
317
- </item>
318
- </channel>
319
- </rss>
320
- <?php
321
- }// End if().
322
- }
323
-
324
- /**
325
- * Create a new RSS secret
326
- *
327
- * @return string new secret
328
- */
329
- public function updateRssSecret()
330
- {
331
-
332
- $rss_secret = '';
333
-
334
- for ($i = 0; $i < 20; $i++) {
335
- $rss_secret .= chr(rand(97, 122));
336
- }
337
-
338
- update_option('simple_history_rss_secret', $rss_secret);
339
-
340
- return $rss_secret;
341
- }
342
-
343
- /**
344
- * Output for settings field that show current RSS address
345
- */
346
- public function settingsFieldRss()
347
- {
348
-
349
- $rss_address = $this->getRssAddress();
350
-
351
- echo "<p><code><a href='$rss_address'>$rss_address</a></code></p>";
352
- }
353
-
354
- /**
355
- * Output for settings field that regenerates the RSS adress/secret
356
- */
357
- public function settingsFieldRssRegenerate()
358
- {
359
-
360
- $update_link = esc_url(add_query_arg('', ''));
361
- $update_link = wp_nonce_url($update_link, 'simple_history_rss_update_secret', 'simple_history_rss_secret_regenerate_nonce');
362
-
363
- echo '<p>';
364
- _e('You can generate a new address for the RSS feed. This is useful if you think that the address has fallen into the wrong hands.', 'simple-history');
365
- echo '</p>';
366
-
367
- echo '<p>';
368
- printf(
369
- '<a class="button" href="%1$s">%2$s</a>',
370
- $update_link, // 1
371
- __('Generate new address', 'simple-history') // 2
372
- );
373
-
374
- echo '</p>';
375
- }
376
-
377
- /**
378
- * Get the URL to the RSS feed
379
- *
380
- * @return string URL
381
- */
382
- public function getRssAddress()
383
- {
384
-
385
- $rss_secret = get_option('simple_history_rss_secret');
386
- $rss_address = add_query_arg(
387
- array(
388
- 'simple_history_get_rss' => '1',
389
- 'rss_secret' => $rss_secret,
390
- ),
391
- get_bloginfo('url') . '/'
392
- );
393
- $rss_address = esc_url($rss_address);
394
- // $rss_address = htmlspecialchars($rss_address, ENT_COMPAT, "UTF-8");
395
- return $rss_address;
396
- }
397
-
398
- /**
399
- * Content for section intro. Leave it be, even if empty.
400
- * Called from add_sections_setting.
401
- */
402
- public function settingsSectionOutput()
403
- {
404
-
405
- echo '<p>';
406
- _e('Simple History has a RSS feed which you can subscribe to and receive log updates. Make sure you only share the feed with people you trust, since it can contain sensitive or confidential information.', 'simple-history');
407
- echo '</p>';
408
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
+
5
  /*
6
  Dropin Name: Global RSS Feed
7
  Dropin URI: http://simple-history.com/
11
  /**
12
  * Simple History RSS Feed drop-in
13
  */
14
+ class SimpleHistoryRSSDropin {
15
+
16
+ public function __construct( $sh ) {
17
+
18
+ $this->sh = $sh;
19
+
20
+ if ( ! function_exists( 'get_editable_roles' ) ) {
21
+ require_once( ABSPATH . '/wp-admin/includes/user.php' );
22
+ }
23
+
24
+ // Check the status of the RSS feed
25
+ $this->isRssEnabled();
26
+
27
+ // Generate a rss secret, if it does not exist
28
+ if ( ! get_option( 'simple_history_rss_secret' ) ) {
29
+ $this->updateRssSecret();
30
+ }
31
+
32
+ add_action( 'init', array( $this, 'checkForRssFeedRequest' ) );
33
+
34
+ // Add settings with prio 11 so it' added after the main Simple History settings
35
+ add_action( 'admin_menu', array( $this, 'addSettings' ), 11 );
36
+ }
37
+
38
+ /**
39
+ * Add settings for the RSS feed
40
+ * + also regenerates the secret if requested
41
+ */
42
+ public function addSettings() {
43
+
44
+ // we register a setting to keep track of the RSS feed status (enabled/disabled)
45
+ register_setting(
46
+ SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP,
47
+ 'simple_history_enable_rss_feed',
48
+ array(
49
+ $this,
50
+ 'public updateRssStatus',
51
+ )
52
+ );
53
+ /**
54
+ * Start new section for RSS feed
55
+ */
56
+ $settings_section_rss_id = 'simple_history_settings_section_rss';
57
+
58
+ add_settings_section(
59
+ $settings_section_rss_id,
60
+ _x( 'RSS feed', 'rss settings headline', 'simple-history' ), // No title __("General", "simple-history"),
61
+ array( $this, 'settingsSectionOutput' ),
62
+ SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
63
+ );
64
+
65
+ // Enable/Disabled RSS feed
66
+ add_settings_field(
67
+ 'simple_history_enable_rss_feed',
68
+ __( 'Enable', 'simple-history' ),
69
+ array( $this, 'settingsFieldRssEnable' ),
70
+ SimpleHistory::SETTINGS_MENU_SLUG,
71
+ $settings_section_rss_id
72
+ );
73
+
74
+ // if RSS is activated we display other fields
75
+ if ( $this->isRssEnabled() ) {
76
+ // RSS address
77
+ add_settings_field(
78
+ 'simple_history_rss_feed',
79
+ __( 'Address', 'simple-history' ),
80
+ array( $this, 'settingsFieldRss' ),
81
+ SimpleHistory::SETTINGS_MENU_SLUG,
82
+ $settings_section_rss_id
83
+ );
84
+
85
+ // Regnerate address
86
+ add_settings_field(
87
+ 'simple_history_rss_feed_regenerate_secret',
88
+ __( 'Regenerate', 'simple-history' ),
89
+ array( $this, 'settingsFieldRssRegenerate' ),
90
+ SimpleHistory::SETTINGS_MENU_SLUG,
91
+ $settings_section_rss_id
92
+ );
93
+ }
94
+
95
+ // Create new RSS secret
96
+ $create_new_secret = false;
97
+ $create_secret_nonce_name = 'simple_history_rss_secret_regenerate_nonce';
98
+ $createNonceOk = isset( $_GET[ $create_secret_nonce_name ] ) && wp_verify_nonce( $_GET[ $create_secret_nonce_name ], 'simple_history_rss_update_secret' );
99
+
100
+ if ( $createNonceOk ) {
101
+ $create_new_secret = true;
102
+ $this->updateRssSecret();
103
+
104
+ // Add updated-message and store in transient and then redirect
105
+ // This is the way options.php does it.
106
+ $msg = __( 'Created new secret RSS address', 'simple-history' );
107
+ add_settings_error( 'simple_history_rss_feed_regenerate_secret', 'simple_history_rss_feed_regenerate_secret', $msg, 'updated' );
108
+ set_transient( 'settings_errors', get_settings_errors(), 30 );
109
+
110
+ $goback = esc_url_raw( add_query_arg( 'settings-updated', 'true', wp_get_referer() ) );
111
+ wp_redirect( $goback );
112
+ exit;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Check if RSS feed is enabled or disabled
118
+ */
119
+ public function isRssEnabled() {
120
+
121
+ // User has never used the plugin we disable RSS feed
122
+ if ( get_option( 'simple_history_rss_secret' ) === false && get_option( 'simple_history_enable_rss_feed' ) === false ) {
123
+ // We disable RSS by default, we use 0/1 to prevent fake disabled with bools from functions returning false for unset
124
+ update_option( 'simple_history_enable_rss_feed', '0' );
125
+ } elseif ( get_option( 'simple_history_enable_rss_feed' ) === false ) {
126
+ // User was using the plugin before RSS feed became disabled by default
127
+ // We activate RSS to prevent a "breaking change"
128
+ update_option( 'simple_history_enable_rss_feed', '1' );
129
+ return true;
130
+ } elseif ( get_option( 'simple_history_enable_rss_feed' ) === '1' ) {
131
+ return true;
132
+ }
133
+
134
+ return false;
135
+ }
136
+
137
+ /**
138
+ * Output for settings field that show current RSS address
139
+ */
140
+ public function settingsFieldRssEnable() {
141
+ ?>
142
+ <input value="1" type="checkbox" id="simple_history_enable_rss_feed" name="simple_history_enable_rss_feed" <?php checked( $this->isRssEnabled(), 1 ); ?> />
143
+ <label for="simple_history_enable_rss_feed"><?php esc_html_e( 'Enable RSS feed', 'simple-history' ); ?></label>
144
+ <?php
145
+ }
146
+
147
+ /**
148
+ * Sanitize RSS enabled/disabled status on update settings
149
+ */
150
+ public function updateRssStatus( $field ) {
151
+
152
+ if ( $field === '1' ) {
153
+ return '1';
154
+ }
155
+
156
+ return '0';
157
+ }
158
+
159
+
160
+ /**
161
+ * Check if current request is a request for the RSS feed
162
+ */
163
+ public function checkForRssFeedRequest() {
164
+ // check for RSS
165
+ // don't know if this is the right way to do this, but it seems to work!
166
+ if ( isset( $_GET['simple_history_get_rss'] ) ) {
167
+ $this->outputRss();
168
+ exit;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Modify capability check so all users reading rss feed (logged in or not) can read all loggers
174
+ */
175
+ public function onCanReadSingleLogger( $user_can_read_logger, $logger_instance, $user_id ) {
176
+ $user_can_read_logger = true;
177
+
178
+ return $user_can_read_logger;
179
+ }
180
+
181
+ /**
182
+ * Output RSS
183
+ */
184
+ public function outputRss() {
185
+
186
+ $rss_secret_option = get_option( 'simple_history_rss_secret' );
187
+ $rss_secret_get = isset( $_GET['rss_secret'] ) ? $_GET['rss_secret'] : '';
188
+
189
+ if ( empty( $rss_secret_option ) || empty( $rss_secret_get ) ) {
190
+ die();
191
+ }
192
+
193
+ $rss_show = true;
194
+ $rss_show = apply_filters( 'simple_history/rss_feed_show', $rss_show );
195
+ if ( ! $rss_show || ! $this->isRssEnabled() ) {
196
+ wp_die( 'Nothing here.' );
197
+ }
198
+
199
+ header( 'Content-Type: text/xml; charset=utf-8' );
200
+ echo '<?xml version="1.0" encoding="UTF-8"?>';
201
+ $self_link = $this->getRssAddress();
202
+
203
+ if ( $rss_secret_option === $rss_secret_get ) {
204
+ ?>
205
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
206
+ <channel>
207
+ <title><![CDATA[<?php printf( esc_html__( 'History for %s', 'simple-history' ), esc_html( get_bloginfo( 'name' ) ) ); ?>]]></title>
208
+ <description><![CDATA[<?php printf( esc_html__( 'WordPress History for %s', 'simple-history' ), esc_html( get_bloginfo( 'name' ) ) ); ?>]]></description>
209
+ <link><?php echo esc_url( get_bloginfo( 'url' ) ); ?></link>
210
+ <atom:link href="<?php echo esc_url( $self_link ); ?>" rel="self" type="application/atom+xml" />
211
+ <?php
212
+
213
+ // Override capability check: if you have a valid rss_secret_key you can read it all
214
+ $action_tag = 'simple_history/loggers_user_can_read/can_read_single_logger';
215
+ add_action( $action_tag, array( $this, 'onCanReadSingleLogger' ), 10, 3 );
216
+
217
+ // Modify header time output so it does not show relative date or time ago-format
218
+ // Because we don't know when a user reads the RSS feed, time ago format may be very inaccurate
219
+ add_action( 'simple_history/header_just_now_max_time', '__return_zero' );
220
+ add_action( 'simple_history/header_time_ago_max_time', '__return_zero' );
221
+
222
+ // Get log rows
223
+ $args = array(
224
+ 'posts_per_page' => 10,
225
+ );
226
+
227
+ $args = apply_filters( 'simple_history/rss_feed_args', $args );
228
+
229
+ $logQuery = new SimpleHistoryLogQuery();
230
+ $queryResults = $logQuery->query( $args );
231
+
232
+ // Remove capability override after query is done
233
+ // remove_action( $action_tag, array($this, "onCanReadSingleLogger") );
234
+ foreach ( $queryResults['log_rows'] as $row ) {
235
+ $header_output = $this->sh->getLogRowHeaderOutput( $row );
236
+ $text_output = $this->sh->getLogRowPlainTextOutput( $row );
237
+ $details_output = $this->sh->getLogRowDetailsOutput( $row );
238
+
239
+ // http://cyber.law.harvard.edu/rss/rss.html#ltguidgtSubelementOfLtitemgt
240
+ // $item_guid = home_url() . "?SimpleHistoryGuid=" . $row->id;
241
+ $item_guid = esc_url( add_query_arg( 'SimpleHistoryGuid', $row->id, home_url() ) );
242
+ $item_link = esc_url( add_query_arg( 'SimpleHistoryGuid', $row->id, home_url() ) );
243
+
244
+ /**
245
+ * Filter the guid/link URL used in RSS feed.
246
+ * Link will be esc_url'ed by simple history, so no need to do that in your filter
247
+ *
248
+ * @since 2.0.23
249
+ *
250
+ * @param string $item_guid link.
251
+ * @param object $row
252
+ */
253
+ $item_link = apply_filters( 'simple_history/rss_item_link', $item_link, $row );
254
+ $item_link = esc_url( $item_link );
255
+
256
+ $item_title = sprintf(
257
+ '%2$s',
258
+ $this->sh->getLogLevelTranslated( $row->level ),
259
+ wp_kses( $text_output, array() )
260
+ );
261
+
262
+ $level_output = sprintf( esc_html__( 'Severity level: %1$s', 'simple-history' ), $this->sh->getLogLevelTranslated( $row->level ) );
263
+
264
+ $wp_kses_attrs = array(
265
+ 'a' => array(
266
+ 'href' => array(),
267
+ 'class' => array(),
268
+ 'data-ip-address' => array(),
269
+ 'target' => array(),
270
+ 'title' => array(),
271
+ ),
272
+ 'em' => array(),
273
+ 'span' => array(
274
+ 'class' => array(),
275
+ 'title' => array(),
276
+ 'aria-hidden' => array(),
277
+ ),
278
+ 'time' => array(
279
+ 'datetime' => array(),
280
+ 'class' => array(),
281
+ ),
282
+ 'strong' => array(
283
+ 'class' => array(),
284
+ ),
285
+ 'div' => array(
286
+ 'class' => array(),
287
+ 'tabindex' => array(),
288
+ ),
289
+ 'p' => array(),
290
+ 'del' => array(),
291
+ 'ins' => array(),
292
+ 'table' => array(
293
+ 'class' => array(),
294
+ ),
295
+ 'tbody' => array(),
296
+ 'tr' => array(),
297
+ 'td' => array(
298
+ 'class' => array(),
299
+ ),
300
+ 'col' => array(
301
+ 'class' => array(),
302
+ ),
303
+ );
304
+ ?>
305
+ <item>
306
+ <title><![CDATA[<?php echo esc_html( $item_title ); ?>]]></title>
307
+ <description><![CDATA[
308
+ <p><?php echo wp_kses( $header_output, $wp_kses_attrs ); ?></p>
309
+ <p><?php echo wp_kses( $text_output, $wp_kses_attrs ); ?></p>
310
+ <div><?php echo wp_kses( $details_output, $wp_kses_attrs ); ?></div>
311
+ <p><?php echo wp_kses( $level_output, $wp_kses_attrs ); ?></p>
312
+ <?php
313
+ $occasions = $row->subsequentOccasions - 1;
314
+ if ( $occasions ) {
315
+ echo '<p>';
316
+ printf(
317
+ esc_html( _n( '+%1$s occasion', '+%1$s occasions', $occasions, 'simple-history' ) ),
318
+ (int) $occasions
319
+ );
320
+ echo '</p>';
321
+ }
322
+ ?>
323
+ ]]></description>
324
+ <?php
325
+ // author must be email to validate, but the field is optional, so we skip it
326
+ /* <author><?php echo $row->initiator ?></author> */
327
+ ?>
328
+ <pubDate><?php echo esc_html( gmdate( 'D, d M Y H:i:s', strtotime( $row->date ) ) ); ?> GMT</pubDate>
329
+ <guid isPermaLink="false"><![CDATA[<?php echo esc_html( $item_guid ); ?>]]></guid>
330
+ <link><![CDATA[<?php echo esc_url( $item_link ); ?>]]></link>
331
+ </item>
332
+ <?php
333
+ } // End foreach().
334
+
335
+ ?>
336
+ </channel>
337
+ </rss>
338
+ <?php
339
+ } else {
340
+ // RSS secret was not ok
341
+ ?>
342
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
343
+ <channel>
344
+ <title><?php printf( esc_html__( 'History for %s', 'simple-history' ), esc_html( get_bloginfo( 'name' ) ) ); ?></title>
345
+ <description><?php printf( esc_html__( 'WordPress History for %s', 'simple-history' ), esc_html( get_bloginfo( 'name' ) ) ); ?></description>
346
+ <link><?php echo esc_url( home_url() ); ?></link>
347
+ <item>
348
+ <title><?php esc_html_e( 'Wrong RSS secret', 'simple-history' ); ?></title>
349
+ <description><?php esc_html_e( 'Your RSS secret for Simple History RSS feed is wrong. Please see WordPress settings for current link to the RSS feed.', 'simple-history' ); ?></description>
350
+ <pubDate><?php echo esc_html( gmdate( 'D, d M Y H:i:s', time() ) ); ?> GMT</pubDate>
351
+ <guid><?php echo esc_url( home_url() . '?SimpleHistoryGuid=wrong-secret' ); ?></guid>
352
+ </item>
353
+ </channel>
354
+ </rss>
355
+ <?php
356
+ }// End if().
357
+ }
358
+
359
+ /**
360
+ * Create a new RSS secret
361
+ *
362
+ * @return string new secret
363
+ */
364
+ public function updateRssSecret() {
365
+
366
+ $rss_secret = '';
367
+
368
+ for ( $i = 0; $i < 20; $i++ ) {
369
+ $rss_secret .= chr( rand( 97, 122 ) );
370
+ }
371
+
372
+ update_option( 'simple_history_rss_secret', $rss_secret );
373
+
374
+ return $rss_secret;
375
+ }
376
+
377
+ /**
378
+ * Output for settings field that show current RSS address
379
+ */
380
+ public function settingsFieldRss() {
381
+ printf(
382
+ '<p><code><a href="%1$s">%1$s</a></code></p>',
383
+ esc_url( $this->getRssAddress() )
384
+ );
385
+ }
386
+
387
+ /**
388
+ * Output for settings field that regenerates the RSS adress/secret
389
+ */
390
+ public function settingsFieldRssRegenerate() {
391
+
392
+ $update_link = esc_url( add_query_arg( '', '' ) );
393
+ $update_link = wp_nonce_url( $update_link, 'simple_history_rss_update_secret', 'simple_history_rss_secret_regenerate_nonce' );
394
+
395
+ echo '<p>';
396
+ esc_html_e( 'You can generate a new address for the RSS feed. This is useful if you think that the address has fallen into the wrong hands.', 'simple-history' );
397
+ echo '</p>';
398
+
399
+ echo '<p>';
400
+ printf(
401
+ '<a class="button" href="%1$s">%2$s</a>',
402
+ esc_url( $update_link ), // 1
403
+ esc_html( 'Generate new address', 'simple-history' ) // 2
404
+ );
405
+
406
+ echo '</p>';
407
+ }
408
+
409
+ /**
410
+ * Get the URL to the RSS feed
411
+ *
412
+ * @return string URL
413
+ */
414
+ public function getRssAddress() {
415
+
416
+ $rss_secret = get_option( 'simple_history_rss_secret' );
417
+ $rss_address = add_query_arg(
418
+ array(
419
+ 'simple_history_get_rss' => '1',
420
+ 'rss_secret' => $rss_secret,
421
+ ),
422
+ get_bloginfo( 'url' ) . '/'
423
+ );
424
+ $rss_address = esc_url( $rss_address );
425
+
426
+ return $rss_address;
427
+ }
428
+
429
+ /**
430
+ * Content for section intro. Leave it be, even if empty.
431
+ * Called from add_sections_setting.
432
+ */
433
+ public function settingsSectionOutput() {
434
+ echo '<p>';
435
+ esc_html_e( 'Simple History has a RSS feed which you can subscribe to and receive log updates. Make sure you only share the feed with people you trust, since it can contain sensitive or confidential information.', 'simple-history' );
436
+ echo '</p>';
437
+ }
438
  }
dropins/SimpleHistorySettingsDebugDropin.php CHANGED
@@ -7,42 +7,27 @@ Dropin URI: http://simple-history.com/
7
  Author: Pär Thernström
8
  */
9
 
10
- defined('ABSPATH') or die();
11
 
12
- class SimpleHistorySettingsDebugDropin
13
- {
14
 
15
- private $sh;
16
 
17
- public function __construct($sh)
18
- {
19
 
20
- $this->sh = $sh;
21
 
22
- // How do we register this to the settings array?
23
- $sh->registerSettingsTab(array(
24
- 'slug' => 'debug',
25
- 'name' => __('Debug', 'simple-history'),
26
- 'function' => array( $this, 'output' ),
27
- ));
 
 
28
 
29
- // add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
30
- }
31
 
32
- /*
33
- public function on_admin_enqueue_scripts() {
34
-
35
- $file_url = plugin_dir_url( __FILE__ );
36
-
37
- wp_enqueue_script( "google-ajax-api", "https://www.google.com/jsapi" );
38
- wp_enqueue_style( "simple_history_SettingsStatsDropin", $file_url . "SimpleHistorySettingsStatsDropin.css", null, SIMPLE_HISTORY_VERSION );
39
-
40
- }
41
- */
42
-
43
- public function output()
44
- {
45
-
46
- include SIMPLE_HISTORY_PATH . 'templates/template-settings-tab-debug.php';
47
- }
48
  }
7
  Author: Pär Thernström
8
  */
9
 
10
+ defined( 'ABSPATH' ) || die();
11
 
12
+ class SimpleHistorySettingsDebugDropin {
 
13
 
14
+ private $sh;
15
 
16
+ public function __construct( $sh ) {
 
17
 
18
+ $this->sh = $sh;
19
 
20
+ $this->sh->registerSettingsTab(
21
+ array(
22
+ 'slug' => 'debug',
23
+ 'name' => __( 'Debug', 'simple-history' ),
24
+ 'function' => array( $this, 'output' ),
25
+ )
26
+ );
27
+ }
28
 
29
+ public function output() {
 
30
 
31
+ include SIMPLE_HISTORY_PATH . 'templates/template-settings-tab-debug.php';
32
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
dropins/SimpleHistorySettingsLogtestDropin.php CHANGED
@@ -1,300 +1,256 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
- class SimpleHistorySettingsLogtestDropin
6
- {
7
 
8
- // Simple History instance
9
- private $sh;
10
 
11
- public function __construct($sh)
12
- {
13
 
14
- // Since it's not quite done yet, it's for da devs only for now
15
- if (! defined('SIMPLE_HISTORY_DEV') || ! SIMPLE_HISTORY_DEV) {
16
- return;
17
- }
18
 
19
- $this->sh = $sh;
20
 
21
- // How do we register this to the settings array?
22
- $sh->registerSettingsTab(array(
23
- 'slug' => 'testLog',
24
- 'name' => __('Test data (debug)', 'simple-history'),
25
- 'function' => array( $this, 'output' ),
26
- ));
 
 
27
 
28
- // add_action( 'admin_enqueue_scripts', array( $this, 'on_admin_enqueue_scripts') );
29
- add_action('admin_head', array( $this, 'on_admin_head' ));
30
- add_action('wp_ajax_SimpleHistoryAddLogTest', array( $this, 'on_ajax_add_logtests' ));
31
- }
32
 
33
- public function on_ajax_add_logtests()
34
- {
35
 
36
- $this->doLogTestThings();
37
 
38
- $arr = array(
39
- 'message' => 'did it!',
40
- );
41
 
42
- wp_send_json_success($arr);
43
- }
44
 
45
- public function on_admin_head()
46
- {
47
 
48
- ?>
49
- <script>
50
 
51
- jQuery(function($) {
52
 
53
- var button = $(".js-SimpleHistorySettingsLogtestDropin-addStuff");
54
- var messageDone = $(".js-SimpleHistorySettingsLogtestDropin-addStuffDone");
55
- var messageWorking = $(".js-SimpleHistorySettingsLogtestDropin-addStuffWorking");
56
-
57
- button.on("click", function(e) {
58
-
59
- messageWorking.show();
60
- messageDone.hide();
61
-
62
- $.post(ajaxurl, {
63
- action: "SimpleHistoryAddLogTest"
64
- }).done(function(r) {
65
-
66
- messageWorking.hide();
67
- messageDone.show();
68
-
69
- });
70
-
71
- });
72
-
73
- });
74
-
75
-
76
- </script>
77
- <?php
78
- }
79
-
80
- public function output()
81
- {
82
-
83
- ?>
84
- <h1>Test data</h1>
85
-
86
- <p>Add lots of test data to the log database.</p>
87
-
88
- <p>
89
- <button class="button js-SimpleHistorySettingsLogtestDropin-addStuff">Ok, add lots of stuff to the log!</button>
90
- </p>
91
-
92
- <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffDone">
93
- <p>Done! Added lots of test rows</p>
94
- </div>
95
-
96
- <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffWorking">
97
- <p>Adding...</p>
98
- </div>
99
-
100
- <?php
101
- }
102
-
103
- public function doLogTestThings()
104
- {
105
-
106
- // Add some data random back in time, to fill up the log to test much data
107
- for ($j = 0; $j < 50; $j++) {
108
- // between yesteday and a month back in time
109
- for ($i = 0; $i < rand(1, 30); $i++) {
110
- $str_date = date('Y-m-d H:i:s', strtotime("now -{$i}days"));
111
- SimpleLogger()->info(
112
- 'Entry with date in the past',
113
- array(
114
- '_date' => $str_date,
115
- '_occasionsID' => "past_date:{$str_date}",
116
- )
117
- );
118
- }
119
- }
120
-
121
- SimpleLogger()->info('This is a message sent to the log');
122
-
123
- // Second log entry with same info will make these two become an occasionGroup,
124
- // collapsing their entries into one expandable log item
125
- SimpleLogger()->info('This is a message sent to the log');
126
-
127
- // Log entries can be of different severity
128
- SimpleLogger()->info("User admin edited page 'About our company'");
129
- SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
130
- SimpleLogger()->debug('Ok, cron job is running!');
131
-
132
- // Log entries can have placeholders and context
133
- // This makes log entried translatable and filterable
134
- for ($i = 0; $i < rand(1, 50); $i++) {
135
- SimpleLogger()->notice(
136
- 'User {username} edited page {pagename}',
137
- array(
138
- 'username' => 'bonnyerden',
139
- 'pagename' => 'My test page',
140
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
141
- '_user_id' => rand(1, 20),
142
- '_user_login' => 'loginname' . rand(1, 20),
143
- '_user_email' => 'user' . rand(1, 20) . '@example.com',
144
- )
145
- );
146
- }
147
- // return;
148
- // Log entried can have custom occasionsID
149
- // This will group items together and a log entry will only be shown once
150
- // in the log overview
151
- for ($i = 0; $i < rand(1, 50); $i++) {
152
- SimpleLogger()->notice('User {username} edited page {pagename}', array(
153
- 'username' => 'admin',
154
- 'pagename' => 'My test page',
155
- '_occasionsID' => 'username:1,postID:24884,action:edited',
156
- ));
157
- }
158
-
159
- SimpleLogger()->info(
160
- 'WordPress updated itself from version {from_version} to {to_version}',
161
- array(
162
- 'from_version' => '3.8',
163
- 'to_version' => '3.8.1',
164
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
165
- )
166
- );
167
-
168
- SimpleLogger()->info(
169
- 'Plugin {plugin_name} was updated from version {plugin_from_version} to version {plugin_to_version}',
170
- array(
171
- 'plugin_name' => 'CMS Tree Page View',
172
- 'plugin_from_version' => '4.0',
173
- 'plugin_to_version' => '4.2',
174
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
175
- )
176
- );
177
-
178
- SimpleLogger()->info(
179
- 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
180
- array(
181
- 'plugin_name' => 'Ninja Forms',
182
- 'plugin_from_version' => '1.1',
183
- 'plugin_to_version' => '1.1.2',
184
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
185
- )
186
- );
187
-
188
- SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
189
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
190
- ));
191
-
192
- SimpleLogger()->info(
193
- 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
194
- array(
195
- 'plugin_name' => 'Simple Fields',
196
- 'plugin_from_version' => '1.3.7',
197
- 'plugin_to_version' => '1.3.8',
198
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
199
- )
200
- );
201
-
202
- SimpleLogger()->error("A JavaScript error was detected on page 'About us'", array(
203
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
204
- ));
205
-
206
- SimpleLogger()->debug("WP Cron 'my_test_cron_job' finished in 0.012 seconds", array(
207
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
208
- ));
209
-
210
- for ($i = 0; $i < rand(50, 1000); $i++) {
211
- SimpleLogger()->warning(
212
- 'An attempt to login as user "{user_login}" failed to login because the wrong password was entered',
213
- array(
214
- 'user_login' => 'admin',
215
- '_userID' => null,
216
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
217
- )
218
- );
219
- }
220
-
221
- // Add more data to context array. Data can be used later on to show detailed info about a log entry.
222
- SimpleLogger()->info("Edited product '{pagename}'", array(
223
- 'pagename' => 'We are hiring!',
224
- '_postType' => 'product',
225
- '_userID' => 1,
226
- '_userLogin' => 'jessie',
227
- '_userEmail' => 'jessie@example.com',
228
- '_occasionsID' => 'username:1,postID:24885,action:edited',
229
- ));
230
-
231
- SimpleLogger()->debug('This is a message with no translation');
232
- SimpleLogger()->debug(__('Plugin'), array(
233
- 'comment' => "This message is 'Plugin' and should contain text domain 'default' since it's a translation that comes with WordPress",
234
- ));
235
- SimpleLogger()->debug(__('Enter title of new page', 'cms-tree-page-view'), array(
236
- 'comment' => 'A translation used in CMS Tree Page View',
237
- ));
238
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  }
240
-
241
-
242
- /*
243
- add_action("init", function() {
244
-
245
- register_post_type("texts", array(
246
- "show_ui" => true
247
- ));
248
-
249
- register_post_type("products", array(
250
- "labels" => array(
251
- "name" => "Products",
252
- "singular_name" => "Product"
253
- ),
254
- "public" => true
255
- ));
256
-
257
- // Example from the codex
258
- $labels = array(
259
- 'name' => _x( 'Books', 'post type general name', 'your-plugin-textdomain' ),
260
- 'singular_name' => _x( 'Book', 'post type singular name', 'your-plugin-textdomain' ),
261
- 'menu_name' => _x( 'Books', 'admin menu', 'your-plugin-textdomain' ),
262
- 'name_admin_bar' => _x( 'Book', 'add new on admin bar', 'your-plugin-textdomain' ),
263
- 'add_new' => _x( 'Add New', 'book', 'your-plugin-textdomain' ),
264
- 'add_new_item' => __( 'Add New Book', 'your-plugin-textdomain' ),
265
- 'new_item' => __( 'New Book', 'your-plugin-textdomain' ),
266
- 'edit_item' => __( 'Edit Book', 'your-plugin-textdomain' ),
267
- 'view_item' => __( 'View Book', 'your-plugin-textdomain' ),
268
- 'all_items' => __( 'All Books', 'your-plugin-textdomain' ),
269
- 'search_items' => __( 'Search Books', 'your-plugin-textdomain' ),
270
- 'parent_item_colon' => __( 'Parent Books:', 'your-plugin-textdomain' ),
271
- 'not_found' => __( 'No books found.', 'your-plugin-textdomain' ),
272
- 'not_found_in_trash' => __( 'No books found in Trash.', 'your-plugin-textdomain' ),
273
- );
274
-
275
- $args = array(
276
- 'labels' => $labels,
277
- 'public' => true,
278
- 'publicly_queryable' => true,
279
- 'show_ui' => true,
280
- 'show_in_menu' => true,
281
- 'query_var' => true,
282
- 'rewrite' => array( 'slug' => 'book' ),
283
- 'capability_type' => 'post',
284
- 'has_archive' => true,
285
- 'hierarchical' => false,
286
- 'menu_position' => null,
287
- 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
288
- );
289
-
290
- register_post_type( 'book', $args );
291
-
292
- });
293
- */
294
-
295
-
296
-
297
- // Log testing beloe
298
- // return;
299
- // *
300
- // */
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
+ class SimpleHistorySettingsLogtestDropin {
 
6
 
7
+ // Simple History instance
8
+ private $sh;
9
 
10
+ public function __construct( $sh ) {
 
11
 
12
+ // Since it's not quite done yet, it's for da devs only for now
13
+ if ( ! defined( 'SIMPLE_HISTORY_DEV' ) || ! SIMPLE_HISTORY_DEV ) {
14
+ return;
15
+ }
16
 
17
+ $this->sh = $sh;
18
 
19
+ // How do we register this to the settings array?
20
+ $sh->registerSettingsTab(
21
+ array(
22
+ 'slug' => 'testLog',
23
+ 'name' => __( 'Test data (debug)', 'simple-history' ),
24
+ 'function' => array( $this, 'output' ),
25
+ )
26
+ );
27
 
28
+ // add_action( 'admin_enqueue_scripts', array( $this, 'on_admin_enqueue_scripts') );
29
+ add_action( 'admin_head', array( $this, 'on_admin_head' ) );
30
+ add_action( 'wp_ajax_SimpleHistoryAddLogTest', array( $this, 'on_ajax_add_logtests' ) );
31
+ }
32
 
33
+ public function on_ajax_add_logtests() {
 
34
 
35
+ $this->doLogTestThings();
36
 
37
+ $arr = array(
38
+ 'message' => 'did it!',
39
+ );
40
 
41
+ wp_send_json_success( $arr );
42
+ }
43
 
44
+ public function on_admin_head() {
 
45
 
46
+ ?>
47
+ <script>
48
 
49
+ jQuery(function($) {
50
 
51
+ var button = $(".js-SimpleHistorySettingsLogtestDropin-addStuff");
52
+ var messageDone = $(".js-SimpleHistorySettingsLogtestDropin-addStuffDone");
53
+ var messageWorking = $(".js-SimpleHistorySettingsLogtestDropin-addStuffWorking");
54
+
55
+ button.on("click", function(e) {
56
+
57
+ messageWorking.show();
58
+ messageDone.hide();
59
+
60
+ $.post(ajaxurl, {
61
+ action: "SimpleHistoryAddLogTest"
62
+ }).done(function(r) {
63
+
64
+ messageWorking.hide();
65
+ messageDone.show();
66
+
67
+ });
68
+
69
+ });
70
+
71
+ });
72
+
73
+
74
+ </script>
75
+ <?php
76
+ }
77
+
78
+ public function output() {
79
+
80
+ ?>
81
+ <h1>Test data</h1>
82
+
83
+ <p>Add lots of test data to the log database.</p>
84
+
85
+ <p>
86
+ <button class="button js-SimpleHistorySettingsLogtestDropin-addStuff">Ok, add lots of stuff to the log!</button>
87
+ </p>
88
+
89
+ <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffDone">
90
+ <p>Done! Added lots of test rows</p>
91
+ </div>
92
+
93
+ <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffWorking">
94
+ <p>Adding...</p>
95
+ </div>
96
+
97
+ <?php
98
+ }
99
+
100
+ public function doLogTestThings() {
101
+
102
+ // Add some data random back in time, to fill up the log to test much data
103
+ for ( $j = 0; $j < 50; $j++ ) {
104
+ // between yesteday and a month back in time
105
+ for ( $i = 0; $i < rand( 1, 30 ); $i++ ) {
106
+ $str_date = gmdate( 'Y-m-d H:i:s', strtotime( "now -{$i}days" ) );
107
+ SimpleLogger()->info(
108
+ 'Entry with date in the past',
109
+ array(
110
+ '_date' => $str_date,
111
+ '_occasionsID' => "past_date:{$str_date}",
112
+ )
113
+ );
114
+ }
115
+ }
116
+
117
+ SimpleLogger()->info( 'This is a message sent to the log' );
118
+
119
+ // Second log entry with same info will make these two become an occasionGroup,
120
+ // collapsing their entries into one expandable log item
121
+ SimpleLogger()->info( 'This is a message sent to the log' );
122
+
123
+ // Log entries can be of different severity
124
+ SimpleLogger()->info( "User admin edited page 'About our company'" );
125
+ SimpleLogger()->warning( "User 'Jessie' deleted user 'Kim'" );
126
+ SimpleLogger()->debug( 'Ok, cron job is running!' );
127
+
128
+ // Log entries can have placeholders and context
129
+ // This makes log entried translatable and filterable
130
+ for ( $i = 0; $i < rand( 1, 50 ); $i++ ) {
131
+ SimpleLogger()->notice(
132
+ 'User {username} edited page {pagename}',
133
+ array(
134
+ 'username' => 'bonnyerden',
135
+ 'pagename' => 'My test page',
136
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
137
+ '_user_id' => rand( 1, 20 ),
138
+ '_user_login' => 'loginname' . rand( 1, 20 ),
139
+ '_user_email' => 'user' . rand( 1, 20 ) . '@example.com',
140
+ )
141
+ );
142
+ }
143
+ // return;
144
+ // Log entried can have custom occasionsID
145
+ // This will group items together and a log entry will only be shown once
146
+ // in the log overview
147
+ for ( $i = 0; $i < rand( 1, 50 ); $i++ ) {
148
+ SimpleLogger()->notice(
149
+ 'User {username} edited page {pagename}',
150
+ array(
151
+ 'username' => 'admin',
152
+ 'pagename' => 'My test page',
153
+ '_occasionsID' => 'username:1,postID:24884,action:edited',
154
+ )
155
+ );
156
+ }
157
+
158
+ SimpleLogger()->info(
159
+ 'WordPress updated itself from version {from_version} to {to_version}',
160
+ array(
161
+ 'from_version' => '3.8',
162
+ 'to_version' => '3.8.1',
163
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
164
+ )
165
+ );
166
+
167
+ SimpleLogger()->info(
168
+ 'Plugin {plugin_name} was updated from version {plugin_from_version} to version {plugin_to_version}',
169
+ array(
170
+ 'plugin_name' => 'CMS Tree Page View',
171
+ 'plugin_from_version' => '4.0',
172
+ 'plugin_to_version' => '4.2',
173
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
174
+ )
175
+ );
176
+
177
+ SimpleLogger()->info(
178
+ 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
179
+ array(
180
+ 'plugin_name' => 'Ninja Forms',
181
+ 'plugin_from_version' => '1.1',
182
+ 'plugin_to_version' => '1.1.2',
183
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
184
+ )
185
+ );
186
+
187
+ SimpleLogger()->warning(
188
+ "An attempt to login as user 'administrator' failed to login because the wrong password was entered",
189
+ array(
190
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
191
+ )
192
+ );
193
+
194
+ SimpleLogger()->info(
195
+ 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
196
+ array(
197
+ 'plugin_name' => 'Simple Fields',
198
+ 'plugin_from_version' => '1.3.7',
199
+ 'plugin_to_version' => '1.3.8',
200
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
201
+ )
202
+ );
203
+
204
+ SimpleLogger()->error(
205
+ "A JavaScript error was detected on page 'About us'",
206
+ array(
207
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
208
+ )
209
+ );
210
+
211
+ SimpleLogger()->debug(
212
+ "WP Cron 'my_test_cron_job' finished in 0.012 seconds",
213
+ array(
214
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
215
+ )
216
+ );
217
+
218
+ for ( $i = 0; $i < rand( 50, 1000 ); $i++ ) {
219
+ SimpleLogger()->warning(
220
+ 'An attempt to login as user "{user_login}" failed to login because the wrong password was entered',
221
+ array(
222
+ 'user_login' => 'admin',
223
+ '_userID' => null,
224
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
225
+ )
226
+ );
227
+ }
228
+
229
+ // Add more data to context array. Data can be used later on to show detailed info about a log entry.
230
+ SimpleLogger()->info(
231
+ "Edited product '{pagename}'",
232
+ array(
233
+ 'pagename' => 'We are hiring!',
234
+ '_postType' => 'product',
235
+ '_userID' => 1,
236
+ '_userLogin' => 'jessie',
237
+ '_userEmail' => 'jessie@example.com',
238
+ '_occasionsID' => 'username:1,postID:24885,action:edited',
239
+ )
240
+ );
241
+
242
+ SimpleLogger()->debug( 'This is a message with no translation' );
243
+ SimpleLogger()->debug(
244
+ 'Plugin',
245
+ array(
246
+ 'comment' => "This message is 'Plugin' and should contain text domain 'default' since it's a translation that comes with WordPress",
247
+ )
248
+ );
249
+ SimpleLogger()->debug(
250
+ 'Enter title of new page',
251
+ array(
252
+ 'comment' => 'A translation used in CMS Tree Page View',
253
+ )
254
+ );
255
+ }
256
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dropins/SimpleHistorySettingsStatsDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Settings stats
@@ -9,105 +9,93 @@ Dropin URI: http://simple-history.com/
9
  Author: Pär Thernström
10
  */
11
 
12
- class SimpleHistorySettingsStatsDropin
13
- {
14
 
15
- // Simple History instance
16
- private $sh;
17
 
18
- public function __construct($sh)
19
- {
20
 
21
- // Since it's not quite done yet, it's for da devs only for now
22
- if (! defined('SIMPLE_HISTORY_DEV') || ! SIMPLE_HISTORY_DEV) {
23
- return;
24
- }
25
 
26
- $this->sh = $sh;
 
 
 
27
 
28
- // How do we register this to the settings array?
29
- $sh->registerSettingsTab(array(
30
- 'slug' => 'stats',
31
- 'name' => __('Stats', 'simple-history'),
32
- 'function' => array( $this, 'output' ),
33
- ));
34
 
35
- add_action('simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ));
36
- }
 
 
 
 
 
 
37
 
38
- public function on_admin_enqueue_scripts()
39
- {
40
 
41
- $file_url = plugin_dir_url(__FILE__);
42
 
43
- wp_enqueue_script('google-ajax-api', 'https://www.google.com/jsapi');
44
- wp_enqueue_style('simple_history_SettingsStatsDropin', $file_url . 'SimpleHistorySettingsStatsDropin.css', null, SIMPLE_HISTORY_VERSION);
45
- }
46
 
47
- public function output()
48
- {
 
49
 
50
- global $wpdb;
51
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
52
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
53
 
54
- // $period_days = (int) 28;
55
- $period_days = (int) 14;
56
- $period_start_date = DateTime::createFromFormat('U', strtotime("-$period_days days"));
57
- $period_end_date = DateTime::createFromFormat('U', time());
58
 
59
- // Colors taken from the gogole chart example that was found in this Stack Overflow thread:
60
- // http://stackoverflow.com/questions/236936/how-pick-colors-for-a-pie-chart
61
- $arr_colors = explode(',', '8a56e2,cf56e2,e256ae,e25668,e28956,e2cf56,aee256,68e256,56e289,56e2cf,56aee2,5668e2');
 
62
 
63
- // Load google charts libraries
64
- ?>
65
- <script>
66
- google.load('visualization', '1', {'packages':['corechart']});
67
- </script>
68
- <?php
69
 
70
- ?>
71
- <!-- Overview, larger text -->
72
- <div class='SimpleHistoryStats__intro'>
73
- <?php
74
- include(SIMPLE_HISTORY_PATH . 'templates/settings-statsIntro.php');
75
- ?>
76
- </div>
 
 
 
 
77
 
78
- <!-- Start charts wrap -->
79
- <div class='SimpleHistoryStats__graphs SimpleHistory__cf'>
80
 
81
- <!-- bar chart with rows per day -->
82
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--rowsPerDay'>
83
- <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsRowsPerDay.php') ?>
84
- </div><!-- // end bar chart rows per day -->
85
 
86
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--loggersPie'>
87
- <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsLoggers.php') ?>
88
- </div>
89
 
90
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--logLevels'>
91
- <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsLogLevels.php') ?>
92
- </div>
93
 
94
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--users'>
95
- <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsUsers.php') ?>
96
- </div>
 
97
 
98
- <!--
99
- <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--initiators'>
100
- <?php include(SIMPLE_HISTORY_PATH . 'templates/settings-statsInitiators.php') ?>
101
- </div>
102
- -->
103
 
104
-
105
- </div><!-- // end charts wrapper -->
106
-
107
- <?php
108
-
109
- include(SIMPLE_HISTORY_PATH . 'templates/settings-statsForGeeks.php');
110
- }
111
  }
112
 
113
 
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Settings stats
9
  Author: Pär Thernström
10
  */
11
 
12
+ class SimpleHistorySettingsStatsDropin {
 
13
 
 
 
14
 
15
+ // Simple History instance
16
+ private $sh;
17
 
18
+ public function __construct( $sh ) {
 
 
 
19
 
20
+ // Since it's not quite done yet, it's for da devs only for now
21
+ if ( ! defined( 'SIMPLE_HISTORY_DEV' ) || ! SIMPLE_HISTORY_DEV ) {
22
+ return;
23
+ }
24
 
25
+ $this->sh = $sh;
 
 
 
 
 
26
 
27
+ // How do we register this to the settings array?
28
+ $sh->registerSettingsTab(
29
+ array(
30
+ 'slug' => 'stats',
31
+ 'name' => __( 'Stats', 'simple-history' ),
32
+ 'function' => array( $this, 'output' ),
33
+ )
34
+ );
35
 
36
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
37
+ }
38
 
39
+ public function on_admin_enqueue_scripts() {
40
 
41
+ $file_url = plugin_dir_url( __FILE__ );
 
 
42
 
43
+ wp_enqueue_script( 'google-ajax-api', 'https://www.google.com/jsapi', array(), 1 );
44
+ wp_enqueue_style( 'simple_history_SettingsStatsDropin', $file_url . 'SimpleHistorySettingsStatsDropin.css', null, SIMPLE_HISTORY_VERSION );
45
+ }
46
 
47
+ public function output() {
 
 
48
 
49
+ global $wpdb;
50
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
51
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
 
52
 
53
+ // $period_days = (int) 28;
54
+ $period_days = (int) 14;
55
+ $period_start_date = DateTime::createFromFormat( 'U', strtotime( "-$period_days days" ) );
56
+ $period_end_date = DateTime::createFromFormat( 'U', time() );
57
 
58
+ // Colors taken from the gogole chart example that was found in this Stack Overflow thread:
59
+ // http://stackoverflow.com/questions/236936/how-pick-colors-for-a-pie-chart
60
+ $arr_colors = explode( ',', '8a56e2,cf56e2,e256ae,e25668,e28956,e2cf56,aee256,68e256,56e289,56e2cf,56aee2,5668e2' );
 
 
 
61
 
62
+ // Load google charts libraries
63
+ ?>
64
+ <script>
65
+ google.load('visualization', '1', {'packages':['corechart']});
66
+ </script>
67
+ <!-- Overview, larger text -->
68
+ <div class='SimpleHistoryStats__intro'>
69
+ <?php
70
+ include( SIMPLE_HISTORY_PATH . 'templates/settings-statsIntro.php' );
71
+ ?>
72
+ </div>
73
 
74
+ <!-- Start charts wrap -->
75
+ <div class='SimpleHistoryStats__graphs SimpleHistory__cf'>
76
 
77
+ <!-- bar chart with rows per day -->
78
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--rowsPerDay'>
79
+ <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsRowsPerDay.php' ); ?>
80
+ </div><!-- // end bar chart rows per day -->
81
 
82
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--loggersPie'>
83
+ <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsLoggers.php' ); ?>
84
+ </div>
85
 
86
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--logLevels'>
87
+ <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsLogLevels.php' ); ?>
88
+ </div>
89
 
90
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--users'>
91
+ <?php include( SIMPLE_HISTORY_PATH . 'templates/settings-statsUsers.php' ); ?>
92
+ </div>
93
+ </div><!-- // end charts wrapper -->
94
 
95
+ <?php
 
 
 
 
96
 
97
+ include( SIMPLE_HISTORY_PATH . 'templates/settings-statsForGeeks.php' );
98
+ }
 
 
 
 
 
99
  }
100
 
101
 
dropins/SimpleHistorySidebarDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Sidebar
@@ -9,57 +9,28 @@ Dropin URI: http://simple-history.com/
9
  Author: Pär Thernström
10
  */
11
 
12
- class SimpleHistorySidebarDropin
13
- {
 
 
 
 
14
 
15
- private $sh;
 
 
16
 
17
- public function __construct($sh)
18
- {
 
 
19
 
20
- $this->sh = $sh;
 
 
 
21
 
22
- add_action('simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ));
23
- add_action('simple_history/history_page/after_gui', array( $this, 'output_sidebar_html' ));
24
- add_action('simple_history/dropin/sidebar/sidebar_html', array( $this, 'default_sidebar_contents' ));
25
- }
26
-
27
- public function default_sidebar_contents()
28
- {
29
-
30
- // Boxes that will appear randomly
31
- // Box about GitHub
32
- $headline = _x('Simple History is on GitHub', 'Sidebar box', 'simple-history');
33
-
34
- $body = sprintf(
35
- _x('You can star, fork, or report issues with this plugin over at the <a href="%1$s">GitHub page</a>.', 'Sidebar box', 'simple-history'),
36
- 'https://github.com/bonny/WordPress-Simple-History'
37
- );
38
-
39
- $boxGithub = '
40
- <div class="postbox">
41
- <h3 class="hndle">' . $headline . '</h3>
42
- <div class="inside">
43
- <p>' . $body . '</p>
44
- </div>
45
- </div>
46
- ';
47
-
48
- // Box about donation
49
- $headline = _x('Donate to support development', 'Sidebar box', 'simple-history');
50
-
51
- $bodyDonate = sprintf(
52
- _x('If you like and use Simple History you should <a href="%1$s">donate to keep this plugin free</a>.', 'Sidebar box', 'simple-history'),
53
- 'https://eskapism.se/sida/donate/'
54
- );
55
-
56
- $bodyGithubSponsors = sprintf(
57
- _x('You can also <a href="%1$s">sponsor me at Github</a>.', 'Sidebar box', 'simple-history'),
58
- 'https://github.com/sponsors/bonny/'
59
- );
60
-
61
-
62
- $boxDonate = '
63
  <div class="postbox">
64
  <h3 class="hndle">' . $headline . '</h3>
65
  <div class="inside">
@@ -69,17 +40,17 @@ class SimpleHistorySidebarDropin
69
  </div>
70
  ';
71
 
72
- // Box about review
73
- $headline = _x('Review this plugin if you like it', 'Sidebar box', 'simple-history');
74
 
75
- $body1 = sprintf(
76
- _x('If you like Simple History then please <a href="%1$s">give it a nice review over at wordpress.org</a>.', 'Sidebar box', 'simple-history'),
77
- 'https://wordpress.org/support/view/plugin-reviews/simple-history'
78
- );
79
 
80
- $body2 = _x('A good review will help new users find this plugin. And it will make the plugin author very happy :)', 'Sidebar box', 'simple-history');
81
 
82
- $boxReview = '
83
  <div class="postbox">
84
  <h3 class="hndle">' . $headline . '</h3>
85
  <div class="inside">
@@ -89,147 +60,74 @@ class SimpleHistorySidebarDropin
89
  </div>
90
  ';
91
 
92
- // Box about tweeting and blogging
93
- /*
94
- $boxSocial = '
95
- <div class="postbox">
96
- <h3 class="hndle">Blog or tweet</h3>
97
- <div class="inside">
98
- <p>Yeah, how about that yo.</p>
99
- </div>
100
- </div>
101
- ';
102
- */
103
-
104
- // Box about possible events missing
105
- $boxMissingEvents = sprintf(
106
- '
107
  <div class="postbox">
108
  <h3 class="hndle">%1$s</h3>
109
  <div class="inside">
110
  <p>%2$s</p>
111
- <p><a href="hello@simple-history.com">hello@simple-history.com</a></p>
112
  </div>
113
  </div>
114
  ',
115
- _x('Add more to the log', 'Sidebar box', 'simple-history'), // 1
116
- _x('Are there things you miss in the history log?', 'Sidebar box', 'simple-history') // 2
117
- );
118
-
119
- // Box about support
120
- $boxSupport = sprintf(
121
- '
122
- <div class="postbox">
123
- <h3 class="hndle">%1$s</h3>
124
- <div class="inside">
125
- <p>%2$s</p>
126
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  </div>
128
- ',
129
- _x('Support', 'Sidebar box', 'simple-history'), // 1
130
- sprintf(_x('<a href="%1$s">Visit the support forum</a> if you need help or have questions.', 'Sidebar box', 'simple-history'), 'https://wordpress.org/support/plugin/simple-history') // 2
131
- );
132
-
133
- $arrBoxes = array(
134
- 'boxReview' => $boxReview,
135
- 'boxSupport' => $boxSupport,
136
- // "boxMissingEvents" => $boxMissingEvents,
137
- 'boxDonate' => $boxDonate,
138
- // "boxGithub" => $boxGithub,
139
- );
140
-
141
- /**
142
- * Filter the default boxes to output in the sidebar
143
- *
144
- * @since 2.0.17
145
- *
146
- * @param array $arrBoxes array with boxes to output. Check the key to determine which box is which.
147
- */
148
- $arrBoxes = apply_filters('simple_history/SidebarDropin/default_sidebar_boxes', $arrBoxes);
149
-
150
- echo implode('', $arrBoxes);
151
-
152
- // Box to encourage people translate plugin.
153
- $current_locale = get_locale();
154
-
155
- /** WordPress Translation Install API. This file exists only since 4.0. */
156
- $translation_install_file = ABSPATH . 'wp-admin/includes/translation-install.php';
157
-
158
- // Show only the translation box if current language is not an english language
159
- if (in_array($current_locale, array( 'en_US', 'en_GB', 'en_CA', 'en_NZ', 'en_AU' )) != $current_locale && file_exists($translation_install_file)) {
160
- require_once $translation_install_file;
161
-
162
- $translations = wp_get_available_translations();
163
-
164
- // This text does not need translation since is's only shown in English
165
- $boxTranslationTmpl = '
166
- <div class="postbox">
167
- <h3 class="hndle">Translate Simple History to %1$s</h3>
168
- <div class="inside">
169
-
170
- <p>
171
- It looks like Simple History is not yet translated to your language.
172
- </p>
173
-
174
- <p>
175
- If you\'re interested in translating it please check out the
176
- <a href="https://developer.wordpress.org/plugins/internationalization/localization/">localization</a>
177
- part of the Plugin Handbook for info on how to translate plugins.
178
- </p>
179
- </div>
180
- </div>
181
- ';
182
-
183
- if (isset($translations[ $current_locale ])) {
184
- // Check if an existing text string returns something else, and that current lang is not en
185
- $teststring_translated = __('Just now', 'simple-history');
186
- $teststring_untranslated = 'Just now';
187
- if ($teststring_untranslated == $teststring_translated) {
188
- // strings are the same, so plugin probably not translated
189
- printf($boxTranslationTmpl, $translations[ $current_locale ]['english_name']);
190
- }
191
- }
192
- } // End if().
193
- }
194
-
195
- public function enqueue_admin_scripts()
196
- {
197
- $file_url = plugin_dir_url(__FILE__);
198
-
199
- wp_enqueue_style('simple_history_SidebarDropin', $file_url . 'SimpleHistorySidebarDropin.css', null, SIMPLE_HISTORY_VERSION);
200
- }
201
-
202
- /**
203
- * Output the outline for the sidebar
204
- * Plugins and dropins simple use the filters to output contents to the sidebar
205
- * Example HTML code to generate meta box:
206
- *
207
- * <div class="postbox">
208
- * <h3 class="hndle">Title</h3>
209
- * <div class="inside">
210
- * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
211
- * </div>
212
- * </div>
213
- */
214
- public function output_sidebar_html()
215
- {
216
-
217
- ?>
218
- <div class="SimpleHistory__pageSidebar">
219
-
220
- <div class="metabox-holder">
221
-
222
- <?php
223
- /**
224
- * Allows to output HTML in sidebar
225
- *
226
- * @since 2.0.16
227
- */
228
- do_action('simple_history/dropin/sidebar/sidebar_html');
229
- ?>
230
- </div>
231
-
232
- </div>
233
- <?php
234
- }
235
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Sidebar
9
  Author: Pär Thernström
10
  */
11
 
12
+ class SimpleHistorySidebarDropin {
13
+ public function __construct( $sh ) {
14
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'enqueue_admin_scripts' ) );
15
+ add_action( 'simple_history/history_page/after_gui', array( $this, 'output_sidebar_html' ) );
16
+ add_action( 'simple_history/dropin/sidebar/sidebar_html', array( $this, 'default_sidebar_contents' ) );
17
+ }
18
 
19
+ public function default_sidebar_contents() {
20
+ // Box about donation
21
+ $headline = _x( 'Donate to support development', 'Sidebar box', 'simple-history' );
22
 
23
+ $bodyDonate = sprintf(
24
+ _x( 'If you like and use Simple History you should <a href="%1$s">donate to keep this plugin free</a>.', 'Sidebar box', 'simple-history' ),
25
+ 'https://eskapism.se/sida/donate/'
26
+ );
27
 
28
+ $bodyGithubSponsors = sprintf(
29
+ _x( 'You can also <a href="%1$s">sponsor me at Github</a>.', 'Sidebar box', 'simple-history' ),
30
+ 'https://github.com/sponsors/bonny/'
31
+ );
32
 
33
+ $boxDonate = '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  <div class="postbox">
35
  <h3 class="hndle">' . $headline . '</h3>
36
  <div class="inside">
40
  </div>
41
  ';
42
 
43
+ // Box about review
44
+ $headline = _x( 'Review this plugin if you like it', 'Sidebar box', 'simple-history' );
45
 
46
+ $body1 = sprintf(
47
+ _x( 'If you like Simple History then please <a href="%1$s">give it a nice review over at wordpress.org</a>.', 'Sidebar box', 'simple-history' ),
48
+ 'https://wordpress.org/support/view/plugin-reviews/simple-history'
49
+ );
50
 
51
+ $body2 = _x( 'A good review will help new users find this plugin. And it will make the plugin author very happy :)', 'Sidebar box', 'simple-history' );
52
 
53
+ $boxReview = '
54
  <div class="postbox">
55
  <h3 class="hndle">' . $headline . '</h3>
56
  <div class="inside">
60
  </div>
61
  ';
62
 
63
+ // Box about support
64
+ $boxSupport = sprintf(
65
+ '
 
 
 
 
 
 
 
 
 
 
 
 
66
  <div class="postbox">
67
  <h3 class="hndle">%1$s</h3>
68
  <div class="inside">
69
  <p>%2$s</p>
 
70
  </div>
71
  </div>
72
  ',
73
+ _x( 'Support', 'Sidebar box', 'simple-history' ), // 1
74
+ sprintf( _x( '<a href="%1$s">Visit the support forum</a> if you need help or have questions.', 'Sidebar box', 'simple-history' ), 'https://wordpress.org/support/plugin/simple-history' ) // 2
75
+ );
76
+
77
+ $arrBoxes = array(
78
+ 'boxReview' => $boxReview,
79
+ 'boxSupport' => $boxSupport,
80
+ 'boxDonate' => $boxDonate,
81
+ );
82
+
83
+ /**
84
+ * Filter the default boxes to output in the sidebar
85
+ *
86
+ * @since 2.0.17
87
+ *
88
+ * @param array $arrBoxes array with boxes to output. Check the key to determine which box is which.
89
+ */
90
+ $arrBoxes = apply_filters( 'simple_history/SidebarDropin/default_sidebar_boxes', $arrBoxes );
91
+
92
+ echo implode( '', $arrBoxes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
93
+ }
94
+
95
+ public function enqueue_admin_scripts() {
96
+ $file_url = plugin_dir_url( __FILE__ );
97
+
98
+ wp_enqueue_style( 'simple_history_SidebarDropin', $file_url . 'SimpleHistorySidebarDropin.css', null, SIMPLE_HISTORY_VERSION );
99
+ }
100
+
101
+ /**
102
+ * Output the outline for the sidebar
103
+ * Plugins and dropins simple use the filters to output contents to the sidebar
104
+ * Example HTML code to generate meta box:
105
+ *
106
+ * <div class="postbox">
107
+ * <h3 class="hndle">Title</h3>
108
+ * <div class="inside">
109
+ * <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
110
+ * </div>
111
+ * </div>
112
+ */
113
+ public function output_sidebar_html() {
114
+
115
+ ?>
116
+ <div class="SimpleHistory__pageSidebar">
117
+
118
+ <div class="metabox-holder">
119
+
120
+ <?php
121
+ /**
122
+ * Allows to output HTML in sidebar
123
+ *
124
+ * @since 2.0.16
125
+ */
126
+ do_action( 'simple_history/dropin/sidebar/sidebar_html' );
127
+ ?>
128
  </div>
129
+
130
+ </div>
131
+ <?php
132
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
dropins/SimpleHistorySidebarSettings.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Sidebar with link to settings
@@ -8,84 +8,81 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistorySidebarSettings
12
- {
13
-
14
- /**
15
- * Simple History isntance
16
- *
17
- * @var object $sh Simple History instance.
18
- */
19
- private $sh;
20
-
21
- /**
22
- * Constructor.
23
- *
24
- * @param object $sh Simple History instance.
25
- */
26
- public function __construct($sh)
27
- {
28
-
29
- $this->init($sh);
30
- }
31
-
32
- /**
33
- * Init
34
- *
35
- * @param object $sh Simple History instance.
36
- */
37
- public function init($sh)
38
- {
39
-
40
- $this->sh = $sh;
41
-
42
- add_action('simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5);
43
- }
44
-
45
- /**
46
- * Output HTML
47
- */
48
- public function on_sidebar_html()
49
- {
50
-
51
- ?>
52
-
53
- <div class="postbox">
54
-
55
- <h3 class="hndle"><?php esc_html_e('Settings', 'simple-history') ?></h3>
56
-
57
- <div class="inside">
58
-
59
- <p>
60
- <?php
61
-
62
- /*
63
- Visit the settings page to change the number of items to show and
64
- where to show
65
- rss feed
66
- clear log
67
-
68
- - Visit the settings page to change the number of events to show, to get
69
- - Visit the settings page
70
- */
71
- printf(
72
- wp_kses(
73
- /* translators: 1: URL to settings page */
74
- __('<a href="%1$s">Visit the settings page</a> to change things like the number of events to show and to get access to the RSS feed with all events, and more.', 'simple-history'),
75
- array(
76
- 'a' => array(
77
- 'href' => array(),
78
- ),
79
- )
80
- ),
81
- esc_url(menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, false))
82
- );
83
- ?>
84
- </p>
85
-
86
- </div>
87
- </div>
88
-
89
- <?php
90
- }
91
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Sidebar with link to settings
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistorySidebarSettings {
12
+
13
+
14
+ /**
15
+ * Simple History isntance
16
+ *
17
+ * @var object $sh Simple History instance.
18
+ */
19
+ private $sh;
20
+
21
+ /**
22
+ * Constructor.
23
+ *
24
+ * @param object $sh Simple History instance.
25
+ */
26
+ public function __construct( $sh ) {
27
+
28
+ $this->init( $sh );
29
+ }
30
+
31
+ /**
32
+ * Init
33
+ *
34
+ * @param object $sh Simple History instance.
35
+ */
36
+ public function init( $sh ) {
37
+
38
+ $this->sh = $sh;
39
+
40
+ add_action( 'simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5 );
41
+ }
42
+
43
+ /**
44
+ * Output HTML
45
+ */
46
+ public function on_sidebar_html() {
47
+
48
+ ?>
49
+
50
+ <div class="postbox">
51
+
52
+ <h3 class="hndle"><?php esc_html_e( 'Settings', 'simple-history' ); ?></h3>
53
+
54
+ <div class="inside">
55
+
56
+ <p>
57
+ <?php
58
+
59
+ /*
60
+ Visit the settings page to change the number of items to show and
61
+ where to show
62
+ rss feed
63
+ clear log
64
+
65
+ - Visit the settings page to change the number of events to show, to get
66
+ - Visit the settings page
67
+ */
68
+ printf(
69
+ wp_kses(
70
+ /* translators: 1: URL to settings page */
71
+ __( '<a href="%1$s">Visit the settings page</a> to change things like the number of events to show and to get access to the RSS feed with all events, and more.', 'simple-history' ),
72
+ array(
73
+ 'a' => array(
74
+ 'href' => array(),
75
+ ),
76
+ )
77
+ ),
78
+ esc_url( menu_page_url( SimpleHistory::SETTINGS_MENU_SLUG, false ) )
79
+ );
80
+ ?>
81
+ </p>
82
+
83
+ </div>
84
+ </div>
85
+
86
+ <?php
87
+ }
 
 
 
88
  }
dropins/SimpleHistorySidebarStats.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: Sidebar with short stats
@@ -8,237 +8,238 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistorySidebarStats
12
- {
13
-
14
- private $sh;
15
 
16
- public function __construct($sh)
17
- {
18
-
19
- $this->init($sh);
20
- }
21
-
22
- public function init($sh)
23
- {
24
 
25
- $this->sh = $sh;
 
26
 
27
- add_action('simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5);
28
 
29
- add_action('simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ));
30
 
31
- add_action('simple_history/admin_footer', array( $this, 'on_admin_footer' ));
32
- }
33
 
34
- public function on_admin_enqueue_scripts()
35
- {
36
 
37
- wp_enqueue_script('simple_history_chart.js', SIMPLE_HISTORY_DIR_URL . 'js/Chart.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true);
38
- }
39
 
40
- public function on_admin_footer()
41
- {
42
 
43
- ?>
44
- <script>
45
 
46
- /**
47
- * JavaScript for SimpleHistory_SidebarChart
48
- */
49
- (function($) {
50
 
51
- $(function() {
 
52
 
53
- var ctx = $(".SimpleHistory_SidebarChart_ChartCanvas");
 
 
 
54
 
55
- if ( ! ctx.length ) {
56
- return;
57
- }
58
 
59
- var chartLabels = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabels").val() );
60
- var chartLabelsToDates = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabelsToDates").val() );
61
- var chartDatasetData = JSON.parse( $(".SimpleHistory_SidebarChart_ChartDatasetData").val() );
62
 
63
- var myChart = new Simple_History_Chart(ctx, {
64
- type: 'bar',
65
- data: {
66
- labels: chartLabels,
67
- datasets: [{
68
- data: chartDatasetData,
69
- backgroundColor: "rgb(210,210,210)",
70
- hoverBackgroundColor: "rgb(175,175,175)",
71
- }]
72
- },
73
- options: {
74
- legend: {
75
- display: false
76
- },
77
- scales: {
78
- yAxes: [{
79
- ticks: {
80
- beginAtZero:true
81
- },
82
- }],
83
- xAxes: [{
84
- display: false
85
- }]
86
- },
87
- onClick: clickChart
88
- },
89
- });
90
 
 
 
 
91
 
92
- // when chart is clicked determine what value/day was clicked
93
- function clickChart(e) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
- var chartElmClicked = this.getElementAtEvent(e)[0];
96
 
97
- if (!chartElmClicked || !chartElmClicked._index) {
98
- console.log("No value found for click");
99
- return;
100
- }
101
 
102
- var label = this.data.labels[chartElmClicked._index];
103
- // var value = this.data.datasets[chartElmClicked._datasetIndex].data[chartElmClicked._index];
104
 
105
- // now we have the label which is like "July 23" or "23 juli" depending on language
106
- // look for that label value in chartLabelsToDates and there we get the date in format Y-m-d
107
- //console.log("chartLabelsToDates", chartLabelsToDates);
108
- var labelDate;
109
- for (idx in chartLabelsToDates) {
110
- if (label == chartLabelsToDates[idx].label) {
111
- //console.log(chartLabelsToDates[idx]);
112
- labelDate = chartLabelsToDates[idx];
113
- }
114
- }
115
 
116
- if (!labelDate) {
117
- return;
118
- }
119
 
120
- // got a date, now reload the history/post search filter form again
121
- var labelDateParts = labelDate.date.split("-"); ["2016", "07", "18"]
 
 
 
 
 
 
 
 
122
 
123
- // show custom date range
124
- $(".SimpleHistory__filters__filter--date").val("customRange").trigger("change");
 
125
 
126
- // set values, same for both from and to because we only want to show one day
127
- SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_aa'], [name='to_aa']").val(labelDateParts[0]);
128
- SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_jj'], [name='to_jj']").val(labelDateParts[2]);
129
- SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_mm'], [name='to_mm']").val(labelDateParts[1]);
130
 
131
- SimpleHistoryFilterDropin.$elms.filter_form.trigger("submit");
 
132
 
133
- }
134
-
135
- });
136
-
137
- })(jQuery);
138
-
139
- </script>
140
-
141
- <?php
142
- }
143
-
144
- public function on_sidebar_html()
145
- {
146
-
147
- $num_days = 28;
148
-
149
- $num_events_per_day_for_period = $this->sh->get_num_events_per_day_last_n_days($num_days);
150
-
151
- // Period = all dates, so empty ones don't get lost
152
- $period_start_date = DateTime::createFromFormat('U', strtotime("-$num_days days"));
153
- $period_end_date = DateTime::createFromFormat('U', time());
154
- $interval = DateInterval::createFromDateString('1 day');
155
- $period = new DatePeriod($period_start_date, $interval, $period_end_date->add(date_interval_create_from_date_string('1 days')));
156
-
157
- ?>
158
-
159
- <div class="postbox">
160
-
161
- <h3 class="hndle"><?php _e('Stats', 'simple-history') ?></h3>
162
-
163
- <div class="inside">
164
-
165
- <p>
166
- <?php
167
-
168
- printf(
169
- __('<b>%1$s events</b> have been logged the last <b>%2$s days</b>.', 'simple-history'),
170
- $this->sh->get_num_events_last_n_days($num_days),
171
- number_format_i18n($num_days)
172
- );
173
 
174
- ?>
175
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
- <!-- wrapper div so sidebar does not "jump" when loading. so annoying. -->
178
- <div style="position: relative; height: 0; overflow: hidden; padding-bottom: 40%;">
179
- <canvas style="position: absolute; left: 0; right: 0;" class="SimpleHistory_SidebarChart_ChartCanvas" width="100" height="40"></canvas>
180
- </div>
181
 
182
- <p class="SimpleHistory_SidebarChart_ChartDescription" style="font-style: italic; color: #777; text-align: center;">
183
- <?php _e('Number of events per day.', 'simple-history') ?>
184
- </p>
185
 
186
- <?php
187
 
188
- $arr_labels = array();
189
- $arr_labels_to_datetime = array();
190
- $arr_dataset_data = array();
191
 
192
- foreach ($period as $dt) {
193
- $datef = _x('M j', 'stats: date in rows per day chart', 'simple-history');
194
- $str_date = date_i18n($datef, $dt->getTimestamp());
195
- $str_date_ymd = date('Y-m-d', $dt->getTimestamp());
196
 
197
- // Get data for this day, if exist
198
- // Day in object is in format '2014-09-07'
199
- $yearDate = $dt->format('Y-m-d');
200
- $day_data = wp_filter_object_list($num_events_per_day_for_period, array(
201
- 'yearDate' => $yearDate,
202
- ));
 
 
 
203
 
204
- $arr_labels[] = $str_date;
205
 
206
- $arr_labels_to_datetime[] = array(
207
- 'label' => $str_date,
208
- 'date' => $str_date_ymd,
209
- );
210
 
211
- if ($day_data) {
212
- $day_data = reset($day_data);
213
- $arr_dataset_data[] = $day_data->count;
214
- } else {
215
- $arr_dataset_data[] = 0;
216
- }
217
- }
218
 
219
- ?>
220
 
221
- <input
222
- type="hidden"
223
- class="SimpleHistory_SidebarChart_ChartLabels"
224
- value="<?php esc_attr_e(json_encode($arr_labels)) ?>"
225
- />
226
 
227
- <input
228
- type="hidden"
229
- class="SimpleHistory_SidebarChart_ChartLabelsToDates"
230
- value="<?php esc_attr_e(json_encode($arr_labels_to_datetime)) ?>"
231
- />
232
-
233
- <input
234
- type="hidden"
235
- class="SimpleHistory_SidebarChart_ChartDatasetData"
236
- value="<?php esc_attr_e(json_encode($arr_dataset_data)) ?>"
237
- />
238
-
239
- </div>
240
- </div>
241
-
242
- <?php
243
- }
244
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: Sidebar with short stats
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistorySidebarStats {
12
+ private $sh;
 
 
13
 
14
+ public function __construct( $sh ) {
 
 
 
 
 
 
 
15
 
16
+ $this->init( $sh );
17
+ }
18
 
19
+ public function init( $sh ) {
20
 
21
+ $this->sh = $sh;
22
 
23
+ add_action( 'simple_history/dropin/sidebar/sidebar_html', array( $this, 'on_sidebar_html' ), 5 );
 
24
 
25
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts' ) );
 
26
 
27
+ add_action( 'simple_history/admin_footer', array( $this, 'on_admin_footer' ) );
28
+ }
29
 
30
+ public function on_admin_enqueue_scripts() {
 
31
 
32
+ wp_enqueue_script( 'simple_history_chart.js', SIMPLE_HISTORY_DIR_URL . 'js/Chart.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION, true );
33
+ }
34
 
35
+ public function on_admin_footer() {
 
 
 
36
 
37
+ ?>
38
+ <script>
39
 
40
+ /**
41
+ * JavaScript for SimpleHistory_SidebarChart
42
+ */
43
+ (function($) {
44
 
45
+ $(function() {
 
 
46
 
47
+ var ctx = $(".SimpleHistory_SidebarChart_ChartCanvas");
 
 
48
 
49
+ if ( ! ctx.length ) {
50
+ return;
51
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ var chartLabels = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabels").val() );
54
+ var chartLabelsToDates = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabelsToDates").val() );
55
+ var chartDatasetData = JSON.parse( $(".SimpleHistory_SidebarChart_ChartDatasetData").val() );
56
 
57
+ var myChart = new Simple_History_Chart(ctx, {
58
+ type: 'bar',
59
+ data: {
60
+ labels: chartLabels,
61
+ datasets: [{
62
+ data: chartDatasetData,
63
+ backgroundColor: "rgb(210,210,210)",
64
+ hoverBackgroundColor: "rgb(175,175,175)",
65
+ }]
66
+ },
67
+ options: {
68
+ legend: {
69
+ display: false
70
+ },
71
+ scales: {
72
+ yAxes: [{
73
+ ticks: {
74
+ beginAtZero:true
75
+ },
76
+ }],
77
+ xAxes: [{
78
+ display: false
79
+ }]
80
+ },
81
+ onClick: clickChart
82
+ },
83
+ });
84
 
 
85
 
86
+ // when chart is clicked determine what value/day was clicked
87
+ function clickChart(e) {
 
 
88
 
89
+ var chartElmClicked = this.getElementAtEvent(e)[0];
 
90
 
91
+ if (!chartElmClicked || !chartElmClicked._index) {
92
+ console.log("No value found for click");
93
+ return;
94
+ }
 
 
 
 
 
 
95
 
96
+ var label = this.data.labels[chartElmClicked._index];
97
+ // var value = this.data.datasets[chartElmClicked._datasetIndex].data[chartElmClicked._index];
 
98
 
99
+ // now we have the label which is like "July 23" or "23 juli" depending on language
100
+ // look for that label value in chartLabelsToDates and there we get the date in format Y-m-d
101
+ //console.log("chartLabelsToDates", chartLabelsToDates);
102
+ var labelDate;
103
+ for (idx in chartLabelsToDates) {
104
+ if (label == chartLabelsToDates[idx].label) {
105
+ //console.log(chartLabelsToDates[idx]);
106
+ labelDate = chartLabelsToDates[idx];
107
+ }
108
+ }
109
 
110
+ if (!labelDate) {
111
+ return;
112
+ }
113
 
114
+ // got a date, now reload the history/post search filter form again
115
+ var labelDateParts = labelDate.date.split("-"); ["2016", "07", "18"]
 
 
116
 
117
+ // show custom date range
118
+ $(".SimpleHistory__filters__filter--date").val("customRange").trigger("change");
119
 
120
+ // set values, same for both from and to because we only want to show one day
121
+ SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_aa'], [name='to_aa']").val(labelDateParts[0]);
122
+ SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_jj'], [name='to_jj']").val(labelDateParts[2]);
123
+ SimpleHistoryFilterDropin.$elms.filter_form.find("[name='from_mm'], [name='to_mm']").val(labelDateParts[1]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ SimpleHistoryFilterDropin.$elms.filter_form.trigger("submit");
126
+
127
+ }
128
+
129
+ });
130
+
131
+ })(jQuery);
132
+
133
+ </script>
134
+
135
+ <?php
136
+ }
137
+
138
+ public function on_sidebar_html() {
139
+
140
+ $num_days = 28;
141
+
142
+ $num_events_per_day_for_period = $this->sh->get_num_events_per_day_last_n_days( $num_days );
143
+
144
+ // Period = all dates, so empty ones don't get lost
145
+ $period_start_date = DateTime::createFromFormat( 'U', strtotime( "-$num_days days" ) );
146
+ $period_end_date = DateTime::createFromFormat( 'U', time() );
147
+ $interval = DateInterval::createFromDateString( '1 day' );
148
+ $period = new DatePeriod( $period_start_date, $interval, $period_end_date->add( date_interval_create_from_date_string( '1 days' ) ) );
149
+
150
+ ?>
151
+
152
+ <div class="postbox">
153
+
154
+ <h3 class="hndle"><?php esc_html_e( 'Stats', 'simple-history' ); ?></h3>
155
+
156
+ <div class="inside">
157
+
158
+ <p>
159
+ <?php
160
+
161
+ echo wp_kses(
162
+ sprintf(
163
+ __( '<b>%1$s events</b> have been logged the last <b>%2$s days</b>.', 'simple-history' ),
164
+ $this->sh->get_num_events_last_n_days( $num_days ),
165
+ number_format_i18n( $num_days )
166
+ ),
167
+ array(
168
+ 'b' => array(),
169
+ )
170
+ );
171
+
172
+ ?>
173
+ </p>
174
 
175
+ <!-- wrapper div so sidebar does not "jump" when loading. so annoying. -->
176
+ <div style="position: relative; height: 0; overflow: hidden; padding-bottom: 40%;">
177
+ <canvas style="position: absolute; left: 0; right: 0;" class="SimpleHistory_SidebarChart_ChartCanvas" width="100" height="40"></canvas>
178
+ </div>
179
 
180
+ <p class="SimpleHistory_SidebarChart_ChartDescription" style="font-style: italic; color: #777; text-align: center;">
181
+ <?php esc_html_e( 'Number of events per day.', 'simple-history' ); ?>
182
+ </p>
183
 
184
+ <?php
185
 
186
+ $arr_labels = array();
187
+ $arr_labels_to_datetime = array();
188
+ $arr_dataset_data = array();
189
 
190
+ foreach ( $period as $dt ) {
191
+ $datef = _x( 'M j', 'stats: date in rows per day chart', 'simple-history' );
192
+ $str_date = date_i18n( $datef, $dt->getTimestamp() );
193
+ $str_date_ymd = gmdate( 'Y-m-d', $dt->getTimestamp() );
194
 
195
+ // Get data for this day, if exist
196
+ // Day in object is in format '2014-09-07'
197
+ $yearDate = $dt->format( 'Y-m-d' );
198
+ $day_data = wp_filter_object_list(
199
+ $num_events_per_day_for_period,
200
+ array(
201
+ 'yearDate' => $yearDate,
202
+ )
203
+ );
204
 
205
+ $arr_labels[] = $str_date;
206
 
207
+ $arr_labels_to_datetime[] = array(
208
+ 'label' => $str_date,
209
+ 'date' => $str_date_ymd,
210
+ );
211
 
212
+ if ( $day_data ) {
213
+ $day_data = reset( $day_data );
214
+ $arr_dataset_data[] = $day_data->count;
215
+ } else {
216
+ $arr_dataset_data[] = 0;
217
+ }
218
+ }
219
 
220
+ ?>
221
 
222
+ <input
223
+ type="hidden"
224
+ class="SimpleHistory_SidebarChart_ChartLabels"
225
+ value="<?php echo esc_attr( json_encode( $arr_labels ) ); ?>"
226
+ />
227
 
228
+ <input
229
+ type="hidden"
230
+ class="SimpleHistory_SidebarChart_ChartLabelsToDates"
231
+ value="<?php echo esc_attr( json_encode( $arr_labels_to_datetime ) ); ?>"
232
+ />
233
+
234
+ <input
235
+ type="hidden"
236
+ class="SimpleHistory_SidebarChart_ChartDatasetData"
237
+ value="<?php echo esc_attr( json_encode( $arr_dataset_data ) ); ?>"
238
+ />
239
+
240
+ </div>
241
+ </div>
242
+
243
+ <?php
244
+ }
245
  }
dropins/SimpleHistoryWPCLIDropin.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /*
6
  Dropin Name: WP CLI
@@ -8,158 +8,147 @@ Dropin URI: http://simple-history.com/
8
  Author: Pär Thernström
9
  */
10
 
11
- class SimpleHistoryWPCLIDropin
12
- {
13
- // Simple History instance
14
- private $sh;
15
-
16
- public function __construct($sh)
17
- {
18
-
19
- $this->sh = $sh;
20
- // add_action( 'admin_menu', array($this, 'add_settings'), 50 );
21
- // add_action( 'plugin_row_meta', array($this, 'action_plugin_row_meta'), 10, 2);
22
- if (defined('WP_CLI') && WP_CLI) {
23
- $this->register_commands();
24
- }
25
- }
26
-
27
- private function register_commands()
28
- {
29
- $commandConfigurationOptions = array(
30
- 'shortdesc' => 'Lists the history log',
31
- 'synopsis' => array(
32
- array(
33
- 'type' => 'assoc',
34
- 'name' => 'format',
35
- 'optional' => true,
36
- 'default' => 'table',
37
- 'options' => array( 'table', 'json', 'csv', 'yaml' ),
38
- ),
39
- array(
40
- 'type' => 'assoc',
41
- 'name' => 'count',
42
- 'optional' => true,
43
- 'default' => '10',
44
- ),
45
- ),
46
- 'when' => 'after_wp_load',
47
- );
48
-
49
- WP_CLI::add_command('simple-history list', array( $this, 'commandList' ), $commandConfigurationOptions);
50
- }
51
-
52
- private function getInitiatorTextFromRow($row)
53
- {
54
- if (! isset($row->initiator)) {
55
- return false;
56
- }
57
-
58
- $initiator = $row->initiator;
59
- $initiatorText = '';
60
-
61
- switch ($initiator) {
62
- case 'wp':
63
- $initiatorText = 'WordPress';
64
- break;
65
- case 'wp_cli':
66
- $initiatorText = 'WP-CLI';
67
- break;
68
- case 'wp_user':
69
- $user_id = isset($row->context['_user_id']) ? $row->context['_user_id'] : null;
70
-
71
- if ($user_id > 0 && $user = get_user_by('id', $user_id)) {
72
- // User still exists
73
- // Get user role, as done in user-edit.php
74
- $wp_roles = $GLOBALS['wp_roles'];
75
- $all_roles = (array) $wp_roles->roles;
76
- $user_roles = array_intersect(array_values((array) $user->roles), array_keys((array) $wp_roles->roles));
77
- $user_role = array_shift($user_roles);
78
-
79
- $initiatorText = sprintf(
80
- '%1$s (%2$s)',
81
- $user->user_login, // 1
82
- $user->user_email // 2
83
- );
84
- } elseif ($user_id > 0) {
85
- // Sender was a user, but user is deleted now
86
- $initiatorText = sprintf(
87
- __('Deleted user (had id %1$s, email %2$s, login %3$s)', 'simple-history'),
88
- $context['_user_id'], // 1
89
- $context['_user_email'], // 2
90
- $context['_user_login'] // 3
91
- );
92
- } // End if().
93
- break;
94
- case 'web_user':
95
- $initiatorText = __('Anonymous web user', 'simple-history');
96
- break;
97
- case 'other':
98
- $initiatorText = _x('Other', 'Event header output, when initiator is unknown', 'simple-history');
99
- break;
100
- default:
101
- $initiatorText = $initiator;
102
- }// End switch().
103
-
104
- return $initiatorText;
105
- }
106
-
107
- /**
108
- * The function for the command "list"
109
- */
110
- public function commandList($args, $assoc_args)
111
- {
112
-
113
- if (! is_numeric($assoc_args['count'])) {
114
- WP_CLI::error(__('Error: parameter "count" must be a number', 'simple-history'));
115
- }
116
-
117
- // Override capability check: if you can run wp cli commands you can read all loggers
118
- add_action('simple_history/loggers_user_can_read/can_read_single_logger', '__return_true', 10, 3);
119
-
120
- // WP_CLI::log( sprintf( 'Showing %1$d events from Simple History', $assoc_args["count"] ) );
121
- $query = new SimpleHistoryLogQuery();
122
-
123
- $query_args = array(
124
- 'paged' => 1,
125
- 'posts_per_page' => $assoc_args['count'],
126
- );
127
-
128
- $events = $query->query($query_args);
129
-
130
- // A cleaned version of the events, formatted for wp cli table output
131
- $eventsCleaned = array();
132
-
133
- foreach ($events['log_rows'] as $row) {
134
- $header_output = $this->sh->getLogRowHeaderOutput($row);
135
- $text_output = $this->sh->getLogRowPlainTextOutput($row);
136
- // $details_output = $this->sh->getLogRowDetailsOutput($row);
137
- $header_output = strip_tags(html_entity_decode($header_output, ENT_QUOTES, 'UTF-8'));
138
- $header_output = trim(preg_replace('/\s\s+/', ' ', $header_output));
139
-
140
- $text_output = strip_tags(html_entity_decode($text_output, ENT_QUOTES, 'UTF-8'));
141
-
142
- $eventsCleaned[] = array(
143
- 'date' => get_date_from_gmt($row->date),
144
- // "initiator" => $row->initiator,
145
- 'initiator' => $this->getInitiatorTextFromRow($row),
146
- 'logger' => $row->logger,
147
- 'level' => $row->level,
148
- 'who_when' => $header_output,
149
- 'description' => $text_output,
150
- 'count' => $row->subsequentOccasions,
151
- // "details" => $details_output
152
- );
153
- }
154
-
155
- $fields = array(
156
- 'date',
157
- 'initiator',
158
- 'description',
159
- 'level',
160
- 'count',
161
- );
162
-
163
- WP_CLI\Utils\format_items($assoc_args['format'], $eventsCleaned, $fields);
164
- }
165
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /*
6
  Dropin Name: WP CLI
8
  Author: Pär Thernström
9
  */
10
 
11
+ class SimpleHistoryWPCLIDropin {
12
+
13
+ // Simple History instance
14
+ private $sh;
15
+
16
+ public function __construct( $sh ) {
17
+ $this->sh = $sh;
18
+
19
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
20
+ $this->register_commands();
21
+ }
22
+ }
23
+
24
+ private function register_commands() {
25
+ $commandConfigurationOptions = array(
26
+ 'shortdesc' => 'Lists the history log',
27
+ 'synopsis' => array(
28
+ array(
29
+ 'type' => 'assoc',
30
+ 'name' => 'format',
31
+ 'optional' => true,
32
+ 'default' => 'table',
33
+ 'options' => array( 'table', 'json', 'csv', 'yaml' ),
34
+ ),
35
+ array(
36
+ 'type' => 'assoc',
37
+ 'name' => 'count',
38
+ 'optional' => true,
39
+ 'default' => '10',
40
+ ),
41
+ ),
42
+ 'when' => 'after_wp_load',
43
+ );
44
+
45
+ WP_CLI::add_command( 'simple-history list', array( $this, 'commandList' ), $commandConfigurationOptions );
46
+ }
47
+
48
+ private function getInitiatorTextFromRow( $row ) {
49
+ if ( ! isset( $row->initiator ) ) {
50
+ return false;
51
+ }
52
+
53
+ $initiator = $row->initiator;
54
+ $initiatorText = '';
55
+
56
+ switch ( $initiator ) {
57
+ case 'wp':
58
+ $initiatorText = 'WordPress';
59
+ break;
60
+ case 'wp_cli':
61
+ $initiatorText = 'WP-CLI';
62
+ break;
63
+ case 'wp_user':
64
+ $user_id = isset( $row->context['_user_id'] ) ? $row->context['_user_id'] : null;
65
+ $user = get_user_by( 'id', $user_id );
66
+
67
+ if ( $user_id > 0 && $user ) {
68
+ // User still exists
69
+ $initiatorText = sprintf(
70
+ '%1$s (%2$s)',
71
+ $user->user_login, // 1
72
+ $user->user_email // 2
73
+ );
74
+ } elseif ( $user_id > 0 ) {
75
+ // Sender was a user, but user is deleted now.
76
+ $initiatorText = sprintf(
77
+ __( 'Deleted user (had id %1$s, email %2$s, login %3$s)', 'simple-history' ),
78
+ $context['_user_id'], // 1
79
+ $context['_user_email'], // 2
80
+ $context['_user_login'] // 3
81
+ );
82
+ } // End if().
83
+ break;
84
+ case 'web_user':
85
+ $initiatorText = __( 'Anonymous web user', 'simple-history' );
86
+ break;
87
+ case 'other':
88
+ $initiatorText = _x( 'Other', 'Event header output, when initiator is unknown', 'simple-history' );
89
+ break;
90
+ default:
91
+ $initiatorText = $initiator;
92
+ }// End switch().
93
+
94
+ return $initiatorText;
95
+ }
96
+
97
+ /**
98
+ * The function for the command "list"
99
+ */
100
+ public function commandList( $args, $assoc_args ) {
101
+
102
+ if ( ! is_numeric( $assoc_args['count'] ) ) {
103
+ WP_CLI::error( __( 'Error: parameter "count" must be a number', 'simple-history' ) );
104
+ }
105
+
106
+ // Override capability check: if you can run wp cli commands you can read all loggers
107
+ add_action( 'simple_history/loggers_user_can_read/can_read_single_logger', '__return_true', 10, 3 );
108
+
109
+ // WP_CLI::log( sprintf( 'Showing %1$d events from Simple History', $assoc_args["count"] ) );
110
+ $query = new SimpleHistoryLogQuery();
111
+
112
+ $query_args = array(
113
+ 'paged' => 1,
114
+ 'posts_per_page' => $assoc_args['count'],
115
+ );
116
+
117
+ $events = $query->query( $query_args );
118
+
119
+ // A cleaned version of the events, formatted for wp cli table output
120
+ $eventsCleaned = array();
121
+
122
+ foreach ( $events['log_rows'] as $row ) {
123
+ $header_output = $this->sh->getLogRowHeaderOutput( $row );
124
+ $text_output = $this->sh->getLogRowPlainTextOutput( $row );
125
+ // $details_output = $this->sh->getLogRowDetailsOutput($row);
126
+ $header_output = strip_tags( html_entity_decode( $header_output, ENT_QUOTES, 'UTF-8' ) );
127
+ $header_output = trim( preg_replace( '/\s\s+/', ' ', $header_output ) );
128
+
129
+ $text_output = strip_tags( html_entity_decode( $text_output, ENT_QUOTES, 'UTF-8' ) );
130
+
131
+ $eventsCleaned[] = array(
132
+ 'date' => get_date_from_gmt( $row->date ),
133
+ // "initiator" => $row->initiator,
134
+ 'initiator' => $this->getInitiatorTextFromRow( $row ),
135
+ 'logger' => $row->logger,
136
+ 'level' => $row->level,
137
+ 'who_when' => $header_output,
138
+ 'description' => $text_output,
139
+ 'count' => $row->subsequentOccasions,
140
+ // "details" => $details_output
141
+ );
142
+ }
143
+
144
+ $fields = array(
145
+ 'date',
146
+ 'initiator',
147
+ 'description',
148
+ 'level',
149
+ 'count',
150
+ );
151
+
152
+ WP_CLI\Utils\format_items( $assoc_args['format'], $eventsCleaned, $fields );
153
+ }
 
 
 
 
 
 
 
 
 
 
 
154
  }
examples/example-dropin.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
 
3
- // No external calls allowed to test file
 
4
  exit;
5
 
6
  /**
@@ -8,56 +9,48 @@ exit;
8
  * that will add a tab to the simple history settings page
9
  */
10
 
11
- // We use the function "register_dropin" to tell tell SimpleHistory that our custom logger exists.
12
  // We call it from inside the filter "simple_history/add_custom_logger".
13
- add_action('simple_history/add_custom_dropin', function ($simpleHistory) {
14
- $simpleHistory->register_dropin('AddSettingsPageTab');
15
- });
 
 
 
16
 
17
 
18
  /**
19
  * This is the class that does the main work!
20
  */
21
- class AddSettingsPageTab
22
- {
23
-
24
- // This will hold a reference to the simple history instance
25
- private $sh;
26
-
27
- // simple history will pass itself to the constructor
28
- public function __construct($sh)
29
- {
30
-
31
- $this->sh = $sh;
32
-
33
- $this->init();
34
- }
35
-
36
- public function init()
37
- {
38
-
39
- add_action('init', array( $this, 'addSettingsTab' ));
40
- }
41
-
42
- public function addSettingsTab()
43
- {
44
-
45
- $this->sh->registerSettingsTab(array(
46
- 'slug' => 'my_unique_settings_tab_slug',
47
- 'name' => __('Example tab', 'simple-history'),
48
- 'function' => array( $this, 'settingsTabOutput' ),
49
- ));
50
- }
51
-
52
- public function settingsTabOutput()
53
- {
54
-
55
- ?>
56
-
57
- <h3>Hi there!</h3>
58
-
59
- <p>I'm the output from on settings tab.</p>
60
-
61
- <?php
62
- }
63
  }
1
  <?php
2
 
3
+ // No external calls allowed to test file.
4
+ // Remove this exit call if you use this file as a template for your own dropin.
5
  exit;
6
 
7
  /**
9
  * that will add a tab to the simple history settings page
10
  */
11
 
12
+ // We use the function "register_dropin" to tell tell Simple History that our custom logger exists.
13
  // We call it from inside the filter "simple_history/add_custom_logger".
14
+ add_action(
15
+ 'simple_history/add_custom_dropin',
16
+ function ( $simpleHistory ) {
17
+ $simpleHistory->register_dropin( 'AddSettingsPageTab' );
18
+ }
19
+ );
20
 
21
 
22
  /**
23
  * This is the class that does the main work!
24
  */
25
+ class AddSettingsPageTab {
26
+
27
+ // This will hold a reference to the simple history instance.
28
+ private $sh;
29
+
30
+ // Simple History will pass itself to the constructor.
31
+ public function __construct( $sh ) {
32
+ $this->sh = $sh;
33
+ $this->init();
34
+ }
35
+
36
+ public function init() {
37
+ add_action( 'init', array( $this, 'add_settings_tab' ) );
38
+ }
39
+
40
+ public function add_settings_tab() {
41
+ $this->sh->registerSettingsTab(
42
+ array(
43
+ 'slug' => 'my_unique_settings_tab_slug',
44
+ 'name' => __( 'Example tab', 'simple-history' ),
45
+ 'function' => array( $this, 'settings_tab_output' ),
46
+ )
47
+ );
48
+ }
49
+
50
+ public function settings_tab_output() {
51
+ ?>
52
+ <h3>Hi there!</h3>
53
+ <p>I'm the output from on settings tab.</p>
54
+ <?php
55
+ }
 
 
 
 
 
 
 
 
 
 
 
56
  }
examples/example-logger.php CHANGED
@@ -10,84 +10,84 @@ exit;
10
 
11
  // We use the function "register_logger" to tell tell SimpleHistory that our custom logger exists.
12
  // We call it from inside the filter "simple_history/add_custom_logger".
13
- add_action('simple_history/add_custom_logger', function ($simpleHistory) {
14
- $simpleHistory->register_logger('FourOhFourLogger');
15
- });
 
 
 
16
 
17
  // We make sure that the SimpleLogger class exists before trying to extend it.
18
  // This prevents error if the Simple History plugin gets inactivated.
19
- if (class_exists('SimpleLogger')) {
20
-
21
- /**
22
- * This is the class that does the main work!
23
- */
24
- class FourOhFourLogger extends SimpleLogger
25
- {
26
-
27
- /**
28
- * The slug is ised to identify this logger in various places.
29
- * We use the name of the class too keep it simple.
30
- */
31
- public $slug = __CLASS__;
32
-
33
- /**
34
- * Return information about this logger.
35
- * Used to show info about the logger at various places.
36
- */
37
- public function getInfo()
38
- {
39
-
40
- $arr_info = array(
41
- 'name' => '404 Logger',
42
- 'description' => 'Logs access to pages that result in page not found errors (error code 404)',
43
- 'capability' => 'edit_pages',
44
- 'messages' => array(
45
- 'page_not_found' => __('Got a 404-page when trying to visit "{request_uri}"', 'simple-history'),
46
- ),
47
- 'labels' => array(
48
- 'search' => array(
49
- 'label' => _x('Pages not found (404 errors)', 'User logger: 404', 'simple-history'),
50
- 'options' => array(
51
- _x('Pages not found', 'User logger: 404', 'simple-history') => array(
52
- 'page_not_found',
53
- ),
54
- ),
55
- ), // end search
56
- ), // end labels
57
- );
58
-
59
- return $arr_info;
60
- }
61
-
62
- /**
63
- * When Simple History has loaded this logger it automagically
64
- * calls a loaded() function. This is where you add your actions
65
- * and other logger functionality.
66
- */
67
- public function loaded()
68
- {
69
-
70
- // Call a function when WordPress finds a 404 page
71
- add_action('404_template', array( $this, 'on404Template' ), 10, 1);
72
- }
73
-
74
- /**
75
- * Function that is called when WordPress finds a 404 page.
76
- * It collects some info and then it logs a warning message
77
- * to the log.
78
- */
79
- public function on404Template($template)
80
- {
81
-
82
- $context = array(
83
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
84
- 'request_uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '',
85
- 'http_referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
86
- );
87
-
88
- $this->warningMessage('page_not_found', $context);
89
-
90
- return $template;
91
- }
92
- }
93
  }// End if().
10
 
11
  // We use the function "register_logger" to tell tell SimpleHistory that our custom logger exists.
12
  // We call it from inside the filter "simple_history/add_custom_logger".
13
+ add_action(
14
+ 'simple_history/add_custom_logger',
15
+ function ( $simpleHistory ) {
16
+ $simpleHistory->register_logger( 'FourOhFourLogger' );
17
+ }
18
+ );
19
 
20
  // We make sure that the SimpleLogger class exists before trying to extend it.
21
  // This prevents error if the Simple History plugin gets inactivated.
22
+ if ( class_exists( 'SimpleLogger' ) ) {
23
+
24
+ /**
25
+ * This is the class that does the main work!
26
+ */
27
+ class FourOhFourLogger extends SimpleLogger {
28
+
29
+
30
+ /**
31
+ * The slug is ised to identify this logger in various places.
32
+ * We use the name of the class too keep it simple.
33
+ */
34
+ public $slug = __CLASS__;
35
+
36
+ /**
37
+ * Return information about this logger.
38
+ * Used to show info about the logger at various places.
39
+ */
40
+ public function getInfo() {
41
+
42
+ $arr_info = array(
43
+ 'name' => '404 Logger',
44
+ 'description' => 'Logs access to pages that result in page not found errors (error code 404)',
45
+ 'capability' => 'edit_pages',
46
+ 'messages' => array(
47
+ 'page_not_found' => __( 'Got a 404-page when trying to visit "{request_uri}"', 'simple-history' ),
48
+ ),
49
+ 'labels' => array(
50
+ 'search' => array(
51
+ 'label' => _x( 'Pages not found (404 errors)', 'User logger: 404', 'simple-history' ),
52
+ 'options' => array(
53
+ _x( 'Pages not found', 'User logger: 404', 'simple-history' ) => array(
54
+ 'page_not_found',
55
+ ),
56
+ ),
57
+ ), // end search
58
+ ), // end labels
59
+ );
60
+
61
+ return $arr_info;
62
+ }
63
+
64
+ /**
65
+ * When Simple History has loaded this logger it automagically
66
+ * calls a loaded() function. This is where you add your actions
67
+ * and other logger functionality.
68
+ */
69
+ public function loaded() {
70
+
71
+ // Call a function when WordPress finds a 404 page
72
+ add_action( '404_template', array( $this, 'on404Template' ), 10, 1 );
73
+ }
74
+
75
+ /**
76
+ * Function that is called when WordPress finds a 404 page.
77
+ * It collects some info and then it logs a warning message
78
+ * to the log.
79
+ */
80
+ public function on404Template( $template ) {
81
+
82
+ $context = array(
83
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
84
+ 'request_uri' => isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '',
85
+ 'http_referer' => isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '',
86
+ );
87
+
88
+ $this->warningMessage( 'page_not_found', $context );
89
+
90
+ return $template;
91
+ }
92
+ }
 
 
 
93
  }// End if().
examples/examples.php CHANGED
@@ -14,7 +14,7 @@ exit;
14
  */
15
 
16
  // Add $_GET, $_POST, and more info to each logged event.
17
- define('SIMPLE_HISTORY_LOG_DEBUG', true);
18
 
19
 
20
  /**
@@ -25,61 +25,79 @@ define('SIMPLE_HISTORY_LOG_DEBUG', true);
25
  * Remove the "Clear log"-button, so a user with admin access can not clear the log
26
  * and wipe their mischievous behavior from the log.
27
  */
28
- add_filter('simple_history/user_can_clear_log', function ($user_can_clear_log) {
29
- $user_can_clear_log = false;
30
- return $user_can_clear_log;
31
- });
 
 
 
32
 
33
  // Modify who can read a logger.
34
  // Modify the if part to give users access or no access to a logger.
35
- add_filter('simple_history/loggers_user_can_read/can_read_single_logger', function ($user_can_read_logger, $logger_instance, $user_id) {
36
-
37
- // in this example user with id 3 gets access to the post logger
38
- // while user with id 8 does not get any access to it
39
- if ($logger_instance->slug == 'SimplePostLogger' && $user_id === 3) {
40
- $user_can_read_logger = true;
41
- } elseif ($logger_instance->slug == 'SimplePostLogger' && $user_id === 9) {
42
- $user_can_read_logger = false;
43
- }
44
-
45
- return $user_can_read_logger;
46
- }, 10, 3);
 
 
 
 
 
47
 
48
 
49
  // Do not log some post types, for example pages and attachments in this case
50
- add_filter('simple_history/log/do_log', function ($do_log = null, $level = null, $message = null, $context = null, $logger = null) {
51
-
52
- $post_types_to_not_log = array(
53
- 'page',
54
- 'attachment',
55
- );
56
-
57
- if (( isset($logger->slug) && ($logger->slug === 'SimplePostLogger' || $logger->slug === 'SimpleMediaLogger') ) && ( isset($context['post_type']) && in_array($context['post_type'], $post_types_to_not_log) )) {
58
- $do_log = false;
59
- }
60
-
61
- return $do_log;
62
- }, 10, 5);
 
 
 
 
 
63
 
64
  // Disable all logging
65
- add_filter('simple_history/log/do_log', '__return_false');
66
 
67
  /**
68
  * Example that modifies the parameters sent to the message template
69
  * This example will change the post type from "post" or "page" or similar to "my own page type"
70
  */
71
- add_filter('simple_history/logger/interpolate/context', function ($context, $message, $row) {
72
-
73
- if (empty($row)) {
74
- return $context;
75
- }
76
-
77
- if ($row->logger == 'SimplePostLogger' && $row->context_message_key == 'post_updated') {
78
- $context['post_type'] = 'my own page type';
79
- }
80
-
81
- return $context;
82
- }, 10, 3);
 
 
 
 
 
83
 
84
 
85
 
@@ -87,11 +105,14 @@ add_filter('simple_history/logger/interpolate/context', function ($context, $mes
87
  * Change capability required to manage the options page of simple history.
88
  * Default capability is "manage_options"
89
  */
90
- add_filter('simple_history/view_settings_capability', function ($capability) {
 
 
91
 
92
- $capability = 'manage_options';
93
- return $capability;
94
- });
 
95
 
96
 
97
  /**
@@ -99,249 +120,302 @@ add_filter('simple_history/view_settings_capability', function ($capability) {
99
  * Default capability is "edit_pages". Change to for example "manage options"
100
  * to only allow admins to view the history log.
101
  */
102
- add_filter('simple_history/view_history_capability', function ($capability) {
 
 
103
 
104
- $capability = 'manage_options';
105
- return $capability;
106
- });
 
107
 
108
 
109
  // Skip adding things to the context table during logging.
110
  // Useful if you don't want to add cool and possible super useful info to your logged events.
111
  // Also nice to have if you want to make sure your database does not grow.
112
- add_filter('simple_history/log_insert_context', function ($context, $data) {
113
-
114
- unset($context['_user_id']);
115
- unset($context['_user_login']);
116
- unset($context['_user_email']);
117
- unset($context['server_http_user_agent']);
118
-
119
- return $context;
120
- }, 10, 2);
 
 
 
 
 
121
 
122
  // Hide some columns from the detailed context view popup window
123
- add_filter('simple_history/log_html_output_details_table/row_keys_to_show', function ($logRowKeysToShow, $oneLogRow) {
124
-
125
- $logRowKeysToShow['id'] = false;
126
- $logRowKeysToShow['logger'] = false;
127
- $logRowKeysToShow['level'] = false;
128
- $logRowKeysToShow['message'] = false;
129
-
130
- return $logRowKeysToShow;
131
- }, 10, 2);
 
 
 
 
 
132
 
133
 
134
  // Hide some more columns from the detailed context view popup window
135
- add_filter('simple_history/log_html_output_details_table/context_keys_to_show', function ($logRowContextKeysToShow, $oneLogRow) {
136
-
137
- $logRowContextKeysToShow['plugin_slug'] = false;
138
- $logRowContextKeysToShow['plugin_name'] = false;
139
- $logRowContextKeysToShow['plugin_title'] = false;
140
- $logRowContextKeysToShow['plugin_description'] = false;
141
-
142
- return $logRowContextKeysToShow;
143
- }, 10, 2);
 
 
 
 
 
144
 
145
 
146
 
147
  // Allow only the users specified in $allowed_users to show the history page, the history widget on the dashboard, or the history settings page
148
- add_filter('simple_history/show_dashboard_page', 'function_show_history_dashboard_or_page');
149
- add_filter('simple_history/show_dashboard_widget', 'function_show_history_dashboard_or_page');
150
- add_filter('simple_history/show_settings_page', 'function_show_history_dashboard_or_page');
151
- function function_show_history_dashboard_or_page($show)
152
- {
153
 
154
- $allowed_users = array(
155
- 'user1@example.com',
156
- 'anotheruser@example.com',
157
- );
158
 
159
- $user = wp_get_current_user();
160
 
161
- if (! in_array($user->user_email, $allowed_users)) {
162
- $show = false;
163
- }
164
 
165
- return $show;
166
  }
167
 
168
 
169
  // Skip loading of loggers
170
- add_filter('simple_history/logger/load_logger', function ($load_logger, $oneLoggerFile) {
171
-
172
- // Don't load loggers for comments or menus, i.e. don't log changes to comments or to menus
173
- if (in_array($oneLoggerFile, array( 'SimpleCommentsLogger', 'SimpleMenuLogger' ))) {
174
- $load_logger = false;
175
- }
176
-
177
- return $load_logger;
178
- }, 10, 2);
 
 
 
 
 
179
 
180
  /**
181
  * Load only the loggers that are specified in the $do_log_us array
182
  */
183
- add_filter('simple_history/logger/load_logger', function ($load_logger, $logger_basename) {
 
 
184
 
185
- $load_logger = false;
186
- $do_log_us = array( 'SimplePostLogger', 'SimplePluginLogger', 'SimpleLogger' );
187
 
188
- if (in_array($logger_basename, $do_log_us)) {
189
- $load_logger = true;
190
- }
191
 
192
- return $load_logger;
193
- }, 10, 2);
 
 
 
194
 
195
 
196
  // Skip the loading of dropins
197
- add_filter('simple_history/dropin/load_dropin', function ($load_dropin, $dropinFileBasename) {
198
-
199
- // Don't load the RSS feed dropin
200
- if ($dropinFileBasename == 'SimpleHistoryRSSDropin') {
201
- $load_dropin = false;
202
- }
203
-
204
- // Don't load the dropin that polls for changes
205
- if ($dropinFileBasename == 'SimpleHistoryNewRowsNotifier') {
206
- $load_dropin = false;
207
- }
208
-
209
- return $load_dropin;
210
- }, 10, 2);
 
 
 
 
 
211
 
212
 
213
  // Don't log failed logins
214
- add_filter('simple_history/simple_logger/log_message_key', function ($doLog, $loggerSlug, $messageKey, $SimpleLoggerLogLevelsLevel, $context) {
215
-
216
- // Don't log login attempts to non existing users
217
- if ('SimpleUserLogger' == $loggerSlug && 'user_unknown_login_failed' == $messageKey) {
218
- $doLog = false;
219
- }
220
-
221
- // Don't log failed logins to existing users
222
- if ('SimpleUserLogger' == $loggerSlug && 'user_login_failed' == $messageKey) {
223
- $doLog = false;
224
- }
225
-
226
- return $doLog;
227
- }, 10, 5);
 
 
 
 
 
228
 
229
  // Never clear the log (default is 60 days)
230
- add_filter('simple_history/db_purge_days_interval', '__return_zero');
231
 
232
  // Clear items that are older than a 7 days (i.e. keep only the most recent 7 days in the log)
233
- add_filter('simple_history/db_purge_days_interval', function ($days) {
 
 
234
 
235
- $days = 7;
236
 
237
- return $days;
238
- });
 
239
 
240
  // Don't let anyone - even with the correct secret - view the RSS feed
241
- add_filter('simple_history/rss_feed_show', '__return_false');
242
 
243
  // Skip loading of a dropin completely (in this case the RSS dropin)
244
- add_filter('simple_history/dropin/load_dropin_SimpleHistoryRSSDropin', '__return_false');
245
 
246
  /**
247
  * Example of logging
248
  */
249
 
250
  // This is the easiest and safest way to add messages to the log:
251
- apply_filters('simple_history_log', 'This is a logged message');
252
- apply_filters('simple_history_log', 'This is a message with some context added', array(
253
- 'isATestMessage' => 'yup',
254
- 'debugRequestData' => $_REQUEST,
255
- ));
256
- apply_filters('simple_history_log', 'This is another logged message, with another severity level', null, 'debug');
 
 
 
 
257
 
258
  // Below is the function way of adding things to the log
259
  // Remember to check that the SimpleLogger function exists before trying to log anything,
260
  // or else your site will break if you disable the Simple History plugin
261
  // (Use the apply_filters method above if you want to stay safer!)
262
- if (function_exists('SimpleLogger')) {
263
- SimpleLogger()->info('This is a message added to the log');
264
  }
265
 
266
  // Add a message to the history log
267
- SimpleLogger()->info('This is a message sent to the log');
268
 
269
  // Add log entries with different severities
270
- SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
271
- SimpleLogger()->debug('Ok, cron job is running!');
272
 
273
  // Add a message to the history log
274
  // and then add a second log entry with same info and Simple History
275
  // will make these two become an "occasionGroup",
276
  // i.e. collapsing their entries into one expandable log item
277
- SimpleLogger()->info('This is a message sent to the log');
278
- SimpleLogger()->info('This is a message sent to the log');
279
 
280
  // Log entries can have placeholders and context
281
  // This makes log entried translatable and filterable
282
  SimpleLogger()->notice(
283
- 'User {username} edited page {pagename}',
284
- array(
285
- 'username' => 'jessie',
286
- 'pagename' => 'My test page',
287
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
288
- '_user_id' => 5,
289
- '_user_login' => 'jess',
290
- '_user_email' => 'jessie@example.com',
291
- )
292
  );
293
 
294
  // Log entried can have custom occasionsID
295
  // This will group items together and a log entry will only be shown once
296
  // in the log overview, even if the logged messages are different
297
- for ($i = 0; $i < rand(1, 50); $i++) {
298
- SimpleLogger()->notice('User {username} edited page {pagename}', array(
299
- 'username' => "example_user_{$i}",
300
- 'pagename' => 'My test page',
301
- '_occasionsID' => 'postID:24884,action:edited',
302
- ));
 
 
 
303
  }
304
 
305
  // Events can have different "initiators",
306
  // i.e. who was responsible for the logged event
307
  // Initiator "WORDPRESS" means that WordPress did something on it's own
308
  SimpleLogger()->info(
309
- 'WordPress updated itself from version {from_version} to {to_version}',
310
- array(
311
- 'from_version' => '3.8',
312
- 'to_version' => '3.8.1',
313
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
314
- )
315
  );
316
 
317
  // Initiator "WP_USER" means that a logged in user did someting
318
  SimpleLogger()->info(
319
- 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
320
- array(
321
- 'plugin_name' => 'Ninja Forms',
322
- 'plugin_from_version' => '1.1',
323
- 'plugin_to_version' => '1.1.2',
324
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
325
- )
326
  );
327
 
328
  // // Initiator "WEB_USER" means that an unknown internet user did something
329
- SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
330
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
331
- ));
 
 
 
332
 
333
 
334
  // Use the "context array" to add more data to your logged event
335
  // Data can be used later on to show detailed info about a log entry
336
  // and does not need to be shown on the overview screen
337
- SimpleLogger()->info("Edited product '{pagename}'", array(
338
- 'pagename' => 'We are hiring!',
339
- '_postType' => 'product',
340
- '_userID' => 1,
341
- '_userLogin' => 'jessie',
342
- '_userEmail' => 'jessie@example.com',
343
- '_occasionsID' => 'username:1,postID:24885,action:edited',
344
- ));
 
 
 
345
 
346
 
347
  // Test log cron things
@@ -352,31 +426,31 @@ wp_schedule_event( time(), "hourly", "simple_history_cron_testhook");
352
  wp_clear_scheduled_hook("simple_history_cron_testhook");
353
  add_action( 'simple_history_cron_testhook', 'simple_history_cron_testhook_function' );
354
  function simple_history_cron_testhook_function() {
355
- SimpleLogger()->info("This is a message inside a cron function");
356
  }
357
  */
358
 
359
  /*
360
  add_action("init", function() {
361
 
362
- global $wp_current_filter;
363
 
364
- $doing_cron = get_transient( 'doing_cron' );
365
- $const_doing_cron = defined('DOING_CRON') && DOING_CRON;
366
 
367
- if ($const_doing_cron) {
368
 
369
- $current_filter = current_filter();
370
 
371
- SimpleLogger()->info("This is a message inside init, trying to log crons", array(
372
- "doing_cron" => simpleHistory::json_encode($doing_cron),
373
- "current_filter" => $current_filter,
374
- "wp_current_filter" => $wp_current_filter,
375
- "wp_current_filter" => simpleHistory::json_encode( $wp_current_filter ),
376
- "const_doing_cron" => simpleHistory::json_encode($const_doing_cron)
377
- ));
378
 
379
- }
380
 
381
  }, 100);
382
  */
@@ -385,7 +459,7 @@ add_action("init", function() {
385
  /*
386
  add_action("init", function() {
387
 
388
- #SimpleLogger()->info("This is a regular info message" . time());
389
 
390
  }, 100);
391
  // */
14
  */
15
 
16
  // Add $_GET, $_POST, and more info to each logged event.
17
+ define( 'SIMPLE_HISTORY_LOG_DEBUG', true );
18
 
19
 
20
  /**
25
  * Remove the "Clear log"-button, so a user with admin access can not clear the log
26
  * and wipe their mischievous behavior from the log.
27
  */
28
+ add_filter(
29
+ 'simple_history/user_can_clear_log',
30
+ function ( $user_can_clear_log ) {
31
+ $user_can_clear_log = false;
32
+ return $user_can_clear_log;
33
+ }
34
+ );
35
 
36
  // Modify who can read a logger.
37
  // Modify the if part to give users access or no access to a logger.
38
+ add_filter(
39
+ 'simple_history/loggers_user_can_read/can_read_single_logger',
40
+ function ( $user_can_read_logger, $logger_instance, $user_id ) {
41
+
42
+ // in this example user with id 3 gets access to the post logger
43
+ // while user with id 8 does not get any access to it
44
+ if ( $logger_instance->slug == 'SimplePostLogger' && $user_id === 3 ) {
45
+ $user_can_read_logger = true;
46
+ } elseif ( $logger_instance->slug == 'SimplePostLogger' && $user_id === 9 ) {
47
+ $user_can_read_logger = false;
48
+ }
49
+
50
+ return $user_can_read_logger;
51
+ },
52
+ 10,
53
+ 3
54
+ );
55
 
56
 
57
  // Do not log some post types, for example pages and attachments in this case
58
+ add_filter(
59
+ 'simple_history/log/do_log',
60
+ function ( $do_log = null, $level = null, $message = null, $context = null, $logger = null ) {
61
+
62
+ $post_types_to_not_log = array(
63
+ 'page',
64
+ 'attachment',
65
+ );
66
+
67
+ if ( ( isset( $logger->slug ) && ( $logger->slug === 'SimplePostLogger' || $logger->slug === 'SimpleMediaLogger' ) ) && ( isset( $context['post_type'] ) && in_array( $context['post_type'], $post_types_to_not_log ) ) ) {
68
+ $do_log = false;
69
+ }
70
+
71
+ return $do_log;
72
+ },
73
+ 10,
74
+ 5
75
+ );
76
 
77
  // Disable all logging
78
+ add_filter( 'simple_history/log/do_log', '__return_false' );
79
 
80
  /**
81
  * Example that modifies the parameters sent to the message template
82
  * This example will change the post type from "post" or "page" or similar to "my own page type"
83
  */
84
+ add_filter(
85
+ 'simple_history/logger/interpolate/context',
86
+ function ( $context, $message, $row ) {
87
+
88
+ if ( empty( $row ) ) {
89
+ return $context;
90
+ }
91
+
92
+ if ( $row->logger == 'SimplePostLogger' && $row->context_message_key == 'post_updated' ) {
93
+ $context['post_type'] = 'my own page type';
94
+ }
95
+
96
+ return $context;
97
+ },
98
+ 10,
99
+ 3
100
+ );
101
 
102
 
103
 
105
  * Change capability required to manage the options page of simple history.
106
  * Default capability is "manage_options"
107
  */
108
+ add_filter(
109
+ 'simple_history/view_settings_capability',
110
+ function ( $capability ) {
111
 
112
+ $capability = 'manage_options';
113
+ return $capability;
114
+ }
115
+ );
116
 
117
 
118
  /**
120
  * Default capability is "edit_pages". Change to for example "manage options"
121
  * to only allow admins to view the history log.
122
  */
123
+ add_filter(
124
+ 'simple_history/view_history_capability',
125
+ function ( $capability ) {
126
 
127
+ $capability = 'manage_options';
128
+ return $capability;
129
+ }
130
+ );
131
 
132
 
133
  // Skip adding things to the context table during logging.
134
  // Useful if you don't want to add cool and possible super useful info to your logged events.
135
  // Also nice to have if you want to make sure your database does not grow.
136
+ add_filter(
137
+ 'simple_history/log_insert_context',
138
+ function ( $context, $data ) {
139
+
140
+ unset( $context['_user_id'] );
141
+ unset( $context['_user_login'] );
142
+ unset( $context['_user_email'] );
143
+ unset( $context['server_http_user_agent'] );
144
+
145
+ return $context;
146
+ },
147
+ 10,
148
+ 2
149
+ );
150
 
151
  // Hide some columns from the detailed context view popup window
152
+ add_filter(
153
+ 'simple_history/log_html_output_details_table/row_keys_to_show',
154
+ function ( $logRowKeysToShow, $oneLogRow ) {
155
+
156
+ $logRowKeysToShow['id'] = false;
157
+ $logRowKeysToShow['logger'] = false;
158
+ $logRowKeysToShow['level'] = false;
159
+ $logRowKeysToShow['message'] = false;
160
+
161
+ return $logRowKeysToShow;
162
+ },
163
+ 10,
164
+ 2
165
+ );
166
 
167
 
168
  // Hide some more columns from the detailed context view popup window
169
+ add_filter(
170
+ 'simple_history/log_html_output_details_table/context_keys_to_show',
171
+ function ( $logRowContextKeysToShow, $oneLogRow ) {
172
+
173
+ $logRowContextKeysToShow['plugin_slug'] = false;
174
+ $logRowContextKeysToShow['plugin_name'] = false;
175
+ $logRowContextKeysToShow['plugin_title'] = false;
176
+ $logRowContextKeysToShow['plugin_description'] = false;
177
+
178
+ return $logRowContextKeysToShow;
179
+ },
180
+ 10,
181
+ 2
182
+ );
183
 
184
 
185
 
186
  // Allow only the users specified in $allowed_users to show the history page, the history widget on the dashboard, or the history settings page
187
+ add_filter( 'simple_history/show_dashboard_page', 'function_show_history_dashboard_or_page' );
188
+ add_filter( 'simple_history/show_dashboard_widget', 'function_show_history_dashboard_or_page' );
189
+ add_filter( 'simple_history/show_settings_page', 'function_show_history_dashboard_or_page' );
190
+ function function_show_history_dashboard_or_page( $show ) {
 
191
 
192
+ $allowed_users = array(
193
+ 'user1@example.com',
194
+ 'anotheruser@example.com',
195
+ );
196
 
197
+ $user = wp_get_current_user();
198
 
199
+ if ( ! in_array( $user->user_email, $allowed_users ) ) {
200
+ $show = false;
201
+ }
202
 
203
+ return $show;
204
  }
205
 
206
 
207
  // Skip loading of loggers
208
+ add_filter(
209
+ 'simple_history/logger/load_logger',
210
+ function ( $load_logger, $oneLoggerFile ) {
211
+
212
+ // Don't load loggers for comments or menus, i.e. don't log changes to comments or to menus
213
+ if ( in_array( $oneLoggerFile, array( 'SimpleCommentsLogger', 'SimpleMenuLogger' ) ) ) {
214
+ $load_logger = false;
215
+ }
216
+
217
+ return $load_logger;
218
+ },
219
+ 10,
220
+ 2
221
+ );
222
 
223
  /**
224
  * Load only the loggers that are specified in the $do_log_us array
225
  */
226
+ add_filter(
227
+ 'simple_history/logger/load_logger',
228
+ function ( $load_logger, $logger_basename ) {
229
 
230
+ $load_logger = false;
231
+ $do_log_us = array( 'SimplePostLogger', 'SimplePluginLogger', 'SimpleLogger' );
232
 
233
+ if ( in_array( $logger_basename, $do_log_us ) ) {
234
+ $load_logger = true;
235
+ }
236
 
237
+ return $load_logger;
238
+ },
239
+ 10,
240
+ 2
241
+ );
242
 
243
 
244
  // Skip the loading of dropins
245
+ add_filter(
246
+ 'simple_history/dropin/load_dropin',
247
+ function ( $load_dropin, $dropinFileBasename ) {
248
+
249
+ // Don't load the RSS feed dropin
250
+ if ( $dropinFileBasename == 'SimpleHistoryRSSDropin' ) {
251
+ $load_dropin = false;
252
+ }
253
+
254
+ // Don't load the dropin that polls for changes
255
+ if ( $dropinFileBasename == 'SimpleHistoryNewRowsNotifier' ) {
256
+ $load_dropin = false;
257
+ }
258
+
259
+ return $load_dropin;
260
+ },
261
+ 10,
262
+ 2
263
+ );
264
 
265
 
266
  // Don't log failed logins
267
+ add_filter(
268
+ 'simple_history/simple_logger/log_message_key',
269
+ function ( $doLog, $loggerSlug, $messageKey, $SimpleLoggerLogLevelsLevel, $context ) {
270
+
271
+ // Don't log login attempts to non existing users
272
+ if ( 'SimpleUserLogger' == $loggerSlug && 'user_unknown_login_failed' == $messageKey ) {
273
+ $doLog = false;
274
+ }
275
+
276
+ // Don't log failed logins to existing users
277
+ if ( 'SimpleUserLogger' == $loggerSlug && 'user_login_failed' == $messageKey ) {
278
+ $doLog = false;
279
+ }
280
+
281
+ return $doLog;
282
+ },
283
+ 10,
284
+ 5
285
+ );
286
 
287
  // Never clear the log (default is 60 days)
288
+ add_filter( 'simple_history/db_purge_days_interval', '__return_zero' );
289
 
290
  // Clear items that are older than a 7 days (i.e. keep only the most recent 7 days in the log)
291
+ add_filter(
292
+ 'simple_history/db_purge_days_interval',
293
+ function ( $days ) {
294
 
295
+ $days = 7;
296
 
297
+ return $days;
298
+ }
299
+ );
300
 
301
  // Don't let anyone - even with the correct secret - view the RSS feed
302
+ add_filter( 'simple_history/rss_feed_show', '__return_false' );
303
 
304
  // Skip loading of a dropin completely (in this case the RSS dropin)
305
+ add_filter( 'simple_history/dropin/load_dropin_SimpleHistoryRSSDropin', '__return_false' );
306
 
307
  /**
308
  * Example of logging
309
  */
310
 
311
  // This is the easiest and safest way to add messages to the log:
312
+ apply_filters( 'simple_history_log', 'This is a logged message' );
313
+ apply_filters(
314
+ 'simple_history_log',
315
+ 'This is a message with some context added',
316
+ array(
317
+ 'isATestMessage' => 'yup',
318
+ 'debugRequestData' => $_REQUEST,
319
+ )
320
+ );
321
+ apply_filters( 'simple_history_log', 'This is another logged message, with another severity level', null, 'debug' );
322
 
323
  // Below is the function way of adding things to the log
324
  // Remember to check that the SimpleLogger function exists before trying to log anything,
325
  // or else your site will break if you disable the Simple History plugin
326
  // (Use the apply_filters method above if you want to stay safer!)
327
+ if ( function_exists( 'SimpleLogger' ) ) {
328
+ SimpleLogger()->info( 'This is a message added to the log' );
329
  }
330
 
331
  // Add a message to the history log
332
+ SimpleLogger()->info( 'This is a message sent to the log' );
333
 
334
  // Add log entries with different severities
335
+ SimpleLogger()->warning( "User 'Jessie' deleted user 'Kim'" );
336
+ SimpleLogger()->debug( 'Ok, cron job is running!' );
337
 
338
  // Add a message to the history log
339
  // and then add a second log entry with same info and Simple History
340
  // will make these two become an "occasionGroup",
341
  // i.e. collapsing their entries into one expandable log item
342
+ SimpleLogger()->info( 'This is a message sent to the log' );
343
+ SimpleLogger()->info( 'This is a message sent to the log' );
344
 
345
  // Log entries can have placeholders and context
346
  // This makes log entried translatable and filterable
347
  SimpleLogger()->notice(
348
+ 'User {username} edited page {pagename}',
349
+ array(
350
+ 'username' => 'jessie',
351
+ 'pagename' => 'My test page',
352
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
353
+ '_user_id' => 5,
354
+ '_user_login' => 'jess',
355
+ '_user_email' => 'jessie@example.com',
356
+ )
357
  );
358
 
359
  // Log entried can have custom occasionsID
360
  // This will group items together and a log entry will only be shown once
361
  // in the log overview, even if the logged messages are different
362
+ for ( $i = 0; $i < rand( 1, 50 ); $i++ ) {
363
+ SimpleLogger()->notice(
364
+ 'User {username} edited page {pagename}',
365
+ array(
366
+ 'username' => "example_user_{$i}",
367
+ 'pagename' => 'My test page',
368
+ '_occasionsID' => 'postID:24884,action:edited',
369
+ )
370
+ );
371
  }
372
 
373
  // Events can have different "initiators",
374
  // i.e. who was responsible for the logged event
375
  // Initiator "WORDPRESS" means that WordPress did something on it's own
376
  SimpleLogger()->info(
377
+ 'WordPress updated itself from version {from_version} to {to_version}',
378
+ array(
379
+ 'from_version' => '3.8',
380
+ 'to_version' => '3.8.1',
381
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
382
+ )
383
  );
384
 
385
  // Initiator "WP_USER" means that a logged in user did someting
386
  SimpleLogger()->info(
387
+ 'Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}',
388
+ array(
389
+ 'plugin_name' => 'Ninja Forms',
390
+ 'plugin_from_version' => '1.1',
391
+ 'plugin_to_version' => '1.1.2',
392
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
393
+ )
394
  );
395
 
396
  // // Initiator "WEB_USER" means that an unknown internet user did something
397
+ SimpleLogger()->warning(
398
+ "An attempt to login as user 'administrator' failed to login because the wrong password was entered",
399
+ array(
400
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
401
+ )
402
+ );
403
 
404
 
405
  // Use the "context array" to add more data to your logged event
406
  // Data can be used later on to show detailed info about a log entry
407
  // and does not need to be shown on the overview screen
408
+ SimpleLogger()->info(
409
+ "Edited product '{pagename}'",
410
+ array(
411
+ 'pagename' => 'We are hiring!',
412
+ '_postType' => 'product',
413
+ '_userID' => 1,
414
+ '_userLogin' => 'jessie',
415
+ '_userEmail' => 'jessie@example.com',
416
+ '_occasionsID' => 'username:1,postID:24885,action:edited',
417
+ )
418
+ );
419
 
420
 
421
  // Test log cron things
426
  wp_clear_scheduled_hook("simple_history_cron_testhook");
427
  add_action( 'simple_history_cron_testhook', 'simple_history_cron_testhook_function' );
428
  function simple_history_cron_testhook_function() {
429
+ SimpleLogger()->info("This is a message inside a cron function");
430
  }
431
  */
432
 
433
  /*
434
  add_action("init", function() {
435
 
436
+ global $wp_current_filter;
437
 
438
+ $doing_cron = get_transient( 'doing_cron' );
439
+ $const_doing_cron = defined('DOING_CRON') && DOING_CRON;
440
 
441
+ if ($const_doing_cron) {
442
 
443
+ $current_filter = current_filter();
444
 
445
+ SimpleLogger()->info("This is a message inside init, trying to log crons", array(
446
+ "doing_cron" => simpleHistory::json_encode($doing_cron),
447
+ "current_filter" => $current_filter,
448
+ "wp_current_filter" => $wp_current_filter,
449
+ "wp_current_filter" => simpleHistory::json_encode( $wp_current_filter ),
450
+ "const_doing_cron" => simpleHistory::json_encode($const_doing_cron)
451
+ ));
452
 
453
+ }
454
 
455
  }, 100);
456
  */
459
  /*
460
  add_action("init", function() {
461
 
462
+ #SimpleLogger()->info("This is a regular info message" . time());
463
 
464
  }, 100);
465
  // */
inc/SimpleHistory.php CHANGED
@@ -2,1509 +2,1478 @@
2
 
3
  // phpcs:disable PSR12.Properties.ConstantVisibility.NotFound
4
 
5
- defined('ABSPATH') or die();
6
 
7
  /**
8
  * Main class for Simple History
9
  */
10
- class SimpleHistory
11
- {
12
- const NAME = 'Simple History';
13
-
14
- /**
15
- * For singleton
16
- */
17
- private static $instance;
18
-
19
- /**
20
- * Array with external loggers to load
21
- */
22
- private $externalLoggers;
23
-
24
- /**
25
- * Array with external dropins to load
26
- */
27
- private $externalDropins;
28
-
29
- /**
30
- * Array with all instantiated loggers
31
- */
32
- private $instantiatedLoggers;
33
-
34
- /**
35
- * Array with all instantiated dropins
36
- */
37
- private $instantiatedDropins;
38
-
39
- /**
40
- * Bool if gettext filter function should be active
41
- * Should only be active during the load of a logger
42
- */
43
- private $doFilterGettext = false;
44
-
45
- /**
46
- * Used by gettext filter to temporarily store current logger
47
- */
48
- private $doFilterGettext_currentLogger = null;
49
-
50
- /**
51
- * Used to store latest translations used by __()
52
- * Required to automagically determine orginal text and text domain
53
- * for calls like this `SimpleLogger()->log( __("My translated message") );`
54
- */
55
- public $gettextLatestTranslations = [];
56
-
57
- /**
58
- * All registered settings tabs
59
- */
60
- private $arr_settings_tabs = [];
61
-
62
- const DBTABLE = 'simple_history';
63
- const DBTABLE_CONTEXTS = 'simple_history_contexts';
64
-
65
- /** Slug for the settings menu */
66
- const SETTINGS_MENU_SLUG = 'simple_history_settings_menu_slug';
67
-
68
- /** Slug for the settings menu */
69
- const SETTINGS_GENERAL_OPTION_GROUP = 'simple_history_settings_group';
70
-
71
- /** ID for the general settings section */
72
- const SETTINGS_SECTION_GENERAL_ID = 'simple_history_settings_section_general';
73
-
74
- public function __construct()
75
- {
76
- $this->init();
77
- }
78
-
79
- /**
80
- * @since 2.5.2
81
- */
82
- public function init()
83
- {
84
- /**
85
- * Fires before Simple History does it's init stuff
86
- *
87
- * @since 2.0
88
- *
89
- * @param SimpleHistory $SimpleHistory This class.
90
- */
91
- do_action('simple_history/before_init', $this);
92
-
93
- $this->setup_variables();
94
-
95
- // Actions and filters, ordered by order specified in codex: http://codex.wordpress.org/Plugin_API/Action_Reference
96
- add_action('after_setup_theme', [$this, 'load_plugin_textdomain']);
97
- add_action('after_setup_theme', [$this, 'add_default_settings_tabs']);
98
-
99
- // Plugins and dropins are loaded using the "after_setup_theme" filter so
100
- // themes can use filters to modify the loading of them.
101
- // The drawback with this is that for example logouts done when plugins like
102
- // iThemes Security is installed is not logged, because those plugins fire wp_logout()
103
- // using filter "plugins_loaded", i.e. before simple history has loaded its filters.
104
- add_action('after_setup_theme', [$this, 'load_loggers']);
105
- add_action('after_setup_theme', [$this, 'load_dropins']);
106
-
107
- // Run before loading of loggers and before menu items are added.
108
- add_action('after_setup_theme', [$this, 'check_for_upgrade'], 5);
109
-
110
- add_action('after_setup_theme', [$this, 'setup_cron']);
111
-
112
- // Filters and actions not called during regular boot.
113
- add_filter('gettext', [$this, 'filter_gettext'], 20, 3);
114
- add_filter('gettext_with_context', [$this, 'filter_gettext_with_context'], 20, 4);
115
-
116
- add_filter('gettext', [$this, 'filter_gettext_storeLatestTranslations'], 10, 3);
117
-
118
- add_action('admin_bar_menu', [$this, 'add_admin_bar_network_menu_item'], 40);
119
- add_action('admin_bar_menu', [$this, 'add_admin_bar_menu_item'], 40);
120
-
121
- /**
122
- * Filter that is used to log things, without the need to check that simple history is available
123
- * i.e. you can have simple history acivated and log things and then you can disable the plugin
124
- * and no errors will occur
125
- *
126
- * Usage:
127
- * apply_filters("simple_history_log", "This is the log message");
128
- * apply_filters("simple_history_log", "This is the log message with some extra data/info", ["extraThing1" => $variableWIihThing]);
129
- * apply_filters("simple_history_log", "This is the log message with severity debug", null, "debug");
130
- * apply_filters("simple_history_log", "This is the log message with severity debug and with some extra info/data logged", ["userData" => $userData, "shoppingCartDebugData" => $shopDebugData], "debug",);
131
- *
132
- * @since 2.13
133
- */
134
- add_filter('simple_history_log', [$this, 'on_filter_simple_history_log'], 10, 3);
135
-
136
- /**
137
- * Filter to log with specific log level, for example:
138
- * apply_filters('simple_history_log_debug', 'My debug message');
139
- * apply_filters('simple_history_log_warning', 'My warning message');
140
- *
141
- * @since 2.17
142
- */
143
- add_filter('simple_history_log_emergency', [$this, 'on_filter_simple_history_log_emergency'], 10, 3);
144
- add_filter('simple_history_log_alert', [$this, 'on_filter_simple_history_log_alert'], 10, 2);
145
- add_filter('simple_history_log_critical', [$this, 'on_filter_simple_history_log_critical'], 10, 2);
146
- add_filter('simple_history_log_error', [$this, 'on_filter_simple_history_log_error'], 10, 2);
147
- add_filter('simple_history_log_warning', [$this, 'on_filter_simple_history_log_warning'], 10, 2);
148
- add_filter('simple_history_log_notice', [$this, 'on_filter_simple_history_log_notice'], 10, 2);
149
- add_filter('simple_history_log_info', [$this, 'on_filter_simple_history_log_info'], 10, 2);
150
- add_filter('simple_history_log_debug', [$this, 'on_filter_simple_history_log_debug'], 10, 2);
151
-
152
- if (is_admin()) {
153
- $this->add_admin_actions();
154
- }
155
-
156
- /**
157
- * Fires after Simple History has done it's init stuff
158
- *
159
- * @since 2.0
160
- *
161
- * @param SimpleHistory $SimpleHistory This class.
162
- */
163
- do_action('simple_history/after_init', $this);
164
- }
165
-
166
- /**
167
- * Log a message
168
- *
169
- * Function called when running filter "simple_history_log"
170
- *
171
- * @since 2.13
172
- * @param string $message The message to log.
173
- * @param array $context Optional context to add to the logged data.
174
- * @param string $level The loglevel. Must be one of the existing ones. Defaults to "info".
175
- */
176
- public function on_filter_simple_history_log($message = null, $context = null, $level = 'info')
177
- {
178
- SimpleLogger()->log($level, $message, $context);
179
- }
180
-
181
- /**
182
- * Log a message, triggered by filter 'on_filter_simple_history_log_emergency'.
183
- *
184
- * @param string $message The message to log.
185
- * @param array $context The context (optional).
186
- */
187
- public function on_filter_simple_history_log_emergency($message = null, $context = null)
188
- {
189
- SimpleLogger()->log('emergency', $message, $context);
190
- }
191
-
192
- /**
193
- * Log a message, triggered by filter 'on_filter_simple_history_log_alert'.
194
- *
195
- * @param string $message The message to log.
196
- * @param array $context The context (optional).
197
- */
198
- public function on_filter_simple_history_log_alert($message = null, $context = null)
199
- {
200
- SimpleLogger()->log('alert', $message, $context);
201
- }
202
-
203
- /**
204
- * Log a message, triggered by filter 'on_filter_simple_history_log_critical'.
205
- *
206
- * @param string $message The message to log.
207
- * @param array $context The context (optional).
208
- */
209
- public function on_filter_simple_history_log_critical($message = null, $context = null)
210
- {
211
- SimpleLogger()->log('critical', $message, $context);
212
- }
213
-
214
- /**
215
- * Log a message, triggered by filter 'on_filter_simple_history_log_error'.
216
- *
217
- * @param string $message The message to log.
218
- * @param array $context The context (optional).
219
- */
220
- public function on_filter_simple_history_log_error($message = null, $context = null)
221
- {
222
- SimpleLogger()->log('error', $message, $context);
223
- }
224
-
225
- /**
226
- * Log a message, triggered by filter 'on_filter_simple_history_log_warning'.
227
- *
228
- * @param string $message The message to log.
229
- * @param array $context The context (optional).
230
- */
231
- public function on_filter_simple_history_log_warning($message = null, $context = null)
232
- {
233
- SimpleLogger()->log('warning', $message, $context);
234
- }
235
-
236
- /**
237
- * Log a message, triggered by filter 'on_filter_simple_history_log_notice'.
238
- *
239
- * @param string $message The message to log.
240
- * @param array $context The context (optional).
241
- */
242
- public function on_filter_simple_history_log_notice($message = null, $context = null)
243
- {
244
- SimpleLogger()->log('notice', $message, $context);
245
- }
246
-
247
- /**
248
- * Log a message, triggered by filter 'on_filter_simple_history_log_info'.
249
- *
250
- * @param string $message The message to log.
251
- * @param array $context The context (optional).
252
- */
253
- public function on_filter_simple_history_log_info($message = null, $context = null)
254
- {
255
- SimpleLogger()->log('info', $message, $context);
256
- }
257
-
258
- /**
259
- * Log a message, triggered by filter 'on_filter_simple_history_log_debug'.
260
- *
261
- * @param string $message The message to log.
262
- * @param array $context The context (optional).
263
- */
264
- public function on_filter_simple_history_log_debug($message = null, $context = null)
265
- {
266
- SimpleLogger()->log('debug', $message, $context);
267
- }
268
-
269
- /**
270
- * @since 2.5.2
271
- */
272
- private function add_admin_actions()
273
- {
274
- add_action('admin_menu', [$this, 'add_admin_pages']);
275
- add_action('admin_menu', [$this, 'add_settings']);
276
-
277
- add_action('admin_footer', [$this, 'add_js_templates']);
278
-
279
- add_action('wp_dashboard_setup', [$this, 'add_dashboard_widget']);
280
-
281
- add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
282
-
283
- add_action('admin_head', [$this, 'onAdminHead']);
284
- add_action('admin_footer', [$this, 'onAdminFooter']);
285
-
286
- add_action('simple_history/history_page/before_gui', [$this, 'output_quick_stats']);
287
- add_action('simple_history/dashboard/before_gui', [$this, 'output_quick_stats']);
288
-
289
- add_action('wp_ajax_simple_history_api', [$this, 'api']);
290
-
291
- add_filter('plugin_action_links_simple-history/index.php', [$this, 'plugin_action_links'], 10, 4);
292
- }
293
-
294
- /**
295
- * Adds a "View history" item/shortcut to the network admin, on blogs where Simple History is installed
296
- *
297
- * Useful because Simple History is something at least the author of this plugin often use on a site :)
298
- *
299
- * @since 2.7.1
300
- */
301
- public function add_admin_bar_network_menu_item($wp_admin_bar)
302
- {
303
- /**
304
- * Filter to control if admin bar shortcut should be added
305
- *
306
- * @since 2.7.1
307
- *
308
- * @param bool Add item
309
- */
310
- $add_items = apply_filters('simple_history/add_admin_bar_network_menu_item', true);
311
-
312
- if (!$add_items) {
313
- return;
314
- }
315
-
316
- // Don't show for logged out users or single site mode.
317
- if (!is_user_logged_in() || !is_multisite()) {
318
- return;
319
- }
320
-
321
- // Show only when the user has at least one site, or they're a super admin.
322
- if (count($wp_admin_bar->user->blogs) < 1 && !is_super_admin()) {
323
- return;
324
- }
325
-
326
- // Setting to show as page must be true
327
- if (!$this->setting_show_as_page()) {
328
- return;
329
- }
330
-
331
- // User must have capability to view the history page
332
- if (!current_user_can($this->get_view_history_capability())) {
333
- return;
334
- }
335
-
336
- /*
337
- menu_page_url() is defined in the WordPress Plugin Administration API, which is not loaded here by default */
338
- /* dito for is_plugin_active() */
339
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
340
-
341
- foreach ((array) $wp_admin_bar->user->blogs as $blog) {
342
- switch_to_blog($blog->userblog_id);
343
-
344
- if (is_plugin_active(SIMPLE_HISTORY_BASENAME)) {
345
- $menu_id = 'simple-history-blog-' . $blog->userblog_id;
346
- $parent_menu_id = 'blog-' . $blog->userblog_id;
347
- $url = admin_url(
348
- apply_filters('simple_history/admin_location', 'index') . '.php?page=simple_history_page'
349
- );
350
-
351
- // Each network site is added by WP core with id "blog-1", "blog-2" ... "blog-n"
352
- // https://codex.wordpress.org/Function_Reference/add_node
353
- $args = [
354
- 'id' => $menu_id,
355
- 'parent' => $parent_menu_id,
356
- 'title' => _x('View History', 'Admin bar network name', 'simple-history'),
357
- 'href' => $url,
358
- 'meta' => [
359
- 'class' => 'ab-item--simplehistory',
360
- ],
361
- ];
362
-
363
- $wp_admin_bar->add_node($args);
364
- } // End if().
365
-
366
- restore_current_blog();
367
- } // End foreach().
368
- }
369
-
370
- /**
371
- * Adds a "View history" item/shortcut to the admin bar
372
- *
373
- * Useful because Simple History is something at least the author of this plugin often use on a site :)
374
- *
375
- * @since 2.7.1
376
- */
377
- public function add_admin_bar_menu_item($wp_admin_bar)
378
- {
379
- /**
380
- * Filter to control if admin bar shortcut should be added
381
- *
382
- * @since 2.7.1
383
- *
384
- * @param bool Add item
385
- */
386
- $add_item = apply_filters('simple_history/add_admin_bar_menu_item', true);
387
-
388
- if (!$add_item) {
389
- return;
390
- }
391
-
392
- // Don't show for logged out users
393
- if (!is_user_logged_in()) {
394
- return;
395
- }
396
-
397
- // Setting to show as page must be true
398
- if (!$this->setting_show_as_page()) {
399
- return;
400
- }
401
-
402
- // User must have capability to view the history page
403
- if (!current_user_can($this->get_view_history_capability())) {
404
- return;
405
- }
406
-
407
- /* menu_page_url() and is_plugin_active()is defined in the WordPress Plugin Administration API, which is not loaded here by default */
408
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
409
-
410
- $menu_id = 'simple-history-view-history';
411
- $parent_menu_id = 'site-name';
412
- $url = admin_url(apply_filters('simple_history/admin_location', 'index') . '.php?page=simple_history_page');
413
-
414
- $args = [
415
- 'id' => $menu_id,
416
- 'parent' => $parent_menu_id,
417
- 'title' => _x('Simple History', 'Admin bar name', 'simple-history'),
418
- 'href' => $url,
419
- 'meta' => [
420
- 'class' => 'ab-item--simplehistory',
421
- ],
422
- ];
423
-
424
- $wp_admin_bar->add_node($args);
425
- }
426
-
427
- /**
428
- * Get singleton intance
429
- *
430
- * @return SimpleHistory instance
431
- */
432
- public static function get_instance()
433
- {
434
- if (!isset(self::$instance)) {
435
- self::$instance = new SimpleHistory();
436
- }
437
-
438
- return self::$instance;
439
- }
440
-
441
- public function filter_gettext_storeLatestTranslations($translation, $text, $domain)
442
- {
443
- // Check that translation is a string or integer, i.ex. the valid values for an array key
444
- if (!is_string($translation) || !is_integer($translation)) {
445
- return $translation;
446
- }
447
-
448
- $array_max_size = 5;
449
-
450
- // Keep a listing of the n latest translation
451
- // when SimpleLogger->log() is called from anywhere we can then search for the
452
- // translated string among our n latest things and find it there, if it's translated
453
- // global $sh_latest_translations;
454
- $sh_latest_translations = $this->gettextLatestTranslations;
455
-
456
- $sh_latest_translations[$translation] = [
457
- 'translation' => $translation,
458
- 'text' => $text,
459
- 'domain' => $domain,
460
- ];
461
-
462
- $arr_length = sizeof($sh_latest_translations);
463
- if ($arr_length > $array_max_size) {
464
- $sh_latest_translations = array_slice($sh_latest_translations, $arr_length - $array_max_size);
465
- }
466
-
467
- $this->gettextLatestTranslations = $sh_latest_translations;
468
-
469
- return $translation;
470
- }
471
-
472
- public function setup_cron()
473
- {
474
- add_filter('simple_history/maybe_purge_db', [$this, 'maybe_purge_db']);
475
-
476
- if (!wp_next_scheduled('simple_history/maybe_purge_db')) {
477
- wp_schedule_event(time(), 'daily', 'simple_history/maybe_purge_db');
478
- } else {
479
- }
480
-
481
- // Remove old schedule (only author dev sites should have it)
482
- $old_next_scheduled = wp_next_scheduled('simple_history/purge_db');
483
- if ($old_next_scheduled) {
484
- wp_unschedule_event($old_next_scheduled, 'simple_history/purge_db');
485
- }
486
- }
487
-
488
- public function onAdminHead()
489
- {
490
- if ($this->is_on_our_own_pages()) {
491
- do_action('simple_history/admin_head', $this);
492
- }
493
- }
494
-
495
- public function onAdminFooter()
496
- {
497
- if ($this->is_on_our_own_pages()) {
498
- do_action('simple_history/admin_footer', $this);
499
- }
500
- }
501
-
502
- /**
503
- * Output JS templated into footer
504
- */
505
- public function add_js_templates($hook)
506
- {
507
- if ($this->is_on_our_own_pages()) { ?>
508
- <script type="text/html" id="tmpl-simple-history-base">
509
-
510
- <div class="SimpleHistory__waitingForFirstLoad">
511
- <img src="<?php echo admin_url('/images/spinner.gif'); ?>" alt="" width="20" height="20">
512
- <?php echo _x(
513
- 'Loading history...',
514
- 'Message visible while waiting for log to load from server the first time',
515
- 'simple-history'
516
- ); ?>
517
- </div>
518
-
519
- <div class="SimpleHistoryLogitemsWrap">
520
- <div class="SimpleHistoryLogitems__beforeTopPagination"></div>
521
- <div class="SimpleHistoryLogitems__above"></div>
522
- <ul class="SimpleHistoryLogitems"></ul>
523
- <div class="SimpleHistoryLogitems__below"></div>
524
- <div class="SimpleHistoryLogitems__pagination"></div>
525
- <div class="SimpleHistoryLogitems__afterBottomPagination"></div>
526
- </div>
527
-
528
- <div class="SimpleHistoryLogitems__debug"></div>
529
-
530
- </script>
531
-
532
- <script type="text/html" id="tmpl-simple-history-logitems-pagination">
533
-
534
- <!-- this uses the (almost) the same html as WP does -->
535
- <div class="SimpleHistoryPaginationPages">
536
- <!--
537
- {{ data.page_rows_from }}–{{ data.page_rows_to }}
538
- <span class="SimpleHistoryPaginationDisplayNum"> of {{ data.total_row_count }} </span>
539
- -->
540
- <span class="SimpleHistoryPaginationLinks">
541
- <a
542
- data-direction="first"
543
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--firstPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
544
- title="{{ data.strings.goToTheFirstPage }}"
545
- href="#">«</a>
546
- <a
547
- data-direction="prev"
548
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--prevPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
549
- title="{{ data.strings.goToThePrevPage }}"
550
- href="#">‹</a>
551
- <span class="SimpleHistoryPaginationInput">
552
- <input class="SimpleHistoryPaginationCurrentPage" title="{{ data.strings.currentPage }}" type="text" name="paged" value="{{ data.api_args.paged }}" size="4">
553
- <?php _x('of', 'page n of n', 'simple-history'); ?>
554
- <span class="total-pages">{{ data.pages_count }}</span>
555
- </span>
556
- <a
557
- data-direction="next"
558
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--nextPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
559
- title="{{ data.strings.goToTheNextPage }}"
560
- href="#">›</a>
561
- <a
562
- data-direction="last"
563
- class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--lastPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
564
- title="{{ data.strings.goToTheLastPage }}"
565
- href="#">»</a>
566
- </span>
567
- </div>
568
-
569
- </script>
570
-
571
- <script type="text/html" id="tmpl-simple-history-logitems-modal">
572
-
573
- <div class="SimpleHistory-modal">
574
- <div class="SimpleHistory-modal__background"></div>
575
- <div class="SimpleHistory-modal__content">
576
- <div class="SimpleHistory-modal__contentInner">
577
- <img class="SimpleHistory-modal__contentSpinner" src="<?php echo esc_url(admin_url('/images/spinner.gif')); ?>" alt="">
578
- </div>
579
- <div class="SimpleHistory-modal__contentClose">
580
- <button class="button">✕</button>
581
- </div>
582
- </div>
583
- </div>
584
-
585
- </script>
586
-
587
- <script type="text/html" id="tmpl-simple-history-occasions-too-many">
588
- <li
589
- class="SimpleHistoryLogitem
590
- SimpleHistoryLogitem--occasion
591
- SimpleHistoryLogitem--occasion-tooMany
592
- ">
593
- <div class="SimpleHistoryLogitem__firstcol"></div>
594
- <div class="SimpleHistoryLogitem__secondcol">
595
- <div class="SimpleHistoryLogitem__text">
596
- <?php _e('Sorry, but there are too many similar events to show.', 'simple-history'); ?>
597
- </div>
598
- </div>
599
- </li>
600
- </script>
601
-
602
- <?php
603
- // Call plugins so they can add their js.
604
- foreach ($this->instantiatedLoggers as $one_logger) {
605
- if (method_exists($one_logger['instance'], 'adminJS')) {
606
- $one_logger['instance']->adminJS();
607
- }
608
- }
609
- }
610
- }
611
-
612
- /**
613
- * Base url is:
614
- * /wp-admin/admin-ajax.php?action=simple_history_api
615
- *
616
- * Examples:
617
- * http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&posts_per_page=5&paged=1&format=html
618
- */
619
- public function api()
620
- {
621
- global $wpdb;
622
-
623
- // Fake slow answers
624
- // sleep(2);
625
- // sleep(rand(0,3));
626
- $args = $_GET;
627
- unset($args['action']);
628
-
629
- // Type = overview | ...
630
- $type = isset($_GET['type']) ? $_GET['type'] : null;
631
-
632
- if (empty($args) || !$type) {
633
- wp_send_json_error([
634
- _x('Not enough args specified', 'API: not enought arguments passed', 'simple-history'),
635
- ]);
636
- }
637
-
638
- // User must have capability to view the history page
639
- if (!current_user_can($this->get_view_history_capability())) {
640
- wp_send_json_error([
641
- 'error' => 'CAPABILITY_ERROR',
642
- ]);
643
- }
644
-
645
- if (isset($args['id'])) {
646
- $args['post__in'] = [$args['id']];
647
- }
648
-
649
- $data = [];
650
-
651
- switch ($type) {
652
- case 'overview':
653
- case 'occasions':
654
- case 'single':
655
- // API use SimpleHistoryLogQuery, so simply pass args on to that
656
- $logQuery = new SimpleHistoryLogQuery();
657
- $data = $logQuery->query($args);
658
-
659
- $data['api_args'] = $args;
660
-
661
- // Output can be array or HMTL
662
- if (isset($args['format']) && 'html' === $args['format']) {
663
- $data['log_rows_raw'] = [];
664
-
665
- foreach ($data['log_rows'] as $key => $oneLogRow) {
666
- $args = [];
667
- if ($type == 'single') {
668
- $args['type'] = 'single';
669
- }
670
-
671
- $data['log_rows'][$key] = $this->getLogRowHTMLOutput($oneLogRow, $args);
672
- $data['num_queries'] = get_num_queries();
673
- }
674
- } else {
675
- // $data["logRows"] = $logRows;
676
- }
677
-
678
- break;
679
-
680
- default:
681
- $data[] = 'Nah.';
682
- } // End switch().
683
-
684
- wp_send_json_success($data);
685
- }
686
-
687
- /**
688
- * During the load of info for a logger we want to get a reference
689
- * to the untranslated text too, because that's the version we want to store
690
- * in the database.
691
- */
692
- public function filter_gettext($translated_text, $untranslated_text, $domain)
693
- {
694
- if (isset($this->doFilterGettext) && $this->doFilterGettext) {
695
- $this->doFilterGettext_currentLogger->messages[] = [
696
- 'untranslated_text' => $untranslated_text,
697
- 'translated_text' => $translated_text,
698
- 'domain' => $domain,
699
- 'context' => null,
700
- ];
701
- }
702
-
703
- return $translated_text;
704
- }
705
-
706
- /**
707
- * Store messages with context
708
- */
709
- public function filter_gettext_with_context($translated_text, $untranslated_text, $context, $domain)
710
- {
711
- if (isset($this->doFilterGettext) && $this->doFilterGettext) {
712
- $this->doFilterGettext_currentLogger->messages[] = [
713
- 'untranslated_text' => $untranslated_text,
714
- 'translated_text' => $translated_text,
715
- 'domain' => $domain,
716
- 'context' => $context,
717
- ];
718
- }
719
-
720
- return $translated_text;
721
- }
722
-
723
- /**
724
- * Load language files.
725
- * Uses the method described here:
726
- * http://geertdedeckere.be/article/loading-wordpress-language-files-the-right-way
727
- *
728
- * @since 2.0
729
- */
730
- public function load_plugin_textdomain()
731
- {
732
- $domain = 'simple-history';
733
-
734
- // The "plugin_locale" filter is also used in load_plugin_textdomain()
735
- $locale = apply_filters('plugin_locale', get_locale(), $domain);
736
- load_textdomain($domain, WP_LANG_DIR . '/simple-history/' . $domain . '-' . $locale . '.mo');
737
- load_plugin_textdomain($domain, false, dirname($this->plugin_basename) . '/languages/');
738
- }
739
-
740
- /**
741
- * Setup variables and things
742
- */
743
- public function setup_variables()
744
- {
745
- $this->externalLoggers = [];
746
- $this->externalDropins = [];
747
- $this->instantiatedLoggers = [];
748
- $this->instantiatedDropins = [];
749
-
750
- $this->plugin_basename = SIMPLE_HISTORY_BASENAME;
751
- }
752
-
753
- /**
754
- * Return capability required to view history = for who will the History page be added
755
- *
756
- * @since 2.1.5
757
- * @return string capability
758
- */
759
- public function get_view_history_capability()
760
- {
761
- $view_history_capability = 'edit_pages';
762
- $view_history_capability = apply_filters('simple_history_view_history_capability', $view_history_capability);
763
- $view_history_capability = apply_filters('simple_history/view_history_capability', $view_history_capability);
764
-
765
- return $view_history_capability;
766
- }
767
-
768
- /**
769
- * Return capability required to view settings
770
- *
771
- * @since 2.1.5
772
- * @return string capability
773
- */
774
- public function get_view_settings_capability()
775
- {
776
- $view_settings_capability = 'manage_options';
777
- $view_settings_capability = apply_filters('simple_history_view_settings_capability', $view_settings_capability);
778
- $view_settings_capability = apply_filters('simple_history/view_settings_capability', $view_settings_capability);
779
-
780
- return $view_settings_capability;
781
- }
782
-
783
- /**
784
- * Check if the current user can clear the log
785
- *
786
- * @since 2.19
787
- * @return bool
788
- */
789
- public function user_can_clear_log()
790
- {
791
- $user_can_clear_log = apply_filters('simple_history/user_can_clear_log', true);
792
-
793
- return $user_can_clear_log;
794
- }
795
-
796
- /**
797
- * Adds default tabs to settings
798
- */
799
- public function add_default_settings_tabs()
800
- {
801
- // Add default settings tabs
802
- $this->arr_settings_tabs = [
803
- [
804
- 'slug' => 'settings',
805
- 'name' => __('Settings', 'simple-history'),
806
- 'function' => [$this, 'settings_output_general'],
807
- ],
808
- ];
809
-
810
- if (defined('SIMPLE_HISTORY_DEV') && SIMPLE_HISTORY_DEV) {
811
- $arr_dev_tabs = [
812
- [
813
- 'slug' => 'log',
814
- 'name' => __('Log (debug)', 'simple-history'),
815
- 'function' => [$this, 'settings_output_log'],
816
- ],
817
- [
818
- 'slug' => 'styles-example',
819
- 'name' => __('Styles example (debug)', 'simple-history'),
820
- 'function' => [$this, 'settings_output_styles_example'],
821
- ],
822
- ];
823
-
824
- $this->arr_settings_tabs = array_merge($this->arr_settings_tabs, $arr_dev_tabs);
825
- }
826
- }
827
-
828
- /**
829
- * Register an external logger so Simple History knows about it.
830
- * Does not load the logger, so file with logger class must be loaded already.
831
- *
832
- * See example-logger.php for an example on how to use this.
833
- *
834
- * @since 2.1
835
- */
836
- public function register_logger($loggerClassName)
837
- {
838
- $this->externalLoggers[] = $loggerClassName;
839
- }
840
-
841
- /**
842
- * Register an external dropin so Simple History knows about it.
843
- * Does not load the dropin, so file with dropin class must be loaded already.
844
- *
845
- * See example-dropin.php for an example on how to use this.
846
- *
847
- * @since 2.1
848
- */
849
- public function register_dropin($dropinClassName)
850
- {
851
- $this->externalDropins[] = $dropinClassName;
852
- }
853
-
854
- /**
855
- * Load built in loggers from all files in /loggers
856
- * and instantiates them
857
- */
858
- public function load_loggers()
859
- {
860
- $loggersDir = SIMPLE_HISTORY_PATH . 'loggers/';
861
-
862
- // SimpleLogger.php must be loaded first and always since the other loggers extend it.
863
- // Load it manually so no risk of anyone using filters or similar disables it.
864
- // Also load files that contain constants used by the SimpleLogger.
865
- include_once $loggersDir . 'SimpleLogger.php';
866
- include_once $loggersDir . 'SimpleLoggerLogInitiators.php';
867
- include_once $loggersDir . 'SimpleLoggerLogTypes.php';
868
- include_once $loggersDir . 'SimpleLoggerLogLevels.php';
869
-
870
- // Bail if we are not in filter after_setup_theme,
871
- // i.e. we are probably calling SimpleLogger() early.
872
- if (!doing_action('after_setup_theme')) {
873
- return;
874
- }
875
-
876
- $loggersFiles = [
877
- // Main loggers.
878
- $loggersDir . 'SimpleCommentsLogger.php',
879
- $loggersDir . 'SimpleCoreUpdatesLogger.php',
880
- $loggersDir . 'SimpleExportLogger.php',
881
- $loggersDir . 'SimpleLegacyLogger.php',
882
- $loggersDir . 'SimpleLogger.php',
883
- $loggersDir . 'SimpleMediaLogger.php',
884
- $loggersDir . 'SimpleMenuLogger.php',
885
- $loggersDir . 'SimpleOptionsLogger.php',
886
- $loggersDir . 'SimplePluginLogger.php',
887
- $loggersDir . 'SimplePostLogger.php',
888
- $loggersDir . 'SimpleThemeLogger.php',
889
- $loggersDir . 'SimpleUserLogger.php',
890
- $loggersDir . 'SimpleCategoriesLogger.php',
891
- $loggersDir . 'AvailableUpdatesLogger.php',
892
- $loggersDir . 'FileEditsLogger.php',
893
- $loggersDir . 'class-sh-privacy-logger.php',
894
- $loggersDir . 'class-sh-translations-logger.php',
895
- $loggersDir . 'class-sh-jetpack-logger.php',
896
-
897
- // Loggers for third party plugins.
898
- $loggersDir . 'PluginUserSwitchingLogger.php',
899
- $loggersDir . 'PluginWPCrontrolLogger.php',
900
- $loggersDir . 'PluginEnableMediaReplaceLogger.php',
901
- $loggersDir . 'Plugin_UltimateMembers_Logger.php',
902
- $loggersDir . 'Plugin_LimitLoginAttempts.php',
903
- $loggersDir . 'Plugin_Redirection.php',
904
- $loggersDir . 'Plugin_DuplicatePost.php',
905
- $loggersDir . 'Plugin_ACF.php',
906
- $loggersDir . 'Plugin_BeaverBuilder.php',
907
- ];
908
-
909
- /**
910
- * Filter the array with absolute paths to logger files to be loaded.
911
- *
912
- * Each file will be loaded and will be assumed to be a logger with a classname
913
- * the same as the filename.
914
- *
915
- * @since 2.0
916
- *
917
- * @param array $loggersFiles Array with filenames
918
- */
919
- $loggersFiles = apply_filters('simple_history/loggers_files', $loggersFiles);
920
-
921
- // Array with slug of loggers to instantiate.
922
- // Slug of logger must also be the name of the logger class.
923
- $arr_loggers_to_instantiate = [];
924
-
925
- // $one_logger_file = "SimpleCommentsLogger.php", "class-privacy-logger.php", and so on.
926
- foreach ($loggersFiles as $one_logger_file) {
927
- $load_logger = true;
928
-
929
- // SimpleCommentsLogger.php -> SimpleCommentsLogger.
930
- // class-privacy-logger.php -> class-privacy-logger.
931
- $basename_no_suffix = basename($one_logger_file, '.php');
932
-
933
- /**
934
- * Filter to completely skip loading of a logger
935
- *
936
- * @since 2.0.22
937
- *
938
- * @param bool if to load the logger. return false to not load it.
939
- * @param string basename of logger, i.e. "SimpleCommentsLogger" or "class-privacy-logger"
940
- */
941
- $load_logger = apply_filters('simple_history/logger/load_logger', $load_logger, $basename_no_suffix);
942
-
943
- // If logger was SimpleLogger then force it to be loaded because for example
944
- // custom extended plugins added later probably depends on it.
945
- if ('SimpleLogger' === $basename_no_suffix) {
946
- $load_logger = true;
947
- }
948
-
949
- if (!$load_logger) {
950
- continue;
951
- }
952
-
953
- include_once $one_logger_file;
954
-
955
- $arr_loggers_to_instantiate[] = $basename_no_suffix;
956
- }
957
-
958
- /**
959
- * Action that plugins can use to add their custom loggers.
960
- * See register_logger() for more info.
961
- *
962
- * @since 2.1
963
- *
964
- * @param SimpleHistory instance
965
- */
966
- do_action('simple_history/add_custom_logger', $this);
967
-
968
- $arr_loggers_to_instantiate = array_merge($arr_loggers_to_instantiate, $this->externalLoggers);
969
-
970
- /**
971
- * Filter the array with names of loggers to instantiate.
972
- *
973
- * Array
974
- * (
975
- * [0] => SimpleCommentsLogger
976
- * [1] => SimpleCoreUpdatesLogger
977
- * ...
978
- * )
979
- *
980
- * @since 2.0
981
- *
982
- * @param array $arr_loggers_to_instantiate Array with class names
983
- */
984
- $arr_loggers_to_instantiate = apply_filters(
985
- 'simple_history/loggers_to_instantiate',
986
- $arr_loggers_to_instantiate
987
- );
988
-
989
- // Instantiate each logger.
990
- foreach ($arr_loggers_to_instantiate as $one_logger_name) {
991
- // Detect logger class name.
992
- $logger_class_name = null;
993
-
994
- if (class_exists($one_logger_name)) {
995
- // Logger name is "SimpleCommentsLogger".
996
- $logger_class_name = $one_logger_name;
997
- } else {
998
- // Check if class is "class-privacy-logger".
999
- $logger_snaked_name = substr($one_logger_name, 6);
1000
- // "privacy-logger" -> "privacy_logger" -> Privacy_Logger
1001
- $logger_snaked_name = str_replace('-', '_', $logger_snaked_name);
1002
- $logger_snaked_name = sh_ucwords($logger_snaked_name, '_');
1003
-
1004
- if (class_exists($logger_snaked_name)) {
1005
- $logger_class_name = $logger_snaked_name;
1006
- }
1007
- }
1008
-
1009
- // Continue to load next logger if no valid logger class found.
1010
- if (!$logger_class_name) {
1011
- continue;
1012
- }
1013
-
1014
- // Init found logger class.
1015
- $logger_instance = new $logger_class_name($this);
1016
-
1017
- if (!is_subclass_of($logger_instance, 'SimpleLogger') && !is_a($logger_instance, 'SimpleLogger')) {
1018
- continue;
1019
- }
1020
-
1021
- $logger_instance->loaded();
1022
-
1023
- // Tell gettext-filter to add untranslated messages.
1024
- $this->doFilterGettext = true;
1025
- $this->doFilterGettext_currentLogger = $logger_instance;
1026
-
1027
- $logger_info = $logger_instance->getInfo();
1028
-
1029
- // Check so no logger has a logger slug with more than 30 chars,
1030
- // because db column is only 30 chars.
1031
- if (strlen($logger_instance->slug) > 30) {
1032
- add_action('admin_notices', [$this, 'admin_notice_logger_slug_to_long']);
1033
- }
1034
-
1035
- // Un-tell gettext filter.
1036
- $this->doFilterGettext = false;
1037
- $this->doFilterGettext_currentLogger = null;
1038
-
1039
- // LoggerInfo contains all messages, both translated an not, by key.
1040
- // Add messages to the loggerInstance.
1041
- $arr_messages_by_message_key = [];
1042
-
1043
- if (isset($logger_info['messages']) && is_array($logger_info['messages'])) {
1044
- foreach ((array) $logger_info['messages'] as $message_key => $message_translated) {
1045
- // Find message in array with both translated and non translated strings.
1046
- foreach ($logger_instance->messages as $one_message_with_translation_info) {
1047
- if ($message_translated == $one_message_with_translation_info['translated_text']) {
1048
- $arr_messages_by_message_key[$message_key] = $one_message_with_translation_info;
1049
- continue;
1050
- }
1051
- }
1052
- }
1053
- }
1054
-
1055
- $logger_instance->messages = $arr_messages_by_message_key;
1056
-
1057
- // Add logger to array of loggers.
1058
- $this->instantiatedLoggers[$logger_instance->slug] = [
1059
- 'name' => $logger_info['name'],
1060
- 'instance' => $logger_instance,
1061
- ];
1062
- } // End foreach().
1063
-
1064
- do_action('simple_history/loggers_loaded');
1065
- }
1066
-
1067
- /**
1068
- * Load built in dropins from all files in /dropins
1069
- * and instantiates them
1070
- */
1071
- public function load_dropins()
1072
- {
1073
- $dropinsDir = SIMPLE_HISTORY_PATH . 'dropins/';
1074
-
1075
- $dropinsFiles = [
1076
- $dropinsDir . 'SimpleHistoryPluginPatchesDropin.php',
1077
- $dropinsDir . 'SimpleHistoryDonateDropin.php',
1078
- $dropinsDir . 'SimpleHistoryExportDropin.php',
1079
- $dropinsDir . 'SimpleHistoryFilterDropin.php',
1080
- $dropinsDir . 'SimpleHistoryIpInfoDropin.php',
1081
- $dropinsDir . 'SimpleHistoryNewRowsNotifier.php',
1082
- $dropinsDir . 'SimpleHistoryRSSDropin.php',
1083
- $dropinsDir . 'SimpleHistorySettingsLogtestDropin.php',
1084
- $dropinsDir . 'SimpleHistorySettingsStatsDropin.php',
1085
- $dropinsDir . 'SimpleHistorySettingsDebugDropin.php',
1086
- $dropinsDir . 'SimpleHistorySidebarDropin.php',
1087
- $dropinsDir . 'SimpleHistorySidebarStats.php',
1088
- $dropinsDir . 'SimpleHistorySidebarSettings.php',
1089
- $dropinsDir . 'SimpleHistoryWPCLIDropin.php',
1090
- $dropinsDir . 'SimpleHistoryDebugDropin.php',
1091
- ];
1092
-
1093
- /**
1094
- * Filter the array with absolute paths to files as returned by glob function.
1095
- * Each file will be loaded and will be assumed to be a dropin with a classname
1096
- * the same as the filename.
1097
- *
1098
- * @since 2.0
1099
- *
1100
- * @param array $dropinsFiles Array with filenames
1101
- */
1102
- $dropinsFiles = apply_filters('simple_history/dropins_files', $dropinsFiles);
1103
-
1104
- $arrDropinsToInstantiate = [];
1105
-
1106
- foreach ($dropinsFiles as $oneDropinFile) {
1107
- // path/path/simplehistory/dropins/SimpleHistoryDonateDropin.php => SimpleHistoryDonateDropin
1108
- $oneDropinFileBasename = basename($oneDropinFile, '.php');
1109
-
1110
- $load_dropin = true;
1111
-
1112
- /**
1113
- * Filter to completely skip loading of dropin
1114
- * complete filer name will be like:
1115
- * simple_history/dropin/load_dropin_SimpleHistoryRSSDropin
1116
- *
1117
- * @since 2.0.6
1118
- *
1119
- * @param bool if to load the dropin. return false to not load it.
1120
- */
1121
- $load_dropin = apply_filters("simple_history/dropin/load_dropin_{$oneDropinFileBasename}", $load_dropin);
1122
-
1123
- /**
1124
- * Filter to completely skip loading of a dropin
1125
- *
1126
- * @since 2.0.22
1127
- *
1128
- * @param bool if to load the dropin. return false to not load it.
1129
- * @param string slug of dropin
1130
- */
1131
- $load_dropin = apply_filters('simple_history/dropin/load_dropin', $load_dropin, $oneDropinFileBasename);
1132
-
1133
- if (!$load_dropin) {
1134
- continue;
1135
- }
1136
-
1137
- include_once $oneDropinFile;
1138
-
1139
- $arrDropinsToInstantiate[] = $oneDropinFileBasename;
1140
- } // End foreach().
1141
-
1142
- /**
1143
- * Action that dropins can use to add their custom loggers.
1144
- * See register_dropin() for more info.
1145
- *
1146
- * @since 2.3.2
1147
- *
1148
- * @param array $arrDropinsToInstantiate Array with class names
1149
- */
1150
- do_action('simple_history/add_custom_dropin', $this);
1151
-
1152
- /**
1153
- * Filter the array with names of dropin to instantiate.
1154
- *
1155
- * @since 2.0
1156
- *
1157
- * @param array $arrDropinsToInstantiate Array with class names
1158
- */
1159
- $arrDropinsToInstantiate = apply_filters('simple_history/dropins_to_instantiate', $arrDropinsToInstantiate);
1160
-
1161
- $arrDropinsToInstantiate = array_merge($arrDropinsToInstantiate, $this->externalDropins);
1162
-
1163
- // Instantiate each dropin
1164
- foreach ($arrDropinsToInstantiate as $oneDropinName) {
1165
- if (!class_exists($oneDropinName)) {
1166
- continue;
1167
- }
1168
-
1169
- $this->instantiatedDropins[$oneDropinName] = [
1170
- 'name' => $oneDropinName,
1171
- 'instance' => new $oneDropinName($this),
1172
- ];
1173
- }
1174
- }
1175
-
1176
- /**
1177
- * Gets the pager size,
1178
- * i.e. the number of items to show on each page in the history
1179
- *
1180
- * @return int
1181
- */
1182
- public function get_pager_size()
1183
- {
1184
- $pager_size = get_option('simple_history_pager_size', 20);
1185
-
1186
- /**
1187
- * Filter the pager size setting
1188
- *
1189
- * @since 2.0
1190
- *
1191
- * @param int $pager_size
1192
- */
1193
- $pager_size = apply_filters('simple_history/pager_size', $pager_size);
1194
-
1195
- return $pager_size;
1196
- }
1197
-
1198
- /**
1199
- * Gets the pager size,
1200
- * i.e. the number of items to show on each page in the history
1201
- *
1202
- * @since 2.12
1203
- * @return int
1204
- */
1205
- public function get_pager_size_dashboard()
1206
- {
1207
- $pager_size = get_option('simple_history_pager_size_dashboard', 5);
1208
-
1209
- /**
1210
- * Filter the pager size setting
1211
- *
1212
- * @since 2.12
1213
- *
1214
- * @param int $pager_size
1215
- */
1216
- $pager_size = apply_filters('simple_history/pager_size_dashboard', $pager_size);
1217
-
1218
- return $pager_size;
1219
- }
1220
-
1221
- /**
1222
- * Show a link to our settings page on the Plugins -> Installed Plugins screen
1223
- */
1224
- public function plugin_action_links($actions, $b, $c, $d)
1225
- {
1226
- // Only add link if user has the right to view the settings page
1227
- if (!current_user_can($this->get_view_settings_capability())) {
1228
- return $actions;
1229
- }
1230
-
1231
- $settings_page_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
1232
-
1233
- if (empty($actions)) {
1234
- // Create array if actions is empty (and therefore is assumed to be a string by PHP & results in PHP 7.1+ fatal error due to trying to make array modifications on what's assumed to be a string)
1235
- $actions = [];
1236
- } elseif (is_string($actions)) {
1237
- // Convert the string (which it might've been retrieved as) to an array for future use as an array
1238
- $actions = [$actions];
1239
- }
1240
- $actions[] = "<a href='$settings_page_url'>" . __('Settings', 'simple-history') . '</a>';
1241
-
1242
- return $actions;
1243
- }
1244
-
1245
- /**
1246
- * Maybe add a dashboard widget,
1247
- * requires current user to have view history capability
1248
- * and a setting to show dashboard to be set
1249
- */
1250
- public function add_dashboard_widget()
1251
- {
1252
- if ($this->setting_show_on_dashboard() && current_user_can($this->get_view_history_capability())) {
1253
- /**
1254
- * Filter to determine if history page should be added to page below dashboard or not
1255
- *
1256
- * @since 2.0.23
1257
- *
1258
- * @param bool Show the page or not
1259
- */
1260
- $show_dashboard_widget = apply_filters('simple_history/show_dashboard_widget', true);
1261
-
1262
- if ($show_dashboard_widget) {
1263
- wp_add_dashboard_widget('simple_history_dashboard_widget', __('Simple History', 'simple-history'), [
1264
- $this,
1265
- 'dashboard_widget_output',
1266
- ]);
1267
- }
1268
- }
1269
- }
1270
-
1271
- /**
1272
- * Output html for the dashboard widget
1273
- */
1274
- public function dashboard_widget_output()
1275
- {
1276
- $pager_size = $this->get_pager_size_dashboard();
1277
-
1278
- /**
1279
- * Filter the pager size setting for the dashboard
1280
- *
1281
- * @since 2.0
1282
- *
1283
- * @param int $pager_size
1284
- */
1285
- $pager_size = apply_filters('simple_history/dashboard_pager_size', $pager_size);
1286
-
1287
- do_action('simple_history/dashboard/before_gui', $this);
1288
- ?>
1289
- <div class="SimpleHistoryGui"
1290
- data-pager-size='<?php echo $pager_size; ?>'
1291
- ></div>
1292
- <?php
1293
- }
1294
-
1295
- public function is_on_our_own_pages($hook = '')
1296
- {
1297
- $current_screen = get_current_screen();
1298
-
1299
- $basePrefix = apply_filters('simple_history/admin_location', 'index');
1300
- $basePrefix = $basePrefix === 'index' ? 'dashboard' : $basePrefix;
1301
-
1302
- if ($current_screen && $current_screen->base == 'settings_page_' . SimpleHistory::SETTINGS_MENU_SLUG) {
1303
- return true;
1304
- } elseif ($current_screen && $current_screen->base == $basePrefix . '_page_simple_history_page') {
1305
- return true;
1306
- } elseif (
1307
- $hook == 'settings_page_' . SimpleHistory::SETTINGS_MENU_SLUG ||
1308
- ($this->setting_show_on_dashboard() && $hook == 'index.php') ||
1309
- ($this->setting_show_as_page() && $hook == $basePrefix . '_page_simple_history_page')
1310
- ) {
1311
- return true;
1312
- } elseif ($current_screen && $current_screen->base == 'dashboard' && $this->setting_show_on_dashboard()) {
1313
- return true;
1314
- }
1315
-
1316
- return false;
1317
- }
1318
-
1319
- /**
1320
- * Enqueue styles and scripts for Simple History but only to our own pages.
1321
- *
1322
- * Only adds scripts to pages where the log is shown or the settings page.
1323
- */
1324
- public function enqueue_admin_scripts($hook)
1325
- {
1326
- if ($this->is_on_our_own_pages()) {
1327
- add_thickbox();
1328
-
1329
- wp_enqueue_style(
1330
- 'simple_history_styles',
1331
- SIMPLE_HISTORY_DIR_URL . 'css/styles.css',
1332
- false,
1333
- SIMPLE_HISTORY_VERSION
1334
- );
1335
- wp_enqueue_script(
1336
- 'simple_history_script',
1337
- SIMPLE_HISTORY_DIR_URL . 'js/scripts.js',
1338
- ['jquery', 'backbone', 'wp-util'],
1339
- SIMPLE_HISTORY_VERSION,
1340
- true
1341
- );
1342
-
1343
- wp_enqueue_script('select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.full.min.js', ['jquery']);
1344
- wp_enqueue_style('select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.min.css');
1345
-
1346
- // Translations that we use in JavaScript
1347
- wp_localize_script('simple_history_script', 'simple_history_script_vars', [
1348
- 'settingsConfirmClearLog' => __('Remove all log items?', 'simple-history'),
1349
- 'pagination' => [
1350
- 'goToTheFirstPage' => __('Go to the first page', 'simple-history'),
1351
- 'goToThePrevPage' => __('Go to the previous page', 'simple-history'),
1352
- 'goToTheNextPage' => __('Go to the next page', 'simple-history'),
1353
- 'goToTheLastPage' => __('Go to the last page', 'simple-history'),
1354
- 'currentPage' => __('Current page', 'simple-history'),
1355
- ],
1356
- 'loadLogAPIError' => __('Oups, the log could not be loaded right now.', 'simple-history'),
1357
- 'ajaxLoadError' => __(
1358
- 'Hm, the log could not be loaded right now. Perhaps another plugin is giving some errors. Anyway, below is the output I got from the server.',
1359
- 'simple-history'
1360
- ),
1361
- 'logNoHits' => __('Your search did not match any history events.', 'simple-history'),
1362
- ]);
1363
-
1364
- // Call plugins adminCSS-method, so they can add their CSS
1365
- foreach ($this->instantiatedLoggers as $one_logger) {
1366
- if (method_exists($one_logger['instance'], 'adminCSS')) {
1367
- $one_logger['instance']->adminCSS();
1368
- }
1369
- }
1370
-
1371
- // Add timeago.js
1372
- wp_enqueue_script(
1373
- 'timeago',
1374
- SIMPLE_HISTORY_DIR_URL . 'js/timeago/jquery.timeago.js',
1375
- ['jquery'],
1376
- '1.5.2',
1377
- true
1378
- );
1379
-
1380
- // Determine current locale to load timeago locale
1381
- $locale = strtolower(substr(get_locale(), 0, 2));
1382
- $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/timeago/locales/jquery.timeago.%s.js';
1383
- $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/timeago/locales/jquery.timeago.%s.js';
1384
-
1385
- // Only enqueue if locale-file exists on file system
1386
- if (file_exists(sprintf($locale_dir_path, $locale))) {
1387
- wp_enqueue_script('timeago-locale', sprintf($locale_url_path, $locale), ['jquery'], '1.5.2', true);
1388
- } else {
1389
- wp_enqueue_script('timeago-locale', sprintf($locale_url_path, 'en'), ['jquery'], '1.5.2', true);
1390
- }
1391
- // end add timeago
1392
- // Load Select2 locale
1393
- $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/select2/i18n/%s.js';
1394
- $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/select2/i18n/%s.js';
1395
-
1396
- if (file_exists(sprintf($locale_dir_path, $locale))) {
1397
- wp_enqueue_script('select2-locale', sprintf($locale_url_path, $locale), ['jquery'], '3.5.1', true);
1398
- }
1399
-
1400
- /**
1401
- * Fires when the admin scripts have been enqueued.
1402
- * Only fires on any of the pages where Simple History is used
1403
- *
1404
- * @since 2.0
1405
- *
1406
- * @param SimpleHistory $SimpleHistory This class.
1407
- */
1408
- do_action('simple_history/enqueue_admin_scripts', $this);
1409
- } // End if().
1410
- }
1411
-
1412
- public function filter_option_page_capability($capability)
1413
- {
1414
- return $capability;
1415
- }
1416
-
1417
- /**
1418
- * Check if plugin version have changed, i.e. has been upgraded
1419
- * If upgrade is detected then maybe modify database and so on for that version
1420
- */
1421
- public function check_for_upgrade()
1422
- {
1423
- global $wpdb;
1424
-
1425
- $db_version = get_option('simple_history_db_version');
1426
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
1427
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
1428
- $first_install = false;
1429
-
1430
- // If no db_version is set then this
1431
- // is a version of Simple History < 0.4
1432
- // or it's a first install
1433
- // Fix database not using UTF-8
1434
- if (false === $db_version || intval($db_version) == 0) {
1435
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1436
-
1437
- // Table creation, used to be in register_activation_hook
1438
- // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
1439
- $sql =
1440
- 'CREATE TABLE ' .
1441
- $table_name .
1442
- ' (
1443
  id bigint(20) NOT NULL AUTO_INCREMENT,
1444
  date datetime NOT NULL,
1445
  PRIMARY KEY (id)
1446
  ) CHARACTER SET=utf8;';
1447
 
1448
- // Upgrade db / fix utf for varchars
1449
- dbDelta($sql);
1450
-
1451
- // Fix UTF-8 for table
1452
- $sql = sprintf('alter table %1$s charset=utf8;', $table_name);
1453
- $wpdb->query($sql);
1454
-
1455
- $db_version_prev = $db_version;
1456
- $db_version = 1;
1457
-
1458
- update_option('simple_history_db_version', $db_version);
1459
-
1460
- // We are not 100% sure that this is a first install,
1461
- // but it is at least a very old version that is being updated
1462
- $first_install = true;
1463
- } // End if().
1464
-
1465
- // If db version is 1 then upgrade to 2
1466
- // Version 2 added the action_description column
1467
- if (1 == intval($db_version)) {
1468
- // V2 used to add column "action_description"
1469
- // but it's not used any more so don't do i
1470
- $db_version_prev = $db_version;
1471
- $db_version = 2;
1472
-
1473
- update_option('simple_history_db_version', $db_version);
1474
- }
1475
-
1476
- // Check that all options we use are set to their defaults, if they miss value
1477
- // Each option that is missing a value will make a sql call otherwise = unnecessary
1478
- $arr_options = [
1479
- [
1480
- 'name' => 'simple_history_show_as_page',
1481
- 'default_value' => 1,
1482
- ],
1483
- [
1484
- 'name' => 'simple_history_show_on_dashboard',
1485
- 'default_value' => 1,
1486
- ],
1487
- ];
1488
-
1489
- foreach ($arr_options as $one_option) {
1490
- if (false === ($option_value = get_option($one_option['name']))) {
1491
- // Value is not set in db, so set it to a default
1492
- update_option($one_option['name'], $one_option['default_value']);
1493
- }
1494
- }
1495
-
1496
- /**
1497
- * If db_version is 2 then upgrade to 3:
1498
- * - Add some fields to existing table wp_simple_history_contexts
1499
- * - Add all new table wp_simple_history_contexts
1500
- *
1501
- * @since 2.0
1502
- */
1503
- if (2 == intval($db_version)) {
1504
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1505
-
1506
- // Update old table
1507
- $sql = "
1508
  CREATE TABLE {$table_name} (
1509
  id bigint(20) NOT NULL AUTO_INCREMENT,
1510
  date datetime NOT NULL,
@@ -1518,10 +1487,10 @@ class SimpleHistory
1518
  KEY loggerdate (logger,date)
1519
  ) CHARSET=utf8;";
1520
 
1521
- dbDelta($sql);
1522
 
1523
- // Add context table
1524
- $sql = "
1525
  CREATE TABLE IF NOT EXISTS {$table_name_contexts} (
1526
  context_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
1527
  history_id bigint(20) unsigned NOT NULL,
@@ -1533,49 +1502,51 @@ class SimpleHistory
1533
  ) CHARSET=utf8;
1534
  ";
1535
 
1536
- $wpdb->query($sql);
 
1537
 
1538
- $db_version_prev = $db_version;
1539
- $db_version = 3;
1540
- update_option('simple_history_db_version', $db_version);
1541
 
1542
- // Update possible old items to use SimpleLegacyLogger
1543
- $sql = sprintf(
1544
- '
1545
  UPDATE %1$s
1546
  SET
1547
- logger = "SimpleLegacyLogger",
1548
  level = "info"
1549
  WHERE logger IS NULL
1550
  ',
1551
- $table_name
1552
- );
1553
-
1554
- $wpdb->query($sql);
1555
-
1556
- // Say welcome, however loggers are not added this early so we need to
1557
- // use a filter to load it later
1558
- add_action('simple_history/loggers_loaded', [$this, 'addWelcomeLogMessage']);
1559
- } // End if().
1560
-
1561
- /**
1562
- * If db version = 3
1563
- * then we need to update database to allow null values for some old columns
1564
- * that used to work in pre wp 4.1 beta, but since 4.1 wp uses STRICT_ALL_TABLES
1565
- * WordPress Commit: https://github.com/WordPress/WordPress/commit/f17d168a0f72211a9bfd9d3fa680713069871bb6
1566
- *
1567
- * @since 2.0
1568
- */
1569
- if (3 == intval($db_version)) {
1570
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1571
-
1572
- // If old columns exist = this is an old install, then modify the columns so we still can keep them
1573
- // we want to keep them because user may have logged items that they want to keep
1574
- $db_cools = $wpdb->get_col("DESCRIBE $table_name");
1575
-
1576
- if (in_array('action', $db_cools)) {
1577
- $sql = sprintf(
1578
- '
 
 
1579
  ALTER TABLE %1$s
1580
  MODIFY `action` varchar(255) NULL,
1581
  MODIFY `object_type` varchar(255) NULL,
@@ -1584,1045 +1555,1045 @@ class SimpleHistory
1584
  MODIFY `object_id` int(10) NULL,
1585
  MODIFY `object_name` varchar(255) NULL
1586
  ',
1587
- $table_name
1588
- );
1589
- $wpdb->query($sql);
1590
- }
1591
-
1592
- $db_version_prev = $db_version;
1593
- $db_version = 4;
1594
-
1595
- update_option('simple_history_db_version', $db_version);
1596
- } // End if().
1597
-
1598
- // Some installs on 2.2.2 got failed installs
1599
- // We detect these by checking for db_version and then running the install stuff again
1600
- if (4 == intval($db_version)) {
1601
- if (!$this->does_database_have_data()) {
1602
- // not ok, decrease db number so installs will run again and hopefully fix things
1603
- $db_version = 0;
1604
- } else {
1605
- // all looks ok, upgrade to db version 5, so this part is not done again
1606
- $db_version = 5;
1607
- }
1608
-
1609
- update_option('simple_history_db_version', $db_version);
1610
- }
1611
- }
1612
-
1613
- /**
1614
- * Check if the database has data/rows
1615
- *
1616
- * @since 2.1.6
1617
- * @return bool True if database is not empty, false if database is empty = contains no data
1618
- */
1619
- public function does_database_have_data()
1620
- {
1621
- global $wpdb;
1622
-
1623
- $tableprefix = $wpdb->prefix;
1624
-
1625
- $simple_history_table = SimpleHistory::DBTABLE;
1626
- $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
1627
-
1628
- $sql_data_exists = "SELECT id AS id_exists FROM {$tableprefix}{$simple_history_table} LIMIT 1";
1629
- $data_exists = (bool) $wpdb->get_var($sql_data_exists, 0);
1630
-
1631
- return $data_exists;
1632
- }
1633
-
1634
- /**
1635
- * Greet users to version 2!
1636
- * Is only called after database has been upgraded, so only on first install (or upgrade).
1637
- * Not called after only plugin activation.
1638
- */
1639
- public function addWelcomeLogMessage()
1640
- {
1641
- $db_data_exists = $this->does_database_have_data();
1642
- // $db_data_exists = false;
1643
- $pluginLogger = $this->getInstantiatedLoggerBySlug('SimplePluginLogger');
1644
- if ($pluginLogger) {
1645
- // Add plugin installed message
1646
- $context = [
1647
- 'plugin_name' => 'Simple History',
1648
- 'plugin_description' =>
1649
- 'Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.',
1650
- 'plugin_url' => 'https://simple-history.com',
1651
- 'plugin_version' => SIMPLE_HISTORY_VERSION,
1652
- 'plugin_author' => 'Pär Thernström',
1653
- ];
1654
-
1655
- $pluginLogger->infoMessage('plugin_installed', $context);
1656
-
1657
- // Add plugin activated message
1658
- $context['plugin_slug'] = 'simple-history';
1659
- $context['plugin_title'] = '<a href="https://simple-history.com/">Simple History</a>';
1660
-
1661
- $pluginLogger->infoMessage('plugin_activated', $context);
1662
- }
1663
-
1664
- if (!$db_data_exists) {
1665
- $welcome_message_1 = __(
1666
- '
1667
  Welcome to Simple History!
1668
 
1669
  This is the main history feed. It will contain events that this plugin has logged.
1670
  ',
1671
- 'simple-history'
1672
- );
1673
 
1674
- $welcome_message_2 = __(
1675
- '
1676
  Because Simple History was only recently installed, this feed does not display many events yet. As long as the plugin remains activated you will soon see detailed information about page edits, plugin updates, users logging in, and much more.
1677
  ',
1678
- 'simple-history'
1679
- );
1680
-
1681
- SimpleLogger()->info($welcome_message_2, [
1682
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
1683
- ]);
1684
-
1685
- SimpleLogger()->info($welcome_message_1, [
1686
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
1687
- ]);
1688
- }
1689
- }
1690
-
1691
- public function registerSettingsTab($arr_tab_settings)
1692
- {
1693
- $this->arr_settings_tabs[] = $arr_tab_settings;
1694
- }
1695
-
1696
- public function getSettingsTabs()
1697
- {
1698
- return $this->arr_settings_tabs;
1699
- }
1700
-
1701
- /**
1702
- * Output HTML for the settings page
1703
- * Called from add_options_page
1704
- */
1705
- public function settings_page_output()
1706
- {
1707
- $arr_settings_tabs = $this->getSettingsTabs(); ?>
1708
- <div class="wrap">
1709
-
1710
- <h1 class="SimpleHistoryPageHeadline">
1711
- <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1712
- <?php _e('Simple History Settings', 'simple-history'); ?>
1713
- </h1>
1714
-
1715
- <?php
1716
- $active_tab = isset($_GET['selected-tab']) ? $_GET['selected-tab'] : 'settings';
1717
- $settings_base_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
1718
- ?>
1719
-
1720
- <h2 class="nav-tab-wrapper">
1721
- <?php foreach ($arr_settings_tabs as $one_tab) {
1722
- $tab_slug = $one_tab['slug'];
1723
-
1724
- printf(
1725
- '<a href="%3$s" class="nav-tab %4$s">%1$s</a>',
1726
- $one_tab['name'], // 1
1727
- $tab_slug, // 2
1728
- esc_url(add_query_arg('selected-tab', $tab_slug, $settings_base_url)), // 3
1729
- $active_tab == $tab_slug ? 'nav-tab-active' : '' // 4
1730
- );
1731
- } ?>
1732
- </h2>
1733
-
1734
- <?php
1735
- // Output contents for selected tab
1736
- $arr_active_tab = wp_filter_object_list($arr_settings_tabs, [
1737
- 'slug' => $active_tab,
1738
- ]);
1739
- $arr_active_tab = current($arr_active_tab);
1740
-
1741
- // We must have found an active tab and it must have a callable function
1742
- if (!$arr_active_tab || !is_callable($arr_active_tab['function'])) {
1743
- wp_die(__('No valid callback found', 'simple-history'));
1744
- }
1745
-
1746
- $args = [
1747
- 'arr_active_tab' => $arr_active_tab,
1748
- ];
1749
-
1750
- call_user_func_array($arr_active_tab['function'], array_values($args));
1751
- ?>
1752
-
1753
- </div>
1754
- <?php
1755
- }
1756
-
1757
- public function settings_output_log()
1758
- {
1759
- include SIMPLE_HISTORY_PATH . 'templates/settings-log.php';
1760
- }
1761
-
1762
- public function settings_output_general()
1763
- {
1764
- include SIMPLE_HISTORY_PATH . 'templates/settings-general.php';
1765
- }
1766
-
1767
- public function settings_output_styles_example()
1768
- {
1769
- include SIMPLE_HISTORY_PATH . 'templates/settings-style-example.php';
1770
- }
1771
-
1772
- /**
1773
- * Content for section intro. Leave it be, even if empty.
1774
- * Called from add_sections_setting.
1775
- */
1776
- public function settings_section_output()
1777
- {
1778
- }
1779
-
1780
- /**
1781
- * Add pages (history page and settings page)
1782
- */
1783
- public function add_admin_pages()
1784
- {
1785
- // Add a history page as a sub-page below the Dashboard menu item
1786
- if ($this->setting_show_as_page()) {
1787
- /**
1788
- * Filter to determine if history page should be added to page below dashboard or not
1789
- *
1790
- * @since 2.0.23
1791
- *
1792
- * @param bool Show the page or not
1793
- */
1794
- $show_dashboard_page = apply_filters('simple_history/show_dashboard_page', true);
1795
-
1796
- if ($show_dashboard_page) {
1797
- add_submenu_page(
1798
- apply_filters('simple_history/admin_location', 'index') . '.php',
1799
- _x('Simple History', 'dashboard title name', 'simple-history'),
1800
- _x('Simple History', 'dashboard menu name', 'simple-history'),
1801
- $this->get_view_history_capability(),
1802
- 'simple_history_page',
1803
- [$this, 'history_page_output']
1804
- );
1805
- }
1806
- }
1807
-
1808
- // Add a settings page
1809
- $show_settings_page = true;
1810
- $show_settings_page = apply_filters('simple_history_show_settings_page', $show_settings_page);
1811
- $show_settings_page = apply_filters('simple_history/show_settings_page', $show_settings_page);
1812
-
1813
- if ($show_settings_page) {
1814
- add_options_page(
1815
- __('Simple History Settings', 'simple-history'),
1816
- _x('Simple History', 'Options page menu title', 'simple-history'),
1817
- $this->get_view_settings_capability(),
1818
- SimpleHistory::SETTINGS_MENU_SLUG,
1819
- [$this, 'settings_page_output']
1820
- );
1821
- }
1822
- }
1823
-
1824
- /**
1825
- * Add setting sections and settings for the settings page
1826
- * Also maybe save some settings before outputing them
1827
- */
1828
- public function add_settings()
1829
- {
1830
- // Clear the log if clear button was clicked in settings
1831
- // and redirect user to show message.
1832
- if (
1833
- isset($_GET['simple_history_clear_log_nonce']) &&
1834
- wp_verify_nonce($_GET['simple_history_clear_log_nonce'], 'simple_history_clear_log')
1835
- ) {
1836
- if ($this->user_can_clear_log()) {
1837
- $this->clear_log();
1838
- }
1839
-
1840
- $msg = __('Cleared database', 'simple-history');
1841
-
1842
- add_settings_error(
1843
- 'simple_history_rss_feed_regenerate_secret',
1844
- 'simple_history_rss_feed_regenerate_secret',
1845
- $msg,
1846
- 'updated'
1847
- );
1848
-
1849
- set_transient('settings_errors', get_settings_errors(), 30);
1850
-
1851
- $goback = esc_url_raw(add_query_arg('settings-updated', 'true', wp_get_referer()));
1852
- wp_redirect($goback);
1853
- exit();
1854
- }
1855
-
1856
- // Section for general options.
1857
- // Will contain settings like where to show simple history and number of items.
1858
- $settings_section_general_id = self::SETTINGS_SECTION_GENERAL_ID;
1859
- add_settings_section(
1860
- $settings_section_general_id,
1861
- '',
1862
- [$this, 'settings_section_output'],
1863
- SimpleHistory::SETTINGS_MENU_SLUG // Same slug as for options menu page.
1864
- );
1865
-
1866
- // Settings for the general settings section
1867
- // Each setting = one row in the settings section
1868
- // add_settings_field( $id, $title, $callback, $page, $section, $args );
1869
- // Checkboxes for where to show simple history
1870
- add_settings_field(
1871
- 'simple_history_show_where',
1872
- __('Show history', 'simple-history'),
1873
- [$this, 'settings_field_where_to_show'],
1874
- SimpleHistory::SETTINGS_MENU_SLUG,
1875
- $settings_section_general_id
1876
- );
1877
-
1878
- // Nonces for show where inputs.
1879
- register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_show_on_dashboard');
1880
- register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_show_as_page');
1881
-
1882
- // Number if items to show on the history page.
1883
- add_settings_field(
1884
- 'simple_history_number_of_items',
1885
- __('Number of items per page on the log page', 'simple-history'),
1886
- [$this, 'settings_field_number_of_items'],
1887
- SimpleHistory::SETTINGS_MENU_SLUG,
1888
- $settings_section_general_id
1889
- );
1890
-
1891
- // Nonces for number of items inputs.
1892
- register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_pager_size');
1893
-
1894
- // Number if items to show on dashboard.
1895
- add_settings_field(
1896
- 'simple_history_number_of_items_dashboard',
1897
- __('Number of items per page on the dashboard', 'simple-history'),
1898
- [$this, 'settings_field_number_of_items_dashboard'],
1899
- SimpleHistory::SETTINGS_MENU_SLUG,
1900
- $settings_section_general_id
1901
- );
1902
-
1903
- // Nonces for number of items inputs.
1904
- register_setting(SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_pager_size_dashboard');
1905
-
1906
- // Link/button to clear log.
1907
- if ($this->user_can_clear_log()) {
1908
- add_settings_field(
1909
- 'simple_history_clear_log',
1910
- __('Clear log', 'simple-history'),
1911
- [$this, 'settings_field_clear_log'],
1912
- SimpleHistory::SETTINGS_MENU_SLUG,
1913
- $settings_section_general_id
1914
- );
1915
- }
1916
- }
1917
-
1918
- /**
1919
- * Output for page with the history
1920
- */
1921
- public function history_page_output()
1922
- {
1923
- // global $simple_history;
1924
- // $this->purge_db();
1925
- global $wpdb;
1926
-
1927
- $pager_size = $this->get_pager_size();
1928
-
1929
- /**
1930
- * Filter the pager size setting for the history page
1931
- *
1932
- * @since 2.0
1933
- *
1934
- * @param int $pager_size
1935
- */
1936
- $pager_size = apply_filters('simple_history/page_pager_size', $pager_size);
1937
- ?>
1938
-
1939
- <div class="wrap SimpleHistoryWrap">
1940
-
1941
- <h1 class="SimpleHistoryPageHeadline">
1942
- <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1943
- <?php echo _x('Simple History', 'history page headline', 'simple-history'); ?>
1944
- </h1>
1945
-
1946
- <?php /**
1947
- * Fires before the gui div
1948
- *
1949
- * @since 2.0
1950
- *
1951
- * @param SimpleHistory $SimpleHistory This class.
1952
- */
1953
- do_action('simple_history/history_page/before_gui', $this); ?>
1954
-
1955
- <div class="SimpleHistoryGuiWrap">
1956
-
1957
- <div class="SimpleHistoryGui"
1958
- data-pager-size='<?php echo $pager_size; ?>'
1959
- ></div>
1960
- <?php /**
1961
- * Fires after the gui div
1962
- *
1963
- * @since 2.0
1964
- *
1965
- * @param SimpleHistory $SimpleHistory This class.
1966
- */
1967
- do_action('simple_history/history_page/after_gui', $this); ?>
1968
- </div>
1969
- </div>
1970
- <?php
1971
- }
1972
-
1973
- /**
1974
- * Get setting if plugin should be visible on dasboard.
1975
- * Defaults to true
1976
- *
1977
- * @return bool
1978
- */
1979
- public function setting_show_on_dashboard()
1980
- {
1981
- $show_on_dashboard = get_option('simple_history_show_on_dashboard', 1);
1982
- $show_on_dashboard = apply_filters('simple_history_show_on_dashboard', $show_on_dashboard);
1983
- return (bool) $show_on_dashboard;
1984
- }
1985
-
1986
- /**
1987
- * Should simple history be shown as a page
1988
- * Defaults to true
1989
- *
1990
- * @return bool
1991
- */
1992
- public function setting_show_as_page()
1993
- {
1994
- $setting = get_option('simple_history_show_as_page', 1);
1995
- $setting = apply_filters('simple_history_show_as_page', $setting);
1996
-
1997
- return (bool) $setting;
1998
- }
1999
-
2000
- /**
2001
- * Settings field for how many rows/items to show in log on the log page
2002
- */
2003
- public function settings_field_number_of_items()
2004
- {
2005
- $current_pager_size = $this->get_pager_size(); ?>
2006
- <select name="simple_history_pager_size">
2007
- <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
2008
- <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
2009
- <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
2010
- <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2011
- <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2012
- <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2013
- <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2014
- <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2015
- <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2016
- <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2017
- </select>
2018
- <?php
2019
- }
2020
-
2021
- /**
2022
- * Settings field for how many rows/items to show in log on the dashboard
2023
- */
2024
- public function settings_field_number_of_items_dashboard()
2025
- {
2026
- $current_pager_size = $this->get_pager_size_dashboard(); ?>
2027
- <select name="simple_history_pager_size_dashboard">
2028
- <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
2029
- <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
2030
- <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
2031
- <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2032
- <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2033
- <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2034
- <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2035
- <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2036
- <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2037
- <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2038
- </select>
2039
- <?php
2040
- }
2041
-
2042
- /**
2043
- * Settings field for where to show the log, page or dashboard
2044
- */
2045
- public function settings_field_where_to_show()
2046
- {
2047
- $show_on_dashboard = $this->setting_show_on_dashboard();
2048
- $show_as_page = $this->setting_show_as_page();
2049
- ?>
2050
-
2051
- <input <?php echo $show_on_dashboard
2052
- ? "checked='checked'"
2053
- : ''; ?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
2054
- <label for="simple_history_show_on_dashboard"><?php _e('on the dashboard', 'simple-history'); ?></label>
2055
-
2056
- <br />
2057
-
2058
- <input <?php echo $show_as_page
2059
- ? "checked='checked'"
2060
- : ''; ?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
2061
- <label for="simple_history_show_as_page">
2062
- <?php _e('as a page under the dashboard menu', 'simple-history'); ?>
2063
- </label>
2064
-
2065
- <?php
2066
- }
2067
-
2068
- /**
2069
- * Settings section to clear database
2070
- */
2071
- public function settings_field_clear_log()
2072
- {
2073
- // Get base URL to current page.
2074
- // Will be like "/wordpress/wp-admin/options-general.php?page=simple_history_settings_menu_slug&"
2075
- $clear_link = add_query_arg('', '');
2076
-
2077
- // Append nonce to URL.
2078
- $clear_link = wp_nonce_url($clear_link, 'simple_history_clear_log', 'simple_history_clear_log_nonce');
2079
-
2080
- $clear_days = $this->get_clear_history_interval();
2081
-
2082
- echo '<p>';
2083
-
2084
- if ($clear_days > 0) {
2085
- echo sprintf(
2086
- __('Items in the database are automatically removed after %1$s days.', 'simple-history'),
2087
- $clear_days
2088
- );
2089
- } else {
2090
- _e('Items in the database are kept forever.', 'simple-history');
2091
- }
2092
-
2093
- echo '</p>';
2094
-
2095
- printf(
2096
- '<p><a class="button js-SimpleHistory-Settings-ClearLog" href="%2$s">%1$s</a></p>',
2097
- __('Clear log now', 'simple-history'),
2098
- esc_url($clear_link)
2099
- );
2100
- }
2101
-
2102
- /**
2103
- * How old log entried are allowed to be.
2104
- * 0 = don't delete old entries.
2105
- *
2106
- * @return int Number of days.
2107
- */
2108
- public function get_clear_history_interval()
2109
- {
2110
- $days = 60;
2111
-
2112
- /**
2113
- * Filter to modify number of days of history to keep.
2114
- * Default is 60 days.
2115
- *
2116
- * @param $days Number of days of history to keep
2117
- */
2118
- $days = (int) apply_filters('simple_history_db_purge_days_interval', $days);
2119
- $days = (int) apply_filters('simple_history/db_purge_days_interval', $days);
2120
-
2121
- return $days;
2122
- }
2123
-
2124
- /**
2125
- * Removes all items from the log
2126
- */
2127
- public function clear_log()
2128
- {
2129
- global $wpdb;
2130
-
2131
- $tableprefix = $wpdb->prefix;
2132
-
2133
- $simple_history_table = SimpleHistory::DBTABLE;
2134
- $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
2135
-
2136
- // Get number of rows before delete.
2137
- $sql_num_rows = "SELECT count(id) AS num_rows FROM {$tableprefix}{$simple_history_table}";
2138
- $num_rows = $wpdb->get_var($sql_num_rows, 0);
2139
-
2140
- // Use truncate instead of delete because it's much faster (I think, writing this much later).
2141
- $sql = "TRUNCATE {$tableprefix}{$simple_history_table}";
2142
- $wpdb->query($sql);
2143
-
2144
- $sql = "TRUNCATE {$tableprefix}{$simple_history_context_table}";
2145
- $wpdb->query($sql);
2146
-
2147
- // Zero state sucks
2148
- SimpleLogger()->info(
2149
- __('The log for Simple History was cleared ({num_rows} rows were removed).', 'simple-history'),
2150
- [
2151
- 'num_rows' => $num_rows,
2152
- ]
2153
- );
2154
-
2155
- $this->get_cache_incrementor(true);
2156
- }
2157
-
2158
- /**
2159
- * Runs the purge_db() method sometimes
2160
- * We don't want to call it each time because it performs SQL queries
2161
- *
2162
- * @since 2.0.17
2163
- */
2164
- public function maybe_purge_db()
2165
- {
2166
- // How often should we try to do this?
2167
- // Once a day = a bit tiresome.
2168
- // Let's go with sundays; purge the log on sundays.
2169
- // Day of week, 1 = mon, 7 = sun.
2170
- $day_of_week = date('N');
2171
- if (7 === (int) $day_of_week) {
2172
- $this->purge_db();
2173
- }
2174
- }
2175
-
2176
- /**
2177
- * Removes old entries from the db
2178
- */
2179
- public function purge_db()
2180
- {
2181
- $do_purge_history = true;
2182
-
2183
- $do_purge_history = apply_filters('simple_history_allow_db_purge', $do_purge_history);
2184
- $do_purge_history = apply_filters('simple_history/allow_db_purge', $do_purge_history);
2185
-
2186
- if (!$do_purge_history) {
2187
- return;
2188
- }
2189
-
2190
- $days = $this->get_clear_history_interval();
2191
-
2192
- // Never clear log if days = 0.
2193
- if (0 == $days) {
2194
- return;
2195
- }
2196
-
2197
- global $wpdb;
2198
-
2199
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
2200
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
2201
-
2202
- while (1 > 0) {
2203
- // Get id of rows to delete.
2204
- $sql = $wpdb->prepare(
2205
- "SELECT id FROM $table_name WHERE DATE_ADD(date, INTERVAL %d DAY) < now() LIMIT 100000",
2206
- $days
2207
- );
2208
-
2209
- $ids_to_delete = $wpdb->get_col($sql);
2210
-
2211
- if (empty($ids_to_delete)) {
2212
- // Nothing to delete.
2213
- return;
2214
- }
2215
-
2216
- $sql_ids_in = implode(',', $ids_to_delete);
2217
-
2218
- // Add number of deleted rows to total_rows option.
2219
- $prev_total_rows = (int) get_option('simple_history_total_rows', 0);
2220
- $total_rows = $prev_total_rows + sizeof($ids_to_delete);
2221
- update_option('simple_history_total_rows', $total_rows);
2222
-
2223
- // Remove rows + contexts.
2224
- $sql_delete_history = "DELETE FROM {$table_name} WHERE id IN ($sql_ids_in)";
2225
- $sql_delete_history_context = "DELETE FROM {$table_name_contexts} WHERE history_id IN ($sql_ids_in)";
2226
-
2227
- $wpdb->query($sql_delete_history);
2228
- $wpdb->query($sql_delete_history_context);
2229
-
2230
- $message = _nx(
2231
- 'Simple History removed one event that were older than {days} days',
2232
- 'Simple History removed {num_rows} events that were older than {days} days',
2233
- count($ids_to_delete),
2234
- 'Database is being cleared automagically',
2235
- 'simple-history'
2236
- );
2237
-
2238
- SimpleLogger()->info($message, [
2239
- 'days' => $days,
2240
- 'num_rows' => count($ids_to_delete),
2241
- ]);
2242
-
2243
- $this->get_cache_incrementor(true);
2244
- }
2245
- }
2246
-
2247
- /**
2248
- * Return plain text output for a log row
2249
- * Uses the getLogRowPlainTextOutput of the logger that logged the row
2250
- * with fallback to SimpleLogger if logger is not available.
2251
- *
2252
- * @param context $row
2253
- * @return string
2254
- */
2255
- public function getLogRowPlainTextOutput($row)
2256
- {
2257
- $row_logger = $row->logger;
2258
- $logger = null;
2259
- $row->context = isset($row->context) && is_array($row->context) ? $row->context : [];
2260
-
2261
- if (!isset($row->context['_message_key'])) {
2262
- $row->context['_message_key'] = null;
2263
- }
2264
-
2265
- // Fallback to SimpleLogger if no logger exists for row
2266
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2267
- $row_logger = 'SimpleLogger';
2268
- }
2269
-
2270
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2271
-
2272
- return $logger->getLogRowPlainTextOutput($row);
2273
- }
2274
-
2275
- /**
2276
- * Return header output for a log row.
2277
- *
2278
- * Uses the getLogRowHeaderOutput of the logger that logged the row
2279
- * with fallback to SimpleLogger if logger is not available.
2280
- *
2281
- * Loggers are discouraged to override this in the loggers,
2282
- * because the output should be the same for all items in the GUI.
2283
- *
2284
- * @param object $row
2285
- * @return string
2286
- */
2287
- public function getLogRowHeaderOutput($row)
2288
- {
2289
- $row_logger = $row->logger;
2290
- $logger = null;
2291
- $row->context = isset($row->context) && is_array($row->context) ? $row->context : [];
2292
-
2293
- // Fallback to SimpleLogger if no logger exists for row
2294
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2295
- $row_logger = 'SimpleLogger';
2296
- }
2297
-
2298
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2299
-
2300
- return $logger->getLogRowHeaderOutput($row);
2301
- }
2302
-
2303
- /**
2304
- *
2305
- *
2306
- * @param object $row
2307
- * @return string
2308
- */
2309
- private function getLogRowSenderImageOutput($row)
2310
- {
2311
- $row_logger = $row->logger;
2312
- $logger = null;
2313
- $row->context = isset($row->context) && is_array($row->context) ? $row->context : [];
2314
-
2315
- // Fallback to SimpleLogger if no logger exists for row
2316
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2317
- $row_logger = 'SimpleLogger';
2318
- }
2319
-
2320
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2321
-
2322
- return $logger->getLogRowSenderImageOutput($row);
2323
- }
2324
-
2325
- public function getLogRowDetailsOutput($row)
2326
- {
2327
- $row_logger = $row->logger;
2328
- $logger = null;
2329
- $row->context = isset($row->context) && is_array($row->context) ? $row->context : [];
2330
-
2331
- // Fallback to SimpleLogger if no logger exists for row
2332
- if (!isset($this->instantiatedLoggers[$row_logger])) {
2333
- $row_logger = 'SimpleLogger';
2334
- }
2335
-
2336
- $logger = $this->instantiatedLoggers[$row_logger]['instance'];
2337
-
2338
- return $logger->getLogRowDetailsOutput($row);
2339
- }
2340
-
2341
- /**
2342
- * Works like json_encode, but adds JSON_PRETTY_PRINT if the current php version supports it
2343
- * i.e. PHP is 5.4.0 or greated
2344
- *
2345
- * @param mixed $value array|object|string|whatever that is json_encode'able.
2346
- */
2347
- public static function json_encode($value)
2348
- {
2349
- return version_compare(PHP_VERSION, '5.4.0') >= 0
2350
- ? json_encode($value, JSON_PRETTY_PRINT)
2351
- : json_encode($value);
2352
- }
2353
-
2354
- /**
2355
- * Returns true if $haystack ends with $needle
2356
- *
2357
- * @param string $haystack
2358
- * @param string $needle
2359
- */
2360
- public static function ends_with($haystack, $needle)
2361
- {
2362
- return $needle === substr($haystack, -strlen($needle));
2363
- }
2364
-
2365
- /**
2366
- * Returns the HTML output for a log row, to be used in the GUI/Activity Feed.
2367
- * This includes HTML for the header, the sender image, and the details.
2368
- *
2369
- * @param object $oneLogRow SimpleHistoryLogQuery array with data from SimpleHistoryLogQuery
2370
- * @return string
2371
- */
2372
- public function getLogRowHTMLOutput($oneLogRow, $args)
2373
- {
2374
- $defaults = [
2375
- 'type' => 'overview', // or "single" to include more stuff (used in for example modal details window)
2376
- ];
2377
-
2378
- $args = wp_parse_args($args, $defaults);
2379
-
2380
- $header_html = $this->getLogRowHeaderOutput($oneLogRow);
2381
- $plain_text_html = $this->getLogRowPlainTextOutput($oneLogRow);
2382
- $sender_image_html = $this->getLogRowSenderImageOutput($oneLogRow);
2383
-
2384
- // Details = for example thumbnail of media
2385
- $details_html = trim($this->getLogRowDetailsOutput($oneLogRow));
2386
- if ($details_html) {
2387
- $details_html = sprintf('<div class="SimpleHistoryLogitem__details">%1$s</div>', $details_html);
2388
- }
2389
-
2390
- // subsequentOccasions = including the current one
2391
- $occasions_count = $oneLogRow->subsequentOccasions - 1;
2392
- $occasions_html = '';
2393
-
2394
- if ($occasions_count > 0) {
2395
- $occasions_html = '<div class="SimpleHistoryLogitem__occasions">';
2396
-
2397
- $occasions_html .= '<a href="#" class="SimpleHistoryLogitem__occasionsLink">';
2398
- $occasions_html .= sprintf(
2399
- _n('+%1$s similar event', '+%1$s similar events', $occasions_count, 'simple-history'),
2400
- $occasions_count
2401
- );
2402
- $occasions_html .= '</a>';
2403
-
2404
- $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoading">';
2405
- $occasions_html .= sprintf(__('Loading…', 'simple-history'), $occasions_count);
2406
- $occasions_html .= '</span>';
2407
-
2408
- $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoaded">';
2409
- $occasions_html .= sprintf(__('Showing %1$s more', 'simple-history'), $occasions_count);
2410
- $occasions_html .= '</span>';
2411
-
2412
- $occasions_html .= '</div>';
2413
- }
2414
-
2415
- // Add data attributes to log row, so plugins can do stuff.
2416
- $data_attrs = '';
2417
- $data_attrs .= sprintf(' data-row-id="%1$d" ', $oneLogRow->id);
2418
- $data_attrs .= sprintf(' data-occasions-count="%1$d" ', $occasions_count);
2419
- $data_attrs .= sprintf(' data-occasions-id="%1$s" ', esc_attr($oneLogRow->occasionsID));
2420
-
2421
- // Add data attributes for remote address and other ip number headers.
2422
- if (isset($oneLogRow->context['_server_remote_addr'])) {
2423
- $data_attrs .= sprintf(' data-ip-address="%1$s" ', esc_attr($oneLogRow->context['_server_remote_addr']));
2424
- }
2425
-
2426
- $arr_found_additional_ip_headers = $this->instantiatedLoggers['SimpleLogger'][
2427
- 'instance'
2428
- ]->get_event_ip_number_headers($oneLogRow);
2429
-
2430
- if ($arr_found_additional_ip_headers) {
2431
- $data_attrs .= sprintf(' data-ip-address-multiple="1" ');
2432
- }
2433
-
2434
- // Add data attributes info for common things like logger, level, data, initiation.
2435
- $data_attrs .= sprintf(' data-logger="%1$s" ', esc_attr($oneLogRow->logger));
2436
- $data_attrs .= sprintf(' data-level="%1$s" ', esc_attr($oneLogRow->level));
2437
- $data_attrs .= sprintf(' data-date="%1$s" ', esc_attr($oneLogRow->date));
2438
- $data_attrs .= sprintf(' data-initiator="%1$s" ', esc_attr($oneLogRow->initiator));
2439
-
2440
- if (isset($oneLogRow->context['_user_id'])) {
2441
- $data_attrs .= sprintf(' data-initiator-user-id="%1$d" ', $oneLogRow->context['_user_id']);
2442
- }
2443
-
2444
- // If type is single then include more details.
2445
- // This is typically shown in the modal window when clickin the event date and time.
2446
- $more_details_html = '';
2447
- if ($args['type'] == 'single') {
2448
- $more_details_html = apply_filters(
2449
- 'simple_history/log_html_output_details_single/html_before_context_table',
2450
- $more_details_html,
2451
- $oneLogRow
2452
- );
2453
-
2454
- $more_details_html .= sprintf(
2455
- '<h2 class="SimpleHistoryLogitem__moreDetailsHeadline">%1$s</h2>',
2456
- __('Context data', 'simple-history')
2457
- );
2458
- $more_details_html .=
2459
- '<p>' . __('This is potentially useful meta data that a logger has saved.', 'simple-history') . '</p>';
2460
- $more_details_html .= "<table class='SimpleHistoryLogitem__moreDetailsContext'>";
2461
- $more_details_html .= sprintf(
2462
- '<tr>
 
 
 
2463
  <th>%1$s</th>
2464
  <th>%2$s</th>
2465
  </tr>',
2466
- 'Key',
2467
- 'Value'
2468
- );
2469
-
2470
- $logRowKeysToShow = array_fill_keys(array_keys((array) $oneLogRow), true);
2471
-
2472
- /**
2473
- * Filter what keys to show from oneLogRow
2474
- *
2475
- * Array is in format
2476
- *
2477
- * Array
2478
- * (
2479
- * [id] => 1
2480
- * [logger] => 1
2481
- * [level] => 1
2482
- * ...
2483
- * )
2484
- *
2485
- * @since 2.0.29
2486
- *
2487
- * @param array with keys to show. key to show = key. value = boolean to show or not.
2488
- * @param object log row to show details from
2489
- */
2490
- $logRowKeysToShow = apply_filters(
2491
- 'simple_history/log_html_output_details_table/row_keys_to_show',
2492
- $logRowKeysToShow,
2493
- $oneLogRow
2494
- );
2495
-
2496
- // Hide some keys by default
2497
- unset(
2498
- $logRowKeysToShow['occasionsID'],
2499
- $logRowKeysToShow['subsequentOccasions'],
2500
- $logRowKeysToShow['rep'],
2501
- $logRowKeysToShow['repeated'],
2502
- $logRowKeysToShow['occasionsIDType'],
2503
- $logRowKeysToShow['context'],
2504
- $logRowKeysToShow['type']
2505
- );
2506
-
2507
- foreach ($oneLogRow as $rowKey => $rowVal) {
2508
- // Only columns from oneLogRow that exist in logRowKeysToShow will be outputed
2509
- if (!array_key_exists($rowKey, $logRowKeysToShow) || !$logRowKeysToShow[$rowKey]) {
2510
- continue;
2511
- }
2512
-
2513
- // skip arrays and objects and such
2514
- if (is_array($rowVal) || is_object($rowVal)) {
2515
- continue;
2516
- }
2517
-
2518
- $more_details_html .= sprintf(
2519
- '<tr>
2520
  <td>%1$s</td>
2521
  <td>%2$s</td>
2522
  </tr>',
2523
- esc_html($rowKey),
2524
- esc_html($rowVal)
2525
- );
2526
- }
2527
-
2528
- $logRowContextKeysToShow = array_fill_keys(array_keys((array) $oneLogRow->context), true);
2529
-
2530
- /**
2531
- * Filter what keys to show from the row context
2532
- *
2533
- * Array is in format
2534
- *
2535
- * Array
2536
- * (
2537
- * [plugin_slug] => 1
2538
- * [plugin_name] => 1
2539
- * [plugin_title] => 1
2540
- * [plugin_description] => 1
2541
- * [plugin_author] => 1
2542
- * [plugin_version] => 1
2543
- * ...
2544
- * )
2545
- *
2546
- * @since 2.0.29
2547
- *
2548
- * @param array with keys to show. key to show = key. value = boolean to show or not.
2549
- * @param object log row to show details from
2550
- */
2551
- $logRowContextKeysToShow = apply_filters(
2552
- 'simple_history/log_html_output_details_table/context_keys_to_show',
2553
- $logRowContextKeysToShow,
2554
- $oneLogRow
2555
- );
2556
-
2557
- foreach ($oneLogRow->context as $contextKey => $contextVal) {
2558
- // Only columns from context that exist in logRowContextKeysToShow will be outputed
2559
- if (
2560
- !array_key_exists($contextKey, $logRowContextKeysToShow) ||
2561
- !$logRowContextKeysToShow[$contextKey]
2562
- ) {
2563
- continue;
2564
- }
2565
-
2566
- $more_details_html .= sprintf(
2567
- '<tr>
2568
  <td>%1$s</td>
2569
  <td>%2$s</td>
2570
  </tr>',
2571
- esc_html($contextKey),
2572
- esc_html($contextVal)
2573
- );
2574
- }
2575
-
2576
- $more_details_html .= '</table>';
2577
-
2578
- $more_details_html = apply_filters(
2579
- 'simple_history/log_html_output_details_single/html_after_context_table',
2580
- $more_details_html,
2581
- $oneLogRow
2582
- );
2583
-
2584
- $more_details_html = sprintf(
2585
- '<div class="SimpleHistoryLogitem__moreDetails">%1$s</div>',
2586
- $more_details_html
2587
- );
2588
- } // End if().
2589
-
2590
- // Classes to add to log item li element
2591
- $classes = [
2592
- 'SimpleHistoryLogitem',
2593
- "SimpleHistoryLogitem--loglevel-{$oneLogRow->level}",
2594
- "SimpleHistoryLogitem--logger-{$oneLogRow->logger}",
2595
- ];
2596
-
2597
- if (isset($oneLogRow->initiator) && !empty($oneLogRow->initiator)) {
2598
- $classes[] = 'SimpleHistoryLogitem--initiator-' . $oneLogRow->initiator;
2599
- }
2600
-
2601
- if ($arr_found_additional_ip_headers) {
2602
- $classes[] = 'SimpleHistoryLogitem--IPAddress-multiple';
2603
- }
2604
-
2605
- // Always append the log level tag
2606
- $log_level_tag_html = sprintf(
2607
- ' <span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%2$s</span>',
2608
- $oneLogRow->level,
2609
- $this->getLogLevelTranslated($oneLogRow->level)
2610
- );
2611
-
2612
- $plain_text_html .= $log_level_tag_html;
2613
-
2614
- /**
2615
- * Filter to modify classes added to item li element
2616
- *
2617
- * @since 2.0.7
2618
- *
2619
- * @param $classes Array with classes
2620
- */
2621
- $classes = apply_filters('simple_history/logrowhtmloutput/classes', $classes);
2622
-
2623
- // Generate the HTML output for a row
2624
- $output = sprintf(
2625
- '
2626
  <li %8$s class="%10$s">
2627
  <div class="SimpleHistoryLogitem__firstcol">
2628
  <div class="SimpleHistoryLogitem__senderImage">%3$s</div>
@@ -2636,344 +2607,338 @@ Because Simple History was only recently installed, this feed does not display m
2636
  </div>
2637
  </li>
2638
  ',
2639
- $header_html, // 1
2640
- $plain_text_html, // 2
2641
- $sender_image_html, // 3
2642
- $occasions_html, // 4
2643
- $oneLogRow->level, // 5
2644
- $details_html, // 6
2645
- $oneLogRow->logger, // 7
2646
- $data_attrs, // 8 data attributes
2647
- $more_details_html, // 9
2648
- esc_attr(join(' ', $classes)) // 10
2649
- );
2650
-
2651
- // Get the main message row.
2652
- // Should be as plain as possible, like plain text
2653
- // but with links to for example users and posts
2654
- // SimpleLoggerFormatter::getRowTextOutput($oneLogRow);
2655
- // Get detailed HTML-based output
2656
- // May include images, lists, any cool stuff needed to view
2657
- // SimpleLoggerFormatter::getRowHTMLOutput($oneLogRow);
2658
- return trim($output);
2659
- }
2660
-
2661
- /**
2662
- * Return translated loglevel
2663
- *
2664
- * @since 2.0.14
2665
- * @param string $loglevel
2666
- * @return string translated loglevel
2667
- */
2668
- public function getLogLevelTranslated($loglevel)
2669
- {
2670
- $str_translated = '';
2671
-
2672
- switch ($loglevel) {
2673
- // Lowercase
2674
- case 'emergency':
2675
- $str_translated = _x('emergency', 'Log level in gui', 'simple-history');
2676
- break;
2677
-
2678
- case 'alert':
2679
- $str_translated = _x('alert', 'Log level in gui', 'simple-history');
2680
- break;
2681
-
2682
- case 'critical':
2683
- $str_translated = _x('critical', 'Log level in gui', 'simple-history');
2684
- break;
2685
-
2686
- case 'error':
2687
- $str_translated = _x('error', 'Log level in gui', 'simple-history');
2688
- break;
2689
-
2690
- case 'warning':
2691
- $str_translated = _x('warning', 'Log level in gui', 'simple-history');
2692
- break;
2693
-
2694
- case 'notice':
2695
- $str_translated = _x('notice', 'Log level in gui', 'simple-history');
2696
- break;
2697
-
2698
- case 'info':
2699
- $str_translated = _x('info', 'Log level in gui', 'simple-history');
2700
- break;
2701
-
2702
- case 'debug':
2703
- $str_translated = _x('debug', 'Log level in gui', 'simple-history');
2704
- break;
2705
-
2706
- // Uppercase
2707
- case 'Emergency':
2708
- $str_translated = _x('Emergency', 'Log level in gui', 'simple-history');
2709
- break;
2710
-
2711
- case 'Alert':
2712
- $str_translated = _x('Alert', 'Log level in gui', 'simple-history');
2713
- break;
2714
-
2715
- case 'Critical':
2716
- $str_translated = _x('Critical', 'Log level in gui', 'simple-history');
2717
- break;
2718
-
2719
- case 'Error':
2720
- $str_translated = _x('Error', 'Log level in gui', 'simple-history');
2721
- break;
2722
-
2723
- case 'Warning':
2724
- $str_translated = _x('Warning', 'Log level in gui', 'simple-history');
2725
- break;
2726
-
2727
- case 'Notice':
2728
- $str_translated = _x('Notice', 'Log level in gui', 'simple-history');
2729
- break;
2730
-
2731
- case 'Info':
2732
- $str_translated = _x('Info', 'Log level in gui', 'simple-history');
2733
- break;
2734
-
2735
- case 'Debug':
2736
- $str_translated = _x('Debug', 'Log level in gui', 'simple-history');
2737
- break;
2738
-
2739
- default:
2740
- $str_translated = $loglevel;
2741
- } // End switch().
2742
-
2743
- return $str_translated;
2744
- }
2745
-
2746
- public function getInstantiatedLoggers()
2747
- {
2748
- return $this->instantiatedLoggers;
2749
- }
2750
-
2751
- public function getInstantiatedDropins()
2752
- {
2753
- return $this->instantiatedDropins;
2754
- }
2755
-
2756
- /**
2757
- * @param string $slug
2758
- * @return mixed logger instance if found, bool false if logger not found
2759
- */
2760
- public function getInstantiatedLoggerBySlug($slug = '')
2761
- {
2762
- if (empty($slug)) {
2763
- return false;
2764
- }
2765
-
2766
- foreach ($this->getInstantiatedLoggers() as $one_logger) {
2767
- if ($slug == $one_logger['instance']->slug) {
2768
- return $one_logger['instance'];
2769
- }
2770
- }
2771
-
2772
- return false;
2773
- }
2774
-
2775
- /**
2776
- * Check which loggers a user has the right to read and return an array
2777
- * with all loggers they are allowed to read
2778
- *
2779
- * @param int $user_id Id of user to get loggers for. Defaults to current user id.
2780
- * @param string $format format to return loggers in. Default is array. Can also be "sql"
2781
- * @return array
2782
- */
2783
- public function getLoggersThatUserCanRead($user_id = '', $format = 'array')
2784
- {
2785
- $arr_loggers_user_can_view = [];
2786
-
2787
- if (!is_numeric($user_id)) {
2788
- $user_id = get_current_user_id();
2789
- }
2790
-
2791
- $loggers = $this->getInstantiatedLoggers();
2792
- foreach ($loggers as $one_logger) {
2793
- $logger_capability = $one_logger['instance']->getCapability();
2794
-
2795
- // $arr_loggers_user_can_view = apply_filters("simple_history/loggers_user_can_read", $user_id, $arr_loggers_user_can_view);
2796
- $user_can_read_logger = user_can($user_id, $logger_capability);
2797
- $user_can_read_logger = apply_filters(
2798
- 'simple_history/loggers_user_can_read/can_read_single_logger',
2799
- $user_can_read_logger,
2800
- $one_logger['instance'],
2801
- $user_id
2802
- );
2803
-
2804
- if ($user_can_read_logger) {
2805
- $arr_loggers_user_can_view[] = $one_logger;
2806
- }
2807
- }
2808
-
2809
- /**
2810
- * Fires before Simple History does it's init stuff
2811
- *
2812
- * @since 2.0
2813
- *
2814
- * @param array $arr_loggers_user_can_view Array with loggers that user $user_id can read
2815
- * @param int user_id ID of user to check read capability for
2816
- */
2817
- $arr_loggers_user_can_view = apply_filters(
2818
- 'simple_history/loggers_user_can_read',
2819
- $arr_loggers_user_can_view,
2820
- $user_id
2821
- );
2822
-
2823
- // just return array with slugs in parenthesis suitable for sql-where
2824
- if ('sql' == $format) {
2825
- $str_return = '(';
2826
-
2827
- if (sizeof($arr_loggers_user_can_view)) {
2828
- foreach ($arr_loggers_user_can_view as $one_logger) {
2829
- $str_return .= sprintf('"%1$s", ', esc_sql($one_logger['instance']->slug));
2830
- }
2831
-
2832
- $str_return = rtrim($str_return, ' ,');
2833
- } else {
2834
- // user was not allowed to read any loggers, return in (NULL) to return nothing
2835
- $str_return .= 'NULL';
2836
- }
2837
-
2838
- $str_return .= ')';
2839
-
2840
- return $str_return;
2841
- }
2842
-
2843
- return $arr_loggers_user_can_view;
2844
- }
2845
-
2846
- /**
2847
- * Retrieve the avatar for a user who provided a user ID or email address.
2848
- * A modified version of the function that comes with WordPress, but we
2849
- * want to allow/show gravatars even if they are disabled in discussion settings
2850
- *
2851
- * @since 2.0
2852
- *
2853
- * @param string $email email address
2854
- * @param int $size Size of the avatar image
2855
- * @param string $default URL to a default image to use if no avatar is available
2856
- * @param string $alt Alternative text to use in image tag. Defaults to blank
2857
- * @return string <img> tag for the user's avatar
2858
- */
2859
- public function get_avatar($email, $size = '96', $default = '', $alt = false)
2860
- {
2861
- // WP setting for avatars is to show, so just use the built in function
2862
- if (get_option('show_avatars')) {
2863
- $avatar = get_avatar($email, $size, $default, $alt);
2864
-
2865
- return $avatar;
2866
- } else {
2867
- // WP setting for avatar was to not show, but we do it anyway, using the same code as get_avatar() would have used
2868
- if (false === $alt) {
2869
- $safe_alt = '';
2870
- } else {
2871
- $safe_alt = esc_attr($alt);
2872
- }
2873
-
2874
- if (!is_numeric($size)) {
2875
- $size = '96';
2876
- }
2877
-
2878
- if (empty($default)) {
2879
- $avatar_default = get_option('avatar_default');
2880
- if (empty($avatar_default)) {
2881
- $default = 'mystery';
2882
- } else {
2883
- $default = $avatar_default;
2884
- }
2885
- }
2886
-
2887
- if (!empty($email)) {
2888
- $email_hash = md5(strtolower(trim($email)));
2889
- }
2890
-
2891
- if (is_ssl()) {
2892
- $host = 'https://secure.gravatar.com';
2893
- } else {
2894
- if (!empty($email)) {
2895
- $host = sprintf('http://%d.gravatar.com', hexdec($email_hash[0]) % 2);
2896
- } else {
2897
- $host = 'http://0.gravatar.com';
2898
- }
2899
- }
2900
-
2901
- if ('mystery' == $default) {
2902
- $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}";
2903
- } elseif ('blank' == $default) {
2904
- $default = $email ? 'blank' : includes_url('images/blank.gif');
2905
- } elseif (!empty($email) && 'gravatar_default' == $default) {
2906
- $default = '';
2907
- } elseif ('gravatar_default' == $default) {
2908
- $default = "$host/avatar/?s={$size}";
2909
- } elseif (empty($email)) {
2910
- $default = "$host/avatar/?d=$default&amp;s={$size}";
2911
- } elseif (strpos($default, 'http://') === 0) {
2912
- $default = add_query_arg('s', $size, $default);
2913
- }
2914
-
2915
- if (!empty($email)) {
2916
- $out = "$host/avatar/";
2917
- $out .= $email_hash;
2918
- $out .= '?s=' . $size;
2919
- $out .= '&amp;d=' . urlencode($default);
2920
-
2921
- $rating = get_option('avatar_rating');
2922
- if (!empty($rating)) {
2923
- $out .= "&amp;r={$rating}";
2924
- }
2925
-
2926
- $out = str_replace('&#038;', '&amp;', esc_url($out));
2927
- $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo' height='{$size}' width='{$size}' />";
2928
- } else {
2929
- $out = esc_url($default);
2930
- $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo avatar-default' height='{$size}' width='{$size}' />";
2931
- }
2932
-
2933
- /**
2934
- * Filter the avatar to retrieve.
2935
- * Same filter WordPress uses
2936
- *
2937
- * @since 2.0.19
2938
- *
2939
- * @param string $avatar Image tag for the user's avatar.
2940
- * @param int|object|string $id_or_email A user ID, email address, or comment object.
2941
- * @param int $size Square avatar width and height in pixels to retrieve.
2942
- * @param string $alt Alternative text to use in the avatar image tag.
2943
- * Default empty.
2944
- */
2945
- $avatar = apply_filters('get_avatar', $avatar, $email, $size, $default, $alt);
2946
-
2947
- return $avatar;
2948
- } // End if().
2949
- }
2950
-
2951
- /**
2952
- * Quick stats above the log
2953
- * Uses filter "simple_history/history_page/before_gui" to output its contents
2954
- */
2955
- public function output_quick_stats()
2956
- {
2957
- global $wpdb;
2958
-
2959
- // Get number of events today
2960
- $logQuery = new SimpleHistoryLogQuery();
2961
- $logResults = $logQuery->query([
2962
- 'posts_per_page' => 1,
2963
- 'date_from' => strtotime('today'),
2964
- ]);
2965
-
2966
- $total_row_count = (int) $logResults['total_row_count'];
2967
-
2968
- // Get sql query for where to read only loggers current user is allowed to read/view
2969
- $sql_loggers_in = $this->getLoggersThatUserCanRead(get_current_user_id(), 'sql');
2970
-
2971
- // Get number of users today, i.e. events with wp_user as initiator
2972
- $sql_users_today = sprintf(
2973
- '
2974
  SELECT
2975
  DISTINCT(c.value) AS user_id
2976
- #h.id, h.logger, h.level, h.initiator, h.date
2977
  FROM %3$s AS h
2978
  INNER JOIN %4$s AS c
2979
  ON c.history_id = h.id AND c.key = "_user_id"
@@ -2982,226 +2947,208 @@ Because Simple History was only recently installed, this feed does not display m
2982
  AND logger IN %1$s
2983
  AND date > "%2$s"
2984
  ',
2985
- $sql_loggers_in,
2986
- date('Y-m-d H:i', strtotime('today')),
2987
- $wpdb->prefix . SimpleHistory::DBTABLE,
2988
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
2989
- );
2990
-
2991
- $cache_key = 'quick_stats_users_today_' . md5(serialize($sql_loggers_in));
2992
- $cache_group = 'simple-history-' . $this->get_cache_incrementor();
2993
- $results_users_today = wp_cache_get($cache_key, $cache_group);
2994
-
2995
- if (false === $results_users_today) {
2996
- $results_users_today = $wpdb->get_results($sql_users_today);
2997
- wp_cache_set($cache_key, $results_users_today, $cache_group);
2998
- }
2999
-
3000
- $count_users_today = sizeof($results_users_today);
3001
-
3002
- // Get number of other sources (not wp_user)
3003
- $sql_other_sources_where = sprintf(
3004
- '
3005
  initiator <> "wp_user"
3006
  AND logger IN %1$s
3007
  AND date > "%2$s"
3008
  ',
3009
- $sql_loggers_in,
3010
- date('Y-m-d H:i', strtotime('today')),
3011
- $wpdb->prefix . SimpleHistory::DBTABLE,
3012
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
3013
- );
3014
 
3015
- $sql_other_sources_where = apply_filters('simple_history/quick_stats_where', $sql_other_sources_where);
3016
 
3017
- $sql_other_sources = sprintf(
3018
- '
3019
  SELECT
3020
  DISTINCT(h.initiator) AS initiator
3021
  FROM %3$s AS h
3022
  WHERE
3023
  %5$s
3024
  ',
3025
- $sql_loggers_in,
3026
- date('Y-m-d H:i', strtotime('today')),
3027
- $wpdb->prefix . SimpleHistory::DBTABLE,
3028
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS,
3029
- $sql_other_sources_where // 5
3030
- );
3031
- // sf_d($sql_other_sources, '$sql_other_sources');
3032
- $cache_key = 'quick_stats_results_other_sources_today_' . md5(serialize($sql_other_sources));
3033
- $results_other_sources_today = wp_cache_get($cache_key, $cache_group);
3034
-
3035
- if (false === $results_other_sources_today) {
3036
- $results_other_sources_today = $wpdb->get_results($sql_other_sources);
3037
- wp_cache_set($cache_key, $results_other_sources_today, $cache_group);
3038
- }
3039
-
3040
- $count_other_sources = sizeof($results_other_sources_today);
3041
- // sf_d($logResults, '$logResults');
3042
- // sf_d($results_users_today, '$sql_users_today');
3043
- // sf_d($results_other_sources_today, '$results_other_sources_today');
3044
- ?>
3045
- <div class="SimpleHistoryQuickStats">
3046
- <p>
3047
- <?php
3048
- $msg_tmpl = '';
3049
-
3050
- // No results today at all
3051
- if ($total_row_count == 0) {
3052
- $msg_tmpl = __('No events today so far.', 'simple-history');
3053
- } else {
3054
- /*
3055
- Type of results
3056
- x1 event today from 1 user.
3057
- x1 event today from 1 source.
3058
- 3 events today from 1 user.
3059
- x2 events today from 2 users.
3060
- x2 events today from 1 user and 1 other source.
3061
- x3 events today from 2 users and 1 other source.
3062
- x3 events today from 1 user and 2 other sources.
3063
- x4 events today from 2 users and 2 other sources.
3064
- */
3065
-
3066
- // A single event existed and was from a user
3067
- // 1 event today from 1 user.
3068
- if ($total_row_count == 1 && $count_users_today == 1) {
3069
- $msg_tmpl .= __('One event today from one user.', 'simple-history');
3070
- }
3071
-
3072
- // A single event existed and was from another source
3073
- // 1 event today from 1 source.
3074
- if ($total_row_count == 1 && !$count_users_today) {
3075
- $msg_tmpl .= __('One event today from one source.', 'simple-history');
3076
- }
3077
-
3078
- // Multiple events from a single user
3079
- // 3 events today from one user.
3080
- if ($total_row_count > 1 && $count_users_today == 1 && !$count_other_sources) {
3081
- $msg_tmpl .= __('%1$d events today from one user.', 'simple-history');
3082
- }
3083
-
3084
- // Multiple events from only users
3085
- // 2 events today from 2 users.
3086
- if ($total_row_count > 1 && $count_users_today == $total_row_count) {
3087
- $msg_tmpl .= __('%1$d events today from %2$d users.', 'simple-history');
3088
- }
3089
-
3090
- // Multiple events from 1 single user and 1 single other source
3091
- // 2 events today from 1 user and 1 other source.
3092
- if ($total_row_count && 1 == $count_users_today && 1 == $count_other_sources) {
3093
- $msg_tmpl .= __('%1$d events today from one user and one other source.', 'simple-history');
3094
- }
3095
-
3096
- // Multiple events from multple users but from only 1 single other source
3097
- // 3 events today from 2 users and 1 other source.
3098
- if ($total_row_count > 1 && $count_users_today > 1 && $count_other_sources == 1) {
3099
- $msg_tmpl .= __('%1$d events today from one user and one other source.', 'simple-history');
3100
- }
3101
-
3102
- // Multiple events from 1 user but from multiple other source
3103
- // 3 events today from 1 user and 2 other sources.
3104
- if ($total_row_count > 1 && 1 == $count_users_today && $count_other_sources > 1) {
3105
- $msg_tmpl .= __('%1$d events today from one user and %3$d other sources.', 'simple-history');
3106
- }
3107
-
3108
- // Multiple events from multiple user and from multiple other sources
3109
- // 4 events today from 2 users and 2 other sources.
3110
- if ($total_row_count > 1 && $count_users_today > 1 && $count_other_sources > 1) {
3111
- $msg_tmpl .= __('%1$s events today from %2$d users and %3$d other sources.', 'simple-history');
3112
- }
3113
- } // End if().
3114
-
3115
- // only show stats if we have something to output
3116
- if ($msg_tmpl) {
3117
- printf(
3118
- $msg_tmpl,
3119
- $logResults['total_row_count'], // 1
3120
- $count_users_today, // 2
3121
- $count_other_sources // 3
3122
- );
3123
-
3124
- // Space between texts
3125
- /*
3126
- echo " ";
3127
-
3128
- // http://playground-root.ep/wp-admin/options-general.php?page=simple_history_settings_menu_slug&selected-tab=stats
3129
- printf(
3130
- '<a href="%1$s">View more stats</a>.',
3131
- add_query_arg("selected-tab", "stats", menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0))
3132
- );
3133
- */
3134
- }?>
3135
- </p>
3136
- </div>
3137
- <?php
3138
- }
3139
-
3140
- /**
3141
- * https://www.tollmanz.com/invalidation-schemes/
3142
- *
3143
- * @param $refresh bool
3144
- * @return string
3145
- */
3146
- public static function get_cache_incrementor($refresh = false)
3147
- {
3148
- $incrementor_key = 'simple_history_incrementor';
3149
- $incrementor_value = wp_cache_get($incrementor_key);
3150
-
3151
- if (false === $incrementor_value || true === $refresh) {
3152
- $incrementor_value = time();
3153
- wp_cache_set($incrementor_key, $incrementor_value);
3154
- }
3155
-
3156
- // echo "<br>incrementor_value: $incrementor_value";
3157
- return $incrementor_value;
3158
- }
3159
-
3160
- // Number of rows the last n days
3161
- public function get_num_events_last_n_days($period_days = 28)
3162
- {
3163
- $transient_key = 'sh_' . md5(__METHOD__ . $period_days . '_2');
3164
-
3165
- $count = get_transient($transient_key);
3166
-
3167
- if (false === $count) {
3168
- global $wpdb;
3169
-
3170
- $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead(null, 'sql');
3171
-
3172
- $sql = sprintf(
3173
- '
3174
  SELECT count(*)
3175
  FROM %1$s
3176
  WHERE UNIX_TIMESTAMP(date) >= %2$d
3177
  AND logger IN %3$s
3178
  ',
3179
- $wpdb->prefix . SimpleHistory::DBTABLE,
3180
- strtotime("-$period_days days"),
3181
- $sqlStringLoggersUserCanRead
3182
- );
3183
 
3184
- $count = $wpdb->get_var($sql);
3185
 
3186
- set_transient($transient_key, $count, HOUR_IN_SECONDS);
3187
- }
3188
 
3189
- return $count;
3190
- }
3191
 
3192
- public function get_num_events_per_day_last_n_days($period_days = 28)
3193
- {
3194
- $transient_key = 'sh_' . md5(__METHOD__ . $period_days . '_2');
3195
 
3196
- $dates = get_transient($transient_key);
 
3197
 
3198
- if (false === $dates) {
3199
- global $wpdb;
3200
 
3201
- $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead(null, 'sql');
3202
-
3203
- $sql = sprintf(
3204
- '
3205
  SELECT
3206
  date_format(date, "%%Y-%%m-%%d") AS yearDate,
3207
  count(date) AS count
@@ -3213,66 +3160,67 @@ Because Simple History was only recently installed, this feed does not display m
3213
  GROUP BY yearDate
3214
  ORDER BY yearDate ASC
3215
  ',
3216
- $wpdb->prefix . SimpleHistory::DBTABLE,
3217
- strtotime("-$period_days days"),
3218
- $sqlStringLoggersUserCanRead
3219
- );
3220
-
3221
- $dates = $wpdb->get_results($sql);
3222
-
3223
- set_transient($transient_key, $dates, HOUR_IN_SECONDS);
3224
- // echo "set";exit;
3225
- } else {
3226
- // echo "get";exit;
3227
- }
3228
-
3229
- return $dates;
3230
- }
3231
-
3232
- // Number of unique events the last n days
3233
- public function get_unique_events_for_days($days = 7)
3234
- {
3235
- global $wpdb;
3236
-
3237
- $days = (int) $days;
3238
-
3239
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
3240
-
3241
- $cache_key = 'sh_' . md5(__METHOD__ . $days);
3242
-
3243
- $numEvents = get_transient($cache_key);
3244
-
3245
- if (false == $numEvents) {
3246
- $sql = $wpdb->prepare(
3247
- "
3248
  SELECT count( DISTINCT occasionsID )
3249
  FROM $table_name
3250
  WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3251
- ",
3252
- $days
3253
- );
3254
-
3255
- $numEvents = $wpdb->get_var($sql);
3256
-
3257
- set_transient($cache_key, $numEvents, HOUR_IN_SECONDS);
3258
- }
3259
-
3260
- return $numEvents;
3261
- }
3262
-
3263
- /**
3264
- * Output an admin notice about logger slug being to long
3265
- */
3266
- public function admin_notice_logger_slug_to_long()
3267
- {
3268
- ?>
3269
- <div class="error notice">
3270
- <p>
3271
- <?php echo esc_html__(
3272
- 'The slug for a logger in Simple History can be max 30 chars long.',
3273
- 'simple-history'
3274
- ); ?></p>
3275
- </div>
3276
- <?php
3277
- }
 
 
 
3278
  }
2
 
3
  // phpcs:disable PSR12.Properties.ConstantVisibility.NotFound
4
 
5
+ defined( 'ABSPATH' ) || die();
6
 
7
  /**
8
  * Main class for Simple History
9
  */
10
+ class SimpleHistory {
11
+
12
+ const NAME = 'Simple History';
13
+
14
+ /**
15
+ * For singleton
16
+ */
17
+ private static $instance;
18
+
19
+ /**
20
+ * Array with external loggers to load
21
+ */
22
+ private $externalLoggers;
23
+
24
+ /**
25
+ * Array with external dropins to load
26
+ */
27
+ private $externalDropins;
28
+
29
+ /**
30
+ * Array with all instantiated loggers
31
+ */
32
+ private $instantiatedLoggers;
33
+
34
+ /**
35
+ * Array with all instantiated dropins
36
+ */
37
+ private $instantiatedDropins;
38
+
39
+ /**
40
+ * Bool if gettext filter function should be active
41
+ * Should only be active during the load of a logger
42
+ */
43
+ private $doFilterGettext = false;
44
+
45
+ /**
46
+ * Used by gettext filter to temporarily store current logger
47
+ */
48
+ private $doFilterGettext_currentLogger = null;
49
+
50
+ /**
51
+ * Used to store latest translations used by __()
52
+ * Required to automagically determine orginal text and text domain
53
+ * for calls like this `SimpleLogger()->log( __("My translated message") );`
54
+ */
55
+ public $gettextLatestTranslations = array();
56
+
57
+ /**
58
+ * All registered settings tabs
59
+ */
60
+ private $arr_settings_tabs = array();
61
+
62
+ const DBTABLE = 'simple_history';
63
+ const DBTABLE_CONTEXTS = 'simple_history_contexts';
64
+
65
+ /** Slug for the settings menu */
66
+ const SETTINGS_MENU_SLUG = 'simple_history_settings_menu_slug';
67
+
68
+ /** Slug for the settings menu */
69
+ const SETTINGS_GENERAL_OPTION_GROUP = 'simple_history_settings_group';
70
+
71
+ /** ID for the general settings section */
72
+ const SETTINGS_SECTION_GENERAL_ID = 'simple_history_settings_section_general';
73
+
74
+ public function __construct() {
75
+ $this->init();
76
+ }
77
+
78
+ /**
79
+ * @since 2.5.2
80
+ */
81
+ public function init() {
82
+ /**
83
+ * Fires before Simple History does it's init stuff
84
+ *
85
+ * @since 2.0
86
+ *
87
+ * @param SimpleHistory $SimpleHistory This class.
88
+ */
89
+ do_action( 'simple_history/before_init', $this );
90
+
91
+ $this->setup_variables();
92
+
93
+ // Actions and filters, ordered by order specified in codex: http://codex.wordpress.org/Plugin_API/Action_Reference
94
+ add_action( 'after_setup_theme', array( $this, 'load_plugin_textdomain' ) );
95
+ add_action( 'after_setup_theme', array( $this, 'add_default_settings_tabs' ) );
96
+
97
+ // Plugins and dropins are loaded using the "after_setup_theme" filter so
98
+ // themes can use filters to modify the loading of them.
99
+ // The drawback with this is that for example logouts done when plugins like
100
+ // iThemes Security is installed is not logged, because those plugins fire wp_logout()
101
+ // using filter "plugins_loaded", i.e. before simple history has loaded its filters.
102
+ add_action( 'after_setup_theme', array( $this, 'load_loggers' ) );
103
+ add_action( 'after_setup_theme', array( $this, 'load_dropins' ) );
104
+
105
+ // Run before loading of loggers and before menu items are added.
106
+ add_action( 'after_setup_theme', array( $this, 'check_for_upgrade' ), 5 );
107
+
108
+ add_action( 'after_setup_theme', array( $this, 'setup_cron' ) );
109
+
110
+ // Filters and actions not called during regular boot.
111
+ add_filter( 'gettext', array( $this, 'filter_gettext' ), 20, 3 );
112
+ add_filter( 'gettext_with_context', array( $this, 'filter_gettext_with_context' ), 20, 4 );
113
+
114
+ add_filter( 'gettext', array( $this, 'filter_gettext_storeLatestTranslations' ), 10, 3 );
115
+
116
+ add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_network_menu_item' ), 40 );
117
+ add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_menu_item' ), 40 );
118
+
119
+ /**
120
+ * Filter that is used to log things, without the need to check that simple history is available
121
+ * i.e. you can have simple history acivated and log things and then you can disable the plugin
122
+ * and no errors will occur
123
+ *
124
+ * Usage:
125
+ * apply_filters("simple_history_log", "This is the log message");
126
+ * apply_filters("simple_history_log", "This is the log message with some extra data/info", ["extraThing1" => $variableWIihThing]);
127
+ * apply_filters("simple_history_log", "This is the log message with severity debug", null, "debug");
128
+ * apply_filters("simple_history_log", "This is the log message with severity debug and with some extra info/data logged", ["userData" => $userData, "shoppingCartDebugData" => $shopDebugData], "debug",);
129
+ *
130
+ * @since 2.13
131
+ */
132
+ add_filter( 'simple_history_log', array( $this, 'on_filter_simple_history_log' ), 10, 3 );
133
+
134
+ /**
135
+ * Filter to log with specific log level, for example:
136
+ * apply_filters('simple_history_log_debug', 'My debug message');
137
+ * apply_filters('simple_history_log_warning', 'My warning message');
138
+ *
139
+ * @since 2.17
140
+ */
141
+ add_filter( 'simple_history_log_emergency', array( $this, 'on_filter_simple_history_log_emergency' ), 10, 3 );
142
+ add_filter( 'simple_history_log_alert', array( $this, 'on_filter_simple_history_log_alert' ), 10, 2 );
143
+ add_filter( 'simple_history_log_critical', array( $this, 'on_filter_simple_history_log_critical' ), 10, 2 );
144
+ add_filter( 'simple_history_log_error', array( $this, 'on_filter_simple_history_log_error' ), 10, 2 );
145
+ add_filter( 'simple_history_log_warning', array( $this, 'on_filter_simple_history_log_warning' ), 10, 2 );
146
+ add_filter( 'simple_history_log_notice', array( $this, 'on_filter_simple_history_log_notice' ), 10, 2 );
147
+ add_filter( 'simple_history_log_info', array( $this, 'on_filter_simple_history_log_info' ), 10, 2 );
148
+ add_filter( 'simple_history_log_debug', array( $this, 'on_filter_simple_history_log_debug' ), 10, 2 );
149
+
150
+ if ( is_admin() ) {
151
+ $this->add_admin_actions();
152
+ }
153
+
154
+ /**
155
+ * Fires after Simple History has done it's init stuff
156
+ *
157
+ * @since 2.0
158
+ *
159
+ * @param SimpleHistory $SimpleHistory This class.
160
+ */
161
+ do_action( 'simple_history/after_init', $this );
162
+ }
163
+
164
+ /**
165
+ * Log a message
166
+ *
167
+ * Function called when running filter "simple_history_log"
168
+ *
169
+ * @since 2.13
170
+ * @param string $message The message to log.
171
+ * @param array $context Optional context to add to the logged data.
172
+ * @param string $level The loglevel. Must be one of the existing ones. Defaults to "info".
173
+ */
174
+ public function on_filter_simple_history_log( $message = null, $context = null, $level = 'info' ) {
175
+ SimpleLogger()->log( $level, $message, $context );
176
+ }
177
+
178
+ /**
179
+ * Log a message, triggered by filter 'on_filter_simple_history_log_emergency'.
180
+ *
181
+ * @param string $message The message to log.
182
+ * @param array $context The context (optional).
183
+ */
184
+ public function on_filter_simple_history_log_emergency( $message = null, $context = null ) {
185
+ SimpleLogger()->log( 'emergency', $message, $context );
186
+ }
187
+
188
+ /**
189
+ * Log a message, triggered by filter 'on_filter_simple_history_log_alert'.
190
+ *
191
+ * @param string $message The message to log.
192
+ * @param array $context The context (optional).
193
+ */
194
+ public function on_filter_simple_history_log_alert( $message = null, $context = null ) {
195
+ SimpleLogger()->log( 'alert', $message, $context );
196
+ }
197
+
198
+ /**
199
+ * Log a message, triggered by filter 'on_filter_simple_history_log_critical'.
200
+ *
201
+ * @param string $message The message to log.
202
+ * @param array $context The context (optional).
203
+ */
204
+ public function on_filter_simple_history_log_critical( $message = null, $context = null ) {
205
+ SimpleLogger()->log( 'critical', $message, $context );
206
+ }
207
+
208
+ /**
209
+ * Log a message, triggered by filter 'on_filter_simple_history_log_error'.
210
+ *
211
+ * @param string $message The message to log.
212
+ * @param array $context The context (optional).
213
+ */
214
+ public function on_filter_simple_history_log_error( $message = null, $context = null ) {
215
+ SimpleLogger()->log( 'error', $message, $context );
216
+ }
217
+
218
+ /**
219
+ * Log a message, triggered by filter 'on_filter_simple_history_log_warning'.
220
+ *
221
+ * @param string $message The message to log.
222
+ * @param array $context The context (optional).
223
+ */
224
+ public function on_filter_simple_history_log_warning( $message = null, $context = null ) {
225
+ SimpleLogger()->log( 'warning', $message, $context );
226
+ }
227
+
228
+ /**
229
+ * Log a message, triggered by filter 'on_filter_simple_history_log_notice'.
230
+ *
231
+ * @param string $message The message to log.
232
+ * @param array $context The context (optional).
233
+ */
234
+ public function on_filter_simple_history_log_notice( $message = null, $context = null ) {
235
+ SimpleLogger()->log( 'notice', $message, $context );
236
+ }
237
+
238
+ /**
239
+ * Log a message, triggered by filter 'on_filter_simple_history_log_info'.
240
+ *
241
+ * @param string $message The message to log.
242
+ * @param array $context The context (optional).
243
+ */
244
+ public function on_filter_simple_history_log_info( $message = null, $context = null ) {
245
+ SimpleLogger()->log( 'info', $message, $context );
246
+ }
247
+
248
+ /**
249
+ * Log a message, triggered by filter 'on_filter_simple_history_log_debug'.
250
+ *
251
+ * @param string $message The message to log.
252
+ * @param array $context The context (optional).
253
+ */
254
+ public function on_filter_simple_history_log_debug( $message = null, $context = null ) {
255
+ SimpleLogger()->log( 'debug', $message, $context );
256
+ }
257
+
258
+ /**
259
+ * @since 2.5.2
260
+ */
261
+ private function add_admin_actions() {
262
+ add_action( 'admin_menu', array( $this, 'add_admin_pages' ) );
263
+ add_action( 'admin_menu', array( $this, 'add_settings' ) );
264
+
265
+ add_action( 'admin_footer', array( $this, 'add_js_templates' ) );
266
+
267
+ add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) );
268
+
269
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
270
+
271
+ add_action( 'admin_head', array( $this, 'onAdminHead' ) );
272
+ add_action( 'admin_footer', array( $this, 'onAdminFooter' ) );
273
+
274
+ add_action( 'simple_history/history_page/before_gui', array( $this, 'output_quick_stats' ) );
275
+ add_action( 'simple_history/dashboard/before_gui', array( $this, 'output_quick_stats' ) );
276
+
277
+ add_action( 'wp_ajax_simple_history_api', array( $this, 'api' ) );
278
+
279
+ add_filter( 'plugin_action_links_simple-history/index.php', array( $this, 'plugin_action_links' ), 10, 4 );
280
+ }
281
+
282
+ /**
283
+ * Adds a "View history" item/shortcut to the network admin, on blogs where Simple History is installed
284
+ *
285
+ * Useful because Simple History is something at least the author of this plugin often use on a site :)
286
+ *
287
+ * @since 2.7.1
288
+ */
289
+ public function add_admin_bar_network_menu_item( $wp_admin_bar ) {
290
+ /**
291
+ * Filter to control if admin bar shortcut should be added
292
+ *
293
+ * @since 2.7.1
294
+ *
295
+ * @param bool Add item
296
+ */
297
+ $add_items = apply_filters( 'simple_history/add_admin_bar_network_menu_item', true );
298
+
299
+ if ( ! $add_items ) {
300
+ return;
301
+ }
302
+
303
+ // Don't show for logged out users or single site mode.
304
+ if ( ! is_user_logged_in() || ! is_multisite() ) {
305
+ return;
306
+ }
307
+
308
+ // Show only when the user has at least one site, or they're a super admin.
309
+ if ( count( $wp_admin_bar->user->blogs ) < 1 && ! is_super_admin() ) {
310
+ return;
311
+ }
312
+
313
+ // Setting to show as page must be true
314
+ if ( ! $this->setting_show_as_page() ) {
315
+ return;
316
+ }
317
+
318
+ // User must have capability to view the history page
319
+ if ( ! current_user_can( $this->get_view_history_capability() ) ) {
320
+ return;
321
+ }
322
+
323
+ /*
324
+ menu_page_url() is defined in the WordPress Plugin Administration API, which is not loaded here by default */
325
+ /* dito for is_plugin_active() */
326
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
327
+
328
+ foreach ( (array) $wp_admin_bar->user->blogs as $blog ) {
329
+ switch_to_blog( $blog->userblog_id );
330
+
331
+ if ( is_plugin_active( SIMPLE_HISTORY_BASENAME ) ) {
332
+ $menu_id = 'simple-history-blog-' . $blog->userblog_id;
333
+ $parent_menu_id = 'blog-' . $blog->userblog_id;
334
+ $url = admin_url(
335
+ apply_filters( 'simple_history/admin_location', 'index' ) . '.php?page=simple_history_page'
336
+ );
337
+
338
+ // Each network site is added by WP core with id "blog-1", "blog-2" ... "blog-n"
339
+ // https://codex.wordpress.org/Function_Reference/add_node
340
+ $args = array(
341
+ 'id' => $menu_id,
342
+ 'parent' => $parent_menu_id,
343
+ 'title' => _x( 'View History', 'Admin bar network name', 'simple-history' ),
344
+ 'href' => $url,
345
+ 'meta' => array(
346
+ 'class' => 'ab-item--simplehistory',
347
+ ),
348
+ );
349
+
350
+ $wp_admin_bar->add_node( $args );
351
+ } // End if().
352
+
353
+ restore_current_blog();
354
+ } // End foreach().
355
+ }
356
+
357
+ /**
358
+ * Adds a "View history" item/shortcut to the admin bar
359
+ *
360
+ * Useful because Simple History is something at least the author of this plugin often use on a site :)
361
+ *
362
+ * @since 2.7.1
363
+ */
364
+ public function add_admin_bar_menu_item( $wp_admin_bar ) {
365
+ /**
366
+ * Filter to control if admin bar shortcut should be added
367
+ *
368
+ * @since 2.7.1
369
+ *
370
+ * @param bool Add item
371
+ */
372
+ $add_item = apply_filters( 'simple_history/add_admin_bar_menu_item', true );
373
+
374
+ if ( ! $add_item ) {
375
+ return;
376
+ }
377
+
378
+ // Don't show for logged out users
379
+ if ( ! is_user_logged_in() ) {
380
+ return;
381
+ }
382
+
383
+ // Setting to show as page must be true
384
+ if ( ! $this->setting_show_as_page() ) {
385
+ return;
386
+ }
387
+
388
+ // User must have capability to view the history page
389
+ if ( ! current_user_can( $this->get_view_history_capability() ) ) {
390
+ return;
391
+ }
392
+
393
+ /* menu_page_url() and is_plugin_active()is defined in the WordPress Plugin Administration API, which is not loaded here by default */
394
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
395
+
396
+ $menu_id = 'simple-history-view-history';
397
+ $parent_menu_id = 'site-name';
398
+ $url = admin_url( apply_filters( 'simple_history/admin_location', 'index' ) . '.php?page=simple_history_page' );
399
+
400
+ $args = array(
401
+ 'id' => $menu_id,
402
+ 'parent' => $parent_menu_id,
403
+ 'title' => _x( 'Simple History', 'Admin bar name', 'simple-history' ),
404
+ 'href' => $url,
405
+ 'meta' => array(
406
+ 'class' => 'ab-item--simplehistory',
407
+ ),
408
+ );
409
+
410
+ $wp_admin_bar->add_node( $args );
411
+ }
412
+
413
+ /**
414
+ * Get singleton intance
415
+ *
416
+ * @return SimpleHistory instance
417
+ */
418
+ public static function get_instance() {
419
+ if ( ! isset( self::$instance ) ) {
420
+ self::$instance = new SimpleHistory();
421
+ }
422
+
423
+ return self::$instance;
424
+ }
425
+
426
+ public function filter_gettext_storeLatestTranslations( $translation, $text, $domain ) {
427
+ // Check that translation is a string or integer, i.ex. the valid values for an array key
428
+ if ( ! is_string( $translation ) || ! is_integer( $translation ) ) {
429
+ return $translation;
430
+ }
431
+
432
+ $array_max_size = 5;
433
+
434
+ // Keep a listing of the n latest translation
435
+ // when SimpleLogger->log() is called from anywhere we can then search for the
436
+ // translated string among our n latest things and find it there, if it's translated
437
+ // global $sh_latest_translations;
438
+ $sh_latest_translations = $this->gettextLatestTranslations;
439
+
440
+ $sh_latest_translations[ $translation ] = array(
441
+ 'translation' => $translation,
442
+ 'text' => $text,
443
+ 'domain' => $domain,
444
+ );
445
+
446
+ $arr_length = count( $sh_latest_translations );
447
+ if ( $arr_length > $array_max_size ) {
448
+ $sh_latest_translations = array_slice( $sh_latest_translations, $arr_length - $array_max_size );
449
+ }
450
+
451
+ $this->gettextLatestTranslations = $sh_latest_translations;
452
+
453
+ return $translation;
454
+ }
455
+
456
+ public function setup_cron() {
457
+ add_filter( 'simple_history/maybe_purge_db', array( $this, 'maybe_purge_db' ) );
458
+
459
+ if ( ! wp_next_scheduled( 'simple_history/maybe_purge_db' ) ) {
460
+ wp_schedule_event( time(), 'daily', 'simple_history/maybe_purge_db' );
461
+ }
462
+
463
+ // Remove old schedule (only author dev sites should have it)
464
+ $old_next_scheduled = wp_next_scheduled( 'simple_history/purge_db' );
465
+ if ( $old_next_scheduled ) {
466
+ wp_unschedule_event( $old_next_scheduled, 'simple_history/purge_db' );
467
+ }
468
+ }
469
+
470
+ public function onAdminHead() {
471
+ if ( $this->is_on_our_own_pages() ) {
472
+ do_action( 'simple_history/admin_head', $this );
473
+ }
474
+ }
475
+
476
+ public function onAdminFooter() {
477
+ if ( $this->is_on_our_own_pages() ) {
478
+ do_action( 'simple_history/admin_footer', $this );
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Output JS templated into footer
484
+ */
485
+ public function add_js_templates( $hook ) {
486
+ if ( $this->is_on_our_own_pages() ) {
487
+ ?>
488
+ <script type="text/html" id="tmpl-simple-history-base">
489
+
490
+ <div class="SimpleHistory__waitingForFirstLoad">
491
+ <img src="<?php echo esc_url( admin_url( '/images/spinner.gif' ) ); ?>" alt="" width="20" height="20">
492
+ <?php
493
+ echo esc_html_x(
494
+ 'Loading history...',
495
+ 'Message visible while waiting for log to load from server the first time',
496
+ 'simple-history'
497
+ );
498
+ ?>
499
+ </div>
500
+
501
+ <div class="SimpleHistoryLogitemsWrap">
502
+ <div class="SimpleHistoryLogitems__beforeTopPagination"></div>
503
+ <div class="SimpleHistoryLogitems__above"></div>
504
+ <ul class="SimpleHistoryLogitems"></ul>
505
+ <div class="SimpleHistoryLogitems__below"></div>
506
+ <div class="SimpleHistoryLogitems__pagination"></div>
507
+ <div class="SimpleHistoryLogitems__afterBottomPagination"></div>
508
+ </div>
509
+
510
+ <div class="SimpleHistoryLogitems__debug"></div>
511
+
512
+ </script>
513
+
514
+ <script type="text/html" id="tmpl-simple-history-logitems-pagination">
515
+
516
+ <!-- this uses the (almost) the same html as WP does -->
517
+ <div class="SimpleHistoryPaginationPages">
518
+ <!--
519
+ {{ data.page_rows_from }}–{{ data.page_rows_to }}
520
+ <span class="SimpleHistoryPaginationDisplayNum"> of {{ data.total_row_count }} </span>
521
+ -->
522
+ <span class="SimpleHistoryPaginationLinks">
523
+ <a
524
+ data-direction="first"
525
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--firstPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
526
+ title="{{ data.strings.goToTheFirstPage }}"
527
+ href="#">«</a>
528
+ <a
529
+ data-direction="prev"
530
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--prevPage <# if ( data.api_args.paged <= 1 ) { #> disabled <# } #>"
531
+ title="{{ data.strings.goToThePrevPage }}"
532
+ href="#">‹</a>
533
+ <span class="SimpleHistoryPaginationInput">
534
+ <input class="SimpleHistoryPaginationCurrentPage" title="{{ data.strings.currentPage }}" type="text" name="paged" value="{{ data.api_args.paged }}" size="4">
535
+ <?php _x( 'of', 'page n of n', 'simple-history' ); ?>
536
+ <span class="total-pages">{{ data.pages_count }}</span>
537
+ </span>
538
+ <a
539
+ data-direction="next"
540
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--nextPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
541
+ title="{{ data.strings.goToTheNextPage }}"
542
+ href="#">›</a>
543
+ <a
544
+ data-direction="last"
545
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--lastPage <# if ( data.api_args.paged >= data.pages_count ) { #> disabled <# } #>"
546
+ title="{{ data.strings.goToTheLastPage }}"
547
+ href="#">»</a>
548
+ </span>
549
+ </div>
550
+
551
+ </script>
552
+
553
+ <script type="text/html" id="tmpl-simple-history-logitems-modal">
554
+
555
+ <div class="SimpleHistory-modal">
556
+ <div class="SimpleHistory-modal__background"></div>
557
+ <div class="SimpleHistory-modal__content">
558
+ <div class="SimpleHistory-modal__contentInner">
559
+ <img class="SimpleHistory-modal__contentSpinner" src="<?php echo esc_url( admin_url( '/images/spinner.gif' ) ); ?>" alt="">
560
+ </div>
561
+ <div class="SimpleHistory-modal__contentClose">
562
+ <button class="button">✕</button>
563
+ </div>
564
+ </div>
565
+ </div>
566
+
567
+ </script>
568
+
569
+ <script type="text/html" id="tmpl-simple-history-occasions-too-many">
570
+ <li
571
+ class="SimpleHistoryLogitem
572
+ SimpleHistoryLogitem--occasion
573
+ SimpleHistoryLogitem--occasion-tooMany
574
+ ">
575
+ <div class="SimpleHistoryLogitem__firstcol"></div>
576
+ <div class="SimpleHistoryLogitem__secondcol">
577
+ <div class="SimpleHistoryLogitem__text">
578
+ <?php esc_html_e( 'Sorry, but there are too many similar events to show.', 'simple-history' ); ?>
579
+ </div>
580
+ </div>
581
+ </li>
582
+ </script>
583
+
584
+ <?php
585
+ // Call plugins so they can add their js.
586
+ foreach ( $this->instantiatedLoggers as $one_logger ) {
587
+ if ( method_exists( $one_logger['instance'], 'adminJS' ) ) {
588
+ $one_logger['instance']->adminJS();
589
+ }
590
+ }
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Base url is:
596
+ * /wp-admin/admin-ajax.php?action=simple_history_api
597
+ *
598
+ * Examples:
599
+ * http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&posts_per_page=5&paged=1&format=html
600
+ */
601
+ public function api() {
602
+ global $wpdb;
603
+
604
+ // Fake slow answers
605
+ // sleep(2);
606
+ // sleep(rand(0,3));
607
+ $args = $_GET;
608
+ unset( $args['action'] );
609
+
610
+ // Type = overview | ...
611
+ $type = isset( $_GET['type'] ) ? $_GET['type'] : null;
612
+
613
+ if ( empty( $args ) || ! $type ) {
614
+ wp_send_json_error(
615
+ array(
616
+ _x( 'Not enough args specified', 'API: not enought arguments passed', 'simple-history' ),
617
+ )
618
+ );
619
+ }
620
+
621
+ // User must have capability to view the history page
622
+ if ( ! current_user_can( $this->get_view_history_capability() ) ) {
623
+ wp_send_json_error(
624
+ array(
625
+ 'error' => 'CAPABILITY_ERROR',
626
+ )
627
+ );
628
+ }
629
+
630
+ if ( isset( $args['id'] ) ) {
631
+ $args['post__in'] = array( $args['id'] );
632
+ }
633
+
634
+ $data = array();
635
+
636
+ switch ( $type ) {
637
+ case 'overview':
638
+ case 'occasions':
639
+ case 'single':
640
+ // API use SimpleHistoryLogQuery, so simply pass args on to that
641
+ $logQuery = new SimpleHistoryLogQuery();
642
+ $data = $logQuery->query( $args );
643
+
644
+ $data['api_args'] = $args;
645
+
646
+ // Output can be array or HMTL
647
+ if ( isset( $args['format'] ) && 'html' === $args['format'] ) {
648
+ $data['log_rows_raw'] = array();
649
+
650
+ foreach ( $data['log_rows'] as $key => $oneLogRow ) {
651
+ $args = array();
652
+ if ( $type == 'single' ) {
653
+ $args['type'] = 'single';
654
+ }
655
+
656
+ $data['log_rows'][ $key ] = $this->getLogRowHTMLOutput( $oneLogRow, $args );
657
+ $data['num_queries'] = get_num_queries();
658
+ }
659
+ }
660
+
661
+ break;
662
+
663
+ default:
664
+ $data[] = 'Nah.';
665
+ } // End switch().
666
+
667
+ wp_send_json_success( $data );
668
+ }
669
+
670
+ /**
671
+ * During the load of info for a logger we want to get a reference
672
+ * to the untranslated text too, because that's the version we want to store
673
+ * in the database.
674
+ */
675
+ public function filter_gettext( $translated_text, $untranslated_text, $domain ) {
676
+ if ( isset( $this->doFilterGettext ) && $this->doFilterGettext ) {
677
+ $this->doFilterGettext_currentLogger->messages[] = array(
678
+ 'untranslated_text' => $untranslated_text,
679
+ 'translated_text' => $translated_text,
680
+ 'domain' => $domain,
681
+ 'context' => null,
682
+ );
683
+ }
684
+
685
+ return $translated_text;
686
+ }
687
+
688
+ /**
689
+ * Store messages with context
690
+ */
691
+ public function filter_gettext_with_context( $translated_text, $untranslated_text, $context, $domain ) {
692
+ if ( isset( $this->doFilterGettext ) && $this->doFilterGettext ) {
693
+ $this->doFilterGettext_currentLogger->messages[] = array(
694
+ 'untranslated_text' => $untranslated_text,
695
+ 'translated_text' => $translated_text,
696
+ 'domain' => $domain,
697
+ 'context' => $context,
698
+ );
699
+ }
700
+
701
+ return $translated_text;
702
+ }
703
+
704
+ /**
705
+ * Load language files.
706
+ * Uses the method described here:
707
+ * http://geertdedeckere.be/article/loading-wordpress-language-files-the-right-way
708
+ *
709
+ * @since 2.0
710
+ */
711
+ public function load_plugin_textdomain() {
712
+ $domain = 'simple-history';
713
+
714
+ // The "plugin_locale" filter is also used in load_plugin_textdomain()
715
+ $locale = apply_filters( 'plugin_locale', get_locale(), $domain ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
716
+ load_textdomain( $domain, WP_LANG_DIR . '/simple-history/' . $domain . '-' . $locale . '.mo' );
717
+ load_plugin_textdomain( $domain, false, dirname( $this->plugin_basename ) . '/languages/' );
718
+ }
719
+
720
+ /**
721
+ * Setup variables and things
722
+ */
723
+ public function setup_variables() {
724
+ $this->externalLoggers = array();
725
+ $this->externalDropins = array();
726
+ $this->instantiatedLoggers = array();
727
+ $this->instantiatedDropins = array();
728
+
729
+ $this->plugin_basename = SIMPLE_HISTORY_BASENAME;
730
+ }
731
+
732
+ /**
733
+ * Return capability required to view history = for who will the History page be added
734
+ *
735
+ * @since 2.1.5
736
+ * @return string capability
737
+ */
738
+ public function get_view_history_capability() {
739
+ $view_history_capability = 'edit_pages';
740
+ $view_history_capability = apply_filters( 'simple_history_view_history_capability', $view_history_capability );
741
+ $view_history_capability = apply_filters( 'simple_history/view_history_capability', $view_history_capability );
742
+
743
+ return $view_history_capability;
744
+ }
745
+
746
+ /**
747
+ * Return capability required to view settings
748
+ *
749
+ * @since 2.1.5
750
+ * @return string capability
751
+ */
752
+ public function get_view_settings_capability() {
753
+ $view_settings_capability = 'manage_options';
754
+ $view_settings_capability = apply_filters( 'simple_history_view_settings_capability', $view_settings_capability );
755
+ $view_settings_capability = apply_filters( 'simple_history/view_settings_capability', $view_settings_capability );
756
+
757
+ return $view_settings_capability;
758
+ }
759
+
760
+ /**
761
+ * Check if the current user can clear the log
762
+ *
763
+ * @since 2.19
764
+ * @return bool
765
+ */
766
+ public function user_can_clear_log() {
767
+ $user_can_clear_log = apply_filters( 'simple_history/user_can_clear_log', true );
768
+
769
+ return $user_can_clear_log;
770
+ }
771
+
772
+ /**
773
+ * Adds default tabs to settings
774
+ */
775
+ public function add_default_settings_tabs() {
776
+ // Add default settings tabs
777
+ $this->arr_settings_tabs = array(
778
+ array(
779
+ 'slug' => 'settings',
780
+ 'name' => __( 'Settings', 'simple-history' ),
781
+ 'function' => array( $this, 'settings_output_general' ),
782
+ ),
783
+ );
784
+
785
+ if ( defined( 'SIMPLE_HISTORY_DEV' ) && SIMPLE_HISTORY_DEV ) {
786
+ $arr_dev_tabs = array(
787
+ array(
788
+ 'slug' => 'log',
789
+ 'name' => __( 'Log (debug)', 'simple-history' ),
790
+ 'function' => array( $this, 'settings_output_log' ),
791
+ ),
792
+ array(
793
+ 'slug' => 'styles-example',
794
+ 'name' => __( 'Styles example (debug)', 'simple-history' ),
795
+ 'function' => array( $this, 'settings_output_styles_example' ),
796
+ ),
797
+ );
798
+
799
+ $this->arr_settings_tabs = array_merge( $this->arr_settings_tabs, $arr_dev_tabs );
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Register an external logger so Simple History knows about it.
805
+ * Does not load the logger, so file with logger class must be loaded already.
806
+ *
807
+ * See example-logger.php for an example on how to use this.
808
+ *
809
+ * @since 2.1
810
+ */
811
+ public function register_logger( $loggerClassName ) {
812
+ $this->externalLoggers[] = $loggerClassName;
813
+ }
814
+
815
+ /**
816
+ * Register an external dropin so Simple History knows about it.
817
+ * Does not load the dropin, so file with dropin class must be loaded already.
818
+ *
819
+ * See example-dropin.php for an example on how to use this.
820
+ *
821
+ * @since 2.1
822
+ */
823
+ public function register_dropin( $dropinClassName ) {
824
+ $this->externalDropins[] = $dropinClassName;
825
+ }
826
+
827
+ /**
828
+ * Load built in loggers from all files in /loggers
829
+ * and instantiates them
830
+ */
831
+ public function load_loggers() {
832
+ $loggersDir = SIMPLE_HISTORY_PATH . 'loggers/';
833
+
834
+ // SimpleLogger.php must be loaded first and always since the other loggers extend it.
835
+ // Load it manually so no risk of anyone using filters or similar disables it.
836
+ // Also load files that contain constants used by the SimpleLogger.
837
+ include_once $loggersDir . 'SimpleLogger.php';
838
+ include_once $loggersDir . 'SimpleLoggerLogInitiators.php';
839
+ include_once $loggersDir . 'SimpleLoggerLogTypes.php';
840
+ include_once $loggersDir . 'SimpleLoggerLogLevels.php';
841
+
842
+ // Bail if we are not in filter after_setup_theme,
843
+ // i.e. we are probably calling SimpleLogger() early.
844
+ if ( ! doing_action( 'after_setup_theme' ) ) {
845
+ return;
846
+ }
847
+
848
+ $loggersFiles = array(
849
+ // Main loggers.
850
+ $loggersDir . 'SimpleCommentsLogger.php',
851
+ $loggersDir . 'SimpleCoreUpdatesLogger.php',
852
+ $loggersDir . 'SimpleExportLogger.php',
853
+ $loggersDir . 'SimpleLogger.php',
854
+ $loggersDir . 'SimpleMediaLogger.php',
855
+ $loggersDir . 'SimpleMenuLogger.php',
856
+ $loggersDir . 'SimpleOptionsLogger.php',
857
+ $loggersDir . 'SimplePluginLogger.php',
858
+ $loggersDir . 'SimplePostLogger.php',
859
+ $loggersDir . 'SimpleThemeLogger.php',
860
+ $loggersDir . 'SimpleUserLogger.php',
861
+ $loggersDir . 'SimpleCategoriesLogger.php',
862
+ $loggersDir . 'AvailableUpdatesLogger.php',
863
+ $loggersDir . 'FileEditsLogger.php',
864
+ $loggersDir . 'class-sh-privacy-logger.php',
865
+ $loggersDir . 'class-sh-translations-logger.php',
866
+ $loggersDir . 'class-sh-jetpack-logger.php',
867
+
868
+ // Loggers for third party plugins.
869
+ $loggersDir . 'PluginUserSwitchingLogger.php',
870
+ $loggersDir . 'PluginWPCrontrolLogger.php',
871
+ $loggersDir . 'PluginEnableMediaReplaceLogger.php',
872
+ $loggersDir . 'Plugin_UltimateMembers_Logger.php',
873
+ $loggersDir . 'Plugin_LimitLoginAttempts.php',
874
+ $loggersDir . 'Plugin_Redirection.php',
875
+ $loggersDir . 'Plugin_DuplicatePost.php',
876
+ $loggersDir . 'Plugin_ACF.php',
877
+ $loggersDir . 'Plugin_BeaverBuilder.php',
878
+ );
879
+
880
+ /**
881
+ * Filter the array with absolute paths to logger files to be loaded.
882
+ *
883
+ * Each file will be loaded and will be assumed to be a logger with a classname
884
+ * the same as the filename.
885
+ *
886
+ * @since 2.0
887
+ *
888
+ * @param array $loggersFiles Array with filenames
889
+ */
890
+ $loggersFiles = apply_filters( 'simple_history/loggers_files', $loggersFiles );
891
+
892
+ // Array with slug of loggers to instantiate.
893
+ // Slug of logger must also be the name of the logger class.
894
+ $arr_loggers_to_instantiate = array();
895
+
896
+ // $one_logger_file = "SimpleCommentsLogger.php", "class-privacy-logger.php", and so on.
897
+ foreach ( $loggersFiles as $one_logger_file ) {
898
+ $load_logger = true;
899
+
900
+ // SimpleCommentsLogger.php -> SimpleCommentsLogger.
901
+ // class-privacy-logger.php -> class-privacy-logger.
902
+ $basename_no_suffix = basename( $one_logger_file, '.php' );
903
+
904
+ /**
905
+ * Filter to completely skip loading of a logger
906
+ *
907
+ * @since 2.0.22
908
+ *
909
+ * @param bool if to load the logger. return false to not load it.
910
+ * @param string basename of logger, i.e. "SimpleCommentsLogger" or "class-privacy-logger"
911
+ */
912
+ $load_logger = apply_filters( 'simple_history/logger/load_logger', $load_logger, $basename_no_suffix );
913
+
914
+ // If logger was SimpleLogger then force it to be loaded because for example
915
+ // custom extended plugins added later probably depends on it.
916
+ if ( 'SimpleLogger' === $basename_no_suffix ) {
917
+ $load_logger = true;
918
+ }
919
+
920
+ if ( ! $load_logger ) {
921
+ continue;
922
+ }
923
+
924
+ include_once $one_logger_file;
925
+
926
+ $arr_loggers_to_instantiate[] = $basename_no_suffix;
927
+ }
928
+
929
+ /**
930
+ * Action that plugins can use to add their custom loggers.
931
+ * See register_logger() for more info.
932
+ *
933
+ * @since 2.1
934
+ *
935
+ * @param SimpleHistory instance
936
+ */
937
+ do_action( 'simple_history/add_custom_logger', $this );
938
+
939
+ $arr_loggers_to_instantiate = array_merge( $arr_loggers_to_instantiate, $this->externalLoggers );
940
+
941
+ /**
942
+ * Filter the array with names of loggers to instantiate.
943
+ *
944
+ * Array
945
+ * (
946
+ * [0] => SimpleCommentsLogger
947
+ * [1] => SimpleCoreUpdatesLogger
948
+ * ...
949
+ * )
950
+ *
951
+ * @since 2.0
952
+ *
953
+ * @param array $arr_loggers_to_instantiate Array with class names
954
+ */
955
+ $arr_loggers_to_instantiate = apply_filters(
956
+ 'simple_history/loggers_to_instantiate',
957
+ $arr_loggers_to_instantiate
958
+ );
959
+
960
+ // Instantiate each logger.
961
+ foreach ( $arr_loggers_to_instantiate as $one_logger_name ) {
962
+ // Detect logger class name.
963
+ $logger_class_name = null;
964
+
965
+ if ( class_exists( $one_logger_name ) ) {
966
+ // Logger name is "SimpleCommentsLogger".
967
+ $logger_class_name = $one_logger_name;
968
+ } else {
969
+ // Check if class is "class-privacy-logger".
970
+ $logger_snaked_name = substr( $one_logger_name, 6 );
971
+ // "privacy-logger" -> "privacy_logger" -> Privacy_Logger
972
+ $logger_snaked_name = str_replace( '-', '_', $logger_snaked_name );
973
+ $logger_snaked_name = sh_ucwords( $logger_snaked_name, '_' );
974
+
975
+ if ( class_exists( $logger_snaked_name ) ) {
976
+ $logger_class_name = $logger_snaked_name;
977
+ }
978
+ }
979
+
980
+ // Continue to load next logger if no valid logger class found.
981
+ if ( ! $logger_class_name ) {
982
+ continue;
983
+ }
984
+
985
+ // Init found logger class.
986
+ $logger_instance = new $logger_class_name( $this );
987
+
988
+ if ( ! is_subclass_of( $logger_instance, 'SimpleLogger' ) && ! is_a( $logger_instance, 'SimpleLogger' ) ) {
989
+ continue;
990
+ }
991
+
992
+ $logger_instance->loaded();
993
+
994
+ // Tell gettext-filter to add untranslated messages.
995
+ $this->doFilterGettext = true;
996
+ $this->doFilterGettext_currentLogger = $logger_instance;
997
+
998
+ $logger_info = $logger_instance->getInfo();
999
+
1000
+ // Check so no logger has a logger slug with more than 30 chars,
1001
+ // because db column is only 30 chars.
1002
+ if ( strlen( $logger_instance->slug ) > 30 ) {
1003
+ add_action( 'admin_notices', array( $this, 'admin_notice_logger_slug_to_long' ) );
1004
+ }
1005
+
1006
+ // Un-tell gettext filter.
1007
+ $this->doFilterGettext = false;
1008
+ $this->doFilterGettext_currentLogger = null;
1009
+
1010
+ // LoggerInfo contains all messages, both translated an not, by key.
1011
+ // Add messages to the loggerInstance.
1012
+ $arr_messages_by_message_key = array();
1013
+
1014
+ if ( isset( $logger_info['messages'] ) && is_array( $logger_info['messages'] ) ) {
1015
+ foreach ( (array) $logger_info['messages'] as $message_key => $message_translated ) {
1016
+ // Find message in array with both translated and non translated strings.
1017
+ foreach ( $logger_instance->messages as $one_message_with_translation_info ) {
1018
+ if ( $message_translated == $one_message_with_translation_info['translated_text'] ) {
1019
+ $arr_messages_by_message_key[ $message_key ] = $one_message_with_translation_info;
1020
+ continue;
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+
1026
+ $logger_instance->messages = $arr_messages_by_message_key;
1027
+
1028
+ // Add logger to array of loggers.
1029
+ $this->instantiatedLoggers[ $logger_instance->slug ] = array(
1030
+ 'name' => $logger_info['name'],
1031
+ 'instance' => $logger_instance,
1032
+ );
1033
+ } // End foreach().
1034
+
1035
+ do_action( 'simple_history/loggers_loaded' );
1036
+ }
1037
+
1038
+ /**
1039
+ * Load built in dropins from all files in /dropins
1040
+ * and instantiates them
1041
+ */
1042
+ public function load_dropins() {
1043
+ $dropinsDir = SIMPLE_HISTORY_PATH . 'dropins/';
1044
+
1045
+ $dropinsFiles = array(
1046
+ $dropinsDir . 'SimpleHistoryPluginPatchesDropin.php',
1047
+ $dropinsDir . 'SimpleHistoryDonateDropin.php',
1048
+ $dropinsDir . 'SimpleHistoryExportDropin.php',
1049
+ $dropinsDir . 'SimpleHistoryFilterDropin.php',
1050
+ $dropinsDir . 'SimpleHistoryIpInfoDropin.php',
1051
+ $dropinsDir . 'SimpleHistoryNewRowsNotifier.php',
1052
+ $dropinsDir . 'SimpleHistoryRSSDropin.php',
1053
+ $dropinsDir . 'SimpleHistorySettingsLogtestDropin.php',
1054
+ $dropinsDir . 'SimpleHistorySettingsStatsDropin.php',
1055
+ $dropinsDir . 'SimpleHistorySettingsDebugDropin.php',
1056
+ $dropinsDir . 'SimpleHistorySidebarDropin.php',
1057
+ $dropinsDir . 'SimpleHistorySidebarStats.php',
1058
+ $dropinsDir . 'SimpleHistorySidebarSettings.php',
1059
+ $dropinsDir . 'SimpleHistoryWPCLIDropin.php',
1060
+ $dropinsDir . 'SimpleHistoryDebugDropin.php',
1061
+ );
1062
+
1063
+ /**
1064
+ * Filter the array with absolute paths to files as returned by glob function.
1065
+ * Each file will be loaded and will be assumed to be a dropin with a classname
1066
+ * the same as the filename.
1067
+ *
1068
+ * @since 2.0
1069
+ *
1070
+ * @param array $dropinsFiles Array with filenames
1071
+ */
1072
+ $dropinsFiles = apply_filters( 'simple_history/dropins_files', $dropinsFiles );
1073
+
1074
+ $arrDropinsToInstantiate = array();
1075
+
1076
+ foreach ( $dropinsFiles as $oneDropinFile ) {
1077
+ // path/path/simplehistory/dropins/SimpleHistoryDonateDropin.php => SimpleHistoryDonateDropin
1078
+ $oneDropinFileBasename = basename( $oneDropinFile, '.php' );
1079
+
1080
+ $load_dropin = true;
1081
+
1082
+ /**
1083
+ * Filter to completely skip loading of dropin
1084
+ * complete filer name will be like:
1085
+ * simple_history/dropin/load_dropin_SimpleHistoryRSSDropin
1086
+ *
1087
+ * @since 2.0.6
1088
+ *
1089
+ * @param bool if to load the dropin. return false to not load it.
1090
+ */
1091
+ $load_dropin = apply_filters( "simple_history/dropin/load_dropin_{$oneDropinFileBasename}", $load_dropin );
1092
+
1093
+ /**
1094
+ * Filter to completely skip loading of a dropin
1095
+ *
1096
+ * @since 2.0.22
1097
+ *
1098
+ * @param bool if to load the dropin. return false to not load it.
1099
+ * @param string slug of dropin
1100
+ */
1101
+ $load_dropin = apply_filters( 'simple_history/dropin/load_dropin', $load_dropin, $oneDropinFileBasename );
1102
+
1103
+ if ( ! $load_dropin ) {
1104
+ continue;
1105
+ }
1106
+
1107
+ include_once $oneDropinFile;
1108
+
1109
+ $arrDropinsToInstantiate[] = $oneDropinFileBasename;
1110
+ } // End foreach().
1111
+
1112
+ /**
1113
+ * Action that dropins can use to add their custom loggers.
1114
+ * See register_dropin() for more info.
1115
+ *
1116
+ * @since 2.3.2
1117
+ *
1118
+ * @param array $arrDropinsToInstantiate Array with class names
1119
+ */
1120
+ do_action( 'simple_history/add_custom_dropin', $this );
1121
+
1122
+ /**
1123
+ * Filter the array with names of dropin to instantiate.
1124
+ *
1125
+ * @since 2.0
1126
+ *
1127
+ * @param array $arrDropinsToInstantiate Array with class names
1128
+ */
1129
+ $arrDropinsToInstantiate = apply_filters( 'simple_history/dropins_to_instantiate', $arrDropinsToInstantiate );
1130
+
1131
+ $arrDropinsToInstantiate = array_merge( $arrDropinsToInstantiate, $this->externalDropins );
1132
+
1133
+ // Instantiate each dropin
1134
+ foreach ( $arrDropinsToInstantiate as $oneDropinName ) {
1135
+ if ( ! class_exists( $oneDropinName ) ) {
1136
+ continue;
1137
+ }
1138
+
1139
+ $this->instantiatedDropins[ $oneDropinName ] = array(
1140
+ 'name' => $oneDropinName,
1141
+ 'instance' => new $oneDropinName( $this ),
1142
+ );
1143
+ }
1144
+ }
1145
+
1146
+ /**
1147
+ * Gets the pager size,
1148
+ * i.e. the number of items to show on each page in the history
1149
+ *
1150
+ * @return int
1151
+ */
1152
+ public function get_pager_size() {
1153
+ $pager_size = get_option( 'simple_history_pager_size', 20 );
1154
+
1155
+ /**
1156
+ * Filter the pager size setting
1157
+ *
1158
+ * @since 2.0
1159
+ *
1160
+ * @param int $pager_size
1161
+ */
1162
+ $pager_size = apply_filters( 'simple_history/pager_size', $pager_size );
1163
+
1164
+ return $pager_size;
1165
+ }
1166
+
1167
+ /**
1168
+ * Gets the pager size,
1169
+ * i.e. the number of items to show on each page in the history
1170
+ *
1171
+ * @since 2.12
1172
+ * @return int
1173
+ */
1174
+ public function get_pager_size_dashboard() {
1175
+ $pager_size = get_option( 'simple_history_pager_size_dashboard', 5 );
1176
+
1177
+ /**
1178
+ * Filter the pager size setting
1179
+ *
1180
+ * @since 2.12
1181
+ *
1182
+ * @param int $pager_size
1183
+ */
1184
+ $pager_size = apply_filters( 'simple_history/pager_size_dashboard', $pager_size );
1185
+
1186
+ return $pager_size;
1187
+ }
1188
+
1189
+ /**
1190
+ * Show a link to our settings page on the Plugins -> Installed Plugins screen
1191
+ */
1192
+ public function plugin_action_links( $actions, $b, $c, $d ) {
1193
+ // Only add link if user has the right to view the settings page
1194
+ if ( ! current_user_can( $this->get_view_settings_capability() ) ) {
1195
+ return $actions;
1196
+ }
1197
+
1198
+ $settings_page_url = menu_page_url( self::SETTINGS_MENU_SLUG, 0 );
1199
+
1200
+ if ( empty( $actions ) ) {
1201
+ // Create array if actions is empty (and therefore is assumed to be a string by PHP & results in PHP 7.1+ fatal error due to trying to make array modifications on what's assumed to be a string)
1202
+ $actions = array();
1203
+ } elseif ( is_string( $actions ) ) {
1204
+ // Convert the string (which it might've been retrieved as) to an array for future use as an array
1205
+ $actions = array( $actions );
1206
+ }
1207
+ $actions[] = "<a href='$settings_page_url'>" . __( 'Settings', 'simple-history' ) . '</a>';
1208
+
1209
+ return $actions;
1210
+ }
1211
+
1212
+ /**
1213
+ * Maybe add a dashboard widget,
1214
+ * requires current user to have view history capability
1215
+ * and a setting to show dashboard to be set
1216
+ */
1217
+ public function add_dashboard_widget() {
1218
+ if ( $this->setting_show_on_dashboard() && current_user_can( $this->get_view_history_capability() ) ) {
1219
+ /**
1220
+ * Filter to determine if history page should be added to page below dashboard or not
1221
+ *
1222
+ * @since 2.0.23
1223
+ *
1224
+ * @param bool Show the page or not
1225
+ */
1226
+ $show_dashboard_widget = apply_filters( 'simple_history/show_dashboard_widget', true );
1227
+
1228
+ if ( $show_dashboard_widget ) {
1229
+ wp_add_dashboard_widget(
1230
+ 'simple_history_dashboard_widget',
1231
+ __( 'Simple History', 'simple-history' ),
1232
+ array(
1233
+ $this,
1234
+ 'dashboard_widget_output',
1235
+ )
1236
+ );
1237
+ }
1238
+ }
1239
+ }
1240
+
1241
+ /**
1242
+ * Output html for the dashboard widget
1243
+ */
1244
+ public function dashboard_widget_output() {
1245
+ $pager_size = $this->get_pager_size_dashboard();
1246
+
1247
+ /**
1248
+ * Filter the pager size setting for the dashboard
1249
+ *
1250
+ * @since 2.0
1251
+ *
1252
+ * @param int $pager_size
1253
+ */
1254
+ $pager_size = apply_filters( 'simple_history/dashboard_pager_size', $pager_size );
1255
+
1256
+ do_action( 'simple_history/dashboard/before_gui', $this );
1257
+ ?>
1258
+ <div class="SimpleHistoryGui"
1259
+ data-pager-size='<?php echo esc_attr( $pager_size ); ?>'
1260
+ ></div>
1261
+ <?php
1262
+ }
1263
+
1264
+ public function is_on_our_own_pages( $hook = '' ) {
1265
+ $current_screen = get_current_screen();
1266
+
1267
+ $basePrefix = apply_filters( 'simple_history/admin_location', 'index' );
1268
+ $basePrefix = $basePrefix === 'index' ? 'dashboard' : $basePrefix;
1269
+
1270
+ if ( $current_screen && $current_screen->base == 'settings_page_' . self::SETTINGS_MENU_SLUG ) {
1271
+ return true;
1272
+ } elseif ( $current_screen && $current_screen->base == $basePrefix . '_page_simple_history_page' ) {
1273
+ return true;
1274
+ } elseif (
1275
+ $hook == 'settings_page_' . self::SETTINGS_MENU_SLUG ||
1276
+ ( $this->setting_show_on_dashboard() && $hook == 'index.php' ) ||
1277
+ ( $this->setting_show_as_page() && $hook == $basePrefix . '_page_simple_history_page' )
1278
+ ) {
1279
+ return true;
1280
+ } elseif ( $current_screen && $current_screen->base == 'dashboard' && $this->setting_show_on_dashboard() ) {
1281
+ return true;
1282
+ }
1283
+
1284
+ return false;
1285
+ }
1286
+
1287
+ /**
1288
+ * Enqueue styles and scripts for Simple History but only to our own pages.
1289
+ *
1290
+ * Only adds scripts to pages where the log is shown or the settings page.
1291
+ */
1292
+ public function enqueue_admin_scripts( $hook ) {
1293
+ if ( $this->is_on_our_own_pages() ) {
1294
+ add_thickbox();
1295
+
1296
+ wp_enqueue_style(
1297
+ 'simple_history_styles',
1298
+ SIMPLE_HISTORY_DIR_URL . 'css/styles.css',
1299
+ false,
1300
+ SIMPLE_HISTORY_VERSION
1301
+ );
1302
+ wp_enqueue_script(
1303
+ 'simple_history_script',
1304
+ SIMPLE_HISTORY_DIR_URL . 'js/scripts.js',
1305
+ array( 'jquery', 'backbone', 'wp-util' ),
1306
+ SIMPLE_HISTORY_VERSION,
1307
+ true
1308
+ );
1309
+
1310
+ wp_enqueue_script( 'select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.full.min.js', array( 'jquery' ), SIMPLE_HISTORY_VERSION );
1311
+ wp_enqueue_style( 'select2', SIMPLE_HISTORY_DIR_URL . 'js/select2/select2.min.css', array(), SIMPLE_HISTORY_VERSION );
1312
+
1313
+ // Translations that we use in JavaScript
1314
+ wp_localize_script(
1315
+ 'simple_history_script',
1316
+ 'simple_history_script_vars',
1317
+ array(
1318
+ 'settingsConfirmClearLog' => __( 'Remove all log items?', 'simple-history' ),
1319
+ 'pagination' => array(
1320
+ 'goToTheFirstPage' => __( 'Go to the first page', 'simple-history' ),
1321
+ 'goToThePrevPage' => __( 'Go to the previous page', 'simple-history' ),
1322
+ 'goToTheNextPage' => __( 'Go to the next page', 'simple-history' ),
1323
+ 'goToTheLastPage' => __( 'Go to the last page', 'simple-history' ),
1324
+ 'currentPage' => __( 'Current page', 'simple-history' ),
1325
+ ),
1326
+ 'loadLogAPIError' => __( 'Oups, the log could not be loaded right now.', 'simple-history' ),
1327
+ 'ajaxLoadError' => __(
1328
+ 'Hm, the log could not be loaded right now. Perhaps another plugin is giving some errors. Anyway, below is the output I got from the server.',
1329
+ 'simple-history'
1330
+ ),
1331
+ 'logNoHits' => __( 'Your search did not match any history events.', 'simple-history' ),
1332
+ )
1333
+ );
1334
+
1335
+ // Call plugins adminCSS-method, so they can add their CSS
1336
+ foreach ( $this->instantiatedLoggers as $one_logger ) {
1337
+ if ( method_exists( $one_logger['instance'], 'adminCSS' ) ) {
1338
+ $one_logger['instance']->adminCSS();
1339
+ }
1340
+ }
1341
+
1342
+ // Add timeago.js
1343
+ wp_enqueue_script(
1344
+ 'timeago',
1345
+ SIMPLE_HISTORY_DIR_URL . 'js/timeago/jquery.timeago.js',
1346
+ array( 'jquery' ),
1347
+ '1.5.2',
1348
+ true
1349
+ );
1350
+
1351
+ // Determine current locale to load timeago locale
1352
+ $locale = strtolower( substr( get_locale(), 0, 2 ) );
1353
+ $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/timeago/locales/jquery.timeago.%s.js';
1354
+ $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/timeago/locales/jquery.timeago.%s.js';
1355
+
1356
+ // Only enqueue if locale-file exists on file system
1357
+ if ( file_exists( sprintf( $locale_dir_path, $locale ) ) ) {
1358
+ wp_enqueue_script( 'timeago-locale', sprintf( $locale_url_path, $locale ), array( 'jquery' ), '1.5.2', true );
1359
+ } else {
1360
+ wp_enqueue_script( 'timeago-locale', sprintf( $locale_url_path, 'en' ), array( 'jquery' ), '1.5.2', true );
1361
+ }
1362
+ // end add timeago
1363
+ // Load Select2 locale
1364
+ $locale_url_path = SIMPLE_HISTORY_DIR_URL . 'js/select2/i18n/%s.js';
1365
+ $locale_dir_path = SIMPLE_HISTORY_PATH . 'js/select2/i18n/%s.js';
1366
+
1367
+ if ( file_exists( sprintf( $locale_dir_path, $locale ) ) ) {
1368
+ wp_enqueue_script( 'select2-locale', sprintf( $locale_url_path, $locale ), array( 'jquery' ), '3.5.1', true );
1369
+ }
1370
+
1371
+ /**
1372
+ * Fires when the admin scripts have been enqueued.
1373
+ * Only fires on any of the pages where Simple History is used
1374
+ *
1375
+ * @since 2.0
1376
+ *
1377
+ * @param SimpleHistory $SimpleHistory This class.
1378
+ */
1379
+ do_action( 'simple_history/enqueue_admin_scripts', $this );
1380
+ } // End if().
1381
+ }
1382
+
1383
+ public function filter_option_page_capability( $capability ) {
1384
+ return $capability;
1385
+ }
1386
+
1387
+ /**
1388
+ * Check if plugin version have changed, i.e. has been upgraded
1389
+ * If upgrade is detected then maybe modify database and so on for that version
1390
+ */
1391
+ public function check_for_upgrade() {
1392
+ global $wpdb;
1393
+
1394
+ $db_version = get_option( 'simple_history_db_version' );
1395
+ $table_name = $wpdb->prefix . self::DBTABLE;
1396
+ $table_name_contexts = $wpdb->prefix . self::DBTABLE_CONTEXTS;
1397
+ $first_install = false;
1398
+
1399
+ // If no db_version is set then this
1400
+ // is a version of Simple History < 0.4
1401
+ // or it's a first install
1402
+ // Fix database not using UTF-8
1403
+ if ( false === $db_version || intval( $db_version ) == 0 ) {
1404
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1405
+
1406
+ // Table creation, used to be in register_activation_hook
1407
+ // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
1408
+ $sql =
1409
+ 'CREATE TABLE ' .
1410
+ $table_name .
1411
+ ' (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1412
  id bigint(20) NOT NULL AUTO_INCREMENT,
1413
  date datetime NOT NULL,
1414
  PRIMARY KEY (id)
1415
  ) CHARACTER SET=utf8;';
1416
 
1417
+ // Upgrade db / fix utf for varchars
1418
+ dbDelta( $sql );
1419
+
1420
+ // Fix UTF-8 for table
1421
+ $sql = sprintf( 'alter table %1$s charset=utf8;', $table_name );
1422
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1423
+ $wpdb->query( $sql );
1424
+
1425
+ $db_version = 1;
1426
+
1427
+ update_option( 'simple_history_db_version', $db_version );
1428
+
1429
+ // We are not 100% sure that this is a first install,
1430
+ // but it is at least a very old version that is being updated
1431
+ $first_install = true;
1432
+ } // End if().
1433
+
1434
+ // If db version is 1 then upgrade to 2
1435
+ // Version 2 added the action_description column
1436
+ if ( 1 == intval( $db_version ) ) {
1437
+ // V2 used to add column "action_description"
1438
+ // but it's not used any more so don't do i
1439
+ $db_version = 2;
1440
+
1441
+ update_option( 'simple_history_db_version', $db_version );
1442
+ }
1443
+
1444
+ // Check that all options we use are set to their defaults, if they miss value
1445
+ // Each option that is missing a value will make a sql call otherwise = unnecessary
1446
+ $arr_options = array(
1447
+ array(
1448
+ 'name' => 'simple_history_show_as_page',
1449
+ 'default_value' => 1,
1450
+ ),
1451
+ array(
1452
+ 'name' => 'simple_history_show_on_dashboard',
1453
+ 'default_value' => 1,
1454
+ ),
1455
+ );
1456
+
1457
+ foreach ( $arr_options as $one_option ) {
1458
+ $option_value = get_option( $one_option['name'] );
1459
+ if ( false === ( $option_value ) ) {
1460
+ // Value is not set in db, so set it to a default
1461
+ update_option( $one_option['name'], $one_option['default_value'] );
1462
+ }
1463
+ }
1464
+
1465
+ /**
1466
+ * If db_version is 2 then upgrade to 3:
1467
+ * - Add some fields to existing table wp_simple_history_contexts
1468
+ * - Add all new table wp_simple_history_contexts
1469
+ *
1470
+ * @since 2.0
1471
+ */
1472
+ if ( 2 == intval( $db_version ) ) {
1473
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1474
+
1475
+ // Update old table
1476
+ $sql = "
1477
  CREATE TABLE {$table_name} (
1478
  id bigint(20) NOT NULL AUTO_INCREMENT,
1479
  date datetime NOT NULL,
1487
  KEY loggerdate (logger,date)
1488
  ) CHARSET=utf8;";
1489
 
1490
+ dbDelta( $sql );
1491
 
1492
+ // Add context table
1493
+ $sql = "
1494
  CREATE TABLE IF NOT EXISTS {$table_name_contexts} (
1495
  context_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
1496
  history_id bigint(20) unsigned NOT NULL,
1502
  ) CHARSET=utf8;
1503
  ";
1504
 
1505
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1506
+ $wpdb->query( $sql );
1507
 
1508
+ $db_version = 3;
1509
+ update_option( 'simple_history_db_version', $db_version );
 
1510
 
1511
+ // Update possible old items to use SimpleLogger.
1512
+ $sql = sprintf(
1513
+ '
1514
  UPDATE %1$s
1515
  SET
1516
+ logger = "SimpleLogger",
1517
  level = "info"
1518
  WHERE logger IS NULL
1519
  ',
1520
+ $table_name
1521
+ );
1522
+
1523
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1524
+ $wpdb->query( $sql );
1525
+
1526
+ // Say welcome, however loggers are not added this early so we need to
1527
+ // use a filter to load it later
1528
+ add_action( 'simple_history/loggers_loaded', array( $this, 'addWelcomeLogMessage' ) );
1529
+ } // End if().
1530
+
1531
+ /**
1532
+ * If db version = 3
1533
+ * then we need to update database to allow null values for some old columns
1534
+ * that used to work in pre wp 4.1 beta, but since 4.1 wp uses STRICT_ALL_TABLES
1535
+ * WordPress Commit: https://github.com/WordPress/WordPress/commit/f17d168a0f72211a9bfd9d3fa680713069871bb6
1536
+ *
1537
+ * @since 2.0
1538
+ */
1539
+ if ( 3 == intval( $db_version ) ) {
1540
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
1541
+
1542
+ // If old columns exist = this is an old install, then modify the columns so we still can keep them
1543
+ // we want to keep them because user may have logged items that they want to keep
1544
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1545
+ $db_cools = $wpdb->get_col( "DESCRIBE $table_name" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1546
+
1547
+ if ( in_array( 'action', $db_cools ) ) {
1548
+ $sql = sprintf(
1549
+ '
1550
  ALTER TABLE %1$s
1551
  MODIFY `action` varchar(255) NULL,
1552
  MODIFY `object_type` varchar(255) NULL,
1555
  MODIFY `object_id` int(10) NULL,
1556
  MODIFY `object_name` varchar(255) NULL
1557
  ',
1558
+ $table_name
1559
+ );
1560
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1561
+ $wpdb->query( $sql );
1562
+ }
1563
+
1564
+ $db_version = 4;
1565
+
1566
+ update_option( 'simple_history_db_version', $db_version );
1567
+ } // End if().
1568
+
1569
+ // Some installs on 2.2.2 got failed installs
1570
+ // We detect these by checking for db_version and then running the install stuff again
1571
+ if ( 4 == intval( $db_version ) ) {
1572
+ if ( ! $this->does_database_have_data() ) {
1573
+ // not ok, decrease db number so installs will run again and hopefully fix things
1574
+ $db_version = 0;
1575
+ } else {
1576
+ // all looks ok, upgrade to db version 5, so this part is not done again
1577
+ $db_version = 5;
1578
+ }
1579
+
1580
+ update_option( 'simple_history_db_version', $db_version );
1581
+ }
1582
+ }
1583
+
1584
+ /**
1585
+ * Check if the database has data/rows
1586
+ *
1587
+ * @since 2.1.6
1588
+ * @return bool True if database is not empty, false if database is empty = contains no data
1589
+ */
1590
+ public function does_database_have_data() {
1591
+ global $wpdb;
1592
+
1593
+ $tableprefix = $wpdb->prefix;
1594
+ $simple_history_table = self::DBTABLE;
1595
+
1596
+ $sql_data_exists = "SELECT id AS id_exists FROM {$tableprefix}{$simple_history_table} LIMIT 1";
1597
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1598
+ $data_exists = (bool) $wpdb->get_var( $sql_data_exists, 0 );
1599
+
1600
+ return $data_exists;
1601
+ }
1602
+
1603
+ /**
1604
+ * Greet users to version 2!
1605
+ * Is only called after database has been upgraded, so only on first install (or upgrade).
1606
+ * Not called after only plugin activation.
1607
+ */
1608
+ public function addWelcomeLogMessage() {
1609
+ $db_data_exists = $this->does_database_have_data();
1610
+ // $db_data_exists = false;
1611
+ $pluginLogger = $this->getInstantiatedLoggerBySlug( 'SimplePluginLogger' );
1612
+ if ( $pluginLogger ) {
1613
+ // Add plugin installed message
1614
+ $context = array(
1615
+ 'plugin_name' => 'Simple History',
1616
+ 'plugin_description' =>
1617
+ 'Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.',
1618
+ 'plugin_url' => 'https://simple-history.com',
1619
+ 'plugin_version' => SIMPLE_HISTORY_VERSION,
1620
+ 'plugin_author' => 'Pär Thernström',
1621
+ );
1622
+
1623
+ $pluginLogger->infoMessage( 'plugin_installed', $context );
1624
+
1625
+ // Add plugin activated message
1626
+ $context['plugin_slug'] = 'simple-history';
1627
+ $context['plugin_title'] = '<a href="https://simple-history.com/">Simple History</a>';
1628
+
1629
+ $pluginLogger->infoMessage( 'plugin_activated', $context );
1630
+ }
1631
+
1632
+ if ( ! $db_data_exists ) {
1633
+ $welcome_message_1 = __(
1634
+ '
 
 
 
1635
  Welcome to Simple History!
1636
 
1637
  This is the main history feed. It will contain events that this plugin has logged.
1638
  ',
1639
+ 'simple-history'
1640
+ );
1641
 
1642
+ $welcome_message_2 = __(
1643
+ '
1644
  Because Simple History was only recently installed, this feed does not display many events yet. As long as the plugin remains activated you will soon see detailed information about page edits, plugin updates, users logging in, and much more.
1645
  ',
1646
+ 'simple-history'
1647
+ );
1648
+
1649
+ SimpleLogger()->info(
1650
+ $welcome_message_2,
1651
+ array(
1652
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
1653
+ )
1654
+ );
1655
+
1656
+ SimpleLogger()->info(
1657
+ $welcome_message_1,
1658
+ array(
1659
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
1660
+ )
1661
+ );
1662
+ }
1663
+ }
1664
+
1665
+ public function registerSettingsTab( $arr_tab_settings ) {
1666
+ $this->arr_settings_tabs[] = $arr_tab_settings;
1667
+ }
1668
+
1669
+ public function getSettingsTabs() {
1670
+ return $this->arr_settings_tabs;
1671
+ }
1672
+
1673
+ /**
1674
+ * Output HTML for the settings page
1675
+ * Called from add_options_page
1676
+ */
1677
+ public function settings_page_output() {
1678
+ $arr_settings_tabs = $this->getSettingsTabs();
1679
+ ?>
1680
+ <div class="wrap">
1681
+
1682
+ <h1 class="SimpleHistoryPageHeadline">
1683
+ <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1684
+ <?php esc_html_e( 'Simple History Settings', 'simple-history' ); ?>
1685
+ </h1>
1686
+
1687
+ <?php
1688
+ $active_tab = isset( $_GET['selected-tab'] ) ? $_GET['selected-tab'] : 'settings';
1689
+ $settings_base_url = menu_page_url( self::SETTINGS_MENU_SLUG, 0 );
1690
+ ?>
1691
+
1692
+ <h2 class="nav-tab-wrapper">
1693
+ <?php
1694
+ foreach ( $arr_settings_tabs as $one_tab ) {
1695
+ $tab_slug = $one_tab['slug'];
1696
+
1697
+ printf(
1698
+ '<a href="%3$s" class="nav-tab %4$s">%1$s</a>',
1699
+ $one_tab['name'], // 1
1700
+ $tab_slug, // 2
1701
+ esc_url( add_query_arg( 'selected-tab', $tab_slug, $settings_base_url ) ), // 3
1702
+ $active_tab == $tab_slug ? 'nav-tab-active' : '' // 4
1703
+ );
1704
+ }
1705
+ ?>
1706
+ </h2>
1707
+
1708
+ <?php
1709
+ // Output contents for selected tab
1710
+ $arr_active_tab = wp_filter_object_list(
1711
+ $arr_settings_tabs,
1712
+ array(
1713
+ 'slug' => $active_tab,
1714
+ )
1715
+ );
1716
+ $arr_active_tab = current( $arr_active_tab );
1717
+
1718
+ // We must have found an active tab and it must have a callable function
1719
+ if ( ! $arr_active_tab || ! is_callable( $arr_active_tab['function'] ) ) {
1720
+ wp_die( esc_html__( 'No valid callback found', 'simple-history' ) );
1721
+ }
1722
+
1723
+ $args = array(
1724
+ 'arr_active_tab' => $arr_active_tab,
1725
+ );
1726
+
1727
+ call_user_func_array( $arr_active_tab['function'], array_values( $args ) );
1728
+ ?>
1729
+
1730
+ </div>
1731
+ <?php
1732
+ }
1733
+
1734
+ public function settings_output_log() {
1735
+ include SIMPLE_HISTORY_PATH . 'templates/settings-log.php';
1736
+ }
1737
+
1738
+ public function settings_output_general() {
1739
+ include SIMPLE_HISTORY_PATH . 'templates/settings-general.php';
1740
+ }
1741
+
1742
+ public function settings_output_styles_example() {
1743
+ include SIMPLE_HISTORY_PATH . 'templates/settings-style-example.php';
1744
+ }
1745
+
1746
+ /**
1747
+ * Content for section intro. Leave it be, even if empty.
1748
+ * Called from add_sections_setting.
1749
+ */
1750
+ public function settings_section_output() {
1751
+ }
1752
+
1753
+ /**
1754
+ * Add pages (history page and settings page)
1755
+ */
1756
+ public function add_admin_pages() {
1757
+ // Add a history page as a sub-page below the Dashboard menu item
1758
+ if ( $this->setting_show_as_page() ) {
1759
+ /**
1760
+ * Filter to determine if history page should be added to page below dashboard or not
1761
+ *
1762
+ * @since 2.0.23
1763
+ *
1764
+ * @param bool Show the page or not
1765
+ */
1766
+ $show_dashboard_page = apply_filters( 'simple_history/show_dashboard_page', true );
1767
+
1768
+ if ( $show_dashboard_page ) {
1769
+ add_submenu_page(
1770
+ apply_filters( 'simple_history/admin_location', 'index' ) . '.php',
1771
+ _x( 'Simple History', 'dashboard title name', 'simple-history' ),
1772
+ _x( 'Simple History', 'dashboard menu name', 'simple-history' ),
1773
+ $this->get_view_history_capability(),
1774
+ 'simple_history_page',
1775
+ array( $this, 'history_page_output' )
1776
+ );
1777
+ }
1778
+ }
1779
+
1780
+ // Add a settings page
1781
+ $show_settings_page = true;
1782
+ $show_settings_page = apply_filters( 'simple_history_show_settings_page', $show_settings_page );
1783
+ $show_settings_page = apply_filters( 'simple_history/show_settings_page', $show_settings_page );
1784
+
1785
+ if ( $show_settings_page ) {
1786
+ add_options_page(
1787
+ __( 'Simple History Settings', 'simple-history' ),
1788
+ _x( 'Simple History', 'Options page menu title', 'simple-history' ),
1789
+ $this->get_view_settings_capability(),
1790
+ self::SETTINGS_MENU_SLUG,
1791
+ array( $this, 'settings_page_output' )
1792
+ );
1793
+ }
1794
+ }
1795
+
1796
+ /**
1797
+ * Add setting sections and settings for the settings page
1798
+ * Also maybe save some settings before outputing them
1799
+ */
1800
+ public function add_settings() {
1801
+ // Clear the log if clear button was clicked in settings
1802
+ // and redirect user to show message.
1803
+ if (
1804
+ isset( $_GET['simple_history_clear_log_nonce'] ) &&
1805
+ wp_verify_nonce( $_GET['simple_history_clear_log_nonce'], 'simple_history_clear_log' )
1806
+ ) {
1807
+ if ( $this->user_can_clear_log() ) {
1808
+ $this->clear_log();
1809
+ }
1810
+
1811
+ $msg = __( 'Cleared database', 'simple-history' );
1812
+
1813
+ add_settings_error(
1814
+ 'simple_history_rss_feed_regenerate_secret',
1815
+ 'simple_history_rss_feed_regenerate_secret',
1816
+ $msg,
1817
+ 'updated'
1818
+ );
1819
+
1820
+ set_transient( 'settings_errors', get_settings_errors(), 30 );
1821
+
1822
+ $goback = esc_url_raw( add_query_arg( 'settings-updated', 'true', wp_get_referer() ) );
1823
+ wp_redirect( $goback );
1824
+ exit();
1825
+ }
1826
+
1827
+ // Section for general options.
1828
+ // Will contain settings like where to show simple history and number of items.
1829
+ $settings_section_general_id = self::SETTINGS_SECTION_GENERAL_ID;
1830
+ add_settings_section(
1831
+ $settings_section_general_id,
1832
+ '',
1833
+ array( $this, 'settings_section_output' ),
1834
+ self::SETTINGS_MENU_SLUG // Same slug as for options menu page.
1835
+ );
1836
+
1837
+ // Settings for the general settings section
1838
+ // Each setting = one row in the settings section
1839
+ // add_settings_field( $id, $title, $callback, $page, $section, $args );
1840
+ // Checkboxes for where to show simple history
1841
+ add_settings_field(
1842
+ 'simple_history_show_where',
1843
+ __( 'Show history', 'simple-history' ),
1844
+ array( $this, 'settings_field_where_to_show' ),
1845
+ self::SETTINGS_MENU_SLUG,
1846
+ $settings_section_general_id
1847
+ );
1848
+
1849
+ // Nonces for show where inputs.
1850
+ register_setting( self::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_show_on_dashboard' );
1851
+ register_setting( self::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_show_as_page' );
1852
+
1853
+ // Number if items to show on the history page.
1854
+ add_settings_field(
1855
+ 'simple_history_number_of_items',
1856
+ __( 'Number of items per page on the log page', 'simple-history' ),
1857
+ array( $this, 'settings_field_number_of_items' ),
1858
+ self::SETTINGS_MENU_SLUG,
1859
+ $settings_section_general_id
1860
+ );
1861
+
1862
+ // Nonces for number of items inputs.
1863
+ register_setting( self::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_pager_size' );
1864
+
1865
+ // Number if items to show on dashboard.
1866
+ add_settings_field(
1867
+ 'simple_history_number_of_items_dashboard',
1868
+ __( 'Number of items per page on the dashboard', 'simple-history' ),
1869
+ array( $this, 'settings_field_number_of_items_dashboard' ),
1870
+ self::SETTINGS_MENU_SLUG,
1871
+ $settings_section_general_id
1872
+ );
1873
+
1874
+ // Nonces for number of items inputs.
1875
+ register_setting( self::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_pager_size_dashboard' );
1876
+
1877
+ // Link/button to clear log.
1878
+ if ( $this->user_can_clear_log() ) {
1879
+ add_settings_field(
1880
+ 'simple_history_clear_log',
1881
+ __( 'Clear log', 'simple-history' ),
1882
+ array( $this, 'settings_field_clear_log' ),
1883
+ self::SETTINGS_MENU_SLUG,
1884
+ $settings_section_general_id
1885
+ );
1886
+ }
1887
+ }
1888
+
1889
+ /**
1890
+ * Output for page with the history
1891
+ */
1892
+ public function history_page_output() {
1893
+ $pager_size = $this->get_pager_size();
1894
+
1895
+ /**
1896
+ * Filter the pager size setting for the history page
1897
+ *
1898
+ * @since 2.0
1899
+ *
1900
+ * @param int $pager_size
1901
+ */
1902
+ $pager_size = apply_filters( 'simple_history/page_pager_size', $pager_size );
1903
+ ?>
1904
+
1905
+ <div class="wrap SimpleHistoryWrap">
1906
+
1907
+ <h1 class="SimpleHistoryPageHeadline">
1908
+ <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1909
+ <?php echo esc_html_x( 'Simple History', 'history page headline', 'simple-history' ); ?>
1910
+ </h1>
1911
+
1912
+ <?php
1913
+ /**
1914
+ * Fires before the gui div
1915
+ *
1916
+ * @since 2.0
1917
+ *
1918
+ * @param SimpleHistory $SimpleHistory This class.
1919
+ */
1920
+ do_action( 'simple_history/history_page/before_gui', $this );
1921
+ ?>
1922
+
1923
+ <div class="SimpleHistoryGuiWrap">
1924
+
1925
+ <div class="SimpleHistoryGui"
1926
+ data-pager-size='<?php echo esc_attr( $pager_size ); ?>'
1927
+ ></div>
1928
+ <?php
1929
+ /**
1930
+ * Fires after the gui div
1931
+ *
1932
+ * @since 2.0
1933
+ *
1934
+ * @param SimpleHistory $SimpleHistory This class.
1935
+ */
1936
+ do_action( 'simple_history/history_page/after_gui', $this );
1937
+ ?>
1938
+ </div>
1939
+ </div>
1940
+ <?php
1941
+ }
1942
+
1943
+ /**
1944
+ * Get setting if plugin should be visible on dasboard.
1945
+ * Defaults to true
1946
+ *
1947
+ * @return bool
1948
+ */
1949
+ public function setting_show_on_dashboard() {
1950
+ $show_on_dashboard = get_option( 'simple_history_show_on_dashboard', 1 );
1951
+ $show_on_dashboard = apply_filters( 'simple_history_show_on_dashboard', $show_on_dashboard );
1952
+ return (bool) $show_on_dashboard;
1953
+ }
1954
+
1955
+ /**
1956
+ * Should simple history be shown as a page
1957
+ * Defaults to true
1958
+ *
1959
+ * @return bool
1960
+ */
1961
+ public function setting_show_as_page() {
1962
+ $setting = get_option( 'simple_history_show_as_page', 1 );
1963
+ $setting = apply_filters( 'simple_history_show_as_page', $setting );
1964
+
1965
+ return (bool) $setting;
1966
+ }
1967
+
1968
+ /**
1969
+ * Settings field for how many rows/items to show in log on the log page
1970
+ */
1971
+ public function settings_field_number_of_items() {
1972
+ $current_pager_size = $this->get_pager_size();
1973
+ ?>
1974
+ <select name="simple_history_pager_size">
1975
+ <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
1976
+ <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
1977
+ <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
1978
+ <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
1979
+ <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
1980
+ <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
1981
+ <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
1982
+ <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
1983
+ <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
1984
+ <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
1985
+ </select>
1986
+ <?php
1987
+ }
1988
+
1989
+ /**
1990
+ * Settings field for how many rows/items to show in log on the dashboard
1991
+ */
1992
+ public function settings_field_number_of_items_dashboard() {
1993
+ $current_pager_size = $this->get_pager_size_dashboard();
1994
+ ?>
1995
+ <select name="simple_history_pager_size_dashboard">
1996
+ <option <?php echo $current_pager_size == 5 ? 'selected' : ''; ?> value="5">5</option>
1997
+ <option <?php echo $current_pager_size == 10 ? 'selected' : ''; ?> value="10">10</option>
1998
+ <option <?php echo $current_pager_size == 15 ? 'selected' : ''; ?> value="15">15</option>
1999
+ <option <?php echo $current_pager_size == 20 ? 'selected' : ''; ?> value="20">20</option>
2000
+ <option <?php echo $current_pager_size == 25 ? 'selected' : ''; ?> value="25">25</option>
2001
+ <option <?php echo $current_pager_size == 30 ? 'selected' : ''; ?> value="30">30</option>
2002
+ <option <?php echo $current_pager_size == 40 ? 'selected' : ''; ?> value="40">40</option>
2003
+ <option <?php echo $current_pager_size == 50 ? 'selected' : ''; ?> value="50">50</option>
2004
+ <option <?php echo $current_pager_size == 75 ? 'selected' : ''; ?> value="75">75</option>
2005
+ <option <?php echo $current_pager_size == 100 ? 'selected' : ''; ?> value="100">100</option>
2006
+ </select>
2007
+ <?php
2008
+ }
2009
+
2010
+ /**
2011
+ * Settings field for where to show the log, page or dashboard
2012
+ */
2013
+ public function settings_field_where_to_show() {
2014
+ $show_on_dashboard = $this->setting_show_on_dashboard();
2015
+ $show_as_page = $this->setting_show_as_page();
2016
+ ?>
2017
+
2018
+ <input
2019
+ <?php
2020
+ echo $show_on_dashboard
2021
+ ? "checked='checked'"
2022
+ : '';
2023
+ ?>
2024
+ type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
2025
+ <label for="simple_history_show_on_dashboard"><?php esc_html_e( 'on the dashboard', 'simple-history' ); ?></label>
2026
+
2027
+ <br />
2028
+
2029
+ <input
2030
+ <?php
2031
+ echo $show_as_page
2032
+ ? "checked='checked'"
2033
+ : '';
2034
+ ?>
2035
+ type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
2036
+ <label for="simple_history_show_as_page">
2037
+ <?php esc_html_e( 'as a page under the dashboard menu', 'simple-history' ); ?>
2038
+ </label>
2039
+
2040
+ <?php
2041
+ }
2042
+
2043
+ /**
2044
+ * Settings section to clear database
2045
+ */
2046
+ public function settings_field_clear_log() {
2047
+ // Get base URL to current page.
2048
+ // Will be like "/wordpress/wp-admin/options-general.php?page=simple_history_settings_menu_slug&"
2049
+ $clear_link = add_query_arg( '', '' );
2050
+
2051
+ // Append nonce to URL.
2052
+ $clear_link = wp_nonce_url( $clear_link, 'simple_history_clear_log', 'simple_history_clear_log_nonce' );
2053
+
2054
+ $clear_days = $this->get_clear_history_interval();
2055
+
2056
+ echo '<p>';
2057
+
2058
+ if ( $clear_days > 0 ) {
2059
+ echo sprintf(
2060
+ esc_html__( 'Items in the database are automatically removed after %1$s days.', 'simple-history' ),
2061
+ esc_html( $clear_days )
2062
+ );
2063
+ } else {
2064
+ esc_html_e( 'Items in the database are kept forever.', 'simple-history' );
2065
+ }
2066
+
2067
+ echo '</p>';
2068
+
2069
+ printf(
2070
+ '<p><a class="button js-SimpleHistory-Settings-ClearLog" href="%2$s">%1$s</a></p>',
2071
+ esc_html__( 'Clear log now', 'simple-history' ),
2072
+ esc_url( $clear_link )
2073
+ );
2074
+ }
2075
+
2076
+ /**
2077
+ * How old log entried are allowed to be.
2078
+ * 0 = don't delete old entries.
2079
+ *
2080
+ * @return int Number of days.
2081
+ */
2082
+ public function get_clear_history_interval() {
2083
+ $days = 60;
2084
+
2085
+ /**
2086
+ * Filter to modify number of days of history to keep.
2087
+ * Default is 60 days.
2088
+ *
2089
+ * @param $days Number of days of history to keep
2090
+ */
2091
+ $days = (int) apply_filters( 'simple_history_db_purge_days_interval', $days );
2092
+ $days = (int) apply_filters( 'simple_history/db_purge_days_interval', $days );
2093
+
2094
+ return $days;
2095
+ }
2096
+
2097
+ /**
2098
+ * Removes all items from the log
2099
+ */
2100
+ public function clear_log() {
2101
+ global $wpdb;
2102
+
2103
+ $tableprefix = $wpdb->prefix;
2104
+
2105
+ $simple_history_table = self::DBTABLE;
2106
+ $simple_history_context_table = self::DBTABLE_CONTEXTS;
2107
+
2108
+ // Get number of rows before delete.
2109
+ $sql_num_rows = "SELECT count(id) AS num_rows FROM {$tableprefix}{$simple_history_table}";
2110
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2111
+ $num_rows = $wpdb->get_var( $sql_num_rows, 0 );
2112
+
2113
+ // Use truncate instead of delete because it's much faster (I think, writing this much later).
2114
+ $sql = "TRUNCATE {$tableprefix}{$simple_history_table}";
2115
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2116
+ $wpdb->query( $sql );
2117
+
2118
+ $sql = "TRUNCATE {$tableprefix}{$simple_history_context_table}";
2119
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2120
+ $wpdb->query( $sql );
2121
+
2122
+ // Zero state sucks
2123
+ SimpleLogger()->info(
2124
+ __( 'The log for Simple History was cleared ({num_rows} rows were removed).', 'simple-history' ),
2125
+ array(
2126
+ 'num_rows' => $num_rows,
2127
+ )
2128
+ );
2129
+
2130
+ $this->get_cache_incrementor( true );
2131
+ }
2132
+
2133
+ /**
2134
+ * Runs the purge_db() method sometimes
2135
+ * We don't want to call it each time because it performs SQL queries
2136
+ *
2137
+ * @since 2.0.17
2138
+ */
2139
+ public function maybe_purge_db() {
2140
+ // How often should we try to do this?
2141
+ // Once a day = a bit tiresome.
2142
+ // Let's go with sundays; purge the log on sundays.
2143
+ // Day of week, 1 = mon, 7 = sun.
2144
+ $day_of_week = gmdate( 'N' );
2145
+ if ( 7 === (int) $day_of_week ) {
2146
+ $this->purge_db();
2147
+ }
2148
+ }
2149
+
2150
+ /**
2151
+ * Removes old entries from the db
2152
+ */
2153
+ public function purge_db() {
2154
+ $do_purge_history = true;
2155
+
2156
+ $do_purge_history = apply_filters( 'simple_history_allow_db_purge', $do_purge_history );
2157
+ $do_purge_history = apply_filters( 'simple_history/allow_db_purge', $do_purge_history );
2158
+
2159
+ if ( ! $do_purge_history ) {
2160
+ return;
2161
+ }
2162
+
2163
+ $days = $this->get_clear_history_interval();
2164
+
2165
+ // Never clear log if days = 0.
2166
+ if ( 0 == $days ) {
2167
+ return;
2168
+ }
2169
+
2170
+ global $wpdb;
2171
+
2172
+ $table_name = $wpdb->prefix . self::DBTABLE;
2173
+ $table_name_contexts = $wpdb->prefix . self::DBTABLE_CONTEXTS;
2174
+
2175
+ while ( 1 > 0 ) {
2176
+ // Get id of rows to delete.
2177
+ $sql = $wpdb->prepare(
2178
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
2179
+ "SELECT id FROM $table_name WHERE DATE_ADD(date, INTERVAL %d DAY) < now() LIMIT 100000",
2180
+ $days
2181
+ );
2182
+
2183
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2184
+ $ids_to_delete = $wpdb->get_col( $sql );
2185
+
2186
+ if ( empty( $ids_to_delete ) ) {
2187
+ // Nothing to delete.
2188
+ return;
2189
+ }
2190
+
2191
+ $sql_ids_in = implode( ',', $ids_to_delete );
2192
+
2193
+ // Add number of deleted rows to total_rows option.
2194
+ $prev_total_rows = (int) get_option( 'simple_history_total_rows', 0 );
2195
+ $total_rows = $prev_total_rows + count( $ids_to_delete );
2196
+ update_option( 'simple_history_total_rows', $total_rows );
2197
+
2198
+ // Remove rows + contexts.
2199
+ $sql_delete_history = "DELETE FROM {$table_name} WHERE id IN ($sql_ids_in)";
2200
+ $sql_delete_history_context = "DELETE FROM {$table_name_contexts} WHERE history_id IN ($sql_ids_in)";
2201
+
2202
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2203
+ $wpdb->query( $sql_delete_history );
2204
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2205
+ $wpdb->query( $sql_delete_history_context );
2206
+
2207
+ $message = _nx(
2208
+ 'Simple History removed one event that were older than {days} days',
2209
+ 'Simple History removed {num_rows} events that were older than {days} days',
2210
+ count( $ids_to_delete ),
2211
+ 'Database is being cleared automagically',
2212
+ 'simple-history'
2213
+ );
2214
+
2215
+ SimpleLogger()->info(
2216
+ $message,
2217
+ array(
2218
+ 'days' => $days,
2219
+ 'num_rows' => count( $ids_to_delete ),
2220
+ )
2221
+ );
2222
+
2223
+ $this->get_cache_incrementor( true );
2224
+ }
2225
+ }
2226
+
2227
+ /**
2228
+ * Return plain text output for a log row
2229
+ * Uses the getLogRowPlainTextOutput of the logger that logged the row
2230
+ * with fallback to SimpleLogger if logger is not available.
2231
+ *
2232
+ * @param context $row
2233
+ * @return string
2234
+ */
2235
+ public function getLogRowPlainTextOutput( $row ) {
2236
+ $row_logger = $row->logger;
2237
+ $logger = null;
2238
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
2239
+
2240
+ if ( ! isset( $row->context['_message_key'] ) ) {
2241
+ $row->context['_message_key'] = null;
2242
+ }
2243
+
2244
+ // Fallback to SimpleLogger if no logger exists for row
2245
+ if ( ! isset( $this->instantiatedLoggers[ $row_logger ] ) ) {
2246
+ $row_logger = 'SimpleLogger';
2247
+ }
2248
+
2249
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2250
+
2251
+ return $logger->getLogRowPlainTextOutput( $row );
2252
+ }
2253
+
2254
+ /**
2255
+ * Return header output for a log row.
2256
+ *
2257
+ * Uses the getLogRowHeaderOutput of the logger that logged the row
2258
+ * with fallback to SimpleLogger if logger is not available.
2259
+ *
2260
+ * Loggers are discouraged to override this in the loggers,
2261
+ * because the output should be the same for all items in the GUI.
2262
+ *
2263
+ * @param object $row
2264
+ * @return string
2265
+ */
2266
+ public function getLogRowHeaderOutput( $row ) {
2267
+ $row_logger = $row->logger;
2268
+ $logger = null;
2269
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
2270
+
2271
+ // Fallback to SimpleLogger if no logger exists for row
2272
+ if ( ! isset( $this->instantiatedLoggers[ $row_logger ] ) ) {
2273
+ $row_logger = 'SimpleLogger';
2274
+ }
2275
+
2276
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2277
+
2278
+ return $logger->getLogRowHeaderOutput( $row );
2279
+ }
2280
+
2281
+ /**
2282
+ *
2283
+ *
2284
+ * @param object $row
2285
+ * @return string
2286
+ */
2287
+ private function getLogRowSenderImageOutput( $row ) {
2288
+ $row_logger = $row->logger;
2289
+ $logger = null;
2290
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
2291
+
2292
+ // Fallback to SimpleLogger if no logger exists for row
2293
+ if ( ! isset( $this->instantiatedLoggers[ $row_logger ] ) ) {
2294
+ $row_logger = 'SimpleLogger';
2295
+ }
2296
+
2297
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2298
+
2299
+ return $logger->getLogRowSenderImageOutput( $row );
2300
+ }
2301
+
2302
+ public function getLogRowDetailsOutput( $row ) {
2303
+ $row_logger = $row->logger;
2304
+ $logger = null;
2305
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
2306
+
2307
+ // Fallback to SimpleLogger if no logger exists for row
2308
+ if ( ! isset( $this->instantiatedLoggers[ $row_logger ] ) ) {
2309
+ $row_logger = 'SimpleLogger';
2310
+ }
2311
+
2312
+ $logger = $this->instantiatedLoggers[ $row_logger ]['instance'];
2313
+
2314
+ return $logger->getLogRowDetailsOutput( $row );
2315
+ }
2316
+
2317
+ /**
2318
+ * Works like json_encode, but adds JSON_PRETTY_PRINT if the current php version supports it
2319
+ * i.e. PHP is 5.4.0 or greated
2320
+ *
2321
+ * @param mixed $value array|object|string|whatever that is json_encode'able.
2322
+ */
2323
+ public static function json_encode( $value ) {
2324
+ return version_compare( PHP_VERSION, '5.4.0' ) >= 0
2325
+ ? json_encode( $value, JSON_PRETTY_PRINT )
2326
+ : json_encode( $value );
2327
+ }
2328
+
2329
+ /**
2330
+ * Returns true if $haystack ends with $needle
2331
+ *
2332
+ * @param string $haystack
2333
+ * @param string $needle
2334
+ */
2335
+ public static function ends_with( $haystack, $needle ) {
2336
+ return $needle === substr( $haystack, -strlen( $needle ) );
2337
+ }
2338
+
2339
+ /**
2340
+ * Returns the HTML output for a log row, to be used in the GUI/Activity Feed.
2341
+ * This includes HTML for the header, the sender image, and the details.
2342
+ *
2343
+ * @param object $oneLogRow SimpleHistoryLogQuery array with data from SimpleHistoryLogQuery
2344
+ * @return string
2345
+ */
2346
+ public function getLogRowHTMLOutput( $oneLogRow, $args ) {
2347
+ $defaults = array(
2348
+ 'type' => 'overview', // or "single" to include more stuff (used in for example modal details window)
2349
+ );
2350
+
2351
+ $args = wp_parse_args( $args, $defaults );
2352
+
2353
+ $header_html = $this->getLogRowHeaderOutput( $oneLogRow );
2354
+ $plain_text_html = $this->getLogRowPlainTextOutput( $oneLogRow );
2355
+ $sender_image_html = $this->getLogRowSenderImageOutput( $oneLogRow );
2356
+
2357
+ // Details = for example thumbnail of media
2358
+ $details_html = trim( $this->getLogRowDetailsOutput( $oneLogRow ) );
2359
+ if ( $details_html ) {
2360
+ $details_html = sprintf( '<div class="SimpleHistoryLogitem__details">%1$s</div>', $details_html );
2361
+ }
2362
+
2363
+ // subsequentOccasions = including the current one
2364
+ $occasions_count = $oneLogRow->subsequentOccasions - 1;
2365
+ $occasions_html = '';
2366
+
2367
+ if ( $occasions_count > 0 ) {
2368
+ $occasions_html = '<div class="SimpleHistoryLogitem__occasions">';
2369
+
2370
+ $occasions_html .= '<a href="#" class="SimpleHistoryLogitem__occasionsLink">';
2371
+ $occasions_html .= sprintf(
2372
+ _n( '+%1$s similar event', '+%1$s similar events', $occasions_count, 'simple-history' ),
2373
+ $occasions_count
2374
+ );
2375
+ $occasions_html .= '</a>';
2376
+
2377
+ $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoading">';
2378
+ $occasions_html .= sprintf( __( 'Loading…', 'simple-history' ), $occasions_count );
2379
+ $occasions_html .= '</span>';
2380
+
2381
+ $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoaded">';
2382
+ $occasions_html .= sprintf( __( 'Showing %1$s more', 'simple-history' ), $occasions_count );
2383
+ $occasions_html .= '</span>';
2384
+
2385
+ $occasions_html .= '</div>';
2386
+ }
2387
+
2388
+ // Add data attributes to log row, so plugins can do stuff.
2389
+ $data_attrs = '';
2390
+ $data_attrs .= sprintf( ' data-row-id="%1$d" ', $oneLogRow->id );
2391
+ $data_attrs .= sprintf( ' data-occasions-count="%1$d" ', $occasions_count );
2392
+ $data_attrs .= sprintf( ' data-occasions-id="%1$s" ', esc_attr( $oneLogRow->occasionsID ) );
2393
+
2394
+ // Add data attributes for remote address and other ip number headers.
2395
+ if ( isset( $oneLogRow->context['_server_remote_addr'] ) ) {
2396
+ $data_attrs .= sprintf( ' data-ip-address="%1$s" ', esc_attr( $oneLogRow->context['_server_remote_addr'] ) );
2397
+ }
2398
+
2399
+ $arr_found_additional_ip_headers = $this->instantiatedLoggers['SimpleLogger']['instance']->get_event_ip_number_headers( $oneLogRow );
2400
+
2401
+ if ( $arr_found_additional_ip_headers ) {
2402
+ $data_attrs .= sprintf( ' data-ip-address-multiple="1" ' );
2403
+ }
2404
+
2405
+ // Add data attributes info for common things like logger, level, data, initiation.
2406
+ $data_attrs .= sprintf( ' data-logger="%1$s" ', esc_attr( $oneLogRow->logger ) );
2407
+ $data_attrs .= sprintf( ' data-level="%1$s" ', esc_attr( $oneLogRow->level ) );
2408
+ $data_attrs .= sprintf( ' data-date="%1$s" ', esc_attr( $oneLogRow->date ) );
2409
+ $data_attrs .= sprintf( ' data-initiator="%1$s" ', esc_attr( $oneLogRow->initiator ) );
2410
+
2411
+ if ( isset( $oneLogRow->context['_user_id'] ) ) {
2412
+ $data_attrs .= sprintf( ' data-initiator-user-id="%1$d" ', $oneLogRow->context['_user_id'] );
2413
+ }
2414
+
2415
+ // If type is single then include more details.
2416
+ // This is typically shown in the modal window when clickin the event date and time.
2417
+ $more_details_html = '';
2418
+ if ( $args['type'] == 'single' ) {
2419
+ $more_details_html = apply_filters(
2420
+ 'simple_history/log_html_output_details_single/html_before_context_table',
2421
+ $more_details_html,
2422
+ $oneLogRow
2423
+ );
2424
+
2425
+ $more_details_html .= sprintf(
2426
+ '<h2 class="SimpleHistoryLogitem__moreDetailsHeadline">%1$s</h2>',
2427
+ __( 'Context data', 'simple-history' )
2428
+ );
2429
+ $more_details_html .=
2430
+ '<p>' . __( 'This is potentially useful meta data that a logger has saved.', 'simple-history' ) . '</p>';
2431
+ $more_details_html .= "<table class='SimpleHistoryLogitem__moreDetailsContext'>";
2432
+ $more_details_html .= sprintf(
2433
+ '<tr>
2434
  <th>%1$s</th>
2435
  <th>%2$s</th>
2436
  </tr>',
2437
+ 'Key',
2438
+ 'Value'
2439
+ );
2440
+
2441
+ $logRowKeysToShow = array_fill_keys( array_keys( (array) $oneLogRow ), true );
2442
+
2443
+ /**
2444
+ * Filter what keys to show from oneLogRow
2445
+ *
2446
+ * Array is in format
2447
+ *
2448
+ * Array
2449
+ * (
2450
+ * [id] => 1
2451
+ * [logger] => 1
2452
+ * [level] => 1
2453
+ * ...
2454
+ * )
2455
+ *
2456
+ * @since 2.0.29
2457
+ *
2458
+ * @param array with keys to show. key to show = key. value = boolean to show or not.
2459
+ * @param object log row to show details from
2460
+ */
2461
+ $logRowKeysToShow = apply_filters(
2462
+ 'simple_history/log_html_output_details_table/row_keys_to_show',
2463
+ $logRowKeysToShow,
2464
+ $oneLogRow
2465
+ );
2466
+
2467
+ // Hide some keys by default
2468
+ unset(
2469
+ $logRowKeysToShow['occasionsID'],
2470
+ $logRowKeysToShow['subsequentOccasions'],
2471
+ $logRowKeysToShow['rep'],
2472
+ $logRowKeysToShow['repeated'],
2473
+ $logRowKeysToShow['occasionsIDType'],
2474
+ $logRowKeysToShow['context'],
2475
+ $logRowKeysToShow['type']
2476
+ );
2477
+
2478
+ foreach ( $oneLogRow as $rowKey => $rowVal ) {
2479
+ // Only columns from oneLogRow that exist in logRowKeysToShow will be outputed
2480
+ if ( ! array_key_exists( $rowKey, $logRowKeysToShow ) || ! $logRowKeysToShow[ $rowKey ] ) {
2481
+ continue;
2482
+ }
2483
+
2484
+ // skip arrays and objects and such
2485
+ if ( is_array( $rowVal ) || is_object( $rowVal ) ) {
2486
+ continue;
2487
+ }
2488
+
2489
+ $more_details_html .= sprintf(
2490
+ '<tr>
2491
  <td>%1$s</td>
2492
  <td>%2$s</td>
2493
  </tr>',
2494
+ esc_html( $rowKey ),
2495
+ esc_html( $rowVal )
2496
+ );
2497
+ }
2498
+
2499
+ $logRowContextKeysToShow = array_fill_keys( array_keys( (array) $oneLogRow->context ), true );
2500
+
2501
+ /**
2502
+ * Filter what keys to show from the row context
2503
+ *
2504
+ * Array is in format
2505
+ *
2506
+ * Array
2507
+ * (
2508
+ * [plugin_slug] => 1
2509
+ * [plugin_name] => 1
2510
+ * [plugin_title] => 1
2511
+ * [plugin_description] => 1
2512
+ * [plugin_author] => 1
2513
+ * [plugin_version] => 1
2514
+ * ...
2515
+ * )
2516
+ *
2517
+ * @since 2.0.29
2518
+ *
2519
+ * @param array with keys to show. key to show = key. value = boolean to show or not.
2520
+ * @param object log row to show details from
2521
+ */
2522
+ $logRowContextKeysToShow = apply_filters(
2523
+ 'simple_history/log_html_output_details_table/context_keys_to_show',
2524
+ $logRowContextKeysToShow,
2525
+ $oneLogRow
2526
+ );
2527
+
2528
+ foreach ( $oneLogRow->context as $contextKey => $contextVal ) {
2529
+ // Only columns from context that exist in logRowContextKeysToShow will be outputed
2530
+ if (
2531
+ ! array_key_exists( $contextKey, $logRowContextKeysToShow ) ||
2532
+ ! $logRowContextKeysToShow[ $contextKey ]
2533
+ ) {
2534
+ continue;
2535
+ }
2536
+
2537
+ $more_details_html .= sprintf(
2538
+ '<tr>
2539
  <td>%1$s</td>
2540
  <td>%2$s</td>
2541
  </tr>',
2542
+ esc_html( $contextKey ),
2543
+ esc_html( $contextVal )
2544
+ );
2545
+ }
2546
+
2547
+ $more_details_html .= '</table>';
2548
+
2549
+ $more_details_html = apply_filters(
2550
+ 'simple_history/log_html_output_details_single/html_after_context_table',
2551
+ $more_details_html,
2552
+ $oneLogRow
2553
+ );
2554
+
2555
+ $more_details_html = sprintf(
2556
+ '<div class="SimpleHistoryLogitem__moreDetails">%1$s</div>',
2557
+ $more_details_html
2558
+ );
2559
+ } // End if().
2560
+
2561
+ // Classes to add to log item li element
2562
+ $classes = array(
2563
+ 'SimpleHistoryLogitem',
2564
+ "SimpleHistoryLogitem--loglevel-{$oneLogRow->level}",
2565
+ "SimpleHistoryLogitem--logger-{$oneLogRow->logger}",
2566
+ );
2567
+
2568
+ if ( isset( $oneLogRow->initiator ) && ! empty( $oneLogRow->initiator ) ) {
2569
+ $classes[] = 'SimpleHistoryLogitem--initiator-' . $oneLogRow->initiator;
2570
+ }
2571
+
2572
+ if ( $arr_found_additional_ip_headers ) {
2573
+ $classes[] = 'SimpleHistoryLogitem--IPAddress-multiple';
2574
+ }
2575
+
2576
+ // Always append the log level tag
2577
+ $log_level_tag_html = sprintf(
2578
+ ' <span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%2$s</span>',
2579
+ $oneLogRow->level,
2580
+ $this->getLogLevelTranslated( $oneLogRow->level )
2581
+ );
2582
+
2583
+ $plain_text_html .= $log_level_tag_html;
2584
+
2585
+ /**
2586
+ * Filter to modify classes added to item li element
2587
+ *
2588
+ * @since 2.0.7
2589
+ *
2590
+ * @param $classes Array with classes
2591
+ */
2592
+ $classes = apply_filters( 'simple_history/logrowhtmloutput/classes', $classes );
2593
+
2594
+ // Generate the HTML output for a row
2595
+ $output = sprintf(
2596
+ '
2597
  <li %8$s class="%10$s">
2598
  <div class="SimpleHistoryLogitem__firstcol">
2599
  <div class="SimpleHistoryLogitem__senderImage">%3$s</div>
2607
  </div>
2608
  </li>
2609
  ',
2610
+ $header_html, // 1
2611
+ $plain_text_html, // 2
2612
+ $sender_image_html, // 3
2613
+ $occasions_html, // 4
2614
+ $oneLogRow->level, // 5
2615
+ $details_html, // 6
2616
+ $oneLogRow->logger, // 7
2617
+ $data_attrs, // 8 data attributes
2618
+ $more_details_html, // 9
2619
+ esc_attr( join( ' ', $classes ) ) // 10
2620
+ );
2621
+
2622
+ // Get the main message row.
2623
+ // Should be as plain as possible, like plain text
2624
+ // but with links to for example users and posts
2625
+ // SimpleLoggerFormatter::getRowTextOutput($oneLogRow);
2626
+ // Get detailed HTML-based output
2627
+ // May include images, lists, any cool stuff needed to view
2628
+ // SimpleLoggerFormatter::getRowHTMLOutput($oneLogRow);
2629
+ return trim( $output );
2630
+ }
2631
+
2632
+ /**
2633
+ * Return translated loglevel
2634
+ *
2635
+ * @since 2.0.14
2636
+ * @param string $loglevel
2637
+ * @return string translated loglevel
2638
+ */
2639
+ public function getLogLevelTranslated( $loglevel ) {
2640
+ $str_translated = '';
2641
+
2642
+ switch ( $loglevel ) {
2643
+ // Lowercase
2644
+ case 'emergency':
2645
+ $str_translated = _x( 'emergency', 'Log level in gui', 'simple-history' );
2646
+ break;
2647
+
2648
+ case 'alert':
2649
+ $str_translated = _x( 'alert', 'Log level in gui', 'simple-history' );
2650
+ break;
2651
+
2652
+ case 'critical':
2653
+ $str_translated = _x( 'critical', 'Log level in gui', 'simple-history' );
2654
+ break;
2655
+
2656
+ case 'error':
2657
+ $str_translated = _x( 'error', 'Log level in gui', 'simple-history' );
2658
+ break;
2659
+
2660
+ case 'warning':
2661
+ $str_translated = _x( 'warning', 'Log level in gui', 'simple-history' );
2662
+ break;
2663
+
2664
+ case 'notice':
2665
+ $str_translated = _x( 'notice', 'Log level in gui', 'simple-history' );
2666
+ break;
2667
+
2668
+ case 'info':
2669
+ $str_translated = _x( 'info', 'Log level in gui', 'simple-history' );
2670
+ break;
2671
+
2672
+ case 'debug':
2673
+ $str_translated = _x( 'debug', 'Log level in gui', 'simple-history' );
2674
+ break;
2675
+
2676
+ // Uppercase
2677
+ case 'Emergency':
2678
+ $str_translated = _x( 'Emergency', 'Log level in gui', 'simple-history' );
2679
+ break;
2680
+
2681
+ case 'Alert':
2682
+ $str_translated = _x( 'Alert', 'Log level in gui', 'simple-history' );
2683
+ break;
2684
+
2685
+ case 'Critical':
2686
+ $str_translated = _x( 'Critical', 'Log level in gui', 'simple-history' );
2687
+ break;
2688
+
2689
+ case 'Error':
2690
+ $str_translated = _x( 'Error', 'Log level in gui', 'simple-history' );
2691
+ break;
2692
+
2693
+ case 'Warning':
2694
+ $str_translated = _x( 'Warning', 'Log level in gui', 'simple-history' );
2695
+ break;
2696
+
2697
+ case 'Notice':
2698
+ $str_translated = _x( 'Notice', 'Log level in gui', 'simple-history' );
2699
+ break;
2700
+
2701
+ case 'Info':
2702
+ $str_translated = _x( 'Info', 'Log level in gui', 'simple-history' );
2703
+ break;
2704
+
2705
+ case 'Debug':
2706
+ $str_translated = _x( 'Debug', 'Log level in gui', 'simple-history' );
2707
+ break;
2708
+
2709
+ default:
2710
+ $str_translated = $loglevel;
2711
+ } // End switch().
2712
+
2713
+ return $str_translated;
2714
+ }
2715
+
2716
+ public function getInstantiatedLoggers() {
2717
+ return $this->instantiatedLoggers;
2718
+ }
2719
+
2720
+ public function getInstantiatedDropins() {
2721
+ return $this->instantiatedDropins;
2722
+ }
2723
+
2724
+ /**
2725
+ * @param string $slug
2726
+ * @return mixed logger instance if found, bool false if logger not found
2727
+ */
2728
+ public function getInstantiatedLoggerBySlug( $slug = '' ) {
2729
+ if ( empty( $slug ) ) {
2730
+ return false;
2731
+ }
2732
+
2733
+ foreach ( $this->getInstantiatedLoggers() as $one_logger ) {
2734
+ if ( $slug == $one_logger['instance']->slug ) {
2735
+ return $one_logger['instance'];
2736
+ }
2737
+ }
2738
+
2739
+ return false;
2740
+ }
2741
+
2742
+ /**
2743
+ * Check which loggers a user has the right to read and return an array
2744
+ * with all loggers they are allowed to read
2745
+ *
2746
+ * @param int $user_id Id of user to get loggers for. Defaults to current user id.
2747
+ * @param string $format format to return loggers in. Default is array. Can also be "sql"
2748
+ * @return array
2749
+ */
2750
+ public function getLoggersThatUserCanRead( $user_id = '', $format = 'array' ) {
2751
+ $arr_loggers_user_can_view = array();
2752
+
2753
+ if ( ! is_numeric( $user_id ) ) {
2754
+ $user_id = get_current_user_id();
2755
+ }
2756
+
2757
+ $loggers = $this->getInstantiatedLoggers();
2758
+ foreach ( $loggers as $one_logger ) {
2759
+ $logger_capability = $one_logger['instance']->getCapability();
2760
+
2761
+ // $arr_loggers_user_can_view = apply_filters("simple_history/loggers_user_can_read", $user_id, $arr_loggers_user_can_view);
2762
+ $user_can_read_logger = user_can( $user_id, $logger_capability );
2763
+ $user_can_read_logger = apply_filters(
2764
+ 'simple_history/loggers_user_can_read/can_read_single_logger',
2765
+ $user_can_read_logger,
2766
+ $one_logger['instance'],
2767
+ $user_id
2768
+ );
2769
+
2770
+ if ( $user_can_read_logger ) {
2771
+ $arr_loggers_user_can_view[] = $one_logger;
2772
+ }
2773
+ }
2774
+
2775
+ /**
2776
+ * Fires before Simple History does it's init stuff
2777
+ *
2778
+ * @since 2.0
2779
+ *
2780
+ * @param array $arr_loggers_user_can_view Array with loggers that user $user_id can read
2781
+ * @param int user_id ID of user to check read capability for
2782
+ */
2783
+ $arr_loggers_user_can_view = apply_filters(
2784
+ 'simple_history/loggers_user_can_read',
2785
+ $arr_loggers_user_can_view,
2786
+ $user_id
2787
+ );
2788
+
2789
+ // just return array with slugs in parenthesis suitable for sql-where
2790
+ if ( 'sql' == $format ) {
2791
+ $str_return = '(';
2792
+
2793
+ if ( count( $arr_loggers_user_can_view ) ) {
2794
+ foreach ( $arr_loggers_user_can_view as $one_logger ) {
2795
+ $str_return .= sprintf( '"%1$s", ', esc_sql( $one_logger['instance']->slug ) );
2796
+ }
2797
+
2798
+ $str_return = rtrim( $str_return, ' ,' );
2799
+ } else {
2800
+ // user was not allowed to read any loggers, return in (NULL) to return nothing
2801
+ $str_return .= 'NULL';
2802
+ }
2803
+
2804
+ $str_return .= ')';
2805
+
2806
+ return $str_return;
2807
+ }
2808
+
2809
+ return $arr_loggers_user_can_view;
2810
+ }
2811
+
2812
+ /**
2813
+ * Retrieve the avatar for a user who provided a user ID or email address.
2814
+ * A modified version of the function that comes with WordPress, but we
2815
+ * want to allow/show gravatars even if they are disabled in discussion settings
2816
+ *
2817
+ * @since 2.0
2818
+ *
2819
+ * @param string $email email address
2820
+ * @param int $size Size of the avatar image
2821
+ * @param string $default URL to a default image to use if no avatar is available
2822
+ * @param string $alt Alternative text to use in image tag. Defaults to blank
2823
+ * @return string <img> tag for the user's avatar
2824
+ */
2825
+ public function get_avatar( $email, $size = '96', $default = '', $alt = false ) {
2826
+ // WP setting for avatars is to show, so just use the built in function
2827
+ if ( get_option( 'show_avatars' ) ) {
2828
+ $avatar = get_avatar( $email, $size, $default, $alt );
2829
+
2830
+ return $avatar;
2831
+ } else {
2832
+ // WP setting for avatar was to not show, but we do it anyway, using the same code as get_avatar() would have used
2833
+ if ( false === $alt ) {
2834
+ $safe_alt = '';
2835
+ } else {
2836
+ $safe_alt = esc_attr( $alt );
2837
+ }
2838
+
2839
+ if ( ! is_numeric( $size ) ) {
2840
+ $size = '96';
2841
+ }
2842
+
2843
+ if ( empty( $default ) ) {
2844
+ $avatar_default = get_option( 'avatar_default' );
2845
+ if ( empty( $avatar_default ) ) {
2846
+ $default = 'mystery';
2847
+ } else {
2848
+ $default = $avatar_default;
2849
+ }
2850
+ }
2851
+
2852
+ if ( ! empty( $email ) ) {
2853
+ $email_hash = md5( strtolower( trim( $email ) ) );
2854
+ }
2855
+
2856
+ if ( is_ssl() ) {
2857
+ $host = 'https://secure.gravatar.com';
2858
+ } else {
2859
+ if ( ! empty( $email ) ) {
2860
+ $host = sprintf( 'http://%d.gravatar.com', hexdec( $email_hash[0] ) % 2 );
2861
+ } else {
2862
+ $host = 'http://0.gravatar.com';
2863
+ }
2864
+ }
2865
+
2866
+ if ( 'mystery' == $default ) {
2867
+ $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}";
2868
+ } elseif ( 'blank' == $default ) {
2869
+ $default = $email ? 'blank' : includes_url( 'images/blank.gif' );
2870
+ } elseif ( ! empty( $email ) && 'gravatar_default' == $default ) {
2871
+ $default = '';
2872
+ } elseif ( 'gravatar_default' == $default ) {
2873
+ $default = "$host/avatar/?s={$size}";
2874
+ } elseif ( empty( $email ) ) {
2875
+ $default = "$host/avatar/?d=$default&amp;s={$size}";
2876
+ } elseif ( strpos( $default, 'http://' ) === 0 ) {
2877
+ $default = add_query_arg( 's', $size, $default );
2878
+ }
2879
+
2880
+ if ( ! empty( $email ) ) {
2881
+ $out = "$host/avatar/";
2882
+ $out .= $email_hash;
2883
+ $out .= '?s=' . $size;
2884
+ $out .= '&amp;d=' . urlencode( $default );
2885
+
2886
+ $rating = get_option( 'avatar_rating' );
2887
+ if ( ! empty( $rating ) ) {
2888
+ $out .= "&amp;r={$rating}";
2889
+ }
2890
+
2891
+ $out = str_replace( '&#038;', '&amp;', esc_url( $out ) );
2892
+ $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo' height='{$size}' width='{$size}' />";
2893
+ } else {
2894
+ $out = esc_url( $default );
2895
+ $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo avatar-default' height='{$size}' width='{$size}' />";
2896
+ }
2897
+
2898
+ /**
2899
+ * Filter the avatar to retrieve.
2900
+ * Same filter WordPress uses
2901
+ *
2902
+ * @since 2.0.19
2903
+ *
2904
+ * @param string $avatar Image tag for the user's avatar.
2905
+ * @param int|object|string $id_or_email A user ID, email address, or comment object.
2906
+ * @param int $size Square avatar width and height in pixels to retrieve.
2907
+ * @param string $alt Alternative text to use in the avatar image tag.
2908
+ * Default empty.
2909
+ */
2910
+ $avatar = apply_filters( 'get_avatar', $avatar, $email, $size, $default, $alt ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
2911
+
2912
+ return $avatar;
2913
+ } // End if().
2914
+ }
2915
+
2916
+ /**
2917
+ * Quick stats above the log
2918
+ * Uses filter "simple_history/history_page/before_gui" to output its contents
2919
+ */
2920
+ public function output_quick_stats() {
2921
+ global $wpdb;
2922
+
2923
+ // Get number of events today
2924
+ $logQuery = new SimpleHistoryLogQuery();
2925
+ $logResults = $logQuery->query(
2926
+ array(
2927
+ 'posts_per_page' => 1,
2928
+ 'date_from' => strtotime( 'today' ),
2929
+ )
2930
+ );
2931
+
2932
+ $total_row_count = (int) $logResults['total_row_count'];
2933
+
2934
+ // Get sql query for where to read only loggers current user is allowed to read/view
2935
+ $sql_loggers_in = $this->getLoggersThatUserCanRead( get_current_user_id(), 'sql' );
2936
+
2937
+ // Get number of users today, i.e. events with wp_user as initiator
2938
+ $sql_users_today = sprintf(
2939
+ '
 
 
 
 
 
2940
  SELECT
2941
  DISTINCT(c.value) AS user_id
 
2942
  FROM %3$s AS h
2943
  INNER JOIN %4$s AS c
2944
  ON c.history_id = h.id AND c.key = "_user_id"
2947
  AND logger IN %1$s
2948
  AND date > "%2$s"
2949
  ',
2950
+ $sql_loggers_in,
2951
+ gmdate( 'Y-m-d H:i', strtotime( 'today' ) ),
2952
+ $wpdb->prefix . self::DBTABLE,
2953
+ $wpdb->prefix . self::DBTABLE_CONTEXTS
2954
+ );
2955
+
2956
+ $cache_key = 'quick_stats_users_today_' . md5( serialize( $sql_loggers_in ) );
2957
+ $cache_group = 'simple-history-' . $this->get_cache_incrementor();
2958
+ $results_users_today = wp_cache_get( $cache_key, $cache_group );
2959
+
2960
+ if ( false === $results_users_today ) {
2961
+ $results_users_today = $wpdb->get_results( $sql_users_today ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2962
+ wp_cache_set( $cache_key, $results_users_today, $cache_group );
2963
+ }
2964
+
2965
+ $count_users_today = count( $results_users_today );
2966
+
2967
+ // Get number of other sources (not wp_user).
2968
+ $sql_other_sources_where = sprintf(
2969
+ '
2970
  initiator <> "wp_user"
2971
  AND logger IN %1$s
2972
  AND date > "%2$s"
2973
  ',
2974
+ $sql_loggers_in,
2975
+ gmdate( 'Y-m-d H:i', strtotime( 'today' ) ),
2976
+ $wpdb->prefix . self::DBTABLE,
2977
+ $wpdb->prefix . self::DBTABLE_CONTEXTS
2978
+ );
2979
 
2980
+ $sql_other_sources_where = apply_filters( 'simple_history/quick_stats_where', $sql_other_sources_where );
2981
 
2982
+ $sql_other_sources = sprintf(
2983
+ '
2984
  SELECT
2985
  DISTINCT(h.initiator) AS initiator
2986
  FROM %3$s AS h
2987
  WHERE
2988
  %5$s
2989
  ',
2990
+ $sql_loggers_in,
2991
+ gmdate( 'Y-m-d H:i', strtotime( 'today' ) ),
2992
+ $wpdb->prefix . self::DBTABLE,
2993
+ $wpdb->prefix . self::DBTABLE_CONTEXTS,
2994
+ $sql_other_sources_where // 5
2995
+ );
2996
+
2997
+ $cache_key = 'quick_stats_results_other_sources_today_' . md5( serialize( $sql_other_sources ) );
2998
+ $results_other_sources_today = wp_cache_get( $cache_key, $cache_group );
2999
+
3000
+ if ( false === $results_other_sources_today ) {
3001
+ $results_other_sources_today = $wpdb->get_results( $sql_other_sources ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3002
+ wp_cache_set( $cache_key, $results_other_sources_today, $cache_group );
3003
+ }
3004
+
3005
+ $count_other_sources = count( $results_other_sources_today );
3006
+ ?>
3007
+ <div class="SimpleHistoryQuickStats">
3008
+ <p>
3009
+ <?php
3010
+ $msg_tmpl = '';
3011
+
3012
+ // No results today at all
3013
+ if ( $total_row_count == 0 ) {
3014
+ $msg_tmpl = __( 'No events today so far.', 'simple-history' );
3015
+ } else {
3016
+ /*
3017
+ Type of results
3018
+ x1 event today from 1 user.
3019
+ x1 event today from 1 source.
3020
+ 3 events today from 1 user.
3021
+ x2 events today from 2 users.
3022
+ x2 events today from 1 user and 1 other source.
3023
+ x3 events today from 2 users and 1 other source.
3024
+ x3 events today from 1 user and 2 other sources.
3025
+ x4 events today from 2 users and 2 other sources.
3026
+ */
3027
+
3028
+ // A single event existed and was from a user
3029
+ // 1 event today from 1 user.
3030
+ if ( $total_row_count == 1 && $count_users_today == 1 ) {
3031
+ $msg_tmpl .= __( 'One event today from one user.', 'simple-history' );
3032
+ }
3033
+
3034
+ // A single event existed and was from another source
3035
+ // 1 event today from 1 source.
3036
+ if ( $total_row_count == 1 && ! $count_users_today ) {
3037
+ $msg_tmpl .= __( 'One event today from one source.', 'simple-history' );
3038
+ }
3039
+
3040
+ // Multiple events from a single user
3041
+ // 3 events today from one user.
3042
+ if ( $total_row_count > 1 && $count_users_today == 1 && ! $count_other_sources ) {
3043
+ $msg_tmpl .= __( '%1$d events today from one user.', 'simple-history' );
3044
+ }
3045
+
3046
+ // Multiple events from only users
3047
+ // 2 events today from 2 users.
3048
+ if ( $total_row_count > 1 && $count_users_today == $total_row_count ) {
3049
+ $msg_tmpl .= __( '%1$d events today from %2$d users.', 'simple-history' );
3050
+ }
3051
+
3052
+ // Multiple events from 1 single user and 1 single other source
3053
+ // 2 events today from 1 user and 1 other source.
3054
+ if ( $total_row_count && 1 == $count_users_today && 1 == $count_other_sources ) {
3055
+ $msg_tmpl .= __( '%1$d events today from one user and one other source.', 'simple-history' );
3056
+ }
3057
+
3058
+ // Multiple events from multple users but from only 1 single other source
3059
+ // 3 events today from 2 users and 1 other source.
3060
+ if ( $total_row_count > 1 && $count_users_today > 1 && $count_other_sources == 1 ) {
3061
+ $msg_tmpl .= __( '%1$d events today from one user and one other source.', 'simple-history' );
3062
+ }
3063
+
3064
+ // Multiple events from 1 user but from multiple other source
3065
+ // 3 events today from 1 user and 2 other sources.
3066
+ if ( $total_row_count > 1 && 1 == $count_users_today && $count_other_sources > 1 ) {
3067
+ $msg_tmpl .= __( '%1$d events today from one user and %3$d other sources.', 'simple-history' );
3068
+ }
3069
+
3070
+ // Multiple events from multiple user and from multiple other sources
3071
+ // 4 events today from 2 users and 2 other sources.
3072
+ if ( $total_row_count > 1 && $count_users_today > 1 && $count_other_sources > 1 ) {
3073
+ $msg_tmpl .= __( '%1$s events today from %2$d users and %3$d other sources.', 'simple-history' );
3074
+ }
3075
+ } // End if().
3076
+
3077
+ // Show stats if we have something to output.
3078
+ if ( $msg_tmpl ) {
3079
+ printf(
3080
+ esc_html( $msg_tmpl ),
3081
+ (int) $logResults['total_row_count'], // 1
3082
+ (int) $count_users_today, // 2
3083
+ (int) $count_other_sources // 3
3084
+ );
3085
+ }
3086
+ ?>
3087
+ </p>
3088
+ </div>
3089
+ <?php
3090
+ }
3091
+
3092
+ /**
3093
+ * https://www.tollmanz.com/invalidation-schemes/
3094
+ *
3095
+ * @param $refresh bool
3096
+ * @return string
3097
+ */
3098
+ public static function get_cache_incrementor( $refresh = false ) {
3099
+ $incrementor_key = 'simple_history_incrementor';
3100
+ $incrementor_value = wp_cache_get( $incrementor_key );
3101
+
3102
+ if ( false === $incrementor_value || true === $refresh ) {
3103
+ $incrementor_value = time();
3104
+ wp_cache_set( $incrementor_key, $incrementor_value );
3105
+ }
3106
+
3107
+ return $incrementor_value;
3108
+ }
3109
+
3110
+ // Number of rows the last n days.
3111
+ public function get_num_events_last_n_days( $period_days = 28 ) {
3112
+ $transient_key = 'sh_' . md5( __METHOD__ . $period_days . '_2' );
3113
+
3114
+ $count = get_transient( $transient_key );
3115
+
3116
+ if ( false === $count ) {
3117
+ global $wpdb;
3118
+
3119
+ $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead( null, 'sql' );
3120
+
3121
+ $sql = sprintf(
3122
+ '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3123
  SELECT count(*)
3124
  FROM %1$s
3125
  WHERE UNIX_TIMESTAMP(date) >= %2$d
3126
  AND logger IN %3$s
3127
  ',
3128
+ $wpdb->prefix . self::DBTABLE,
3129
+ strtotime( "-$period_days days" ),
3130
+ $sqlStringLoggersUserCanRead
3131
+ );
3132
 
3133
+ $count = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3134
 
3135
+ set_transient( $transient_key, $count, HOUR_IN_SECONDS );
3136
+ }
3137
 
3138
+ return $count;
3139
+ }
3140
 
3141
+ public function get_num_events_per_day_last_n_days( $period_days = 28 ) {
3142
+ $transient_key = 'sh_' . md5( __METHOD__ . $period_days . '_2' );
3143
+ $dates = get_transient( $transient_key );
3144
 
3145
+ if ( false === $dates ) {
3146
+ global $wpdb;
3147
 
3148
+ $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead( null, 'sql' );
 
3149
 
3150
+ $sql = sprintf(
3151
+ '
 
 
3152
  SELECT
3153
  date_format(date, "%%Y-%%m-%%d") AS yearDate,
3154
  count(date) AS count
3160
  GROUP BY yearDate
3161
  ORDER BY yearDate ASC
3162
  ',
3163
+ $wpdb->prefix . self::DBTABLE,
3164
+ strtotime( "-$period_days days" ),
3165
+ $sqlStringLoggersUserCanRead
3166
+ );
3167
+
3168
+ $dates = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3169
+
3170
+ set_transient( $transient_key, $dates, HOUR_IN_SECONDS );
3171
+ }
3172
+
3173
+ return $dates;
3174
+ }
3175
+
3176
+ /**
3177
+ * Get number of unique events the last n days.
3178
+ *
3179
+ * @param int $days
3180
+ * @return int Number of days.
3181
+ */
3182
+ public function get_unique_events_for_days( $days = 7 ) {
3183
+ global $wpdb;
3184
+ $days = (int) $days;
3185
+ $table_name = $wpdb->prefix . self::DBTABLE;
3186
+ $cache_key = 'sh_' . md5( __METHOD__ . $days );
3187
+ $numEvents = get_transient( $cache_key );
3188
+
3189
+ if ( false == $numEvents ) {
3190
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
3191
+ $sql = $wpdb->prepare(
3192
+ "
 
 
3193
  SELECT count( DISTINCT occasionsID )
3194
  FROM $table_name
3195
  WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3196
+ ",
3197
+ $days
3198
+ );
3199
+ // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
3200
+
3201
+ $numEvents = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3202
+
3203
+ set_transient( $cache_key, $numEvents, HOUR_IN_SECONDS );
3204
+ }
3205
+
3206
+ return $numEvents;
3207
+ }
3208
+
3209
+ /**
3210
+ * Output an admin notice about logger slug being to long
3211
+ */
3212
+ public function admin_notice_logger_slug_to_long() {
3213
+ ?>
3214
+ <div class="error notice">
3215
+ <p>
3216
+ <?php
3217
+ echo esc_html__(
3218
+ 'The slug for a logger in Simple History can be max 30 chars long.',
3219
+ 'simple-history'
3220
+ );
3221
+ ?>
3222
+ </p>
3223
+ </div>
3224
+ <?php
3225
+ }
3226
  }
inc/SimpleHistoryLogQuery.php CHANGED
@@ -1,134 +1,128 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Queries the Simple History Log
7
  */
8
- class SimpleHistoryLogQuery
9
- {
10
 
11
- public function __construct()
12
- {
 
 
 
 
 
 
13
 
14
- /*
15
- if ( is_array($args) && ! empty($args) ) {
16
 
17
- return $this->query($args);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- }
20
- */
21
- }
22
-
23
- public function query($args)
24
- {
25
- $defaults = array(
26
-
27
- // overview | occasions
28
- 'type' => 'overview',
29
-
30
- // Number of posts to show per page. 0 to show all.
31
- 'posts_per_page' => 0,
32
-
33
- // Page to show. 1 = first page
34
- 'paged' => 1,
35
-
36
- // Array. Only get posts that are in array.
37
- 'post__in' => null,
38
-
39
- // array or html
40
- 'format' => 'array',
41
-
42
- // If max_id_first_page is set then only get rows
43
- // that have id equal or lower than this, to make
44
- 'max_id_first_page' => null,
45
-
46
- // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id
47
- 'since_id' => null,
48
-
49
- // date range
50
- // in unix datetime or Y-m-d H:i (or format compatible with strtotime())
51
- 'date_from' => null,
52
- 'date_to' => null,
53
-
54
- // months in format "Y-m"
55
- // array or comma separated
56
- 'months' => null,
57
-
58
- // dates in format
59
- // "month:2015-06" for june 2015
60
- // "lastdays:7" for the last 7 days
61
- 'dates' => null,
62
-
63
- // search
64
- 'search' => null,
65
-
66
- // log levels to include. comma separated or as array. defaults to all.
67
- 'loglevels' => null,
68
-
69
- // loggers to include. comma separated. defaults to all the user can read
70
- 'loggers' => null,
71
-
72
- 'messages' => null,
73
-
74
- // userID as number
75
- 'user' => null,
76
-
77
- // user ids, comma separated
78
- 'users' => null,
79
-
80
- // Can also contain:
81
- // occasionsCount
82
- // occasionsCountMaxReturn
83
- // occasionsID
84
- // If rows should be returned, or the actualy sql query used
85
- 'returnQuery' => false,
86
-
87
- );
88
-
89
- $args = wp_parse_args($args, $defaults);
90
- // sf_d($args, "Run log query with args");
91
- $cache_key = 'SimpleHistoryLogQuery_' . md5(serialize($args)) . '_get_' . md5(serialize($_GET)) . '_userid_' . get_current_user_id();
92
- $cache_group = 'simple-history-' . SimpleHistory::get_cache_incrementor();
93
- $arr_return = wp_cache_get($cache_key, $cache_group);
94
-
95
- if (false !== $arr_return) {
96
- return $arr_return;
97
- }
98
-
99
- /*
100
- Subequent occasions query thanks to this Stack Overflow thread:
101
- http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320
102
- Similar questions that I didn't manage to understand, work, or did try:
103
- - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent
104
- - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent
105
- - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences
106
- - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows
107
- - http://stackoverflow.com/questions/17061156/mysql-group-by-range
108
- - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql
109
- */
110
-
111
- global $wpdb;
112
-
113
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
114
- $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
115
-
116
- $where = '1 = 1';
117
- $limit = '';
118
- $inner_where = '1 = 1';
119
-
120
- if ('overview' === $args['type'] || 'single' === $args['type']) {
121
- // Set variables used by query
122
- $sql_set_var = "SET @a:='', @counter:=1, @groupby:=0";
123
- $wpdb->query($sql_set_var);
124
-
125
- // New and slightly faster query
126
- // 1 = where
127
- // 2 = limit
128
- // 3 = db name
129
- // 4 = where for inner calc sql query thingie
130
- // 5 = db name contexts
131
- $sql_tmpl = '
132
  /*NO_SELECT_FOUND_ROWS*/
133
  SELECT
134
  SQL_CALC_FOUND_ROWS
@@ -174,18 +168,18 @@ class SimpleHistoryLogQuery
174
  %2$s
175
  ';
176
 
177
- $sh = SimpleHistory::get_instance();
178
-
179
- // Only include loggers that the current user can view
180
- // @TODO: this causes error if user has no access to any logger at all
181
- $sql_loggers_user_can_view = $sh->getLoggersThatUserCanRead(get_current_user_id(), 'sql');
182
- $inner_where .= " AND logger IN {$sql_loggers_user_can_view}";
183
- } elseif ('occasions' === $args['type']) {
184
- // Query template
185
- // 1 = where
186
- // 2 = limit
187
- // 3 = db name
188
- $sql_tmpl = '
189
  SELECT h.*,
190
  # fake columns that exist in overview query
191
  1 as subsequentOccasions
@@ -195,147 +189,154 @@ class SimpleHistoryLogQuery
195
  %2$s
196
  ';
197
 
198
- $where .= ' AND h.id < ' . (int) $args['logRowID'];
199
- $where .= " AND h.occasionsID = '" . esc_sql($args['occasionsID']) . "'";
200
-
201
- if (isset($args['occasionsCountMaxReturn']) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount']) {
202
- // Limit to max nn events if occasionsCountMaxReturn is set.
203
- // Used in gui to prevent top many events returned, that can stall the browser.
204
- $limit = 'LIMIT ' . (int) $args['occasionsCountMaxReturn'];
205
- } else {
206
- // Regular limit that gets all occasions
207
- $limit = 'LIMIT ' . (int) $args['occasionsCount'];
208
- }
209
- }// End if().
210
-
211
- // Determine limit
212
- // Both posts_per_page and paged must be set
213
- $is_limit_query = ( is_numeric($args['posts_per_page']) && $args['posts_per_page'] > 0 );
214
- $is_limit_query = $is_limit_query && ( is_numeric($args['paged']) && $args['paged'] > 0 );
215
- if ($is_limit_query) {
216
- $limit_offset = ($args['paged'] - 1) * $args['posts_per_page'];
217
- $limit .= sprintf('LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page']);
218
- }
219
-
220
- // Determine where
221
- if ($args['post__in'] && is_array($args['post__in'])) {
222
- // make sure all vals are integers
223
- $args['post__in'] = array_map('intval', $args['post__in']);
224
-
225
- $inner_where .= sprintf(' AND id IN (%1$s)', implode(',', $args['post__in']));
226
- }
227
-
228
- // If max_id_first_page is then then only include rows
229
- // with id equal to or earlier
230
- if (isset($args['max_id_first_page']) && is_numeric($args['max_id_first_page'])) {
231
- $max_id_first_page = (int) $args['max_id_first_page'];
232
- $inner_where .= sprintf(
233
- ' AND id <= %1$d',
234
- $max_id_first_page
235
- );
236
- }
237
-
238
- if (isset($args['since_id']) && is_numeric($args['since_id'])) {
239
- $since_id = (int) $args['since_id'];
240
- /*
241
- $where .= sprintf(
242
- ' AND t.id > %1$d',
243
- $since_id
244
- );
245
- */
246
- // Add where to inner because that's faster
247
- $inner_where .= sprintf(
248
- ' AND id > %1$d',
249
- $since_id
250
- );
251
- }
252
-
253
- // Append date where
254
- if (! empty($args['date_from'])) {
255
- // date_from=2014-08-01
256
- // if date is not numeric assume Y-m-d H:i-format
257
- $date_from = $args['date_from'];
258
- if (! is_numeric($date_from)) {
259
- $date_from = strtotime($date_from);
260
- }
261
-
262
- $inner_where .= "\n" . sprintf(' AND date >= "%1$s"', esc_sql(date('Y-m-d H:i:s', $date_from)));
263
- }
264
-
265
- if (! empty($args['date_to'])) {
266
- // date_to=2014-08-01
267
- // if date is not numeric assume Y-m-d H:i-format
268
- $date_to = $args['date_to'];
269
- if (! is_numeric($date_to)) {
270
- $date_to = strtotime($date_to);
271
- }
272
-
273
- $inner_where .= "\n" . sprintf(' AND date <= "%1$s"', date('Y-m-d H:i:s', $date_to));
274
- }
275
-
276
- /*
277
- AND date >= "2015-01-01 00:00:00" AND date <= "2015-01-31 00:00:00"
278
- */
279
- // echo $inner_where;exit;
280
- // dats
281
- // if months they translate to $args["months"] because we already have support for that
282
- // can't use months and dates and the same time
283
- if (! empty($args['dates'])) {
284
- if (is_array($args['dates'])) {
285
- $arr_dates = $args['dates'];
286
- } else {
287
- $arr_dates = explode(',', $args['dates']);
288
- }
289
-
290
- $args['months'] = array();
291
- $args['lastdays'] = 0;
292
-
293
- foreach ($arr_dates as $one_date) {
294
- // If begins with "month:" then strip string and keep only month numbers
295
- if (strpos($one_date, 'month:') === 0) {
296
- $args['months'][] = substr($one_date, strlen('month:'));
297
- } elseif (strpos($one_date, 'lastdays:') === 0) {
298
- // Only keep largest lastdays value
299
- $args['lastdays'] = max($args['lastdays'], substr($one_date, strlen('lastdays:')));
300
- // $args["lastdays"][] = substr($one_date, strlen("lastdays:"));
301
- }
302
- }
303
- }
304
-
305
- // lastdays, as int
306
- if (! empty($args['lastdays'])) {
307
- $inner_where .= sprintf('
 
 
 
 
 
308
  # lastdays
309
  AND date >= DATE(NOW()) - INTERVAL %d DAY
310
- ', $args['lastdays']);
311
- }
312
-
313
- // months, in format "Y-m"
314
- if (! empty($args['months'])) {
315
- if (is_array($args['months'])) {
316
- $arr_months = $args['months'];
317
- } else {
318
- $arr_months = explode(',', $args['months']);
319
- }
320
-
321
- $sql_months = '
 
 
322
  # sql_months
323
  AND (
324
  ';
325
 
326
- foreach ($arr_months as $one_month) {
327
- // beginning of month
328
- // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n";
329
- // >> 2014-08-01 00:00
330
- $date_month_beginning = strtotime($one_month);
331
 
332
- // end of month
333
- // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";'
334
- // >> 2014-09-01 00:00
335
- $date_month_end = strtotime("{$one_month} + 1 month");
336
 
337
- $sql_months .= sprintf(
338
- '
339
  (
340
  date >= "%1$s"
341
  AND date <= "%2$s"
@@ -343,446 +344,378 @@ class SimpleHistoryLogQuery
343
 
344
  OR
345
  ',
346
- date('Y-m-d H:i:s', $date_month_beginning), // 1
347
- date('Y-m-d H:i:s', $date_month_end) // 2
348
- );
349
- }
350
 
351
- $sql_months = trim($sql_months);
352
- $sql_months = rtrim($sql_months, ' OR ');
353
 
354
- $sql_months .= '
355
  # end sql_months and wrap
356
  )
357
  ';
358
 
359
- $inner_where .= $sql_months;
360
- // echo $inner_where;exit;
361
- }// End if().
362
-
363
- // search
364
- if (! empty($args['search'])) {
365
- $search_words = $args['search'];
366
- $str_search_conditions = '';
367
- $arr_search_words = preg_split('/[\s,]+/', $search_words);
368
-
369
- // create array of all searched words
370
- // split both spaces and commas and such
371
- $arr_sql_like_cols = array( 'message', 'logger', 'level' );
372
-
373
- foreach ($arr_sql_like_cols as $one_col) {
374
- $str_sql_search_words = '';
375
-
376
- foreach ($arr_search_words as $one_search_word) {
377
- if (method_exists($wpdb, 'esc_like')) {
378
- $str_like = esc_sql($wpdb->esc_like($one_search_word));
379
- } else {
380
- $str_like = esc_sql(like_escape($one_search_word));
381
- }
382
-
383
- $str_sql_search_words .= sprintf(
384
- ' AND %1$s LIKE "%2$s" ',
385
- $one_col,
386
- "%{$str_like}%"
387
- );
388
- }
389
-
390
- $str_sql_search_words = ltrim($str_sql_search_words, ' AND ');
391
-
392
- $str_search_conditions .= "\n" . sprintf(
393
- ' OR ( %1$s ) ',
394
- $str_sql_search_words
395
- );
396
- }
397
-
398
- $str_search_conditions = preg_replace('/^OR /', ' ', trim($str_search_conditions));
399
-
400
- // also search contexts
401
- $str_search_conditions .= "\n OR ( ";
402
- foreach ($arr_search_words as $one_search_word) {
403
- if (method_exists($wpdb, 'esc_like')) {
404
- $str_like = esc_sql($wpdb->esc_like($one_search_word));
405
- } else {
406
- $str_like = esc_sql(like_escape($one_search_word));
407
- }
408
-
409
- $str_search_conditions .= "\n" . sprintf(
410
- ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ',
411
- $table_name_contexts, // 1
412
- '%' . $str_like . '%' // 2
413
- );
414
- }
415
- $str_search_conditions = preg_replace('/ AND $/', '', $str_search_conditions);
416
-
417
- $str_search_conditions .= "\n ) "; // end or for contexts
418
-
419
- $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) ";
420
-
421
- // echo $inner_where;exit;
422
- }// End if().
423
-
424
- // log levels
425
- // comma separated
426
- // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loglevel=error,warn
427
- if (! empty($args['loglevels'])) {
428
- $sql_loglevels = '';
429
-
430
- if (is_array($args['loglevels'])) {
431
- $arr_loglevels = $args['loglevels'];
432
- } else {
433
- $arr_loglevels = explode(',', $args['loglevels']);
434
- }
435
-
436
- foreach ($arr_loglevels as $one_loglevel) {
437
- $sql_loglevels .= sprintf(' "%s", ', esc_sql($one_loglevel));
438
- }
439
-
440
- if ($sql_loglevels) {
441
- $sql_loglevels = rtrim($sql_loglevels, ' ,');
442
- $sql_loglevels = "\n AND level IN ({$sql_loglevels}) ";
443
- }
444
-
445
- $inner_where .= $sql_loglevels;
446
- }
447
-
448
- // messages
449
- if (! empty($args['messages'])) {
450
- // print_r($args["messages"]);exit;
451
- /*
452
- Array
453
- (
454
- [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted
455
- [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam
456
- )
457
- */
458
-
459
- // Array with loggers and messages
460
- $arr_loggers_and_messages = array();
461
-
462
- // Tranform from get'et format to our own internal format
463
- foreach ((array) $args['messages'] as $one_arr_messages_row) {
464
- $arr_row_messages = explode(',', $one_arr_messages_row);
465
- // print_r($arr_row_messages);#exit;
466
- /*
467
- Array
468
- (
469
- [0] => SimpleCommentsLogger:anon_comment_added
470
- [1] => SimpleCommentsLogger:user_comment_added
471
- [2] => SimpleCommentsLogger:anon_trackback_added
472
- */
473
- foreach ($arr_row_messages as $one_row_logger_and_message) {
474
- $arr_one_logger_and_message = explode(':', $one_row_logger_and_message);
475
-
476
- if (! isset($arr_loggers_and_messages[ $arr_one_logger_and_message[0] ])) {
477
- $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array();
478
- }
479
-
480
- $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1];
481
- }
482
- }
483
-
484
- // Now create sql where based on loggers and messages
485
- $sql_messages_where = ' AND (';
486
- // print_r($arr_loggers_and_messages);exit;
487
- foreach ($arr_loggers_and_messages as $logger_slug => $logger_messages) {
488
- $sql_messages_where .= sprintf(
489
- '
490
  (
491
  h.logger = "%1$s"
492
- AND c1.value IN (%2$s)
493
  )
494
  OR ',
495
- esc_sql($logger_slug),
496
- "'" . implode("','", $logger_messages) . "'"
497
- );
498
- }
499
- // remove last or
500
- $sql_messages_where = preg_replace('/OR $/', '', $sql_messages_where);
501
-
502
- $sql_messages_where .= "\n )";
503
- // echo $sql_messages_where;exit;
504
- $where .= $sql_messages_where;
505
-
506
- /*
507
- print_r($arr_loggers_and_messages);exit;
508
- Array
509
- (
510
- [SimpleCommentsLogger] => Array
511
- (
512
- [0] => anon_comment_added
513
- [1] => user_comment_added
514
- [2] => anon_trackback_added
515
- [3] => user_trackback_added
516
- [4] => anon_pingback_added
517
- [5] => user_pingback_added
518
- [6] => comment_edited
519
- [7] => trackback_edited
520
- [8] => pingback_edited
521
- [9] => comment_status_approve
522
- [10] => trackback_status_approve
523
- [11] => pingback_status_approve
524
- [12] => comment_status_hold
525
- [13] => trackback_status_hold
526
- [14] => pingback_status_hold
527
- [15] => comment_status_spam
528
- [16] => trackback_status_spam
529
- [17] => pingback_status_spam
530
- [18] => comment_status_trash
531
- [19] => trackback_status_trash
532
- [20] => pingback_status_trash
533
- [21] => comment_untrashed
534
- [22] => trackback_untrashed
535
- [23] => pingback_untrashed
536
- [24] => comment_deleted
537
- [25] => trackback_deleted
538
- [26] => pingback_deleted
539
- )
540
-
541
- [SimpleUserLogger] => Array
542
- (
543
- [0] => SimpleUserLogger
544
- [1] => SimpleUserLogger
545
- )
546
-
547
- )
548
-
549
- */
550
- }// End if().
551
-
552
- // loggers
553
- // comma separated
554
- // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger
555
- if (! empty($args['loggers'])) {
556
- $sql_loggers = '';
557
- if (is_array($args['loggers'])) {
558
- $arr_loggers = $args['loggers'];
559
- } else {
560
- $arr_loggers = explode(',', $args['loggers']);
561
- }
562
-
563
- // print_r($args["loggers"]);exit;
564
- // print_r($arr_loggers);exit;
565
- /*
566
- Example of version with logger + message keys
567
- Array
568
- (
569
- [0] => SimpleUserLogger:user_created
570
- [1] => SimpleUserLogger:user_deleted
571
- )
572
- */
573
-
574
- foreach ($arr_loggers as $one_logger) {
575
- $sql_loggers .= sprintf(' "%s", ', esc_sql($one_logger));
576
- }
577
-
578
- if ($sql_loggers) {
579
- $sql_loggers = rtrim($sql_loggers, ' ,');
580
- $sql_loggers = "\n AND logger IN ({$sql_loggers}) ";
581
- }
582
-
583
- $inner_where .= $sql_loggers;
584
- }
585
-
586
- // user, a single userID
587
- if (! empty($args['user']) && is_numeric($args['user'])) {
588
- $userID = (int) $args['user'];
589
- $sql_user = sprintf(
590
- '
591
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )
592
  ',
593
- $table_name_contexts, // 1
594
- $userID // 2
595
- );
596
 
597
- $inner_where .= $sql_user;
598
- }
599
 
600
- // If users is array, make it comma separated.
601
- if (isset($args['users']) && is_array($args['users'])) {
602
- $args['users'] = implode(',', $args['users']);
603
- }
604
 
605
- // Users, comma separated.
606
- if (! empty($args['users']) && is_string($args['users'])) {
607
- $users = explode(',', $args['users']);
608
- $users = array_map('intval', $users);
609
 
610
- if ($users) {
611
- $users_in = implode(',', $users);
612
 
613
- $sql_user = sprintf(
614
- '
615
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )
616
  ',
617
- $table_name_contexts, // 1
618
- $users_in // 2
619
- );
620
-
621
- $inner_where .= $sql_user;
622
-
623
- // echo $inner_where;exit;
624
- }
625
- }
626
-
627
- /**
628
- * Filter the sql template
629
- *
630
- * @since 2.0
631
- *
632
- * @param string $sql_tmpl
633
- */
634
- $sql_tmpl = apply_filters('simple_history/log_query_sql_template', $sql_tmpl);
635
-
636
- /**
637
- * Filter the sql template where clause
638
- *
639
- * @since 2.0
640
- *
641
- * @param string $where
642
- */
643
- $where = apply_filters('simple_history/log_query_sql_where', $where);
644
-
645
- /**
646
- * Filter the sql template limit
647
- *
648
- * @since 2.0
649
- *
650
- * @param string $limit
651
- */
652
- $limit = apply_filters('simple_history/log_query_limit', $limit);
653
-
654
- /**
655
- * Filter the sql template limit
656
- *
657
- * @since 2.0
658
- *
659
- * @param string $limit
660
- */
661
- $inner_where = apply_filters('simple_history/log_query_inner_where', $inner_where);
662
-
663
- $sql = sprintf(
664
- $sql_tmpl, // sprintf template
665
- $where, // 1
666
- $limit, // 2
667
- $table_name, // 3
668
- $inner_where, // 4
669
- $table_name_contexts // 5
670
- );
671
-
672
- /**
673
- * Filter the final sql query
674
- *
675
- * @since 2.0
676
- *
677
- * @param string $sql
678
- */
679
- $sql = apply_filters('simple_history/log_query_sql', $sql);
680
-
681
- // Remove comments below to debug query (includes query in json result)
682
- // $include_query_in_result = true;
683
- if (isset($_GET['SimpleHistoryLogQuery-showDebug']) && $_GET['SimpleHistoryLogQuery-showDebug']) {
684
- echo '<pre>';
685
- echo $sql_set_var;
686
- echo $sql;
687
- exit;
688
- }
689
-
690
- // Only return sql query
691
- if ($args['returnQuery']) {
692
- return $sql;
693
- }
694
-
695
- $log_rows = $wpdb->get_results($sql, OBJECT_K);
696
- $num_rows = sizeof($log_rows);
697
-
698
- // Find total number of rows that we would have gotten without pagination
699
- // This is the number of rows with occasions taken into consideration
700
- $sql_found_rows = 'SELECT FOUND_ROWS()';
701
- $total_found_rows = (int) $wpdb->get_var($sql_found_rows);
702
-
703
- // Add context
704
- $post_ids = wp_list_pluck($log_rows, 'id');
705
-
706
- if (empty($post_ids)) {
707
- $context_results = array();
708
- } else {
709
- $sql_context = sprintf('SELECT * FROM %2$s WHERE history_id IN (%1$s)', join(',', $post_ids), $table_name_contexts);
710
- $context_results = $wpdb->get_results($sql_context);
711
- }
712
-
713
- foreach ($context_results as $context_row) {
714
- if (! isset($log_rows[ $context_row->history_id ]->context)) {
715
- $log_rows[ $context_row->history_id ]->context = array();
716
- }
717
-
718
- $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value;
719
- }
720
-
721
- // Remove id from keys, because they are cumbersome when working with JSON
722
- $log_rows = array_values($log_rows);
723
- $min_id = null;
724
- $max_id = null;
725
-
726
- if (sizeof($log_rows)) {
727
- // Max id is simply the id of the first row
728
- $max_id = reset($log_rows)->id;
729
-
730
- // Min id = to find the lowest id we must take occasions into consideration
731
- $last_row = end($log_rows);
732
- $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1;
733
- if ($last_row_occasions_count === 0) {
734
- // Last row did not have any more occasions, so get min_id directly from the row
735
- $min_id = $last_row->id;
736
- } else {
737
- // Last row did have occaions, so fetch all occasions, and find id of last one
738
- $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
739
- $sql = sprintf(
740
- '
741
  SELECT id, date, occasionsID
742
  FROM %1$s
743
  WHERE id <= %2$s
744
  ORDER BY id DESC
745
  LIMIT %3$s
746
  ',
747
- $db_table,
748
- $last_row->id,
749
- $last_row_occasions_count + 1
750
- );
751
-
752
- $results = $wpdb->get_results($sql);
753
-
754
- // the last occasion has the id we consider last in this paged result
755
- $min_id = end($results)->id;
756
- }
757
- }// End if().
758
-
759
- // Calc pages
760
- if ($args['posts_per_page']) {
761
- $pages_count = Ceil($total_found_rows / (int) $args['posts_per_page']);
762
- } else {
763
- $pages_count = 1;
764
- }
765
-
766
- // Create array to return
767
- // Make all rows a sub key because we want to add some meta info too
768
- $log_rows_count = sizeof($log_rows);
769
- $page_rows_from = ( (int) $args['paged'] * (int) $args['posts_per_page'] ) - (int) $args['posts_per_page'] + 1;
770
- $page_rows_to = $page_rows_from + $log_rows_count - 1;
771
- $arr_return = array(
772
- 'total_row_count' => $total_found_rows,
773
- 'pages_count' => $pages_count,
774
- 'page_current' => (int) $args['paged'],
775
- 'page_rows_from' => $page_rows_from,
776
- 'page_rows_to' => $page_rows_to,
777
- 'max_id' => (int) $max_id,
778
- 'min_id' => (int) $min_id,
779
- 'log_rows_count' => $log_rows_count,
780
- 'log_rows' => $log_rows,
781
- );
782
-
783
- // sf_d($arr_return, '$arr_return');exit;
784
- wp_cache_set($cache_key, $arr_return, $cache_group);
785
-
786
- return $arr_return;
787
- }
788
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Queries the Simple History Log
7
  */
8
+ class SimpleHistoryLogQuery {
 
9
 
10
+ /**
11
+ * Query the log.
12
+ *
13
+ * @param string|array|object $args
14
+ * @return array
15
+ */
16
+ public function query( $args ) {
17
+ $defaults = array(
18
 
19
+ // overview | occasions
20
+ 'type' => 'overview',
21
 
22
+ // Number of posts to show per page. 0 to show all.
23
+ 'posts_per_page' => 0,
24
+
25
+ // Page to show. 1 = first page
26
+ 'paged' => 1,
27
+
28
+ // Array. Only get posts that are in array.
29
+ 'post__in' => null,
30
+
31
+ // array or html
32
+ 'format' => 'array',
33
+
34
+ // If max_id_first_page is set then only get rows
35
+ // that have id equal or lower than this, to make
36
+ 'max_id_first_page' => null,
37
+
38
+ // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id
39
+ 'since_id' => null,
40
+
41
+ // date range
42
+ // in unix datetime or Y-m-d H:i (or format compatible with strtotime())
43
+ 'date_from' => null,
44
+ 'date_to' => null,
45
+
46
+ // months in format "Y-m"
47
+ // array or comma separated
48
+ 'months' => null,
49
+
50
+ // dates in format
51
+ // "month:2015-06" for june 2015
52
+ // "lastdays:7" for the last 7 days
53
+ 'dates' => null,
54
+
55
+ // search
56
+ 'search' => null,
57
+
58
+ // log levels to include. comma separated or as array. defaults to all.
59
+ 'loglevels' => null,
60
+
61
+ // loggers to include. comma separated. defaults to all the user can read
62
+ 'loggers' => null,
63
+
64
+ 'messages' => null,
65
+
66
+ // userID as number
67
+ 'user' => null,
68
+
69
+ // user ids, comma separated
70
+ 'users' => null,
71
+
72
+ // Can also contain:
73
+ // occasionsCount
74
+ // occasionsCountMaxReturn
75
+ // occasionsID
76
+ // If rows should be returned, or the actualy sql query used
77
+ 'returnQuery' => false,
78
+
79
+ );
80
+
81
+ $args = wp_parse_args( $args, $defaults );
82
+
83
+ // Create cache key based on args and request and current user.
84
+ $cache_key = 'SimpleHistoryLogQuery_' . md5( serialize( $args ) ) . '_get_' . md5( serialize( $_GET ) ) . '_userid_' . get_current_user_id();
85
+ $cache_group = 'simple-history-' . SimpleHistory::get_cache_incrementor();
86
+ $arr_return = wp_cache_get( $cache_key, $cache_group );
87
+
88
+ if ( false !== $arr_return ) {
89
+ return $arr_return;
90
+ }
91
+
92
+ /*
93
+ Subequent occasions query thanks to this Stack Overflow thread:
94
+ http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320
95
+ Similar questions that I didn't manage to understand, work, or did try:
96
+ - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent
97
+ - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent
98
+ - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences
99
+ - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows
100
+ - http://stackoverflow.com/questions/17061156/mysql-group-by-range
101
+ - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql
102
+ */
103
+
104
+ global $wpdb;
105
+
106
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
107
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
108
+
109
+ $where = '1 = 1';
110
+ $limit = '';
111
+ $inner_where = '1 = 1';
112
+
113
+ if ( 'overview' === $args['type'] || 'single' === $args['type'] ) {
114
+ // Set variables used by query.
115
+ $sql_set_var = "SET @a:='', @counter:=1, @groupby:=0";
116
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
117
+ $wpdb->query( $sql_set_var );
118
 
119
+ // Main query
120
+ // 1 = where
121
+ // 2 = limit
122
+ // 3 = db name
123
+ // 4 = where for inner calc sql query thingie
124
+ // 5 = db name contexts
125
+ $sql_tmpl = '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  /*NO_SELECT_FOUND_ROWS*/
127
  SELECT
128
  SQL_CALC_FOUND_ROWS
168
  %2$s
169
  ';
170
 
171
+ $sh = SimpleHistory::get_instance();
172
+
173
+ // Only include loggers that the current user can view
174
+ // @TODO: this causes error if user has no access to any logger at all
175
+ $sql_loggers_user_can_view = $sh->getLoggersThatUserCanRead( get_current_user_id(), 'sql' );
176
+ $inner_where .= " AND logger IN {$sql_loggers_user_can_view}";
177
+ } elseif ( 'occasions' === $args['type'] ) {
178
+ // Query template
179
+ // 1 = where
180
+ // 2 = limit
181
+ // 3 = db name
182
+ $sql_tmpl = '
183
  SELECT h.*,
184
  # fake columns that exist in overview query
185
  1 as subsequentOccasions
189
  %2$s
190
  ';
191
 
192
+ $where .= ' AND h.id < ' . (int) $args['logRowID'];
193
+ $where .= " AND h.occasionsID = '" . esc_sql( $args['occasionsID'] ) . "'";
194
+
195
+ if ( isset( $args['occasionsCountMaxReturn'] ) && (int) $args['occasionsCountMaxReturn'] < (int) $args['occasionsCount'] ) {
196
+ // Limit to max nn events if occasionsCountMaxReturn is set.
197
+ // Used in gui to prevent to many events returned, that can stall the browser.
198
+ $limit = 'LIMIT ' . (int) $args['occasionsCountMaxReturn'];
199
+ } else {
200
+ // Regular limit that gets all occasions
201
+ $limit = 'LIMIT ' . (int) $args['occasionsCount'];
202
+ }
203
+ }// End if().
204
+
205
+ // Determine limit
206
+ // Both posts_per_page and paged must be set
207
+ $is_limit_query = ( is_numeric( $args['posts_per_page'] ) && $args['posts_per_page'] > 0 );
208
+ $is_limit_query = $is_limit_query && ( is_numeric( $args['paged'] ) && $args['paged'] > 0 );
209
+ if ( $is_limit_query ) {
210
+ $limit_offset = ( $args['paged'] - 1 ) * $args['posts_per_page'];
211
+ $limit .= sprintf( 'LIMIT %1$d, %2$d', $limit_offset, $args['posts_per_page'] );
212
+ }
213
+
214
+ // Determine where
215
+ if ( $args['post__in'] && is_array( $args['post__in'] ) ) {
216
+ // make sure all vals are integers
217
+ $args['post__in'] = array_map( 'intval', $args['post__in'] );
218
+
219
+ $inner_where .= sprintf( ' AND id IN (%1$s)', implode( ',', $args['post__in'] ) );
220
+ }
221
+
222
+ // If max_id_first_page is then then only include rows
223
+ // with id equal to or earlier
224
+ if ( isset( $args['max_id_first_page'] ) && is_numeric( $args['max_id_first_page'] ) ) {
225
+ $max_id_first_page = (int) $args['max_id_first_page'];
226
+ $inner_where .= sprintf(
227
+ ' AND id <= %1$d',
228
+ $max_id_first_page
229
+ );
230
+ }
231
+
232
+ if ( isset( $args['since_id'] ) && is_numeric( $args['since_id'] ) ) {
233
+ $since_id = (int) $args['since_id'];
234
+ // Add where to inner because that's faster
235
+ $inner_where .= sprintf(
236
+ ' AND id > %1$d',
237
+ $since_id
238
+ );
239
+ }
240
+
241
+ // Append date where
242
+ if ( ! empty( $args['date_from'] ) ) {
243
+ // date_from=2014-08-01
244
+ // if date is not numeric assume Y-m-d H:i-format
245
+ $date_from = $args['date_from'];
246
+ if ( ! is_numeric( $date_from ) ) {
247
+ $date_from = strtotime( $date_from );
248
+ }
249
+
250
+ $inner_where .= "\n" . sprintf( ' AND date >= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_from ) );
251
+ }
252
+
253
+ if ( ! empty( $args['date_to'] ) ) {
254
+ // date_to=2014-08-01
255
+ // if date is not numeric assume Y-m-d H:i-format
256
+ $date_to = $args['date_to'];
257
+ if ( ! is_numeric( $date_to ) ) {
258
+ $date_to = strtotime( $date_to );
259
+ }
260
+
261
+ $inner_where .= "\n" . sprintf( ' AND date <= "%1$s"', gmdate( 'Y-m-d H:i:s', $date_to ) );
262
+ }
263
+
264
+ // If months they translate to $args["months"] because we already have support for that
265
+ // can't use months and dates and the same time.
266
+ if ( ! empty( $args['dates'] ) ) {
267
+ if ( is_array( $args['dates'] ) ) {
268
+ $arr_dates = $args['dates'];
269
+ } else {
270
+ $arr_dates = explode( ',', $args['dates'] );
271
+ }
272
+
273
+ /*
274
+ $arr_dates can be a month:
275
+
276
+ Array
277
+ (
278
+ [0] => month:2021-11
279
+ )
280
+
281
+ $arr_dates can be a number of days:
282
+ Array
283
+ (
284
+ [0] => lastdays:7
285
+ )
286
+ */
287
+
288
+ $args['months'] = array();
289
+ $args['lastdays'] = 0;
290
+
291
+ foreach ( $arr_dates as $one_date ) {
292
+ if ( strpos( $one_date, 'month:' ) === 0 ) {
293
+ // If begins with "month:" then strip string and keep only month numbers.
294
+ $args['months'][] = substr( $one_date, strlen( 'month:' ) );
295
+ // If begins with "lastdays:" then strip string and keep only number of days.
296
+ } elseif ( strpos( $one_date, 'lastdays:' ) === 0 ) {
297
+ // Only keep largest lastdays value
298
+ $args['lastdays'] = max( $args['lastdays'], substr( $one_date, strlen( 'lastdays:' ) ) );
299
+ }
300
+ }
301
+ }
302
+
303
+ // lastdays, as int
304
+ if ( ! empty( $args['lastdays'] ) ) {
305
+ $inner_where .= "\n" . sprintf(
306
+ '
307
  # lastdays
308
  AND date >= DATE(NOW()) - INTERVAL %d DAY
309
+ ',
310
+ $args['lastdays']
311
+ );
312
+ }
313
+
314
+ // months, in format "Y-m"
315
+ if ( ! empty( $args['months'] ) ) {
316
+ if ( is_array( $args['months'] ) ) {
317
+ $arr_months = $args['months'];
318
+ } else {
319
+ $arr_months = explode( ',', $args['months'] );
320
+ }
321
+
322
+ $sql_months = "\n" . '
323
  # sql_months
324
  AND (
325
  ';
326
 
327
+ foreach ( $arr_months as $one_month ) {
328
+ // beginning of month
329
+ // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n";
330
+ // >> 2014-08-01 00:00
331
+ $date_month_beginning = strtotime( $one_month );
332
 
333
+ // end of month
334
+ // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";'
335
+ // >> 2014-09-01 00:00
336
+ $date_month_end = strtotime( "{$one_month} + 1 month" );
337
 
338
+ $sql_months .= sprintf(
339
+ '
340
  (
341
  date >= "%1$s"
342
  AND date <= "%2$s"
344
 
345
  OR
346
  ',
347
+ gmdate( 'Y-m-d H:i:s', $date_month_beginning ), // 1
348
+ gmdate( 'Y-m-d H:i:s', $date_month_end ) // 2
349
+ );
350
+ }
351
 
352
+ $sql_months = trim( $sql_months );
353
+ $sql_months = rtrim( $sql_months, ' OR ' );
354
 
355
+ $sql_months .= '
356
  # end sql_months and wrap
357
  )
358
  ';
359
 
360
+ $inner_where .= $sql_months;
361
+ } // End if().
362
+
363
+ // Search.
364
+ if ( ! empty( $args['search'] ) ) {
365
+ $search_words = $args['search'];
366
+ $str_search_conditions = '';
367
+ $arr_search_words = preg_split( '/[\s,]+/', $search_words );
368
+
369
+ // create array of all searched words
370
+ // split both spaces and commas and such
371
+ $arr_sql_like_cols = array( 'message', 'logger', 'level' );
372
+
373
+ foreach ( $arr_sql_like_cols as $one_col ) {
374
+ $str_sql_search_words = '';
375
+
376
+ foreach ( $arr_search_words as $one_search_word ) {
377
+ $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) );
378
+
379
+ $str_sql_search_words .= sprintf(
380
+ ' AND %1$s LIKE "%2$s" ',
381
+ $one_col,
382
+ "%{$str_like}%"
383
+ );
384
+ }
385
+
386
+ $str_sql_search_words = ltrim( $str_sql_search_words, ' AND ' );
387
+
388
+ $str_search_conditions .= "\n" . sprintf(
389
+ ' OR ( %1$s ) ',
390
+ $str_sql_search_words
391
+ );
392
+ }
393
+
394
+ $str_search_conditions = preg_replace( '/^OR /', ' ', trim( $str_search_conditions ) );
395
+
396
+ // Also search contexts.
397
+ $str_search_conditions .= "\n OR ( ";
398
+ foreach ( $arr_search_words as $one_search_word ) {
399
+ $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) );
400
+
401
+ $str_search_conditions .= "\n" . sprintf(
402
+ ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ',
403
+ $table_name_contexts, // 1
404
+ '%' . $str_like . '%' // 2
405
+ );
406
+ }
407
+ $str_search_conditions = preg_replace( '/ AND $/', '', $str_search_conditions );
408
+
409
+ $str_search_conditions .= "\n ) "; // end OR for contexts
410
+
411
+ $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) ";
412
+ }// End if().
413
+
414
+ // log levels
415
+ // comma separated
416
+ // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loglevel=error,warn
417
+ if ( ! empty( $args['loglevels'] ) ) {
418
+ $sql_loglevels = '';
419
+
420
+ if ( is_array( $args['loglevels'] ) ) {
421
+ $arr_loglevels = $args['loglevels'];
422
+ } else {
423
+ $arr_loglevels = explode( ',', $args['loglevels'] );
424
+ }
425
+
426
+ foreach ( $arr_loglevels as $one_loglevel ) {
427
+ $sql_loglevels .= sprintf( ' "%s", ', esc_sql( $one_loglevel ) );
428
+ }
429
+
430
+ if ( $sql_loglevels ) {
431
+ $sql_loglevels = rtrim( $sql_loglevels, ' ,' );
432
+ $sql_loglevels = "\n AND level IN ({$sql_loglevels}) ";
433
+ }
434
+
435
+ $inner_where .= $sql_loglevels;
436
+ }
437
+
438
+ // messages
439
+ if ( ! empty( $args['messages'] ) ) {
440
+ /*
441
+ $args['messages']:
442
+ Array
443
+ (
444
+ [0] => SimpleCommentsLogger:anon_comment_added,SimpleCommentsLogger:user_comment_added,SimpleCommentsLogger:anon_trackback_added,SimpleCommentsLogger:user_trackback_added,SimpleCommentsLogger:anon_pingback_added,SimpleCommentsLogger:user_pingback_added,SimpleCommentsLogger:comment_edited,SimpleCommentsLogger:trackback_edited,SimpleCommentsLogger:pingback_edited,SimpleCommentsLogger:comment_status_approve,SimpleCommentsLogger:trackback_status_approve,SimpleCommentsLogger:pingback_status_approve,SimpleCommentsLogger:comment_status_hold,SimpleCommentsLogger:trackback_status_hold,SimpleCommentsLogger:pingback_status_hold,SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam,SimpleCommentsLogger:comment_status_trash,SimpleCommentsLogger:trackback_status_trash,SimpleCommentsLogger:pingback_status_trash,SimpleCommentsLogger:comment_untrashed,SimpleCommentsLogger:trackback_untrashed,SimpleCommentsLogger:pingback_untrashed,SimpleCommentsLogger:comment_deleted,SimpleCommentsLogger:trackback_deleted,SimpleCommentsLogger:pingback_deleted
445
+ [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam
446
+ )
447
+ */
448
+
449
+ // Array with loggers and messages.
450
+ $arr_loggers_and_messages = array();
451
+
452
+ // Tranform from get'et format to our own internal format.
453
+ foreach ( (array) $args['messages'] as $one_arr_messages_row ) {
454
+ $arr_row_messages = explode( ',', $one_arr_messages_row );
455
+ /*
456
+ $one_arr_messages_row:
457
+ Array
458
+ (
459
+ [0] => SimpleCommentsLogger:anon_comment_added
460
+ [1] => SimpleCommentsLogger:user_comment_added
461
+ [2] => SimpleCommentsLogger:anon_trackback_added
462
+ */
463
+ foreach ( $arr_row_messages as $one_row_logger_and_message ) {
464
+ $arr_one_logger_and_message = explode( ':', $one_row_logger_and_message );
465
+
466
+ if ( ! isset( $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] ) ) {
467
+ $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ] = array();
468
+ }
469
+
470
+ $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1];
471
+ }
472
+ }
473
+
474
+ // Create sql where based on loggers and messages.
475
+ $sql_messages_where = ' AND (';
476
+
477
+ foreach ( $arr_loggers_and_messages as $logger_slug => $logger_messages ) {
478
+
479
+ $sql_logger_messages_in = '';
480
+ foreach ( $logger_messages as $one_logger_message ) {
481
+ $sql_logger_messages_in .= sprintf( '"%s",', esc_sql( $one_logger_message ) );
482
+ }
483
+
484
+ if ( $sql_logger_messages_in ) {
485
+ $sql_logger_messages_in = rtrim( $sql_logger_messages_in, ' ,' );
486
+ $sql_logger_messages_in = "\n AND c1.value IN ({$sql_logger_messages_in}) ";
487
+ }
488
+
489
+ $sql_messages_where .= sprintf(
490
+ '
491
  (
492
  h.logger = "%1$s"
493
+ %2$s
494
  )
495
  OR ',
496
+ esc_sql( $logger_slug ),
497
+ $sql_logger_messages_in
498
+ );
499
+ }
500
+ // remove last or
501
+ $sql_messages_where = preg_replace( '/OR $/', '', $sql_messages_where );
502
+
503
+ $sql_messages_where .= "\n )";
504
+ $where .= $sql_messages_where;
505
+ } // End if().
506
+
507
+ // loggers
508
+ // comma separated
509
+ // http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&type=overview&format=&posts_per_page=10&paged=1&max_id_first_page=27273&SimpleHistoryLogQuery-showDebug=0&loggers=SimpleCommentsLogger,SimpleCoreUpdatesLogger
510
+ if ( ! empty( $args['loggers'] ) ) {
511
+ $sql_loggers = '';
512
+ if ( is_array( $args['loggers'] ) ) {
513
+ $arr_loggers = $args['loggers'];
514
+ } else {
515
+ $arr_loggers = explode( ',', $args['loggers'] );
516
+ }
517
+
518
+ foreach ( $arr_loggers as $one_logger ) {
519
+ $sql_loggers .= sprintf( ' "%s", ', esc_sql( $one_logger ) );
520
+ }
521
+
522
+ if ( $sql_loggers ) {
523
+ $sql_loggers = rtrim( $sql_loggers, ' ,' );
524
+ $sql_loggers = "\n AND logger IN ({$sql_loggers}) ";
525
+ }
526
+
527
+ $inner_where .= $sql_loggers;
528
+ }
529
+
530
+ // user, a single userID
531
+ if ( ! empty( $args['user'] ) && is_numeric( $args['user'] ) ) {
532
+ $userID = (int) $args['user'];
533
+ $sql_user = sprintf(
534
+ '
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )
536
  ',
537
+ $table_name_contexts, // 1
538
+ $userID // 2
539
+ );
540
 
541
+ $inner_where .= $sql_user;
542
+ }
543
 
544
+ // If users is array, make it comma separated.
545
+ if ( isset( $args['users'] ) && is_array( $args['users'] ) ) {
546
+ $args['users'] = implode( ',', $args['users'] );
547
+ }
548
 
549
+ // Users, comma separated.
550
+ if ( ! empty( $args['users'] ) && is_string( $args['users'] ) ) {
551
+ $users = explode( ',', $args['users'] );
552
+ $users = array_map( 'intval', $users );
553
 
554
+ if ( $users ) {
555
+ $users_in = implode( ',', $users );
556
 
557
+ $sql_user = sprintf(
558
+ '
559
  AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value IN (%2$s) )
560
  ',
561
+ $table_name_contexts, // 1
562
+ $users_in // 2
563
+ );
564
+
565
+ $inner_where .= $sql_user;
566
+
567
+ // echo $inner_where;exit;
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Filter the sql template
573
+ *
574
+ * @since 2.0
575
+ *
576
+ * @param string $sql_tmpl
577
+ */
578
+ $sql_tmpl = apply_filters( 'simple_history/log_query_sql_template', $sql_tmpl );
579
+
580
+ /**
581
+ * Filter the sql template where clause
582
+ *
583
+ * @since 2.0
584
+ *
585
+ * @param string $where
586
+ */
587
+ $where = apply_filters( 'simple_history/log_query_sql_where', $where );
588
+
589
+ /**
590
+ * Filter the sql template limit
591
+ *
592
+ * @since 2.0
593
+ *
594
+ * @param string $limit
595
+ */
596
+ $limit = apply_filters( 'simple_history/log_query_limit', $limit );
597
+
598
+ /**
599
+ * Filter the sql template limit
600
+ *
601
+ * @since 2.0
602
+ *
603
+ * @param string $limit
604
+ */
605
+ $inner_where = apply_filters( 'simple_history/log_query_inner_where', $inner_where );
606
+
607
+ $sql = sprintf(
608
+ $sql_tmpl, // sprintf template
609
+ $where, // 1
610
+ $limit, // 2
611
+ $table_name, // 3
612
+ $inner_where, // 4
613
+ $table_name_contexts // 5
614
+ );
615
+
616
+ /**
617
+ * Filter the final sql query
618
+ *
619
+ * @since 2.0
620
+ *
621
+ * @param string $sql
622
+ */
623
+ $sql = apply_filters( 'simple_history/log_query_sql', $sql );
624
+
625
+ // Only return sql query
626
+ if ( $args['returnQuery'] ) {
627
+ return $sql;
628
+ }
629
+
630
+ $log_rows = $wpdb->get_results( $sql, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
631
+
632
+ // Find total number of rows that we would have gotten without pagination
633
+ // This is the number of rows with occasions taken into consideration
634
+ $sql_found_rows = 'SELECT FOUND_ROWS()';
635
+ $total_found_rows = (int) $wpdb->get_var( $sql_found_rows ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
636
+
637
+ // Add context
638
+ $post_ids = wp_list_pluck( $log_rows, 'id' );
639
+
640
+ if ( empty( $post_ids ) ) {
641
+ $context_results = array();
642
+ } else {
643
+ $sql_context = sprintf( 'SELECT * FROM %2$s WHERE history_id IN (%1$s)', join( ',', $post_ids ), $table_name_contexts );
644
+ $context_results = $wpdb->get_results( $sql_context ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
645
+ }
646
+
647
+ foreach ( $context_results as $context_row ) {
648
+ if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) {
649
+ $log_rows[ $context_row->history_id ]->context = array();
650
+ }
651
+
652
+ $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value;
653
+ }
654
+
655
+ // Remove id from keys, because they are cumbersome when working with JSON.
656
+ $log_rows = array_values( $log_rows );
657
+ $min_id = null;
658
+ $max_id = null;
659
+
660
+ if ( count( $log_rows ) ) {
661
+ // Max id is simply the id of the first row.
662
+ $max_id = reset( $log_rows )->id;
663
+
664
+ // Min id = to find the lowest id we must take occasions into consideration
665
+ $last_row = end( $log_rows );
666
+ $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1;
667
+ if ( $last_row_occasions_count === 0 ) {
668
+ // Last row did not have any more occasions, so get min_id directly from the row.
669
+ $min_id = $last_row->id;
670
+ } else {
671
+ // Last row did have occasions, so fetch all occasions, and find id of last one.
672
+ $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
673
+ $sql = sprintf(
674
+ '
 
 
 
 
 
 
 
 
 
 
675
  SELECT id, date, occasionsID
676
  FROM %1$s
677
  WHERE id <= %2$s
678
  ORDER BY id DESC
679
  LIMIT %3$s
680
  ',
681
+ $db_table,
682
+ $last_row->id,
683
+ $last_row_occasions_count + 1
684
+ );
685
+
686
+ $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
687
+
688
+ // the last occasion has the id we consider last in this paged result
689
+ $min_id = end( $results )->id;
690
+ }
691
+ } // End if().
692
+
693
+ // Calc pages.
694
+ if ( $args['posts_per_page'] ) {
695
+ $pages_count = Ceil( $total_found_rows / (int) $args['posts_per_page'] );
696
+ } else {
697
+ $pages_count = 1;
698
+ }
699
+
700
+ // Create array to return.
701
+ // Make all rows a sub key because we want to add some meta info too.
702
+ $log_rows_count = count( $log_rows );
703
+ $page_rows_from = ( (int) $args['paged'] * (int) $args['posts_per_page'] ) - (int) $args['posts_per_page'] + 1;
704
+ $page_rows_to = $page_rows_from + $log_rows_count - 1;
705
+ $arr_return = array(
706
+ 'total_row_count' => $total_found_rows,
707
+ 'pages_count' => $pages_count,
708
+ 'page_current' => (int) $args['paged'],
709
+ 'page_rows_from' => $page_rows_from,
710
+ 'page_rows_to' => $page_rows_to,
711
+ 'max_id' => (int) $max_id,
712
+ 'min_id' => (int) $min_id,
713
+ 'log_rows_count' => $log_rows_count,
714
+ 'log_rows' => $log_rows,
715
+ );
716
+
717
+ wp_cache_set( $cache_key, $arr_return, $cache_group );
718
+
719
+ return $arr_return;
720
+ }
 
721
  }
inc/helpers.php CHANGED
@@ -6,15 +6,14 @@
6
  * Makes call like this possible:
7
  * SimpleLogger()->info("This is a message sent to the log");
8
  */
9
- function SimpleLogger()
10
- {
11
- // Load loggers if main SimpleLogger class is not yet available.
12
- // Makes it possible to log things early,
13
- // before loggers are loaded "normally" on filter "after_setup_theme".
14
- if (!class_exists('SimpleLogger')) {
15
- SimpleHistory::get_instance()->load_loggers();
16
- }
17
- return new SimpleLogger(SimpleHistory::get_instance());
18
  }
19
 
20
  /**
@@ -23,24 +22,34 @@ function SimpleLogger()
23
  * If you use this please consider using
24
  * SimpleHistory()->info();
25
  * instead
 
 
 
 
 
 
 
 
 
 
 
26
  */
27
- function simple_history_add($args)
28
- {
29
- $defaults = array(
30
- 'action' => null,
31
- 'object_type' => null,
32
- 'object_subtype' => null,
33
- 'object_id' => null,
34
- 'object_name' => null,
35
- 'user_id' => null,
36
- 'description' => null
37
- );
38
 
39
- $context = wp_parse_args($args, $defaults);
40
 
41
- $message = "{$context["object_type"]} {$context["object_name"]} {$context["action"]}";
42
 
43
- SimpleLogger()->info($message, $context);
44
  }
45
 
46
  /**
@@ -48,7 +57,6 @@ function simple_history_add($args)
48
  *
49
  * @since 2.0.29
50
  *
51
- *
52
  * Original description from wp_text_diff():
53
  *
54
  * Displays a human readable HTML representation of the difference between two strings.
@@ -75,76 +83,75 @@ function simple_history_add($args)
75
  * @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults. And leading_context_lines and trailing_context_lines.
76
  * @return string Empty string if strings are equivalent or HTML with differences.
77
  */
78
- function simple_history_text_diff($left_string, $right_string, $args = null)
79
- {
80
- $defaults = array(
81
- 'title' => '',
82
- 'title_left' => '',
83
- 'title_right' => '',
84
- 'leading_context_lines' => 1,
85
- 'trailing_context_lines' => 1
86
- );
87
 
88
- $args = wp_parse_args($args, $defaults);
89
 
90
- if (!class_exists('WP_Text_Diff_Renderer_Table')) {
91
- require ABSPATH . WPINC . '/wp-diff.php';
92
- }
93
 
94
- $left_string = normalize_whitespace($left_string);
95
- $right_string = normalize_whitespace($right_string);
96
 
97
- $left_lines = explode("\n", $left_string);
98
- $right_lines = explode("\n", $right_string);
99
- $text_diff = new Text_Diff($left_lines, $right_lines);
100
 
101
- $renderer = new WP_Text_Diff_Renderer_Table($args);
102
- $renderer->_leading_context_lines = $args['leading_context_lines'];
103
- $renderer->_trailing_context_lines = $args['trailing_context_lines'];
104
 
105
- $diff = $renderer->render($text_diff);
106
 
107
- if (!$diff) {
108
- return '';
109
- }
110
 
111
- $r = '';
112
 
113
- $r .= "<div class='SimpleHistory__diff__contents' tabindex='0'>";
114
- $r .= "<div class='SimpleHistory__diff__contentsInner'>";
115
 
116
- $r .= "<table class='diff SimpleHistory__diff'>\n";
117
 
118
- if (!empty($args['show_split_view'])) {
119
- $r .=
120
- "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
121
- } else {
122
- $r .= "<col class='content' />";
123
- }
124
 
125
- if ($args['title'] || $args['title_left'] || $args['title_right']) {
126
- $r .= '<thead>';
127
- }
128
- if ($args['title']) {
129
- $r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
130
- }
131
- if ($args['title_left'] || $args['title_right']) {
132
- $r .= "<tr class='diff-sub-title'>\n";
133
- $r .= "\t<td></td><th>$args[title_left]</th>\n";
134
- $r .= "\t<td></td><th>$args[title_right]</th>\n";
135
- $r .= "</tr>\n";
136
- }
137
- if ($args['title'] || $args['title_left'] || $args['title_right']) {
138
- $r .= "</thead>\n";
139
- }
140
 
141
- $r .= "<tbody>\n$diff</div>\n</tbody>\n";
142
- $r .= '</table>';
143
 
144
- $r .= '</div>';
145
- $r .= '</div>';
146
 
147
- return $r;
148
  }
149
 
150
  /**
@@ -159,18 +166,17 @@ function simple_history_text_diff($left_string, $right_string, $args = null)
159
  * $handler['callback'][1]
160
  * );
161
  */
162
- function sh_error_log()
163
- {
164
- foreach (func_get_args() as $var) {
165
- if (is_bool($var)) {
166
- $bool_string = true === $var ? 'true' : 'false';
167
- error_log("$bool_string (boolean value)");
168
- } elseif (is_null($var)) {
169
- error_log('null (null value)');
170
- } else {
171
- error_log(print_r($var, true));
172
- }
173
- }
174
  }
175
 
176
  /**
@@ -184,32 +190,32 @@ function sh_error_log()
184
  *
185
  * @mixed Vars Variables to output.
186
  */
187
- function sh_d()
188
- {
189
- $output = '';
190
 
191
- foreach (func_get_args() as $var) {
192
- $loopOutput = '';
193
- if (is_bool($var)) {
194
- $bool_string = true === $var ? 'true' : 'false';
195
- $loopOutput = "$bool_string (boolean value)";
196
- } elseif (is_null($var)) {
197
- $loopOutput = ('null (null value)');
198
- } else {
199
- $loopOutput = print_r($var, true);
200
- }
201
 
202
- if ($loopOutput) {
203
- $output = $output . sprintf(
204
- '
205
  <pre>%1$s</pre>
206
  ',
207
- esc_html($loopOutput)
208
- );
209
- }
210
- }
211
-
212
- echo $output;
 
213
  }
214
 
215
  /**
@@ -229,21 +235,20 @@ function sh_d()
229
  * @param callable $callable The callable thing to check.
230
  * @return string Name of callable.
231
  */
232
- function sh_get_callable_name($callable)
233
- {
234
- if (is_string($callable)) {
235
- return trim($callable);
236
- } elseif (is_array($callable)) {
237
- if (is_object($callable[0])) {
238
- return sprintf('%s::%s', get_class($callable[0]), trim($callable[1]));
239
- } else {
240
- return sprintf('%s::%s', trim($callable[0]), trim($callable[1]));
241
- }
242
- } elseif ($callable instanceof Closure) {
243
- return 'closure';
244
- } else {
245
- return 'unknown';
246
- }
247
  }
248
 
249
  /**
@@ -255,10 +260,9 @@ function sh_get_callable_name($callable)
255
  *
256
  * @return string with words uppercased.
257
  */
258
- function sh_ucwords($str, $separator = ' ')
259
- {
260
- $str = str_replace($separator, ' ', $str);
261
- $str = ucwords(strtolower($str));
262
- $str = str_replace(' ', $separator, $str);
263
- return $str;
264
  }
6
  * Makes call like this possible:
7
  * SimpleLogger()->info("This is a message sent to the log");
8
  */
9
+ function SimpleLogger() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid
10
+ // Load loggers if main SimpleLogger class is not yet available.
11
+ // Makes it possible to log things early,
12
+ // before loggers are loaded "normally" on filter "after_setup_theme".
13
+ if ( ! class_exists( 'SimpleLogger' ) ) {
14
+ SimpleHistory::get_instance()->load_loggers();
15
+ }
16
+ return new SimpleLogger( SimpleHistory::get_instance() );
 
17
  }
18
 
19
  /**
22
  * If you use this please consider using
23
  * SimpleHistory()->info();
24
  * instead
25
+ *
26
+ * Example usage:
27
+ * simple_history_add(
28
+ * array(
29
+ * 'object_type' => 'post',
30
+ * 'object_name' => 'Lorem ispum dolor',
31
+ * 'action' => 'updated',
32
+ * )
33
+ * );
34
+ *
35
+ * @param array $args Array with at least keys object_type, object_name, action.
36
  */
37
+ function simple_history_add( $args ) {
38
+ $defaults = array(
39
+ 'action' => null,
40
+ 'object_type' => null,
41
+ 'object_subtype' => null,
42
+ 'object_id' => null,
43
+ 'object_name' => null,
44
+ 'user_id' => null,
45
+ 'description' => null,
46
+ );
 
47
 
48
+ $context = wp_parse_args( $args, $defaults );
49
 
50
+ $message = "{$context["object_type"]} {$context["object_name"]} {$context["action"]}";
51
 
52
+ SimpleLogger()->info( $message, $context );
53
  }
54
 
55
  /**
57
  *
58
  * @since 2.0.29
59
  *
 
60
  * Original description from wp_text_diff():
61
  *
62
  * Displays a human readable HTML representation of the difference between two strings.
83
  * @param string|array $args Optional. Change 'title', 'title_left', and 'title_right' defaults. And leading_context_lines and trailing_context_lines.
84
  * @return string Empty string if strings are equivalent or HTML with differences.
85
  */
86
+ function simple_history_text_diff( $left_string, $right_string, $args = null ) {
87
+ $defaults = array(
88
+ 'title' => '',
89
+ 'title_left' => '',
90
+ 'title_right' => '',
91
+ 'leading_context_lines' => 1,
92
+ 'trailing_context_lines' => 1,
93
+ );
 
94
 
95
+ $args = wp_parse_args( $args, $defaults );
96
 
97
+ if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) ) {
98
+ require ABSPATH . WPINC . '/wp-diff.php';
99
+ }
100
 
101
+ $left_string = normalize_whitespace( $left_string );
102
+ $right_string = normalize_whitespace( $right_string );
103
 
104
+ $left_lines = explode( "\n", $left_string );
105
+ $right_lines = explode( "\n", $right_string );
106
+ $text_diff = new Text_Diff( $left_lines, $right_lines );
107
 
108
+ $renderer = new WP_Text_Diff_Renderer_Table( $args );
109
+ $renderer->_leading_context_lines = $args['leading_context_lines'];
110
+ $renderer->_trailing_context_lines = $args['trailing_context_lines'];
111
 
112
+ $diff = $renderer->render( $text_diff );
113
 
114
+ if ( ! $diff ) {
115
+ return '';
116
+ }
117
 
118
+ $r = '';
119
 
120
+ $r .= "<div class='SimpleHistory__diff__contents' tabindex='0'>";
121
+ $r .= "<div class='SimpleHistory__diff__contentsInner'>";
122
 
123
+ $r .= "<table class='diff SimpleHistory__diff'>\n";
124
 
125
+ if ( ! empty( $args['show_split_view'] ) ) {
126
+ $r .=
127
+ "<col class='content diffsplit left' /><col class='content diffsplit middle' /><col class='content diffsplit right' />";
128
+ } else {
129
+ $r .= "<col class='content' />";
130
+ }
131
 
132
+ if ( $args['title'] || $args['title_left'] || $args['title_right'] ) {
133
+ $r .= '<thead>';
134
+ }
135
+ if ( $args['title'] ) {
136
+ $r .= "<tr class='diff-title'><th colspan='4'>$args[title]</th></tr>\n";
137
+ }
138
+ if ( $args['title_left'] || $args['title_right'] ) {
139
+ $r .= "<tr class='diff-sub-title'>\n";
140
+ $r .= "\t<td></td><th>$args[title_left]</th>\n";
141
+ $r .= "\t<td></td><th>$args[title_right]</th>\n";
142
+ $r .= "</tr>\n";
143
+ }
144
+ if ( $args['title'] || $args['title_left'] || $args['title_right'] ) {
145
+ $r .= "</thead>\n";
146
+ }
147
 
148
+ $r .= "<tbody>\n$diff</div>\n</tbody>\n";
149
+ $r .= '</table>';
150
 
151
+ $r .= '</div>';
152
+ $r .= '</div>';
153
 
154
+ return $r;
155
  }
156
 
157
  /**
166
  * $handler['callback'][1]
167
  * );
168
  */
169
+ function sh_error_log() {
170
+ foreach ( func_get_args() as $var ) {
171
+ if ( is_bool( $var ) ) {
172
+ $bool_string = true === $var ? 'true' : 'false';
173
+ error_log( "$bool_string (boolean value)" );
174
+ } elseif ( is_null( $var ) ) {
175
+ error_log( 'null (null value)' );
176
+ } else {
177
+ error_log( print_r( $var, true ) );
178
+ }
179
+ }
 
180
  }
181
 
182
  /**
190
  *
191
  * @mixed Vars Variables to output.
192
  */
193
+ function sh_d() {
194
+ $output = '';
 
195
 
196
+ foreach ( func_get_args() as $var ) {
197
+ $loopOutput = '';
198
+ if ( is_bool( $var ) ) {
199
+ $bool_string = true === $var ? 'true' : 'false';
200
+ $loopOutput = "$bool_string (boolean value)";
201
+ } elseif ( is_null( $var ) ) {
202
+ $loopOutput = ( 'null (null value)' );
203
+ } else {
204
+ $loopOutput = print_r( $var, true );
205
+ }
206
 
207
+ if ( $loopOutput ) {
208
+ $output = $output . sprintf(
209
+ '
210
  <pre>%1$s</pre>
211
  ',
212
+ esc_html( $loopOutput )
213
+ );
214
+ }
215
+ }
216
+
217
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
218
+ echo $output;
219
  }
220
 
221
  /**
235
  * @param callable $callable The callable thing to check.
236
  * @return string Name of callable.
237
  */
238
+ function sh_get_callable_name( $callable ) {
239
+ if ( is_string( $callable ) ) {
240
+ return trim( $callable );
241
+ } elseif ( is_array( $callable ) ) {
242
+ if ( is_object( $callable[0] ) ) {
243
+ return sprintf( '%s::%s', get_class( $callable[0] ), trim( $callable[1] ) );
244
+ } else {
245
+ return sprintf( '%s::%s', trim( $callable[0] ), trim( $callable[1] ) );
246
+ }
247
+ } elseif ( $callable instanceof Closure ) {
248
+ return 'closure';
249
+ } else {
250
+ return 'unknown';
251
+ }
 
252
  }
253
 
254
  /**
260
  *
261
  * @return string with words uppercased.
262
  */
263
+ function sh_ucwords( $str, $separator = ' ' ) {
264
+ $str = str_replace( $separator, ' ', $str );
265
+ $str = ucwords( strtolower( $str ) );
266
+ $str = str_replace( ' ', $separator, $str );
267
+ return $str;
 
268
  }
inc/oldversions.php CHANGED
@@ -2,42 +2,42 @@
2
  /**
3
  * Show an admin message if old PHP version.
4
  */
5
- function simple_history_old_version_admin_notice()
6
- {
7
- $ok_wp_version = version_compare($GLOBALS['wp_version'], '5.2', '>=');
8
- $ok_php_version = version_compare(phpversion(), '5.6', '>=');
9
- ?>
10
- <div class="updated error">
11
- <?php
12
- if (!$ok_php_version) {
13
- echo '<p>';
14
- printf(
15
- /* translators: 1: PHP version */
16
- esc_html(
17
- __(
18
- 'Simple History is a great plugin, but to use it your server must have at least PHP 5.6 installed (you have version %s).',
19
- 'simple-history'
20
- )
21
- ),
22
- phpversion() // 1
23
- );
24
- echo '</p>';
25
- }
26
 
27
- if (!$ok_wp_version) {
28
- echo '<p>';
29
- printf(
30
- /* translators: 1: WordPress version */
31
- esc_html(
32
- __(
33
- 'Simple History requires WordPress version 5.2 or higher (you have version %s).',
34
- 'simple-history'
35
- )
36
- ),
37
- $GLOBALS['wp_version'] // 1
38
- );
39
- echo '</p>';
40
- }?>
41
- </div>
42
- <?php
 
43
  }
2
  /**
3
  * Show an admin message if old PHP version.
4
  */
5
+ function simple_history_old_version_admin_notice() {
6
+ $ok_wp_version = version_compare( $GLOBALS['wp_version'], '5.2', '>=' );
7
+ $ok_php_version = version_compare( phpversion(), '5.6', '>=' );
8
+ ?>
9
+ <div class="updated error">
10
+ <?php
11
+ if ( ! $ok_php_version ) {
12
+ echo '<p>';
13
+ printf(
14
+ /* translators: 1: PHP version */
15
+ esc_html(
16
+ __(
17
+ 'Simple History is a great plugin, but to use it your server must have at least PHP 5.6 installed (you have version %s).',
18
+ 'simple-history'
19
+ )
20
+ ),
21
+ esc_html( phpversion() ) // 1
22
+ );
23
+ echo '</p>';
24
+ }
 
25
 
26
+ if ( ! $ok_wp_version ) {
27
+ echo '<p>';
28
+ printf(
29
+ /* translators: 1: WordPress version */
30
+ esc_html(
31
+ __(
32
+ 'Simple History requires WordPress version 5.2 or higher (you have version %s).',
33
+ 'simple-history'
34
+ )
35
+ ),
36
+ esc_html( $GLOBALS['wp_version'] ) // 1
37
+ );
38
+ echo '</p>';
39
+ }
40
+ ?>
41
+ </div>
42
+ <?php
43
  }
index.php CHANGED
@@ -6,11 +6,11 @@
6
  * Text Domain: simple-history
7
  * Domain Path: /languages
8
  * Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
9
- * Version: 2.43.0
10
  * Author: Pär Thernström
11
  * Author URI: http://simple-history.com/
12
  * License: GPL2
13
- * GitHub Plugin URI: https://github.com/bonny/WordPress-Simple-History
14
  *
15
  * @package Simple History
16
  */
@@ -29,37 +29,37 @@
29
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30
  */
31
 
32
- if (!defined('WPINC')) {
33
- die();
34
  }
35
 
36
  // Plugin requires at least WordPress version "4.5.1", because usage of functions like wp_get_raw_referer.
37
  // true if version ok, false if too old version.
38
- $ok_wp_version = version_compare($GLOBALS['wp_version'], '5.2', '>=');
39
- $ok_php_version = version_compare(phpversion(), '5.6', '>=');
40
 
41
- if ($ok_php_version && $ok_wp_version) {
42
- /**
43
- * Register function that is called when plugin is installed
44
- *
45
- * @TODO: make activation multi site aware, as in https://github.com/scribu/wp-proper-network-activation
46
- * register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
47
- */
48
- define('SIMPLE_HISTORY_VERSION', '2.43.0');
49
- define('SIMPLE_HISTORY_PATH', plugin_dir_path(__FILE__));
50
- define('SIMPLE_HISTORY_BASENAME', plugin_basename(__FILE__));
51
- define('SIMPLE_HISTORY_DIR_URL', plugin_dir_url(__FILE__));
52
- define('SIMPLE_HISTORY_FILE', __FILE__);
53
 
54
- /** Load required files */
55
- require_once __DIR__ . '/inc/SimpleHistory.php';
56
- require_once __DIR__ . '/inc/SimpleHistoryLogQuery.php';
57
- require_once __DIR__ . '/inc/helpers.php';
58
 
59
- /** Boot up */
60
- SimpleHistory::get_instance();
61
  } else {
62
- // User is running to old version of php, add admin notice about that.
63
- require_once __DIR__ . '/inc/oldversions.php';
64
- add_action('admin_notices', 'simple_history_old_version_admin_notice');
65
  } // End if().
6
  * Text Domain: simple-history
7
  * Domain Path: /languages
8
  * Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
9
+ * Version: 3.0.0
10
  * Author: Pär Thernström
11
  * Author URI: http://simple-history.com/
12
  * License: GPL2
13
+
14
  *
15
  * @package Simple History
16
  */
29
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30
  */
31
 
32
+ if ( ! defined( 'WPINC' ) ) {
33
+ die();
34
  }
35
 
36
  // Plugin requires at least WordPress version "4.5.1", because usage of functions like wp_get_raw_referer.
37
  // true if version ok, false if too old version.
38
+ $ok_wp_version = version_compare( $GLOBALS['wp_version'], '5.2', '>=' );
39
+ $ok_php_version = version_compare( phpversion(), '5.6', '>=' );
40
 
41
+ if ( $ok_php_version && $ok_wp_version ) {
42
+ /**
43
+ * Register function that is called when plugin is installed
44
+ *
45
+ * @TODO: make activation multi site aware, as in https://github.com/scribu/wp-proper-network-activation
46
+ * register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
47
+ */
48
+ define( 'SIMPLE_HISTORY_VERSION', '3.0.0' );
49
+ define( 'SIMPLE_HISTORY_PATH', plugin_dir_path( __FILE__ ) );
50
+ define( 'SIMPLE_HISTORY_BASENAME', plugin_basename( __FILE__ ) );
51
+ define( 'SIMPLE_HISTORY_DIR_URL', plugin_dir_url( __FILE__ ) );
52
+ define( 'SIMPLE_HISTORY_FILE', __FILE__ );
53
 
54
+ /** Load required files */
55
+ require_once __DIR__ . '/inc/SimpleHistory.php';
56
+ require_once __DIR__ . '/inc/SimpleHistoryLogQuery.php';
57
+ require_once __DIR__ . '/inc/helpers.php';
58
 
59
+ /** Boot up */
60
+ SimpleHistory::get_instance();
61
  } else {
62
+ // User is running to old version of php, add admin notice about that.
63
+ require_once __DIR__ . '/inc/oldversions.php';
64
+ add_action( 'admin_notices', 'simple_history_old_version_admin_notice' );
65
  } // End if().
loggers/AvailableUpdatesLogger.php CHANGED
@@ -6,303 +6,306 @@
6
  * @package SimpleHistory
7
  */
8
 
9
- if (! class_exists('AvailableUpdatesLogger')) {
10
-
11
- /**
12
- * Class.
13
- */
14
- class AvailableUpdatesLogger extends SimpleLogger
15
- {
16
-
17
- /**
18
- * Slug for logger.
19
- *
20
- * @var $slug string
21
- */
22
- public $slug = __CLASS__;
23
-
24
- /**
25
- * Return logger info
26
- *
27
- * @return array
28
- */
29
- public function getInfo()
30
- {
31
-
32
- $arr_info = array(
33
- 'name' => 'AvailableUpdatesLogger',
34
- 'description' => 'Logs found updates to WordPress, plugins, and themes',
35
- 'capability' => 'manage_options',
36
- 'messages' => array(
37
- 'core_update_available' => __('Found an update to WordPress.', 'simple-history'),
38
- 'plugin_update_available' => __('Found an update to plugin "{plugin_name}"', 'simple-history'),
39
- 'theme_update_available' => __('Found an update to theme "{theme_name}"', 'simple-history'),
40
- ),
41
- 'labels' => array(
42
- 'search' => array(
43
- 'label' => _x('WordPress and plugins updates found', 'Plugin logger: updates found', 'simple-history'),
44
- 'label_all' => _x('All found updates', 'Plugin logger: updates found', 'simple-history'),
45
- 'options' => array(
46
- _x('WordPress updates found', 'Plugin logger: updates found', 'simple-history') => array(
47
- 'core_update_available'
48
- ),
49
- _x('Plugin updates found', 'Plugin logger: updates found', 'simple-history') => array(
50
- 'plugin_update_available',
51
- ),
52
- _x('Theme updates found', 'Plugin logger: updates found', 'simple-history') => array(
53
- 'theme_update_available'
54
- ),
55
- ),
56
- ), // search array.
57
- ), // labels.
58
- );
59
-
60
- return $arr_info;
61
- }
62
-
63
- /**
64
- * Called when logger is loaded.
65
- */
66
- public function loaded()
67
- {
68
-
69
- // When WP is done checking for core updates it sets a site transient called "update_core"
70
- // set_site_transient( 'update_core', null ); // Uncomment to test
71
- add_action('set_site_transient_update_core', array( $this, 'on_setted_update_core_transient' ), 10, 1);
72
-
73
- // Dito for plugins
74
- // set_site_transient( 'update_plugins', null ); // Uncomment to test
75
- add_action('set_site_transient_update_plugins', array( $this, 'on_setted_update_plugins_transient' ), 10, 1);
76
-
77
- add_action('set_site_transient_update_themes', array( $this, 'on_setted_update_update_themes' ), 10, 1);
78
- }
79
-
80
- public function on_setted_update_core_transient($updates)
81
- {
82
-
83
- global $wp_version;
84
-
85
- $last_version_checked = get_option("simplehistory_{$this->slug}_wp_core_version_available");
86
-
87
- // During update of network sites this was not set, so make sure to check
88
- if (empty($updates->updates[0]->current)) {
89
- return;
90
- }
91
-
92
- $new_wp_core_version = $updates->updates[0]->current; // The new WP core version
93
-
94
- // Some plugins can mess with version, so get fresh from the version file.
95
- require_once ABSPATH . WPINC . '/version.php';
96
-
97
- // If found version is same version as we have logged about before then don't continue
98
- if ($last_version_checked == $new_wp_core_version) {
99
- return;
100
- }
101
-
102
- // is WP core update available?
103
- if ('upgrade' == $updates->updates[0]->response) {
104
- $this->noticeMessage('core_update_available', array(
105
- 'wp_core_current_version' => $wp_version,
106
- 'wp_core_new_version' => $new_wp_core_version,
107
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
108
- ));
109
-
110
- // Store updated version available, so we don't log that version again
111
- update_option("simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version);
112
- }
113
- }
114
-
115
- /**
116
- * Called when WordPress is done checking for plugin updates.
117
- * WP sets site transient 'update_plugins' when done.
118
- * Log found plugin updates.
119
- */
120
- public function on_setted_update_plugins_transient($updates)
121
- {
122
-
123
- if (empty($updates->response) || ! is_array($updates->response)) {
124
- return;
125
- }
126
-
127
- // If we only want to notify about active plugins
128
- /*
129
- $active_plugins = get_option( 'active_plugins' );
130
- $active_plugins = array_flip( $active_plugins ); // find which plugins are active
131
- $plugins_need_update = array_intersect_key( $plugins_need_update, $active_plugins ); // only keep plugins that are active
132
- */
133
-
134
- // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
135
- $option_key = "simplehistory_{$this->slug}_plugin_updates_available";
136
- $checked_updates = get_option($option_key);
137
-
138
- if (! is_array($checked_updates)) {
139
- $checked_updates = array();
140
- }
141
-
142
- // File needed plugin API
143
- if (! function_exists('get_plugin_data')) {
144
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
145
- }
146
-
147
- // For each available update.
148
- foreach ($updates->response as $key => $data) {
149
- // Make sure plugin directory exists or get_plugin_data will
150
- // give warning like
151
- // "PHP Warning: fread() expects parameter 1 to be resource, boolean given in /wp/wp-includes/functions.php on line 4837"
152
- $file = WP_PLUGIN_DIR . '/' . $key;
153
-
154
- // Continue with next plugin if plugin file did not exist.
155
- if (! file_exists($file)) {
156
- continue;
157
- }
158
-
159
- $fp = fopen($file, 'r');
160
-
161
- // Continue with next plugin if plugin file could not be read.
162
- if (false === $fp) {
163
- continue;
164
- }
165
-
166
- $plugin_info = get_plugin_data($file, true, false);
167
-
168
- $plugin_new_version = isset($data->new_version) ? $data->new_version : '';
169
-
170
- // Check if this plugin and this version has been checked/logged already.
171
- if (! array_key_exists($key, $checked_updates)) {
172
- $checked_updates[ $key ] = array(
173
- 'checked_version' => null,
174
- );
175
- }
176
-
177
- if ($checked_updates[ $key ]['checked_version'] == $plugin_new_version) {
178
- // This version has been checked/logged already
179
- continue;
180
- }
181
-
182
- $checked_updates[ $key ]['checked_version'] = $plugin_new_version;
183
-
184
- $this->noticeMessage('plugin_update_available', array(
185
- 'plugin_name' => isset($plugin_info['Name']) ? $plugin_info['Name'] : '',
186
- 'plugin_current_version' => isset($plugin_info['Version']) ? $plugin_info['Version'] : '',
187
- 'plugin_new_version' => $plugin_new_version,
188
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
189
- // "plugin_info" => $plugin_info,
190
- // "remote_plugin_info" => $remote_plugin_info,
191
- // "active_plugins" => $active_plugins,
192
- // "updates" => $updates
193
- ));
194
- } // End foreach().
195
-
196
- update_option($option_key, $checked_updates);
197
- }
198
-
199
- public function on_setted_update_update_themes($updates)
200
- {
201
-
202
- if (empty($updates->response) || ! is_array($updates->response)) {
203
- return;
204
- }
205
-
206
- // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
207
- $option_key = "simplehistory_{$this->slug}_theme_updates_available";
208
- $checked_updates = get_option($option_key);
209
-
210
- if (! is_array($checked_updates)) {
211
- $checked_updates = array();
212
- }
213
-
214
- // For each available update
215
- foreach ($updates->response as $key => $data) {
216
- $theme_info = wp_get_theme($key);
217
-
218
- // $message .= "\n" . sprintf( __( "Theme: %s is out of date. Please update from version %s to %s", "wp-updates-notifier" ), $theme_info['Name'], $theme_info['Version'], $data['new_version'] ) . "\n";
219
- $settings['notified']['theme'][ $key ] = $data['new_version']; // set theme version we are notifying about
220
-
221
- $theme_new_version = isset($data['new_version']) ? $data['new_version'] : '';
222
-
223
- // check if this plugin and this version has been checked/logged already
224
- if (! array_key_exists($key, $checked_updates)) {
225
- $checked_updates[ $key ] = array(
226
- 'checked_version' => null,
227
- );
228
- }
229
-
230
- if ($checked_updates[ $key ]['checked_version'] == $theme_new_version) {
231
- // This version has been checked/logged already
232
- continue;
233
- }
234
-
235
- $checked_updates[ $key ]['checked_version'] = $theme_new_version;
236
-
237
- $this->noticeMessage('theme_update_available', array(
238
- 'theme_name' => isset($theme_info['Name']) ? $theme_info['Name'] : '',
239
- 'theme_current_version' => isset($theme_info['Version']) ? $theme_info['Version'] : '',
240
- 'theme_new_version' => $theme_new_version,
241
- '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
242
- // "plugin_info" => $plugin_info,
243
- // "remote_plugin_info" => $remote_plugin_info,
244
- // "active_plugins" => $active_plugins,
245
- // "updates" => $updates,
246
- ));
247
- } // End foreach().
248
-
249
- update_option($option_key, $checked_updates);
250
- }
251
-
252
- /**
253
- * Append prev and current version of update object as details in the output
254
- */
255
- public function getLogRowDetailsOutput($row)
256
- {
257
-
258
- $output = '';
259
-
260
- $current_version = null;
261
- $new_version = null;
262
- $context_message_key = isset($row->context_message_key) ? $row->context_message_key : null;
263
-
264
- $context = isset($row->context) ? $row->context : array();
265
-
266
- switch ($context_message_key) {
267
- case 'core_update_available':
268
- $current_version = isset($context['wp_core_current_version']) ? $context['wp_core_current_version'] : null;
269
- $new_version = isset($context['wp_core_new_version']) ? $context['wp_core_new_version'] : null;
270
- break;
271
-
272
- case 'plugin_update_available':
273
- $current_version = isset($context['plugin_current_version']) ? $context['plugin_current_version'] : null;
274
- $new_version = isset($context['plugin_new_version']) ? $context['plugin_new_version'] : null;
275
- break;
276
-
277
- case 'theme_update_available':
278
- $current_version = isset($context['theme_current_version']) ? $context['theme_current_version'] : null;
279
- $new_version = isset($context['theme_new_version']) ? $context['theme_new_version'] : null;
280
- break;
281
- }
282
-
283
- if ($current_version && $new_version) {
284
- $output .= '<p>';
285
- $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
286
- $output .= '<em>' . __('Available version', 'simple-history') . '</em> ' . esc_html($new_version);
287
- $output .= '</span> ';
288
-
289
- $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
290
- $output .= '<em>' . __('Installed version', 'simple-history') . '</em> ' . esc_html($current_version);
291
- $output .= '</span>';
292
-
293
- $output .= '</p>';
294
-
295
- // Add link to update-page, if user is allowed to that page.
296
- $is_allowed_to_update_page = current_user_can('update_core') || current_user_can('update_themes') || current_user_can('update_plugins');
297
-
298
- if ($is_allowed_to_update_page) {
299
- $output .= sprintf('<p><a href="%1$s">', admin_url('update-core.php'));
300
- $output .= __('View all updates', 'simple-history');
301
- $output .= '</a></p>';
302
- }
303
- }
304
-
305
- return $output;
306
- }
307
- }
 
 
 
308
  }
6
  * @package SimpleHistory
7
  */
8
 
9
+ if ( ! class_exists( 'AvailableUpdatesLogger' ) ) {
10
+
11
+ /**
12
+ * Class.
13
+ */
14
+ class AvailableUpdatesLogger extends SimpleLogger {
15
+
16
+
17
+ /**
18
+ * Slug for logger.
19
+ *
20
+ * @var $slug string
21
+ */
22
+ public $slug = __CLASS__;
23
+
24
+ /**
25
+ * Return logger info
26
+ *
27
+ * @return array
28
+ */
29
+ public function getInfo() {
30
+
31
+ $arr_info = array(
32
+ 'name' => 'AvailableUpdatesLogger',
33
+ 'description' => 'Logs found updates to WordPress, plugins, and themes',
34
+ 'capability' => 'manage_options',
35
+ 'messages' => array(
36
+ 'core_update_available' => __( 'Found an update to WordPress.', 'simple-history' ),
37
+ 'plugin_update_available' => __( 'Found an update to plugin "{plugin_name}"', 'simple-history' ),
38
+ 'theme_update_available' => __( 'Found an update to theme "{theme_name}"', 'simple-history' ),
39
+ ),
40
+ 'labels' => array(
41
+ 'search' => array(
42
+ 'label' => _x( 'WordPress and plugins updates found', 'Plugin logger: updates found', 'simple-history' ),
43
+ 'label_all' => _x( 'All found updates', 'Plugin logger: updates found', 'simple-history' ),
44
+ 'options' => array(
45
+ _x( 'WordPress updates found', 'Plugin logger: updates found', 'simple-history' ) => array(
46
+ 'core_update_available',
47
+ ),
48
+ _x( 'Plugin updates found', 'Plugin logger: updates found', 'simple-history' ) => array(
49
+ 'plugin_update_available',
50
+ ),
51
+ _x( 'Theme updates found', 'Plugin logger: updates found', 'simple-history' ) => array(
52
+ 'theme_update_available',
53
+ ),
54
+ ),
55
+ ), // search array.
56
+ ), // labels.
57
+ );
58
+
59
+ return $arr_info;
60
+ }
61
+
62
+ /**
63
+ * Called when logger is loaded.
64
+ */
65
+ public function loaded() {
66
+
67
+ // When WP is done checking for core updates it sets a site transient called "update_core"
68
+ // set_site_transient( 'update_core', null ); // Uncomment to test
69
+ add_action( 'set_site_transient_update_core', array( $this, 'on_setted_update_core_transient' ), 10, 1 );
70
+
71
+ // Dito for plugins
72
+ // set_site_transient( 'update_plugins', null ); // Uncomment to test
73
+ add_action( 'set_site_transient_update_plugins', array( $this, 'on_setted_update_plugins_transient' ), 10, 1 );
74
+
75
+ add_action( 'set_site_transient_update_themes', array( $this, 'on_setted_update_update_themes' ), 10, 1 );
76
+ }
77
+
78
+ public function on_setted_update_core_transient( $updates ) {
79
+
80
+ global $wp_version;
81
+
82
+ $last_version_checked = get_option( "simplehistory_{$this->slug}_wp_core_version_available" );
83
+
84
+ // During update of network sites this was not set, so make sure to check
85
+ if ( empty( $updates->updates[0]->current ) ) {
86
+ return;
87
+ }
88
+
89
+ $new_wp_core_version = $updates->updates[0]->current; // The new WP core version
90
+
91
+ // Some plugins can mess with version, so get fresh from the version file.
92
+ require_once ABSPATH . WPINC . '/version.php';
93
+
94
+ // If found version is same version as we have logged about before then don't continue
95
+ if ( $last_version_checked == $new_wp_core_version ) {
96
+ return;
97
+ }
98
+
99
+ // is WP core update available?
100
+ if ( 'upgrade' == $updates->updates[0]->response ) {
101
+ $this->noticeMessage(
102
+ 'core_update_available',
103
+ array(
104
+ 'wp_core_current_version' => $wp_version,
105
+ 'wp_core_new_version' => $new_wp_core_version,
106
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
107
+ )
108
+ );
109
+
110
+ // Store updated version available, so we don't log that version again
111
+ update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Called when WordPress is done checking for plugin updates.
117
+ * WP sets site transient 'update_plugins' when done.
118
+ * Log found plugin updates.
119
+ */
120
+ public function on_setted_update_plugins_transient( $updates ) {
121
+
122
+ if ( empty( $updates->response ) || ! is_array( $updates->response ) ) {
123
+ return;
124
+ }
125
+
126
+ // If we only want to notify about active plugins
127
+ /*
128
+ $active_plugins = get_option( 'active_plugins' );
129
+ $active_plugins = array_flip( $active_plugins ); // find which plugins are active
130
+ $plugins_need_update = array_intersect_key( $plugins_need_update, $active_plugins ); // only keep plugins that are active
131
+ */
132
+
133
+ // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
134
+ $option_key = "simplehistory_{$this->slug}_plugin_updates_available";
135
+ $checked_updates = get_option( $option_key );
136
+
137
+ if ( ! is_array( $checked_updates ) ) {
138
+ $checked_updates = array();
139
+ }
140
+
141
+ // File needed plugin API
142
+ if ( ! function_exists( 'get_plugin_data' ) ) {
143
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
144
+ }
145
+
146
+ // For each available update.
147
+ foreach ( $updates->response as $key => $data ) {
148
+ // Make sure plugin directory exists or get_plugin_data will
149
+ // give warning like
150
+ // "PHP Warning: fread() expects parameter 1 to be resource, boolean given in /wp/wp-includes/functions.php on line 4837"
151
+ $file = WP_PLUGIN_DIR . '/' . $key;
152
+
153
+ // Continue with next plugin if plugin file did not exist.
154
+ if ( ! file_exists( $file ) ) {
155
+ continue;
156
+ }
157
+
158
+ $fp = fopen( $file, 'r' );
159
+
160
+ // Continue with next plugin if plugin file could not be read.
161
+ if ( false === $fp ) {
162
+ continue;
163
+ }
164
+
165
+ $plugin_info = get_plugin_data( $file, true, false );
166
+
167
+ $plugin_new_version = isset( $data->new_version ) ? $data->new_version : '';
168
+
169
+ // Check if this plugin and this version has been checked/logged already.
170
+ if ( ! array_key_exists( $key, $checked_updates ) ) {
171
+ $checked_updates[ $key ] = array(
172
+ 'checked_version' => null,
173
+ );
174
+ }
175
+
176
+ if ( $checked_updates[ $key ]['checked_version'] == $plugin_new_version ) {
177
+ // This version has been checked/logged already
178
+ continue;
179
+ }
180
+
181
+ $checked_updates[ $key ]['checked_version'] = $plugin_new_version;
182
+
183
+ $this->noticeMessage(
184
+ 'plugin_update_available',
185
+ array(
186
+ 'plugin_name' => isset( $plugin_info['Name'] ) ? $plugin_info['Name'] : '',
187
+ 'plugin_current_version' => isset( $plugin_info['Version'] ) ? $plugin_info['Version'] : '',
188
+ 'plugin_new_version' => $plugin_new_version,
189
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
190
+ // "plugin_info" => $plugin_info,
191
+ // "remote_plugin_info" => $remote_plugin_info,
192
+ // "active_plugins" => $active_plugins,
193
+ // "updates" => $updates
194
+ )
195
+ );
196
+ } // End foreach().
197
+
198
+ update_option( $option_key, $checked_updates );
199
+ }
200
+
201
+ public function on_setted_update_update_themes( $updates ) {
202
+
203
+ if ( empty( $updates->response ) || ! is_array( $updates->response ) ) {
204
+ return;
205
+ }
206
+
207
+ // update_option( "simplehistory_{$this->slug}_wp_core_version_available", $new_wp_core_version );
208
+ $option_key = "simplehistory_{$this->slug}_theme_updates_available";
209
+ $checked_updates = get_option( $option_key );
210
+
211
+ if ( ! is_array( $checked_updates ) ) {
212
+ $checked_updates = array();
213
+ }
214
+
215
+ // For each available update
216
+ foreach ( $updates->response as $key => $data ) {
217
+ $theme_info = wp_get_theme( $key );
218
+
219
+ // $message .= "\n" . sprintf( __( "Theme: %s is out of date. Please update from version %s to %s", "wp-updates-notifier" ), $theme_info['Name'], $theme_info['Version'], $data['new_version'] ) . "\n";
220
+ $settings['notified']['theme'][ $key ] = $data['new_version']; // set theme version we are notifying about
221
+
222
+ $theme_new_version = isset( $data['new_version'] ) ? $data['new_version'] : '';
223
+
224
+ // check if this plugin and this version has been checked/logged already
225
+ if ( ! array_key_exists( $key, $checked_updates ) ) {
226
+ $checked_updates[ $key ] = array(
227
+ 'checked_version' => null,
228
+ );
229
+ }
230
+
231
+ if ( $checked_updates[ $key ]['checked_version'] == $theme_new_version ) {
232
+ // This version has been checked/logged already
233
+ continue;
234
+ }
235
+
236
+ $checked_updates[ $key ]['checked_version'] = $theme_new_version;
237
+
238
+ $this->noticeMessage(
239
+ 'theme_update_available',
240
+ array(
241
+ 'theme_name' => isset( $theme_info['Name'] ) ? $theme_info['Name'] : '',
242
+ 'theme_current_version' => isset( $theme_info['Version'] ) ? $theme_info['Version'] : '',
243
+ 'theme_new_version' => $theme_new_version,
244
+ '_initiator' => SimpleLoggerLogInitiators::WORDPRESS,
245
+ // "plugin_info" => $plugin_info,
246
+ // "remote_plugin_info" => $remote_plugin_info,
247
+ // "active_plugins" => $active_plugins,
248
+ // "updates" => $updates,
249
+ )
250
+ );
251
+ } // End foreach().
252
+
253
+ update_option( $option_key, $checked_updates );
254
+ }
255
+
256
+ /**
257
+ * Append prev and current version of update object as details in the output
258
+ */
259
+ public function getLogRowDetailsOutput( $row ) {
260
+
261
+ $output = '';
262
+
263
+ $current_version = null;
264
+ $new_version = null;
265
+ $context_message_key = isset( $row->context_message_key ) ? $row->context_message_key : null;
266
+
267
+ $context = isset( $row->context ) ? $row->context : array();
268
+
269
+ switch ( $context_message_key ) {
270
+ case 'core_update_available':
271
+ $current_version = isset( $context['wp_core_current_version'] ) ? $context['wp_core_current_version'] : null;
272
+ $new_version = isset( $context['wp_core_new_version'] ) ? $context['wp_core_new_version'] : null;
273
+ break;
274
+
275
+ case 'plugin_update_available':
276
+ $current_version = isset( $context['plugin_current_version'] ) ? $context['plugin_current_version'] : null;
277
+ $new_version = isset( $context['plugin_new_version'] ) ? $context['plugin_new_version'] : null;
278
+ break;
279
+
280
+ case 'theme_update_available':
281
+ $current_version = isset( $context['theme_current_version'] ) ? $context['theme_current_version'] : null;
282
+ $new_version = isset( $context['theme_new_version'] ) ? $context['theme_new_version'] : null;
283
+ break;
284
+ }
285
+
286
+ if ( $current_version && $new_version ) {
287
+ $output .= '<p>';
288
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
289
+ $output .= '<em>' . __( 'Available version', 'simple-history' ) . '</em> ' . esc_html( $new_version );
290
+ $output .= '</span> ';
291
+
292
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
293
+ $output .= '<em>' . __( 'Installed version', 'simple-history' ) . '</em> ' . esc_html( $current_version );
294
+ $output .= '</span>';
295
+
296
+ $output .= '</p>';
297
+
298
+ // Add link to update-page, if user is allowed to that page.
299
+ $is_allowed_to_update_page = current_user_can( 'update_core' ) || current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' );
300
+
301
+ if ( $is_allowed_to_update_page ) {
302
+ $output .= sprintf( '<p><a href="%1$s">', admin_url( 'update-core.php' ) );
303
+ $output .= __( 'View all updates', 'simple-history' );
304
+ $output .= '</a></p>';
305
+ }
306
+ }
307
+
308
+ return $output;
309
+ }
310
+ }
311
  }
loggers/FileEditsLogger.php CHANGED
@@ -3,252 +3,256 @@
3
  /**
4
  * Logs edits to theme or plugin files done from Appearance -> Editor or Plugins -> Editor
5
  */
6
- class FileEditsLogger extends SimpleLogger
7
- {
8
-
9
- public $slug = __CLASS__;
10
-
11
- public function getInfo()
12
- {
13
-
14
- $arr_info = array(
15
- 'name' => 'FileEditsLogger',
16
- 'description' => 'Logs edits to theme and plugin files',
17
- 'capability' => 'manage_options',
18
- 'messages' => array(
19
- 'theme_file_edited' => __('Edited file "{file_name}" in theme "{theme_name}"', 'simple-history'),
20
- 'plugin_file_edited' => __('Edited file "{file_name}" in plugin "{plugin_name}"', 'simple-history'),
21
- ),
22
- 'labels' => array(
23
- 'search' => array(
24
- 'label' => _x('Edited theme and plugin files', 'Plugin logger: file edits', 'simple-history'),
25
- 'label_all' => _x('All file edits', 'Plugin logger: file edits', 'simple-history'),
26
- 'options' => array(
27
- _x('Edited theme files', 'Plugin logger: file edits', 'simple-history') => array(
28
- 'theme_file_edited'
29
- ),
30
- _x('Edited plugin files', 'Plugin logger: file edits', 'simple-history') => array(
31
- 'plugin_file_edited',
32
- ),
33
- ),
34
- ),// search array
35
- ),// labels
36
- );
37
-
38
- return $arr_info;
39
- }
40
-
41
- public function loaded()
42
- {
43
- add_action('load-theme-editor.php', array( $this, 'on_load_theme_editor' ), 10, 1);
44
- add_action('load-plugin-editor.php', array( $this, 'on_load_plugin_editor' ), 10, 1);
45
- }
46
-
47
- /**
48
- * Called when /wp/wp-admin/plugin-editor.php is loaded
49
- * Both using regular GET and during POST with updated file data
50
- *
51
- * todo:
52
- * - log edits
53
- * - log failed edits that result in error and plugin deactivation
54
- */
55
- public function on_load_plugin_editor()
56
- {
57
- if (isset($_POST) && isset($_POST['action'])) {
58
- $action = isset($_POST['action']) ? $_POST['action'] : null;
59
- $file = isset($_POST['file']) ? $_POST['file'] : null;
60
- $plugin_file = isset($_POST['plugin']) ? $_POST['plugin'] : null;
61
- $fileNewContents = isset($_POST['newcontent']) ? wp_unslash($_POST['newcontent']) : null;
62
- $scrollto = isset($_POST['scrollto']) ? (int) $_POST['scrollto'] : 0;
63
-
64
- // if 'phperror' is set then there was an error and an edit is done and wp tries to activate the plugin again
65
- // $phperror = isset($_POST["phperror"]) ? $_POST["phperror"] : null;
66
- // Get info about the edited plugin
67
- $pluginInfo = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_file);
68
- $pluginName = isset($pluginInfo['Name']) ? $pluginInfo['Name'] : null;
69
- $pluginVersion = isset($pluginInfo['Version']) ? $pluginInfo['Version'] : null;
70
-
71
- // Get contents before save
72
- $fileContentsBeforeEdit = file_get_contents(WP_PLUGIN_DIR . '/' . $file);
73
-
74
- $context = array(
75
- 'file_name' => $plugin_file,
76
- 'plugin_name' => $pluginName,
77
- 'plugin_version' => $pluginVersion,
78
- 'old_file_contents' => $fileContentsBeforeEdit,
79
- 'new_file_contents' => $fileNewContents,
80
- '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$plugin_file/$file",
81
- );
82
-
83
- $loggerInstance = $this;
84
- add_filter('wp_redirect', function ($location, $status) use ($context, $loggerInstance) {
85
- $locationParsed = parse_url($location);
86
-
87
- if ($locationParsed === false || empty($locationParsed['query'])) {
88
- return $location;
89
- }
90
-
91
- parse_str($locationParsed['query'], $queryStringParsed);
92
- // ddd($_POST, $context, $queryStringParsed, $location);
93
- if (empty($queryStringParsed)) {
94
- return $location;
95
- }
96
-
97
- // If query string "a=te" exists or "liveupdate=1" then plugin file was updated
98
- $teIsSet = isset($queryStringParsed['a']) && $queryStringParsed['a'] === 'te';
99
- $liveUpdateIsSet = isset($queryStringParsed['liveupdate']) && $queryStringParsed['liveupdate'] === '1';
100
- if ($teIsSet || $liveUpdateIsSet) {
101
- // File was updated
102
- $loggerInstance->infoMessage('plugin_file_edited', $context);
103
- }
104
-
105
- return $location;
106
-
107
- // location when successful edit to non-active plugin
108
- // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
109
- // locations when activated plugin edited successfully
110
- // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
111
- // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
112
- // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
113
- // locations when editing active plugin and error occurs
114
- // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
115
- // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
116
- // locations when error edit is fixed and saved and plugin is activated again
117
- // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
118
- // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
119
- // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
120
- }, 10, 2);
121
- }// End if().
122
- /*
123
- <?php if (isset($_GET['a'])) : ?>
124
- <div id="message" class="updated notice is-dismissible"><p><?php _e('File edited successfully.') ?></p></div>
125
- <?php elseif (isset($_GET['phperror'])) : ?>
126
- <div id="message" class="updated"><p><?php _e('This plugin has been deactivated because your changes resulted in a <strong>fatal error</strong>.') ?></p>
127
- */
128
- }
129
-
130
- /**
131
- * Called when /wp/wp-admin/theme-editor.php is loaded
132
- * Both using regular GET and during POST with updated file data
133
- *
134
- * When this action is fired we don't know if a file will be successfully saved or not.
135
- * There are no filters/actions fired when the edit is saved. On the end wp_redirect() is
136
- * called however and we know the location for the redirect and wp_redirect() has filters
137
- * so we hook onto that to save the edit.
138
- */
139
- public function on_load_theme_editor()
140
- {
141
- // Only continue if method is post and action is update
142
- if (isset($_POST) && isset($_POST['action']) && $_POST['action'] === 'update') {
143
- /*
144
- POST data is like
145
- array(8)
146
- '_wpnonce' => string(10) "9b5e46634f"
147
- '_wp_http_referer' => string(88) "/wp/wp-admin/theme-editor.php?file=style.css&theme=twentyfifteen&scrollto=0&upda…"
148
- 'newcontent' => string(104366) "/* Theme Name: Twenty Fifteen Theme URI: https://wordpress.org/themes/twentyfift…"
149
- 'action' => string(6) "update"
150
- 'file' => string(9) "style.css"
151
- 'theme' => string(13) "twentyfifteen"
152
- 'scrollto' => string(3) "638"
153
- 'submit' => string(11) "Update File"
154
- */
155
-
156
- $action = isset($_POST['action']) ? $_POST['action'] : null;
157
- $file = isset($_POST['file']) ? $_POST['file'] : null;
158
- $theme = isset($_POST['theme']) ? $_POST['theme'] : null;
159
- $fileNewContents = isset($_POST['newcontent']) ? wp_unslash($_POST['newcontent']) : null;
160
- $scrollto = isset($_POST['scrollto']) ? (int) $_POST['scrollto'] : 0;
161
-
162
- // Same code as in theme-editor.php
163
- if ($theme) {
164
- $stylesheet = $theme;
165
- } else {
166
- $stylesheet = get_stylesheet();
167
- }
168
-
169
- $theme = wp_get_theme($stylesheet);
170
-
171
- if (! is_a($theme, 'WP_Theme')) {
172
- return;
173
- }
174
-
175
- // Same code as in theme-editor.php
176
- $relative_file = $file;
177
- $file = $theme->get_stylesheet_directory() . '/' . $relative_file;
178
-
179
- // Get file contents, so we have something to compare with later
180
- $fileContentsBeforeEdit = file_get_contents($file);
181
-
182
- $context = array(
183
- 'theme_name' => $theme->name,
184
- 'theme_stylesheet_path' => $theme->get_stylesheet(),
185
- 'theme_stylesheet_dir' => $theme->get_stylesheet_directory(),
186
- 'file_name' => $relative_file,
187
- 'file_dir' => $file,
188
- 'old_file_contents' => $fileContentsBeforeEdit,
189
- 'new_file_contents' => $fileNewContents,
190
- '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$file",
191
- );
192
-
193
- // Hook into wp_redirect
194
- // This hook is only added when we know a POST is done from theme-editor.php
195
- $loggerInstance = $this;
196
- add_filter('wp_redirect', function ($location, $status) use ($context, $loggerInstance) {
197
- $locationParsed = parse_url($location);
198
-
199
- if ($locationParsed === false || empty($locationParsed['query'])) {
200
- return $location;
201
- }
202
-
203
- parse_str($locationParsed['query'], $queryStringParsed);
204
-
205
- if (empty($queryStringParsed)) {
206
- return $location;
207
- }
208
-
209
- if (isset($queryStringParsed['updated']) && $queryStringParsed['updated']) {
210
- // File was updated
211
- $loggerInstance->infoMessage('theme_file_edited', $context);
212
- } else {
213
- // File was not updated. Unknown reason, but probably because could not be written.
214
- }
215
-
216
- return $location;
217
- }, 10, 2); // add_filter
218
- } // End if().
219
- }
220
-
221
-
222
- public function getLogRowDetailsOutput($row)
223
- {
224
-
225
- $context = $row->context;
226
- $message_key = isset($context['_message_key']) ? $context['_message_key'] : null;
227
-
228
- if (! $message_key) {
229
- return;
230
- }
231
-
232
- $out = '';
233
-
234
- $diff_table_output = '';
235
-
236
- if (! empty($context['new_file_contents']) && ! empty($context['old_file_contents'])) {
237
- if ($context['new_file_contents'] !== $context['old_file_contents']) {
238
- $diff_table_output .= sprintf(
239
- '<tr><td>%1$s</td><td>%2$s</td></tr>',
240
- __('File contents', 'simple-history'),
241
- simple_history_text_diff($context['old_file_contents'], $context['new_file_contents'])
242
- );
243
- }
244
- }
245
-
246
- if ($diff_table_output) {
247
- $diff_table_output = '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
248
- }
249
-
250
- $out .= $diff_table_output;
251
-
252
- return $out;
253
- }
 
 
 
 
254
  }
3
  /**
4
  * Logs edits to theme or plugin files done from Appearance -> Editor or Plugins -> Editor
5
  */
6
+ class FileEditsLogger extends SimpleLogger {
7
+ public $slug = __CLASS__;
8
+
9
+ public function getInfo() {
10
+
11
+ $arr_info = array(
12
+ 'name' => 'FileEditsLogger',
13
+ 'description' => 'Logs edits to theme and plugin files',
14
+ 'capability' => 'manage_options',
15
+ 'messages' => array(
16
+ 'theme_file_edited' => __( 'Edited file "{file_name}" in theme "{theme_name}"', 'simple-history' ),
17
+ 'plugin_file_edited' => __( 'Edited file "{file_name}" in plugin "{plugin_name}"', 'simple-history' ),
18
+ ),
19
+ 'labels' => array(
20
+ 'search' => array(
21
+ 'label' => _x( 'Edited theme and plugin files', 'Plugin logger: file edits', 'simple-history' ),
22
+ 'label_all' => _x( 'All file edits', 'Plugin logger: file edits', 'simple-history' ),
23
+ 'options' => array(
24
+ _x( 'Edited theme files', 'Plugin logger: file edits', 'simple-history' ) => array(
25
+ 'theme_file_edited',
26
+ ),
27
+ _x( 'Edited plugin files', 'Plugin logger: file edits', 'simple-history' ) => array(
28
+ 'plugin_file_edited',
29
+ ),
30
+ ),
31
+ ), // search array
32
+ ), // labels
33
+ );
34
+
35
+ return $arr_info;
36
+ }
37
+
38
+ public function loaded() {
39
+ add_action( 'load-theme-editor.php', array( $this, 'on_load_theme_editor' ), 10, 1 );
40
+ add_action( 'load-plugin-editor.php', array( $this, 'on_load_plugin_editor' ), 10, 1 );
41
+ }
42
+
43
+ /**
44
+ * Called when /wp/wp-admin/plugin-editor.php is loaded
45
+ * Both using regular GET and during POST with updated file data
46
+ *
47
+ * todo:
48
+ * - log edits
49
+ * - log failed edits that result in error and plugin deactivation
50
+ */
51
+ public function on_load_plugin_editor() {
52
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
53
+ if ( isset( $_POST ) && isset( $_POST['action'] ) ) {
54
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
55
+ $file = isset( $_POST['file'] ) ? $_POST['file'] : null;
56
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
57
+ $plugin_file = isset( $_POST['plugin'] ) ? $_POST['plugin'] : null;
58
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
59
+ $fileNewContents = isset( $_POST['newcontent'] ) ? wp_unslash( $_POST['newcontent'] ) : null;
60
+
61
+ // if 'phperror' is set then there was an error and an edit is done and wp tries to activate the plugin again
62
+ // $phperror = isset($_POST["phperror"]) ? $_POST["phperror"] : null;
63
+ // Get info about the edited plugin
64
+ $pluginInfo = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
65
+ $pluginName = isset( $pluginInfo['Name'] ) ? $pluginInfo['Name'] : null;
66
+ $pluginVersion = isset( $pluginInfo['Version'] ) ? $pluginInfo['Version'] : null;
67
+
68
+ // Get contents before save
69
+ $fileContentsBeforeEdit = file_get_contents( WP_PLUGIN_DIR . '/' . $file );
70
+
71
+ $context = array(
72
+ 'file_name' => $plugin_file,
73
+ 'plugin_name' => $pluginName,
74
+ 'plugin_version' => $pluginVersion,
75
+ 'old_file_contents' => $fileContentsBeforeEdit,
76
+ 'new_file_contents' => $fileNewContents,
77
+ '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$plugin_file/$file",
78
+ );
79
+
80
+ $loggerInstance = $this;
81
+ add_filter(
82
+ 'wp_redirect',
83
+ function ( $location, $status ) use ( $context, $loggerInstance ) {
84
+ $locationParsed = parse_url( $location );
85
+
86
+ if ( $locationParsed === false || empty( $locationParsed['query'] ) ) {
87
+ return $location;
88
+ }
89
+
90
+ parse_str( $locationParsed['query'], $queryStringParsed );
91
+ // ddd($_POST, $context, $queryStringParsed, $location);
92
+ if ( empty( $queryStringParsed ) ) {
93
+ return $location;
94
+ }
95
+
96
+ // If query string "a=te" exists or "liveupdate=1" then plugin file was updated
97
+ $teIsSet = isset( $queryStringParsed['a'] ) && $queryStringParsed['a'] === 'te';
98
+ $liveUpdateIsSet = isset( $queryStringParsed['liveupdate'] ) && $queryStringParsed['liveupdate'] === '1';
99
+ if ( $teIsSet || $liveUpdateIsSet ) {
100
+ // File was updated
101
+ $loggerInstance->infoMessage( 'plugin_file_edited', $context );
102
+ }
103
+
104
+ return $location;
105
+
106
+ // location when successful edit to non-active plugin
107
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
108
+ // locations when activated plugin edited successfully
109
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
110
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
111
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
112
+ // locations when editing active plugin and error occurs
113
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
114
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
115
+ // locations when error edit is fixed and saved and plugin is activated again
116
+ // plugin-editor.php?file=akismet%2Fakismet.php&plugin=akismet%2Fakismet.php&liveupdate=1&scrollto=0&networkwide&_wpnonce=b3f399fe94
117
+ // plugin-editor.php?file=akismet%2Fakismet.php&phperror=1&_error_nonce=63511c266d
118
+ // http://wp-playground.dev/wp/wp-admin/plugin-editor.php?file=akismet/akismet.php&plugin=akismet/akismet.php&a=te&scrollto=0
119
+ },
120
+ 10,
121
+ 2
122
+ );
123
+ }// End if().
124
+ /*
125
+ <?php if (isset($_GET['a'])) : ?>
126
+ <div id="message" class="updated notice is-dismissible"><p><?php _e('File edited successfully.') ?></p></div>
127
+ <?php elseif (isset($_GET['phperror'])) : ?>
128
+ <div id="message" class="updated"><p><?php _e('This plugin has been deactivated because your changes resulted in a <strong>fatal error</strong>.') ?></p>
129
+ */
130
+ }
131
+
132
+ /**
133
+ * Called when /wp/wp-admin/theme-editor.php is loaded
134
+ * Both using regular GET and during POST with updated file data
135
+ *
136
+ * When this action is fired we don't know if a file will be successfully saved or not.
137
+ * There are no filters/actions fired when the edit is saved. On the end wp_redirect() is
138
+ * called however and we know the location for the redirect and wp_redirect() has filters
139
+ * so we hook onto that to save the edit.
140
+ */
141
+ public function on_load_theme_editor() {
142
+ // Only continue if method is post and action is update
143
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
144
+ if ( isset( $_POST ) && isset( $_POST['action'] ) && $_POST['action'] === 'update' ) {
145
+ /*
146
+ POST data is like
147
+ array(8)
148
+ '_wpnonce' => string(10) "9b5e46634f"
149
+ '_wp_http_referer' => string(88) "/wp/wp-admin/theme-editor.php?file=style.css&theme=twentyfifteen&scrollto=0&upda…"
150
+ 'newcontent' => string(104366) "/* Theme Name: Twenty Fifteen Theme URI: https://wordpress.org/themes/twentyfift…"
151
+ 'action' => string(6) "update"
152
+ 'file' => string(9) "style.css"
153
+ 'theme' => string(13) "twentyfifteen"
154
+ 'scrollto' => string(3) "638"
155
+ 'submit' => string(11) "Update File"
156
+ */
157
+
158
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
159
+ $file = isset( $_POST['file'] ) ? $_POST['file'] : null;
160
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
161
+ $theme = isset( $_POST['theme'] ) ? $_POST['theme'] : null;
162
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
163
+ $fileNewContents = isset( $_POST['newcontent'] ) ? wp_unslash( $_POST['newcontent'] ) : null;
164
+
165
+ // Same code as in theme-editor.php
166
+ if ( $theme ) {
167
+ $stylesheet = $theme;
168
+ } else {
169
+ $stylesheet = get_stylesheet();
170
+ }
171
+
172
+ $theme = wp_get_theme( $stylesheet );
173
+
174
+ if ( ! is_a( $theme, 'WP_Theme' ) ) {
175
+ return;
176
+ }
177
+
178
+ // Same code as in theme-editor.php
179
+ $relative_file = $file;
180
+ $file = $theme->get_stylesheet_directory() . '/' . $relative_file;
181
+
182
+ // Get file contents, so we have something to compare with later
183
+ $fileContentsBeforeEdit = file_get_contents( $file );
184
+
185
+ $context = array(
186
+ 'theme_name' => $theme->name,
187
+ 'theme_stylesheet_path' => $theme->get_stylesheet(),
188
+ 'theme_stylesheet_dir' => $theme->get_stylesheet_directory(),
189
+ 'file_name' => $relative_file,
190
+ 'file_dir' => $file,
191
+ 'old_file_contents' => $fileContentsBeforeEdit,
192
+ 'new_file_contents' => $fileNewContents,
193
+ '_occasionsID' => __CLASS__ . '/' . __FUNCTION__ . "/file-edit/$file",
194
+ );
195
+
196
+ // Hook into wp_redirect
197
+ // This hook is only added when we know a POST is done from theme-editor.php
198
+ $loggerInstance = $this;
199
+ add_filter(
200
+ 'wp_redirect',
201
+ function ( $location, $status ) use ( $context, $loggerInstance ) {
202
+ $locationParsed = parse_url( $location );
203
+
204
+ if ( $locationParsed === false || empty( $locationParsed['query'] ) ) {
205
+ return $location;
206
+ }
207
+
208
+ parse_str( $locationParsed['query'], $queryStringParsed );
209
+
210
+ if ( empty( $queryStringParsed ) ) {
211
+ return $location;
212
+ }
213
+
214
+ if ( isset( $queryStringParsed['updated'] ) && $queryStringParsed['updated'] ) {
215
+ // File was updated
216
+ $loggerInstance->infoMessage( 'theme_file_edited', $context );
217
+ }
218
+
219
+ return $location;
220
+ },
221
+ 10,
222
+ 2
223
+ ); // add_filter
224
+ } // End if().
225
+ }
226
+
227
+ public function getLogRowDetailsOutput( $row ) {
228
+
229
+ $context = $row->context;
230
+ $message_key = isset( $context['_message_key'] ) ? $context['_message_key'] : null;
231
+
232
+ if ( ! $message_key ) {
233
+ return;
234
+ }
235
+
236
+ $out = '';
237
+
238
+ $diff_table_output = '';
239
+
240
+ if ( ! empty( $context['new_file_contents'] ) && ! empty( $context['old_file_contents'] ) ) {
241
+ if ( $context['new_file_contents'] !== $context['old_file_contents'] ) {
242
+ $diff_table_output .= sprintf(
243
+ '<tr><td>%1$s</td><td>%2$s</td></tr>',
244
+ __( 'File contents', 'simple-history' ),
245
+ simple_history_text_diff( $context['old_file_contents'], $context['new_file_contents'] )
246
+ );
247
+ }
248
+ }
249
+
250
+ if ( $diff_table_output ) {
251
+ $diff_table_output = '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
252
+ }
253
+
254
+ $out .= $diff_table_output;
255
+
256
+ return $out;
257
+ }
258
  }
loggers/PluginEnableMediaReplaceLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs attachments updated with the great Enable Media Replace plugin
@@ -8,97 +8,99 @@ defined('ABSPATH') or die();
8
  *
9
  * @since 2.2
10
  */
11
- class PluginEnableMediaReplaceLogger extends SimpleLogger
12
- {
13
-
14
- public $slug = __CLASS__;
15
-
16
- /**
17
- * Get array with information about this logger
18
- *
19
- * @return array
20
- */
21
- public function getInfo()
22
- {
23
-
24
- $arr_info = array(
25
- 'name' => _x('Enable Media Replace Logger', 'PluginEnableMediaReplaceLogger', 'simple-history'),
26
- 'description' => _x('Logs media updates made with the Enable Media Replace Plugin', 'PluginEnableMediaReplaceLogger', 'simple-history'),
27
- 'name_via' => _x('Using plugin Enable Media Replace', 'PluginUserSwitchingLogger', 'simple-history'),
28
- 'capability' => 'upload_files',
29
- 'messages' => array(
30
- 'replaced_file' => _x('Replaced attachment "{prev_attachment_title}" with new attachment "{new_attachment_title}"', 'PluginEnableMediaReplaceLogger', 'simple-history'),
31
- ),
32
- );
33
-
34
- return $arr_info;
35
- }
36
-
37
- public function loaded()
38
- {
39
-
40
- // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
41
- add_action('load-media_page_enable-media-replace/enable-media-replace', array( $this, 'on_load_plugin_admin_page' ), 10, 1);
42
- }
43
-
44
- public function on_load_plugin_admin_page()
45
- {
46
-
47
- if (empty($_POST)) {
48
- return;
49
- }
50
-
51
- if (isset($_GET['action']) && $_GET['action'] == 'media_replace_upload') {
52
- $attachment_id = empty($_POST['ID']) ? null : (int) $_POST['ID'];
53
- $replace_type = empty($_POST['replace_type']) ? null : sanitize_text_field($_POST['replace_type']);
54
- $new_file = empty($_FILES['userfile']) ? null : (array) $_FILES['userfile'];
55
-
56
- $prev_attachment_post = get_post($attachment_id);
57
-
58
- if (empty($attachment_id) || empty($new_file) || empty($prev_attachment_post)) {
59
- return;
60
- }
61
-
62
- /*
63
- get {
64
- "page": "enable-media-replace\/enable-media-replace.php",
65
- "noheader": "true",
66
- "action": "media_replace_upload",
67
- "attachment_id": "64085",
68
- "_wpnonce": "1089573e0c"
69
- }
70
-
71
- post {
72
- "ID": "64085",
73
- "replace_type": "replace"
74
- }
75
-
76
- files {
77
- "userfile": {
78
- "name": "earth-transparent.png",
79
- "type": "image\/png",
80
- "tmp_name": "\/Applications\/MAMP\/tmp\/php\/phpKA2XOo",
81
- "error": 0,
82
- "size": 4325729
83
- }
84
- }
85
- */
86
-
87
- $this->infoMessage('replaced_file', array(
88
- 'attachment_id' => $attachment_id,
89
- 'prev_attachment_title' => get_the_title($prev_attachment_post),
90
- 'new_attachment_title' => $new_file['name'],
91
- 'new_attachment_type' => $new_file['type'],
92
- 'new_attachment_size' => $new_file['size'],
93
- 'replace_type' => $replace_type,
94
- /*
95
- "get" => $_GET,
96
- "post" => $_POST,
97
- "files" => $_FILES,
98
- "old_attachment_post" => $prev_attachment_post,
99
- "old_attachment_meta" => $prev_attachment_meta
100
- */
101
- ));
102
- }// End if().
103
- }
 
 
104
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs attachments updated with the great Enable Media Replace plugin
8
  *
9
  * @since 2.2
10
  */
11
+ class PluginEnableMediaReplaceLogger extends SimpleLogger {
12
+
13
+ public $slug = __CLASS__;
14
+
15
+ /**
16
+ * Get array with information about this logger
17
+ *
18
+ * @return array
19
+ */
20
+ public function getInfo() {
21
+
22
+ $arr_info = array(
23
+ 'name' => _x( 'Enable Media Replace Logger', 'PluginEnableMediaReplaceLogger', 'simple-history' ),
24
+ 'description' => _x( 'Logs media updates made with the Enable Media Replace Plugin', 'PluginEnableMediaReplaceLogger', 'simple-history' ),
25
+ 'name_via' => _x( 'Using plugin Enable Media Replace', 'PluginUserSwitchingLogger', 'simple-history' ),
26
+ 'capability' => 'upload_files',
27
+ 'messages' => array(
28
+ 'replaced_file' => _x( 'Replaced attachment "{prev_attachment_title}" with new attachment "{new_attachment_title}"', 'PluginEnableMediaReplaceLogger', 'simple-history' ),
29
+ ),
30
+ );
31
+
32
+ return $arr_info;
33
+ }
34
+
35
+ public function loaded() {
36
+
37
+ // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
38
+ add_action( 'load-media_page_enable-media-replace/enable-media-replace', array( $this, 'on_load_plugin_admin_page' ), 10, 1 );
39
+ }
40
+
41
+ public function on_load_plugin_admin_page() {
42
+
43
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
44
+ if ( empty( $_POST ) ) {
45
+ return;
46
+ }
47
+
48
+ if ( isset( $_GET['action'] ) && $_GET['action'] == 'media_replace_upload' ) {
49
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
50
+ $attachment_id = empty( $_POST['ID'] ) ? null : (int) $_POST['ID'];
51
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
52
+ $replace_type = empty( $_POST['replace_type'] ) ? null : sanitize_text_field( $_POST['replace_type'] );
53
+ $new_file = empty( $_FILES['userfile'] ) ? null : (array) $_FILES['userfile'];
54
+
55
+ $prev_attachment_post = get_post( $attachment_id );
56
+
57
+ if ( empty( $attachment_id ) || empty( $new_file ) || empty( $prev_attachment_post ) ) {
58
+ return;
59
+ }
60
+
61
+ /*
62
+ get {
63
+ "page": "enable-media-replace\/enable-media-replace.php",
64
+ "noheader": "true",
65
+ "action": "media_replace_upload",
66
+ "attachment_id": "64085",
67
+ "_wpnonce": "1089573e0c"
68
+ }
69
+
70
+ post {
71
+ "ID": "64085",
72
+ "replace_type": "replace"
73
+ }
74
+
75
+ files {
76
+ "userfile": {
77
+ "name": "earth-transparent.png",
78
+ "type": "image\/png",
79
+ "tmp_name": "\/Applications\/MAMP\/tmp\/php\/phpKA2XOo",
80
+ "error": 0,
81
+ "size": 4325729
82
+ }
83
+ }
84
+ */
85
+
86
+ $this->infoMessage(
87
+ 'replaced_file',
88
+ array(
89
+ 'attachment_id' => $attachment_id,
90
+ 'prev_attachment_title' => get_the_title( $prev_attachment_post ),
91
+ 'new_attachment_title' => $new_file['name'],
92
+ 'new_attachment_type' => $new_file['type'],
93
+ 'new_attachment_size' => $new_file['size'],
94
+ 'replace_type' => $replace_type,
95
+ /*
96
+ "get" => $_GET,
97
+ "post" => $_POST,
98
+ "files" => $_FILES,
99
+ "old_attachment_post" => $prev_attachment_post,
100
+ "old_attachment_meta" => $prev_attachment_meta
101
+ */
102
+ )
103
+ );
104
+ }// End if().
105
+ }
106
  }
loggers/PluginUserSwitchingLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs user switching from the great User Switching plugin
@@ -8,133 +8,128 @@ defined('ABSPATH') or die();
8
  *
9
  * @since 2.2
10
  */
11
- class PluginUserSwitchingLogger extends SimpleLogger
12
- {
13
-
14
- public $slug = __CLASS__;
15
-
16
- /**
17
- * Get array with information about this logger
18
- *
19
- * @return array
20
- */
21
- public function getInfo()
22
- {
23
-
24
- $arr_info = array(
25
- 'name' => _x('User Switching Logger', 'PluginUserSwitchingLogger', 'simple-history'),
26
- 'description' => _x('Logs user switches', 'PluginUserSwitchingLogger', 'simple-history'),
27
- // Definition of via: by way of, through the medium or agency of; also : by means of
28
- 'name_via' => _x('Using plugin User Switching', 'PluginUserSwitchingLogger', 'simple-history'),
29
- 'capability' => 'edit_users',
30
- 'messages' => array(
31
- 'switched_to_user' => _x('Switched to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history'),
32
- 'switched_back_user' => _x('Switched back to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history'),
33
- 'switched_back_themself' => _x('Switched back to user "{user_login_to}"', 'PluginUserSwitchingLogger', 'simple-history'),
34
- 'switched_off_user' => _x('Switched off user "{user_login}"', 'PluginUserSwitchingLogger', 'simple-history'),
35
- ),
36
- );
37
-
38
- return $arr_info;
39
- }
40
-
41
- public function loaded()
42
- {
43
-
44
- add_action('switch_to_user', array( $this, 'on_switch_to_user' ), 10, 2);
45
- add_action('switch_back_user', array( $this, 'on_switch_back_user' ), 10, 2);
46
- add_action('switch_off_user', array( $this, 'on_switch_off_user' ), 10, 1);
47
- }
48
-
49
- public function on_switch_to_user($user_id, $old_user_id)
50
- {
51
-
52
- $user_to = get_user_by('id', $user_id);
53
- $user_from = get_user_by('id', $old_user_id);
54
-
55
- if (! is_a($user_to, 'WP_User') || ! is_a($user_from, 'WP_User')) {
56
- return;
57
- }
58
-
59
- $this->infoMessage(
60
- 'switched_to_user',
61
- array(
62
- // It is the old user who initiates the switching
63
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
64
- '_user_id' => $old_user_id,
65
- 'user_id' => $user_id,
66
- 'old_user_id' => $old_user_id,
67
- 'user_login_to' => $user_to->user_login,
68
- 'user_login_from' => $user_from->user_login,
69
- )
70
- );
71
- }
72
-
73
- /**
74
- * Function is called when a user switches back to their originating account.
75
- * When you switch back after being logged off the
76
- *
77
- * Note: $old_user_id parameter is boolean false because there is no old user.
78
- *
79
- * @param int $user_id The ID of the user being switched back to.
80
- * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back
81
- * after having been switched off.
82
- */
83
- public function on_switch_back_user($user_id, $old_user_id)
84
- {
85
-
86
- $user_to = get_user_by('id', $user_id);
87
-
88
- $user_from = $old_user_id == false ? null : get_user_by('id', $old_user_id);
89
-
90
- if (! is_a($user_to, 'WP_User')) {
91
- return;
92
- }
93
-
94
- if ($user_from) {
95
- // User switched back from another user
96
- $this->infoMessage(
97
- 'switched_back_user',
98
- array(
99
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
100
- '_user_id' => $old_user_id,
101
- 'user_id' => $user_id,
102
- 'old_user_id' => $old_user_id,
103
- 'user_login_to' => $user_to->user_login,
104
- 'user_login_from' => $user_from->user_login,
105
- )
106
- );
107
- } else {
108
- // User switched back to themself (no prev user)
109
- $this->infoMessage(
110
- 'switched_back_themself',
111
- array(
112
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
113
- '_user_id' => $user_id,
114
- 'user_login_to' => $user_to->user_login,
115
- )
116
- );
117
- }
118
- }
119
-
120
- public function on_switch_off_user($user_id)
121
- {
122
-
123
- $user = get_user_by('id', $user_id);
124
-
125
- if (! is_a($user, 'WP_User')) {
126
- return;
127
- }
128
-
129
- $this->infoMessage(
130
- 'switched_off_user',
131
- array(
132
- '_initiator' => SimpleLoggerLogInitiators::WP_USER,
133
- '_user_id' => $user_id,
134
-
135
- 'user_id' => $user_id,
136
- 'user_login' => $user->user_login,
137
- )
138
- );
139
- }
140
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs user switching from the great User Switching plugin
8
  *
9
  * @since 2.2
10
  */
11
+ class PluginUserSwitchingLogger extends SimpleLogger {
12
+
13
+
14
+ public $slug = __CLASS__;
15
+
16
+ /**
17
+ * Get array with information about this logger
18
+ *
19
+ * @return array
20
+ */
21
+ public function getInfo() {
22
+
23
+ $arr_info = array(
24
+ 'name' => _x( 'User Switching Logger', 'PluginUserSwitchingLogger', 'simple-history' ),
25
+ 'description' => _x( 'Logs user switches', 'PluginUserSwitchingLogger', 'simple-history' ),
26
+ // Definition of via: by way of, through the medium or agency of; also : by means of
27
+ 'name_via' => _x( 'Using plugin User Switching', 'PluginUserSwitchingLogger', 'simple-history' ),
28
+ 'capability' => 'edit_users',
29
+ 'messages' => array(
30
+ 'switched_to_user' => _x( 'Switched to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history' ),
31
+ 'switched_back_user' => _x( 'Switched back to user "{user_login_to}" from user "{user_login_from}"', 'PluginUserSwitchingLogger', 'simple-history' ),
32
+ 'switched_back_themself' => _x( 'Switched back to user "{user_login_to}"', 'PluginUserSwitchingLogger', 'simple-history' ),
33
+ 'switched_off_user' => _x( 'Switched off user "{user_login}"', 'PluginUserSwitchingLogger', 'simple-history' ),
34
+ ),
35
+ );
36
+
37
+ return $arr_info;
38
+ }
39
+
40
+ public function loaded() {
41
+
42
+ add_action( 'switch_to_user', array( $this, 'on_switch_to_user' ), 10, 2 );
43
+ add_action( 'switch_back_user', array( $this, 'on_switch_back_user' ), 10, 2 );
44
+ add_action( 'switch_off_user', array( $this, 'on_switch_off_user' ), 10, 1 );
45
+ }
46
+
47
+ public function on_switch_to_user( $user_id, $old_user_id ) {
48
+
49
+ $user_to = get_user_by( 'id', $user_id );
50
+ $user_from = get_user_by( 'id', $old_user_id );
51
+
52
+ if ( ! is_a( $user_to, 'WP_User' ) || ! is_a( $user_from, 'WP_User' ) ) {
53
+ return;
54
+ }
55
+
56
+ $this->infoMessage(
57
+ 'switched_to_user',
58
+ array(
59
+ // It is the old user who initiates the switching
60
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
61
+ '_user_id' => $old_user_id,
62
+ 'user_id' => $user_id,
63
+ 'old_user_id' => $old_user_id,
64
+ 'user_login_to' => $user_to->user_login,
65
+ 'user_login_from' => $user_from->user_login,
66
+ )
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Function is called when a user switches back to their originating account.
72
+ * When you switch back after being logged off the
73
+ *
74
+ * Note: $old_user_id parameter is boolean false because there is no old user.
75
+ *
76
+ * @param int $user_id The ID of the user being switched back to.
77
+ * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back
78
+ * after having been switched off.
79
+ */
80
+ public function on_switch_back_user( $user_id, $old_user_id ) {
81
+
82
+ $user_to = get_user_by( 'id', $user_id );
83
+
84
+ $user_from = $old_user_id == false ? null : get_user_by( 'id', $old_user_id );
85
+
86
+ if ( ! is_a( $user_to, 'WP_User' ) ) {
87
+ return;
88
+ }
89
+
90
+ if ( $user_from ) {
91
+ // User switched back from another user
92
+ $this->infoMessage(
93
+ 'switched_back_user',
94
+ array(
95
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
96
+ '_user_id' => $old_user_id,
97
+ 'user_id' => $user_id,
98
+ 'old_user_id' => $old_user_id,
99
+ 'user_login_to' => $user_to->user_login,
100
+ 'user_login_from' => $user_from->user_login,
101
+ )
102
+ );
103
+ } else {
104
+ // User switched back to themself (no prev user)
105
+ $this->infoMessage(
106
+ 'switched_back_themself',
107
+ array(
108
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
109
+ '_user_id' => $user_id,
110
+ 'user_login_to' => $user_to->user_login,
111
+ )
112
+ );
113
+ }
114
+ }
115
+
116
+ public function on_switch_off_user( $user_id ) {
117
+
118
+ $user = get_user_by( 'id', $user_id );
119
+
120
+ if ( ! is_a( $user, 'WP_User' ) ) {
121
+ return;
122
+ }
123
+
124
+ $this->infoMessage(
125
+ 'switched_off_user',
126
+ array(
127
+ '_initiator' => SimpleLoggerLogInitiators::WP_USER,
128
+ '_user_id' => $user_id,
129
+
130
+ 'user_id' => $user_id,
131
+ 'user_login' => $user->user_login,
132
+ )
133
+ );
134
+ }
 
 
 
 
 
135
  }
loggers/PluginWPCrontrolLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs cron event management from the WP Crontrol plugin
@@ -10,419 +10,412 @@ defined('ABSPATH') or die();
10
  *
11
  * @since x.x
12
  */
13
- class PluginWPCrontrolLogger extends SimpleLogger
14
- {
15
-
16
- public $slug = __CLASS__;
17
-
18
- /**
19
- * Get array with information about this logger
20
- *
21
- * @return array
22
- */
23
- public function getInfo()
24
- {
25
-
26
- $arr_info = array(
27
- 'name' => _x('WP Crontrol Logger', 'PluginWPCrontrolLogger', 'simple-history'),
28
- 'description' => _x('Logs management of cron events', 'PluginWPCrontrolLogger', 'simple-history'),
29
- 'name_via' => _x('Using plugin WP Crontrol', 'PluginWPCrontrolLogger', 'simple-history'),
30
- 'capability' => 'manage_options',
31
- 'messages' => array(
32
- 'added_new_event' => _x('Added cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history'),
33
- 'ran_event' => _x('Manually ran cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history'),
34
- 'deleted_event' => _x('Deleted cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history'),
35
- 'deleted_all_with_hook' => _x('Deleted all "{event_hook}" cron events', 'PluginWPCrontrolLogger', 'simple-history'),
36
- 'edited_event' => _x('Edited cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history'),
37
- 'added_new_schedule' => _x('Added cron schedule "{schedule_name}"', 'PluginWPCrontrolLogger', 'simple-history'),
38
- 'deleted_schedule' => _x('Deleted cron schedule "{schedule_name}"', 'PluginWPCrontrolLogger', 'simple-history'),
39
- ),
40
- );
41
-
42
- return $arr_info;
43
- }
44
-
45
- public function loaded()
46
- {
47
-
48
- add_action('crontrol/added_new_event', array( $this, 'added_new_event' ));
49
- add_action('crontrol/added_new_php_event', array( $this, 'added_new_event' ));
50
- add_action('crontrol/ran_event', array( $this, 'ran_event' ));
51
- add_action('crontrol/deleted_event', array( $this, 'deleted_event' ));
52
- add_action('crontrol/deleted_all_with_hook', array( $this, 'deleted_all_with_hook' ), 10, 2);
53
- add_action('crontrol/edited_event', array( $this, 'edited_event' ), 10, 2);
54
- add_action('crontrol/edited_php_event', array( $this, 'edited_event' ), 10, 2);
55
- add_action('crontrol/added_new_schedule', array( $this, 'added_new_schedule' ), 10, 3);
56
- add_action('crontrol/deleted_schedule', array( $this, 'deleted_schedule' ));
57
- }
58
-
59
- /**
60
- * Fires after a new cron event is added.
61
- *
62
- * @param object $event {
63
- * An object containing the event's data.
64
- *
65
- * @type string $hook Action hook to execute when the event is run.
66
- * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
67
- * @type string|false $schedule How often the event should subsequently recur.
68
- * @type array $args Array containing each separate argument to pass to the hook's callback function.
69
- * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
70
- * }
71
- */
72
- public function added_new_event($event)
73
- {
74
- $context = array(
75
- 'event_hook' => $event->hook,
76
- 'event_timestamp' => $event->timestamp,
77
- 'event_args' => $event->args,
78
- );
79
-
80
- if ( $event->schedule ) {
81
- $context['event_schedule_name'] = $event->schedule;
82
-
83
- if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
84
- $context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
85
- }
86
- } else {
87
- $context['event_schedule_name'] = _x('None', 'PluginWPCrontrolLogger', 'simple-history');
88
- }
89
-
90
- $this->infoMessage(
91
- 'added_new_event',
92
- $context
93
- );
94
- }
95
-
96
- /**
97
- * Fires after a cron event is ran manually.
98
- *
99
- * @param object $event {
100
- * An object containing the event's data.
101
- *
102
- * @type string $hook Action hook to execute when the event is run.
103
- * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
104
- * @type string|false $schedule How often the event should subsequently recur.
105
- * @type array $args Array containing each separate argument to pass to the hook's callback function.
106
- * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
107
- * }
108
- */
109
- public function ran_event($event)
110
- {
111
- $context = array(
112
- 'event_hook' => $event->hook,
113
- 'event_args' => $event->args,
114
- );
115
-
116
- $this->infoMessage(
117
- 'ran_event',
118
- $context
119
- );
120
- }
121
-
122
- /**
123
- * Fires after a cron event is deleted.
124
- *
125
- * @param object $event {
126
- * An object containing the event's data.
127
- *
128
- * @type string $hook Action hook to execute when the event is run.
129
- * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
130
- * @type string|false $schedule How often the event should subsequently recur.
131
- * @type array $args Array containing each separate argument to pass to the hook's callback function.
132
- * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
133
- * }
134
- */
135
- public function deleted_event($event)
136
- {
137
- $context = array(
138
- 'event_hook' => $event->hook,
139
- 'event_timestamp' => $event->timestamp,
140
- 'event_args' => $event->args,
141
- );
142
-
143
- if ( $event->schedule ) {
144
- $context['event_schedule_name'] = $event->schedule;
145
-
146
- if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
147
- $context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
148
- }
149
- } else {
150
- $context['event_schedule_name'] = _x('None', 'PluginWPCrontrolLogger', 'simple-history');
151
- }
152
-
153
- $this->infoMessage(
154
- 'deleted_event',
155
- $context
156
- );
157
- }
158
-
159
- /**
160
- * Fires after all cron events with the given hook are deleted.
161
- *
162
- * @param string $hook The hook name.
163
- * @param int $deleted The number of events that were deleted.
164
- */
165
- public function deleted_all_with_hook($hook, $deleted)
166
- {
167
- $context = array(
168
- 'event_hook' => $hook,
169
- 'events_deleted' => $deleted,
170
- );
171
-
172
- $this->infoMessage(
173
- 'deleted_all_with_hook',
174
- $context
175
- );
176
- }
177
-
178
- /**
179
- * Fires after a cron event is edited.
180
- *
181
- * @param object $event {
182
- * An object containing the new event's data.
183
- *
184
- * @type string $hook Action hook to execute when the event is run.
185
- * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
186
- * @type string|false $schedule How often the event should subsequently recur.
187
- * @type array $args Array containing each separate argument to pass to the hook's callback function.
188
- * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
189
- * }
190
- * @param object $original {
191
- * An object containing the original event's data.
192
- *
193
- * @type string $hook Action hook to execute when the event is run.
194
- * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
195
- * @type string|false $schedule How often the event should subsequently recur.
196
- * @type array $args Array containing each separate argument to pass to the hook's callback function.
197
- * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
198
- * }
199
- */
200
- public function edited_event($event, $original)
201
- {
202
- $context = array(
203
- 'event_hook' => $event->hook,
204
- 'event_timestamp' => $event->timestamp,
205
- 'event_args' => $event->args,
206
- 'event_original_hook' => $original->hook,
207
- 'event_original_timestamp' => $original->timestamp,
208
- 'event_original_args' => $original->args,
209
- );
210
-
211
- if ( $event->schedule ) {
212
- $context['event_schedule_name'] = $event->schedule;
213
-
214
- if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
215
- $context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
216
- }
217
- } else {
218
- $context['event_schedule_name'] = _x('None', 'PluginWPCrontrolLogger', 'simple-history');
219
- }
220
-
221
- if ( $original->schedule ) {
222
- $context['event_original_schedule_name'] = $original->schedule;
223
-
224
- if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
225
- $context['event_original_schedule_name'] = \Crontrol\Event\get_schedule_name( $original );
226
- }
227
- } else {
228
- $context['event_original_schedule_name'] = _x('None', 'PluginWPCrontrolLogger', 'simple-history');
229
- }
230
-
231
- $this->infoMessage(
232
- 'edited_event',
233
- $context
234
- );
235
- }
236
-
237
- /**
238
- * Fires after a new cron schedule is added.
239
- *
240
- * @param string $name The internal name of the schedule.
241
- * @param int $interval The interval between executions of the new schedule.
242
- * @param string $display The display name of the schedule.
243
- */
244
- public function added_new_schedule( $name, $interval, $display ) {
245
- $context = array(
246
- 'schedule_name' => $name,
247
- 'schedule_interval' => $interval,
248
- 'schedule_display' => $display,
249
- );
250
-
251
- $this->infoMessage(
252
- 'added_new_schedule',
253
- $context
254
- );
255
- }
256
-
257
- /**
258
- * Fires after a cron schedule is deleted.
259
- *
260
- * @param string $name The internal name of the schedule.
261
- */
262
- public function deleted_schedule( $name ) {
263
- $context = array(
264
- 'schedule_name' => $name,
265
- );
266
-
267
- $this->infoMessage(
268
- 'deleted_schedule',
269
- $context
270
- );
271
- }
272
-
273
- public function getLogRowDetailsOutput($row) {
274
- switch ( $row->context_message_key ) {
275
- case 'added_new_event':
276
- case 'ran_event':
277
- case 'deleted_event':
278
- case 'deleted_all_with_hook':
279
- case 'edited_event':
280
- return $this->cronEventDetailsOutput( $row );
281
- break;
282
- case 'added_new_schedule':
283
- case 'deleted_schedule':
284
- return $this->cronScheduleDetailsOutput( $row );
285
- break;
286
- }
287
-
288
- return '';
289
- }
290
-
291
- protected function cronEventDetailsOutput($row) {
292
- $tmpl_row = '
293
  <tr>
294
  <td>%1$s</td>
295
  <td>%2$s</td>
296
  </tr>
297
  ';
298
- $context = $row->context;
299
- $output = '<table class="SimpleHistoryLogitem__keyValueTable">';
300
-
301
- if ( isset( $context['event_original_hook'] ) && ( $context['event_original_hook'] !== $context['event_hook'] ) ) {
302
- $key_text_diff = simple_history_text_diff(
303
- $context['event_original_hook'],
304
- $context['event_hook']
305
- );
306
-
307
- if ($key_text_diff) {
308
- $output .= sprintf(
309
- $tmpl_row,
310
- _x('Hook', 'PluginWPCrontrolLogger', 'simple-history'),
311
- $key_text_diff
312
- );
313
- }
314
- }
315
-
316
- if ( isset( $context['event_original_args'] ) && ( $context['event_original_args'] !== $context['event_args'] ) ) {
317
- $key_text_diff = simple_history_text_diff(
318
- $context['event_original_args'],
319
- $context['event_args']
320
- );
321
-
322
- if ($key_text_diff) {
323
- $output .= sprintf(
324
- $tmpl_row,
325
- _x('Arguments', 'PluginWPCrontrolLogger', 'simple-history'),
326
- $key_text_diff
327
- );
328
- }
329
- } else if ( isset( $context['event_args'] ) ) {
330
- if ( '[]' !== $context['event_args'] ) {
331
- $args = $context['event_args'];
332
- } else {
333
- $args = _x('None', 'PluginWPCrontrolLogger', 'simple-history');
334
- }
335
-
336
- $output .= sprintf(
337
- $tmpl_row,
338
- _x('Arguments', 'PluginWPCrontrolLogger', 'simple-history'),
339
- esc_html( $args )
340
- );
341
- }
342
-
343
- if ( isset( $context['event_original_timestamp'] ) && ( $context['event_original_timestamp'] !== $context['event_timestamp'] ) ) {
344
- $key_text_diff = simple_history_text_diff(
345
- gmdate( 'Y-m-d H:i:s', $context['event_original_timestamp'] ),
346
- gmdate( 'Y-m-d H:i:s', $context['event_timestamp'] )
347
- );
348
-
349
- if ($key_text_diff) {
350
- $output .= sprintf(
351
- $tmpl_row,
352
- _x('Next Run', 'PluginWPCrontrolLogger', 'simple-history'),
353
- $key_text_diff
354
- );
355
- }
356
- } else if ( isset( $context['event_timestamp'] ) ) {
357
- $output .= sprintf(
358
- $tmpl_row,
359
- _x('Next Run', 'PluginWPCrontrolLogger', 'simple-history'),
360
- esc_html( gmdate( 'Y-m-d H:i:s', $context['event_timestamp'] ) . ' UTC' )
361
- );
362
- }
363
-
364
- if ( isset( $context['event_original_schedule_name'] ) && ( $context['event_original_schedule_name'] !== $context['event_schedule_name'] ) ) {
365
- $key_text_diff = simple_history_text_diff(
366
- $context['event_original_schedule_name'],
367
- $context['event_schedule_name']
368
- );
369
-
370
- if ($key_text_diff) {
371
- $output .= sprintf(
372
- $tmpl_row,
373
- _x('Recurrence', 'PluginWPCrontrolLogger', 'simple-history'),
374
- $key_text_diff
375
- );
376
- }
377
- } else if ( isset( $context['event_schedule_name'] ) ) {
378
- $output .= sprintf(
379
- $tmpl_row,
380
- _x('Recurrence', 'PluginWPCrontrolLogger', 'simple-history'),
381
- esc_html( $context['event_schedule_name'] )
382
- );
383
- }
384
-
385
- $output .= '</table>';
386
-
387
- return $output;
388
- }
389
-
390
- protected function cronScheduleDetailsOutput($row) {
391
- $tmpl_row = '
392
  <tr>
393
  <td>%1$s</td>
394
  <td>%2$s</td>
395
  </tr>
396
  ';
397
- $context = $row->context;
398
- $output = '<table class="SimpleHistoryLogitem__keyValueTable">';
399
-
400
- if ( isset( $context['schedule_name'] ) ) {
401
- $output .= sprintf(
402
- $tmpl_row,
403
- _x('Name', 'PluginWPCrontrolLogger', 'simple-history'),
404
- esc_html( $context['schedule_name'] )
405
- );
406
- }
407
-
408
- if ( isset( $context['schedule_interval'] ) ) {
409
- $output .= sprintf(
410
- $tmpl_row,
411
- _x('Interval', 'PluginWPCrontrolLogger', 'simple-history'),
412
- esc_html( $context['schedule_interval'] )
413
- );
414
- }
415
-
416
- if ( isset( $context['schedule_display'] ) ) {
417
- $output .= sprintf(
418
- $tmpl_row,
419
- _x('Display Name', 'PluginWPCrontrolLogger', 'simple-history'),
420
- esc_html( $context['schedule_display'] )
421
- );
422
- }
423
-
424
- $output .= '</table>';
425
-
426
- return $output;
427
- }
428
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs cron event management from the WP Crontrol plugin
10
  *
11
  * @since x.x
12
  */
13
+ class PluginWPCrontrolLogger extends SimpleLogger {
14
+
15
+
16
+ public $slug = __CLASS__;
17
+
18
+ /**
19
+ * Get array with information about this logger
20
+ *
21
+ * @return array
22
+ */
23
+ public function getInfo() {
24
+
25
+ $arr_info = array(
26
+ 'name' => _x( 'WP Crontrol Logger', 'PluginWPCrontrolLogger', 'simple-history' ),
27
+ 'description' => _x( 'Logs management of cron events', 'PluginWPCrontrolLogger', 'simple-history' ),
28
+ 'name_via' => _x( 'Using plugin WP Crontrol', 'PluginWPCrontrolLogger', 'simple-history' ),
29
+ 'capability' => 'manage_options',
30
+ 'messages' => array(
31
+ 'added_new_event' => _x( 'Added cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
32
+ 'ran_event' => _x( 'Manually ran cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
33
+ 'deleted_event' => _x( 'Deleted cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
34
+ 'deleted_all_with_hook' => _x( 'Deleted all "{event_hook}" cron events', 'PluginWPCrontrolLogger', 'simple-history' ),
35
+ 'edited_event' => _x( 'Edited cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
36
+ 'added_new_schedule' => _x( 'Added cron schedule "{schedule_name}"', 'PluginWPCrontrolLogger', 'simple-history' ),
37
+ 'deleted_schedule' => _x( 'Deleted cron schedule "{schedule_name}"', 'PluginWPCrontrolLogger', 'simple-history' ),
38
+ ),
39
+ );
40
+
41
+ return $arr_info;
42
+ }
43
+
44
+ public function loaded() {
45
+
46
+ add_action( 'crontrol/added_new_event', array( $this, 'added_new_event' ) );
47
+ add_action( 'crontrol/added_new_php_event', array( $this, 'added_new_event' ) );
48
+ add_action( 'crontrol/ran_event', array( $this, 'ran_event' ) );
49
+ add_action( 'crontrol/deleted_event', array( $this, 'deleted_event' ) );
50
+ add_action( 'crontrol/deleted_all_with_hook', array( $this, 'deleted_all_with_hook' ), 10, 2 );
51
+ add_action( 'crontrol/edited_event', array( $this, 'edited_event' ), 10, 2 );
52
+ add_action( 'crontrol/edited_php_event', array( $this, 'edited_event' ), 10, 2 );
53
+ add_action( 'crontrol/added_new_schedule', array( $this, 'added_new_schedule' ), 10, 3 );
54
+ add_action( 'crontrol/deleted_schedule', array( $this, 'deleted_schedule' ) );
55
+ }
56
+
57
+ /**
58
+ * Fires after a new cron event is added.
59
+ *
60
+ * @param object $event {
61
+ * An object containing the event's data.
62
+ *
63
+ * @type string $hook Action hook to execute when the event is run.
64
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
65
+ * @type string|false $schedule How often the event should subsequently recur.
66
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
67
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
68
+ * }
69
+ */
70
+ public function added_new_event( $event ) {
71
+ $context = array(
72
+ 'event_hook' => $event->hook,
73
+ 'event_timestamp' => $event->timestamp,
74
+ 'event_args' => $event->args,
75
+ );
76
+
77
+ if ( $event->schedule ) {
78
+ $context['event_schedule_name'] = $event->schedule;
79
+
80
+ if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
81
+ $context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
82
+ }
83
+ } else {
84
+ $context['event_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
85
+ }
86
+
87
+ $this->infoMessage(
88
+ 'added_new_event',
89
+ $context
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Fires after a cron event is ran manually.
95
+ *
96
+ * @param object $event {
97
+ * An object containing the event's data.
98
+ *
99
+ * @type string $hook Action hook to execute when the event is run.
100
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
101
+ * @type string|false $schedule How often the event should subsequently recur.
102
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
103
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
104
+ * }
105
+ */
106
+ public function ran_event( $event ) {
107
+ $context = array(
108
+ 'event_hook' => $event->hook,
109
+ 'event_args' => $event->args,
110
+ );
111
+
112
+ $this->infoMessage(
113
+ 'ran_event',
114
+ $context
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Fires after a cron event is deleted.
120
+ *
121
+ * @param object $event {
122
+ * An object containing the event's data.
123
+ *
124
+ * @type string $hook Action hook to execute when the event is run.
125
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
126
+ * @type string|false $schedule How often the event should subsequently recur.
127
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
128
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
129
+ * }
130
+ */
131
+ public function deleted_event( $event ) {
132
+ $context = array(
133
+ 'event_hook' => $event->hook,
134
+ 'event_timestamp' => $event->timestamp,
135
+ 'event_args' => $event->args,
136
+ );
137
+
138
+ if ( $event->schedule ) {
139
+ $context['event_schedule_name'] = $event->schedule;
140
+
141
+ if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
142
+ $context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
143
+ }
144
+ } else {
145
+ $context['event_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
146
+ }
147
+
148
+ $this->infoMessage(
149
+ 'deleted_event',
150
+ $context
151
+ );
152
+ }
153
+
154
+ /**
155
+ * Fires after all cron events with the given hook are deleted.
156
+ *
157
+ * @param string $hook The hook name.
158
+ * @param int $deleted The number of events that were deleted.
159
+ */
160
+ public function deleted_all_with_hook( $hook, $deleted ) {
161
+ $context = array(
162
+ 'event_hook' => $hook,
163
+ 'events_deleted' => $deleted,
164
+ );
165
+
166
+ $this->infoMessage(
167
+ 'deleted_all_with_hook',
168
+ $context
169
+ );
170
+ }
171
+
172
+ /**
173
+ * Fires after a cron event is edited.
174
+ *
175
+ * @param object $event {
176
+ * An object containing the new event's data.
177
+ *
178
+ * @type string $hook Action hook to execute when the event is run.
179
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
180
+ * @type string|false $schedule How often the event should subsequently recur.
181
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
182
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
183
+ * }
184
+ * @param object $original {
185
+ * An object containing the original event's data.
186
+ *
187
+ * @type string $hook Action hook to execute when the event is run.
188
+ * @type int $timestamp Unix timestamp (UTC) for when to next run the event.
189
+ * @type string|false $schedule How often the event should subsequently recur.
190
+ * @type array $args Array containing each separate argument to pass to the hook's callback function.
191
+ * @type int $interval The interval time in seconds for the schedule. Only present for recurring events.
192
+ * }
193
+ */
194
+ public function edited_event( $event, $original ) {
195
+ $context = array(
196
+ 'event_hook' => $event->hook,
197
+ 'event_timestamp' => $event->timestamp,
198
+ 'event_args' => $event->args,
199
+ 'event_original_hook' => $original->hook,
200
+ 'event_original_timestamp' => $original->timestamp,
201
+ 'event_original_args' => $original->args,
202
+ );
203
+
204
+ if ( $event->schedule ) {
205
+ $context['event_schedule_name'] = $event->schedule;
206
+
207
+ if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
208
+ $context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
209
+ }
210
+ } else {
211
+ $context['event_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
212
+ }
213
+
214
+ if ( $original->schedule ) {
215
+ $context['event_original_schedule_name'] = $original->schedule;
216
+
217
+ if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
218
+ $context['event_original_schedule_name'] = \Crontrol\Event\get_schedule_name( $original );
219
+ }
220
+ } else {
221
+ $context['event_original_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
222
+ }
223
+
224
+ $this->infoMessage(
225
+ 'edited_event',
226
+ $context
227
+ );
228
+ }
229
+
230
+ /**
231
+ * Fires after a new cron schedule is added.
232
+ *
233
+ * @param string $name The internal name of the schedule.
234
+ * @param int $interval The interval between executions of the new schedule.
235
+ * @param string $display The display name of the schedule.
236
+ */
237
+ public function added_new_schedule( $name, $interval, $display ) {
238
+ $context = array(
239
+ 'schedule_name' => $name,
240
+ 'schedule_interval' => $interval,
241
+ 'schedule_display' => $display,
242
+ );
243
+
244
+ $this->infoMessage(
245
+ 'added_new_schedule',
246
+ $context
247
+ );
248
+ }
249
+
250
+ /**
251
+ * Fires after a cron schedule is deleted.
252
+ *
253
+ * @param string $name The internal name of the schedule.
254
+ */
255
+ public function deleted_schedule( $name ) {
256
+ $context = array(
257
+ 'schedule_name' => $name,
258
+ );
259
+
260
+ $this->infoMessage(
261
+ 'deleted_schedule',
262
+ $context
263
+ );
264
+ }
265
+
266
+ public function getLogRowDetailsOutput( $row ) {
267
+ switch ( $row->context_message_key ) {
268
+ case 'added_new_event':
269
+ case 'ran_event':
270
+ case 'deleted_event':
271
+ case 'deleted_all_with_hook':
272
+ case 'edited_event':
273
+ return $this->cronEventDetailsOutput( $row );
274
+ break;
275
+ case 'added_new_schedule':
276
+ case 'deleted_schedule':
277
+ return $this->cronScheduleDetailsOutput( $row );
278
+ break;
279
+ }
280
+
281
+ return '';
282
+ }
283
+
284
+ protected function cronEventDetailsOutput( $row ) {
285
+ $tmpl_row = '
 
 
 
 
 
 
 
286
  <tr>
287
  <td>%1$s</td>
288
  <td>%2$s</td>
289
  </tr>
290
  ';
291
+ $context = $row->context;
292
+ $output = '<table class="SimpleHistoryLogitem__keyValueTable">';
293
+
294
+ if ( isset( $context['event_original_hook'] ) && ( $context['event_original_hook'] !== $context['event_hook'] ) ) {
295
+ $key_text_diff = simple_history_text_diff(
296
+ $context['event_original_hook'],
297
+ $context['event_hook']
298
+ );
299
+
300
+ if ( $key_text_diff ) {
301
+ $output .= sprintf(
302
+ $tmpl_row,
303
+ _x( 'Hook', 'PluginWPCrontrolLogger', 'simple-history' ),
304
+ $key_text_diff
305
+ );
306
+ }
307
+ }
308
+
309
+ if ( isset( $context['event_original_args'] ) && ( $context['event_original_args'] !== $context['event_args'] ) ) {
310
+ $key_text_diff = simple_history_text_diff(
311
+ $context['event_original_args'],
312
+ $context['event_args']
313
+ );
314
+
315
+ if ( $key_text_diff ) {
316
+ $output .= sprintf(
317
+ $tmpl_row,
318
+ _x( 'Arguments', 'PluginWPCrontrolLogger', 'simple-history' ),
319
+ $key_text_diff
320
+ );
321
+ }
322
+ } else if ( isset( $context['event_args'] ) ) {
323
+ if ( '[]' !== $context['event_args'] ) {
324
+ $args = $context['event_args'];
325
+ } else {
326
+ $args = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
327
+ }
328
+
329
+ $output .= sprintf(
330
+ $tmpl_row,
331
+ _x( 'Arguments', 'PluginWPCrontrolLogger', 'simple-history' ),
332
+ esc_html( $args )
333
+ );
334
+ }
335
+
336
+ if ( isset( $context['event_original_timestamp'] ) && ( $context['event_original_timestamp'] !== $context['event_timestamp'] ) ) {
337
+ $key_text_diff = simple_history_text_diff(
338
+ gmdate( 'Y-m-d H:i:s', $context['event_original_timestamp'] ),
339
+ gmdate( 'Y-m-d H:i:s', $context['event_timestamp'] )
340
+ );
341
+
342
+ if ( $key_text_diff ) {
343
+ $output .= sprintf(
344
+ $tmpl_row,
345
+ _x( 'Next Run', 'PluginWPCrontrolLogger', 'simple-history' ),
346
+ $key_text_diff
347
+ );
348
+ }
349
+ } else if ( isset( $context['event_timestamp'] ) ) {
350
+ $output .= sprintf(
351
+ $tmpl_row,
352
+ _x( 'Next Run', 'PluginWPCrontrolLogger', 'simple-history' ),
353
+ esc_html( gmdate( 'Y-m-d H:i:s', $context['event_timestamp'] ) . ' UTC' )
354
+ );
355
+ }
356
+
357
+ if ( isset( $context['event_original_schedule_name'] ) && ( $context['event_original_schedule_name'] !== $context['event_schedule_name'] ) ) {
358
+ $key_text_diff = simple_history_text_diff(
359
+ $context['event_original_schedule_name'],
360
+ $context['event_schedule_name']
361
+ );
362
+
363
+ if ( $key_text_diff ) {
364
+ $output .= sprintf(
365
+ $tmpl_row,
366
+ _x( 'Recurrence', 'PluginWPCrontrolLogger', 'simple-history' ),
367
+ $key_text_diff
368
+ );
369
+ }
370
+ } else if ( isset( $context['event_schedule_name'] ) ) {
371
+ $output .= sprintf(
372
+ $tmpl_row,
373
+ _x( 'Recurrence', 'PluginWPCrontrolLogger', 'simple-history' ),
374
+ esc_html( $context['event_schedule_name'] )
375
+ );
376
+ }
377
+
378
+ $output .= '</table>';
379
+
380
+ return $output;
381
+ }
382
+
383
+ protected function cronScheduleDetailsOutput( $row ) {
384
+ $tmpl_row = '
385
  <tr>
386
  <td>%1$s</td>
387
  <td>%2$s</td>
388
  </tr>
389
  ';
390
+ $context = $row->context;
391
+ $output = '<table class="SimpleHistoryLogitem__keyValueTable">';
392
+
393
+ if ( isset( $context['schedule_name'] ) ) {
394
+ $output .= sprintf(
395
+ $tmpl_row,
396
+ _x( 'Name', 'PluginWPCrontrolLogger', 'simple-history' ),
397
+ esc_html( $context['schedule_name'] )
398
+ );
399
+ }
400
+
401
+ if ( isset( $context['schedule_interval'] ) ) {
402
+ $output .= sprintf(
403
+ $tmpl_row,
404
+ _x( 'Interval', 'PluginWPCrontrolLogger', 'simple-history' ),
405
+ esc_html( $context['schedule_interval'] )
406
+ );
407
+ }
408
+
409
+ if ( isset( $context['schedule_display'] ) ) {
410
+ $output .= sprintf(
411
+ $tmpl_row,
412
+ _x( 'Display Name', 'PluginWPCrontrolLogger', 'simple-history' ),
413
+ esc_html( $context['schedule_display'] )
414
+ );
415
+ }
416
+
417
+ $output .= '</table>';
418
+
419
+ return $output;
420
+ }
421
  }
loggers/Plugin_ACF.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') || die();
4
 
5
  /**
6
  * Logger for the Advanced Custom Fields (ACF) plugin
@@ -9,1074 +9,1072 @@ defined('ABSPATH') || die();
9
  * @package SimpleHistory
10
  * @since 2.21
11
  */
12
- if (! class_exists('Plugin_ACF')) {
13
-
14
- /**
15
- * Class for ACF logging.
16
- */
17
- class Plugin_ACF extends SimpleLogger
18
- {
19
-
20
- /**
21
- * The slug for this logger.
22
- *
23
- * @var string $slug
24
- */
25
- public $slug = __CLASS__;
26
-
27
- /**
28
- * Will contain field groups and fields, before and after post save.
29
- *
30
- * @var string $oldAndNewFieldGroupsAndFields
31
- */
32
- private $oldAndNewFieldGroupsAndFields = array(
33
- 'fieldGroup' => array(
34
- 'old' => null,
35
- 'new' => null,
36
- ),
37
- 'modifiedFields' => array(
38
- 'old' => null,
39
- 'new' => null,
40
- ),
41
- 'addedFields' => array(),
42
- 'deletedFields' => array(),
43
- );
44
-
45
- /**
46
- * Will contain the post data before save, i.e. the previous version of the post.
47
- *
48
- * @var string $oldPostData
49
- */
50
- private $oldPostData = array();
51
-
52
- /**
53
- * Get info for this logger.
54
- *
55
- * @return array Array with info about the logger.
56
- */
57
- public function getInfo()
58
- {
59
- $arr_info = array(
60
- 'name' => 'Plugin ACF',
61
- 'description' => _x('Logs ACF stuff', 'Logger: Plugin ACF', 'simple-history'),
62
- 'name_via' => _x('Using plugin ACF', 'Logger: Plugin ACF', 'simple-history'),
63
- 'capability' => 'manage_options',
64
- );
65
-
66
- return $arr_info;
67
- }
68
-
69
- private function isACFInstalled()
70
- {
71
- return defined('ACF') && ACF;
72
- }
73
-
74
- /**
75
- * Method called when logger is loaded.
76
- */
77
- public function loaded()
78
- {
79
-
80
- // Bail if no ACF found.
81
- if (! $this->isACFInstalled()) {
82
- return;
83
- }
84
-
85
- // Remove ACF Fields from the post types that postlogger logs.
86
- add_filter('simple_history/post_logger/skip_posttypes', array( $this, 'remove_acf_from_postlogger' ));
87
-
88
- // Get prev version of acf field group.
89
- // This is called before transition_post_status.
90
- add_filter('wp_insert_post_data', array( $this, 'on_wp_insert_post_data' ), 10, 2);
91
-
92
- // Store old and new field data when a post is saved.
93
- add_action('transition_post_status', array( $this, 'on_transition_post_status' ), 5, 3);
94
-
95
- // Append ACF data to post context
96
- add_filter('simple_history/post_logger/post_updated/context', array( $this, 'on_post_updated_context' ), 10, 2);
97
-
98
- // Add ACF diff data to activity feed detailed output.
99
- add_filter('simple_history/post_logger/post_updated/diff_table_output', array( $this, 'on_diff_table_output_field_group' ), 10, 2);
100
-
101
- // Store prev ACF field values before new values are added.
102
- // Called from filter admin_action_editpost that is fired at top of admin.php
103
- add_action('admin_action_editpost', array( $this, 'on_admin_action_editpost' ));
104
-
105
- // Fired when ACF saves a post. Adds ACF context to logged row.
106
- add_filter('acf/save_post', array( $this, 'on_acf_save_post' ), 50);
107
-
108
- // Fired after a log row is inserted. Add filter so field group save is is not logged again.
109
- add_action('simple_history/log/inserted', array( $this, 'on_log_inserted' ), 10, 3);
110
- }
111
-
112
- /**
113
- * Fired after a log row is inserted.
114
- */
115
- public function on_log_inserted($context, $data_parent_row, $simple_history_instance)
116
- {
117
- $message_key = ! empty($context['_message_key']) ? $context['_message_key'] : false;
118
- $logger = ! empty($data_parent_row['logger']) ? $data_parent_row['logger'] : false;
119
- $post_id = ! empty($context['post_id']) ? $context['post_id'] : false;
120
- $post_type = ! empty($context['post_type']) ? $context['post_type'] : false;
121
-
122
- // Bail if not all required vars are set.
123
- if (! $message_key || ! $logger || ! $post_id || ! $post_type) {
124
- return;
125
- }
126
-
127
- // Only act when logger was SimplePostLogger.
128
- if ($logger !== 'SimplePostLogger') {
129
- return;
130
- }
131
-
132
- // Only act when the saved type was a ACF Field Group.
133
- if ($post_type !== 'acf-field-group') {
134
- return;
135
- }
136
-
137
- // Ok, a row was inserted using the log function on SimplePostLogger,
138
- // now ACF will call save_post again and trigger
139
- // another log of the same row. To prevent this we
140
- // now add a filter to prevent the next log.
141
- add_filter('simple_history/post_logger/post_updated/ok_to_log', array( $this, 'prevent_second_acf_field_group_post_save_log' ), 10, 4);
142
- }
143
-
144
- /**
145
- * Fired from SimpleLogger action 'simple_history/post_logger/post_updated/ok_to_log' and added only after
146
- * a row already has been logged.
147
- *
148
- * This function checks if post type logged by SimplePostLogger is a ACF Field Group, and if it is
149
- * then don't log that log. This way we prevent the post logger from logging the field group changes twice.
150
- */
151
- public function prevent_second_acf_field_group_post_save_log($ok_to_log, $new_status, $old_status, $post)
152
- {
153
- if (isset($post->post_type) && $post->post_type === 'acf-field-group') {
154
- $ok_to_log = false;
155
- }
156
-
157
- return $ok_to_log;
158
- }
159
-
160
- /**
161
- * Append info about changes in ACF fields input,
162
- * i.e. store all info we later use to show changes that a user has done.
163
- *
164
- * Called when ACF saves a post.
165
- *
166
- * @param mixed int $post_id ID of post that is being saved. string "option" or "options" when saving an options page.
167
- */
168
- public function on_acf_save_post($post_id)
169
- {
170
- if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
171
- return;
172
- }
173
-
174
- // Only act when $post_id is numeric, can be "options" too when
175
- // ACF saves an options page.
176
- if (! is_numeric($post_id)) {
177
- return;
178
- }
179
-
180
- // Don't act on post revision.
181
- if (wp_is_post_revision($post_id)) {
182
- return;
183
- }
184
-
185
- /*
186
- Meta values look like
187
- [product_images_0_image] => 625
188
- [_product_images_0_image] => field_59a091044812e
189
- [product_images_0_image_caption] => Image row yes
190
- [_product_images_0_image_caption] => field_59a0910f4812f
191
- [product_images_0_image_related_0_related_name] => Related one
192
- [_product_images_0_image_related_0_related_name] => field_59aaedd43ae11
193
- [product_images_0_image_related_0_related_item_post] =>
194
- [_product_images_0_image_related_0_related_item_post] => field_59aaede43ae12
195
- [product_images_0_image_related_1_related_name] => Another related
196
- [_product_images_0_image_related_1_related_name] => field_59aaedd43ae11
197
- [product_images_0_image_related_1_related_item_post] =>
198
- [_product_images_0_image_related_1_related_item_post] => field_59aaede43ae12
199
- [product_images_0_image_related] => 2
200
- [_product_images_0_image_related] => field_59aaedbc3ae10
201
- [product_images_1_image] => 574
202
- */
203
- $prev_post_meta = isset($this->oldPostData['prev_post_meta']) ? $this->oldPostData['prev_post_meta'] : array();
204
-
205
- $new_post_meta = get_post_custom($post_id);
206
- array_walk($new_post_meta, function(&$value, $key){
207
- $value = reset($value);
208
- });
209
-
210
- // New and old post meta can contain different amount of keys,
211
- // join them so we have the name of all post meta thaf have been added, removed, or modified.
212
- $new_and_old_post_meta = array_merge($prev_post_meta, $new_post_meta);
213
- ksort($new_and_old_post_meta, SORT_REGULAR);
214
-
215
- // array1 - The array to compare from
216
- // array2 - An array to compare against
217
- // Returns an array containing all the values from array1 that are not present in any of the other arrays.
218
- // Keep only ACF fields in prev and new post meta.
219
- $prev_post_meta = $this->keep_only_acf_stuff_in_array($prev_post_meta, $new_and_old_post_meta);
220
- $new_post_meta = $this->keep_only_acf_stuff_in_array($new_post_meta, $new_and_old_post_meta);
221
- $new_and_old_post_meta_acf_fields = array_merge($prev_post_meta, $new_post_meta);
222
-
223
- // Map field name with fieldkey so we can get field objects when needed.
224
- // Final array have values like:
225
- // [product_images_0_image] => field_59a091044812e
226
- // [product_images_0_image_caption] => field_59a0910f4812f
227
- // [product_images_0_image_related_0_related_name] => field_59aaedd43ae11.
228
- $fieldnames_to_field_keys = array();
229
- foreach ($new_and_old_post_meta_acf_fields as $meta_key => $meta_value) {
230
- // $key is like [product_images_0_image_related_1_related_name].
231
- // Get ACF fieldkey for that value. Will be in $new_and_old_post_meta
232
- // as the same as key but with underscore first
233
- $meta_key_to_look_for = "_{$meta_key}";
234
- if (isset($new_and_old_post_meta[ $meta_key_to_look_for ])) {
235
- $fieldnames_to_field_keys[ $meta_key ] = $new_and_old_post_meta[ $meta_key_to_look_for ];
236
- }
237
- }
238
-
239
- // Compare old with new = get only changed, not added, deleted are here.
240
- $post_meta_diff1 = array_diff_assoc($prev_post_meta, $new_post_meta);
241
-
242
- // Compare new with old = get an diff with added and changed stuff.
243
- $post_meta_diff2 = array_diff_assoc($new_post_meta, $prev_post_meta);
244
-
245
- // Compare keys, gets added fields.
246
- $post_meta_added_fields = array_diff(array_keys($post_meta_diff2), array_keys($post_meta_diff1));
247
- $post_meta_added_fields = array_values($post_meta_added_fields);
248
-
249
- // Keys that exist in diff1 but not in diff2 = deleted.
250
- $post_meta_removed_fields = array_diff_assoc(array_keys($post_meta_diff1), array_keys($post_meta_diff2));
251
- $post_meta_removed_fields = array_values($post_meta_removed_fields);
252
-
253
- $post_meta_changed_fields = array_keys($post_meta_diff1);
254
-
255
- /*
256
- * value is changed: added to both diff and diff2
257
- * value is added, like in repeater: added to diff2 (not to diff)
258
- * $diff3: contains only added things.
259
- * Compare old and new values
260
- * Loop all keys in $new_and_old_post_meta
261
- * But act only on those whose keys begins with "_" and where the value begins with "field_" and ends with alphanum.
262
- */
263
-
264
- /*
265
- * We have the diff, now add it to the context
266
- * This is called after Simple History already has added its row
267
- * So... we must add to the context late somehow
268
- * Get the latest inserted row from the SimplePostLogger, check if that postID is
269
- * same as the
270
- */
271
- $post_logger = $this->simpleHistory->getInstantiatedLoggerBySlug('SimplePostLogger');
272
-
273
- // Save ACF diff if detected post here is same as the last one used in Postlogger.
274
- if (isset($post_logger->lastInsertContext['post_id']) && $post_id === $post_logger->lastInsertContext['post_id']) {
275
- $last_insert_id = $post_logger->lastInsertID;
276
-
277
- // Append new info to the context of history item with id $post_logger->lastInsertID.
278
- $acf_context = array();
279
- $acf_context = $this->add_acf_context($acf_context, 'added', $post_meta_added_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys);
280
- $acf_context = $this->add_acf_context($acf_context, 'changed', $post_meta_changed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys);
281
- $acf_context = $this->add_acf_context($acf_context, 'removed', $post_meta_removed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys);
282
-
283
- $post_logger->append_context($last_insert_id, $acf_context);
284
-
285
- // Prev and new post meta for testing.
286
- /*
287
- $post_logger->append_context(
288
- $last_insert_id,
289
- array(
290
- 'prev_post_meta' => $prev_post_meta,
291
- )
292
- );
293
- $post_logger->append_context(
294
- $last_insert_id,
295
- array(
296
- 'new_post_meta' => $new_post_meta,
297
- )
298
- );
299
- */
300
- } // End if().
301
- }
302
-
303
- /**
304
- * Add ACF context for added, removed, or changed fields.
305
- *
306
- * @param array $context Context.
307
- * @param string $modify_type Type. added | removed | changed.
308
- * @param array $relevant_acf_fields Fields.
309
- * @param array $prev_post_meta Prev meta.
310
- * @param array $new_post_meta New meta.
311
- * @param array $fieldnames_to_field_keys Fieldnames to field keys mapping.
312
- * @return array Modified context.
313
- */
314
- public function add_acf_context($context = array(), $modify_type = '', $relevant_acf_fields = array(), $prev_post_meta = [], $new_post_meta = [], $fieldnames_to_field_keys = [])
315
- {
316
-
317
- if (! is_array($context) || empty($modify_type) || empty($relevant_acf_fields)) {
318
- return $context;
319
- }
320
-
321
- $loopnum = 0;
322
- foreach ($relevant_acf_fields as $field_slug) {
323
- /*
324
- Store just the names to begin with
325
- acf_field_added_0 = url.
326
- acf_field_added_1 = first_name.
327
-
328
- If field slug contains a number, like in "product_images_2_image"
329
- that probably means that that field is a repeater with name "product_images"
330
- with a sub field called "image" and that the image is the 2:nd among it's selected sub fields.
331
-
332
- Example of how fields can look:
333
- acf_field_added_0 product_images_2_image
334
- acf_field_added_1 product_images_2_image_caption
335
- acf_field_added_2 product_images_2_image_related
336
- acf_field_changed_0 my_field_in_acf
337
- acf_field_changed_1 product_images
338
- acf_field_changed_2 price
339
- acf_field_changed_3 description
340
- */
341
- $context_key = "acf_field_{$modify_type}_{$loopnum}";
342
- $context[ "{$context_key}/slug" ] = $field_slug;
343
-
344
- /*
345
- * Try to get som extra info, like display name and type for this field.
346
- * For a nice context in the feed we want: parent field group name and type?
347
- */
348
- if (isset($fieldnames_to_field_keys[ $field_slug ])) {
349
- $field_key = $fieldnames_to_field_keys[ $field_slug ];
350
- $context[ "{$context_key}/key" ] = $field_key;
351
-
352
- // Interesting stuff in field object:
353
- // - Label = the human readable name of the field
354
- // - Type = the type of the field
355
- // - Parent = id of parent field post id.
356
- $field_object = get_field_object($field_key);
357
-
358
- if (is_array($field_object)) {
359
- $context[ "{$context_key}/label" ] = $field_object['label'];
360
- if (! empty($field_object['type'])) {
361
- $context[ "{$context_key}/type" ] = $field_object['type'];
362
- }
363
-
364
- // If no parent just continue to next field.
365
- if (empty($field_object['parent'])) {
366
- continue;
367
- }
368
-
369
- // We have at least one parent, get them all, including the field group
370
- // $context[ "{$context_key}/field_parent_object" ] = $parent_field;
371
- $field_parents = array();
372
- $field_field_group = null;
373
-
374
- // Begin with the direct parent.
375
- $parent_field = $field_object;
376
-
377
- while (! empty($parent_field['parent'])) {
378
- $parentFieldParent = $parent_field['parent'];
379
-
380
- // acf-field | acf-field-group.
381
- $parent_field_post_type = get_post_type($parentFieldParent);
382
-
383
- if (false === $parent_field_post_type) {
384
- break;
385
- }
386
-
387
- if ('acf-field' === $parent_field_post_type) {
388
- // Field is when field is for example a sub field of a repeater.
389
- if (function_exists('acf_get_field')) {
390
- // Since ACF 5.7.10 the acf_get_field() function is available.
391
- $parent_field = acf_get_field($parentFieldParent);
392
- } elseif (function_exists('_acf_get_field_by_id')) {
393
- // ACF function _acf_get_field_by_id() is available before ACF 5.7.10.
394
- $parent_field = _acf_get_field_by_id($parentFieldParent);
395
- }
396
- } elseif ('acf-field-group' === $parent_field_post_type) {
397
- $parent_field = acf_get_field_group($parentFieldParent);
398
- } else {
399
- // Unknown post type.
400
- break;
401
- }
402
-
403
- if (false === $parent_field) {
404
- break;
405
- }
406
-
407
- if ('acf-field' === $parent_field_post_type) {
408
- $field_parents[] = $parent_field;
409
- } elseif ('acf-field-group' === $parent_field_post_type) {
410
- $field_field_group = $parent_field;
411
- } // End if().
412
- } // End while().
413
-
414
- $field_parents = array_reverse($field_parents);
415
-
416
- // Array with info about each parent.
417
- $arr_field_path = array();
418
-
419
- if (! empty($field_field_group['title'])) {
420
- $arr_field_path[] = array(
421
- 'name' => $field_field_group['title'],
422
- 'type' => 'field_group',
423
- );
424
- }
425
-
426
- foreach ($field_parents as $one_field_parent) {
427
- $arr_field_path[] = array(
428
- 'name' => $one_field_parent['label'],
429
- 'type' => 'field',
430
- 'field_type' => $one_field_parent['type'],
431
- );
432
- }
433
-
434
- if (! empty($arr_field_path)) {
435
- $path_loop_num = 0;
436
- foreach ($arr_field_path as $one_field_path) {
437
- $context[ "{$context_key}/path_{$path_loop_num}/name" ] = $one_field_path['name'];
438
- $context[ "{$context_key}/path_{$path_loop_num}/type" ] = $one_field_path['type'];
439
- if (! empty($one_field_path['field_type'])) {
440
- $context[ "{$context_key}/path_{$path_loop_num}/field_type" ] = $one_field_path['field_type'];
441
- }
442
- $path_loop_num++;
443
- }
444
- }
445
-
446
- // Add value of fields if they are not part of
447
- // repeatable or flexible fields or similar.
448
- // error_log( "Final parents" . print_r( $field_parents, 1 ) );
449
- // error_log( "Final field group" . print_r( $field_field_group['title'], 1 ) );
450
- // error_log( "context" . print_r( $context, 1 ) );
451
- } // End if().
452
- } // End if().
453
-
454
- $loopnum++;
455
- } // End foreach().
456
-
457
- // error_log( "---------------------------" );
458
- // error_log( "field_path_string: $field_path_string");
459
- // error_log( "context" . print_r( $context, 1 ) );
460
- return $context;
461
- }
462
-
463
- /**
464
- * Clean array and keep only ACF related things.
465
- *
466
- * Remove
467
- * - underscore fields
468
- * - fields with value field_*
469
- *
470
- * Keep
471
- * - vals that are acf
472
- *
473
- * @param array $arr Array.
474
- * @param array $all_fields Array fields.
475
- */
476
- public function keep_only_acf_stuff_in_array($arr, $all_fields)
477
- {
478
- $new_arr = array();
479
-
480
- foreach ($arr as $key => $val) {
481
- // Don't keep keys that begin with underscore "_".
482
- if (strpos($key, '_') === 0) {
483
- continue;
484
- }
485
-
486
- // Don't keep keys that begin with "field_".
487
- if (strpos($val, 'field_') === 0) {
488
- continue;
489
- }
490
-
491
- // Don't keep fields that does not have a corresponding _field value.
492
- // Each key has both the name, for example 'color' and another
493
- // key called '_color'. We check that the underscore version exists
494
- // and contains 'field_'. After this check only ACF fields should exist
495
- // in the array..
496
- if (! isset($all_fields[ "_{$key}" ])) {
497
- continue;
498
- }
499
-
500
- if (strpos($all_fields[ "_{$key}" ], 'field_') !== 0) {
501
- continue;
502
- }
503
-
504
- $new_arr[ $key ] = $val;
505
- }
506
-
507
- return $new_arr;
508
- }
509
-
510
- /**
511
- * Store prev post meta when post is saved.
512
- * Stores data in $this->oldPostData.
513
- */
514
- public function on_admin_action_editpost()
515
- {
516
- $post_ID = isset($_POST['post_ID']) ? (int) $_POST['post_ID'] : 0;
517
-
518
- if (! $post_ID) {
519
- return;
520
- }
521
-
522
- $prev_post = get_post($post_ID);
523
-
524
- if (is_wp_error($prev_post)) {
525
- return;
526
- }
527
-
528
- $post_meta = get_post_custom($post_ID);
529
-
530
- // Meta is array of arrays, get first value of each array value.
531
- array_walk($post_meta, function(&$value, $key){
532
- $value = reset($value);
533
- });
534
-
535
- $this->oldPostData['prev_post_meta'] = $post_meta;
536
- }
537
-
538
- /**
539
- * Called from PostLogger and its diff table output using filter 'simple_history/post_logger/post_updated/diff_table_output'.
540
- * Diff table is generated only for post type 'acf-field-group'.
541
- *
542
- * @param string $diff_table_output
543
- * @param array $context
544
- * @return string
545
- */
546
- public function on_diff_table_output_field_group($diff_table_output, $context)
547
- {
548
- $post_type = ! empty($context['post_type']) ? $context['post_type'] : false;
549
-
550
- // Bail if not ACF Field Group.
551
- if ($post_type !== 'acf-field-group') {
552
- return $diff_table_output;
553
- }
554
-
555
- // Field group fields to check for and output if found
556
- $arrKeys = array(
557
- 'instruction_placement' => array(
558
- 'name' => _x('Instruction placement', 'Logger: Plugin ACF', 'simple-history'),
559
- ),
560
- 'label_placement' => array(
561
- 'name' => _x('Label placement', 'Logger: Plugin ACF', 'simple-history'),
562
- ),
563
- 'description' => array(
564
- 'name' => _x('Description', 'Logger: Plugin ACF', 'simple-history'),
565
- ),
566
- 'menu_order' => array(
567
- 'name' => _x('Menu order', 'Logger: Plugin ACF', 'simple-history'),
568
- ),
569
- 'position' => array(
570
- 'name' => _x('Position', 'Logger: Plugin ACF', 'simple-history'),
571
- ),
572
- 'active' => array(
573
- 'name' => _x('Active', 'Logger: Plugin ACF', 'simple-history'),
574
- ),
575
- 'style' => array(
576
- 'name' => _x('Style', 'Logger: Plugin ACF', 'simple-history'),
577
- ),
578
- );
579
-
580
- foreach ($arrKeys as $acfKey => $acfVals) {
581
- if (isset($context[ "acf_new_$acfKey" ]) && isset($context[ "acf_prev_$acfKey" ])) {
582
- $diff_table_output .= sprintf(
583
- '<tr>
584
  <td>%1$s</td>
585
  <td>
586
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
587
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
588
  </td>
589
  </tr>',
590
- $acfVals['name'],
591
- esc_html($context[ "acf_new_$acfKey" ]),
592
- esc_html($context[ "acf_prev_$acfKey" ])
593
- );
594
- }
595
- }
596
-
597
- // If only acf_hide_on_screen_removed exists nothing is outputed.
598
- $acf_hide_on_screen_added = empty($context['acf_hide_on_screen_added']) ? null : $context['acf_hide_on_screen_added'];
599
- $acf_hide_on_screen_removed = empty($context['acf_hide_on_screen_removed']) ? null : $context['acf_hide_on_screen_removed'];
600
-
601
- if ($acf_hide_on_screen_added || $acf_hide_on_screen_removed) {
602
- $strCheckedHideOnScreen = '';
603
- $strUncheckedHideOnScreen = '';
604
-
605
- if ($acf_hide_on_screen_added) {
606
- $strCheckedHideOnScreen = sprintf(
607
- '%1$s %2$s',
608
- _x('Checked', 'Logger: Plugin ACF', 'simple-history'), // 1
609
- esc_html($acf_hide_on_screen_added) // 2
610
- );
611
- }
612
-
613
- if ($acf_hide_on_screen_removed) {
614
- $strUncheckedHideOnScreen = sprintf(
615
- '%1$s %2$s',
616
- _x('Unchecked', 'Logger: Plugin ACF', 'simple-history'), // 1
617
- esc_html($acf_hide_on_screen_removed) // 2
618
- );
619
- }
620
-
621
- $diff_table_output .= sprintf(
622
- '<tr>
623
  <td>%1$s</td>
624
  <td>
625
  %2$s
626
  %3$s
627
  </td>
628
  </tr>',
629
- _x('Hide on screen', 'Logger: Plugin ACF', 'simple-history'), // 1
630
- $strCheckedHideOnScreen, // 2
631
- $strUncheckedHideOnScreen // 3
632
- );
633
- }
634
-
635
- // Check for deleted fields.
636
- if (isset($context['acf_deleted_fields_0_key'])) {
637
- // 1 or more deleted fields exist in context.
638
- $loopnum = 0;
639
- $strDeletedFields = '';
640
-
641
- while (isset($context[ "acf_deleted_fields_{$loopnum}_key" ])) {
642
- $strDeletedFields .= sprintf(
643
- '%1$s (%3$s), ',
644
- esc_html($context[ "acf_deleted_fields_{$loopnum}_label" ]),
645
- esc_html($context[ "acf_deleted_fields_{$loopnum}_name" ]),
646
- esc_html($context[ "acf_deleted_fields_{$loopnum}_type" ])
647
- );
648
-
649
- $loopnum++;
650
- }
651
-
652
- $strDeletedFields = trim($strDeletedFields, ', ');
653
-
654
- $diff_table_output .= sprintf(
655
- '<tr>
656
  <td>%1$s</td>
657
  <td>%2$s</td>
658
  </tr>',
659
- _nx('Deleted field', 'Deleted fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
660
- $strDeletedFields
661
- );
662
- } // if deleted fields
663
-
664
- // Check for added fields
665
- if (isset($context['acf_added_fields_0_key'])) {
666
- // 1 or more deleted fields exist in context
667
- $loopnum = 0;
668
- $strAddedFields = '';
669
-
670
- while (isset($context[ "acf_added_fields_{$loopnum}_key" ])) {
671
- $strAddedFields .= sprintf(
672
- '%1$s (%3$s), ',
673
- esc_html($context[ "acf_added_fields_{$loopnum}_label" ]), // 1
674
- esc_html($context[ "acf_added_fields_{$loopnum}_name" ]), // 2
675
- esc_html($context[ "acf_added_fields_{$loopnum}_type" ]) // 3
676
- );
677
-
678
- $loopnum++;
679
- }
680
-
681
- $strAddedFields = trim($strAddedFields, ', ');
682
-
683
- $diff_table_output .= sprintf(
684
- '<tr>
685
  <td>%1$s</td>
686
  <td>%2$s</td>
687
  </tr>',
688
- _nx('Added field', 'Added fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
689
- $strAddedFields
690
- );
691
- } // if deleted fields
692
-
693
- // Check for modified fields
694
- if (isset($context['acf_modified_fields_0_ID_prev'])) {
695
- // 1 or more modifiedfields exist in context
696
- $loopnum = 0;
697
- $strModifiedFields = '';
698
- $arrAddedFieldsKeysToCheck = array(
699
- 'name' => array(
700
- 'name' => _x('Name: ', 'Logger: Plugin ACF', 'simple-history'),
701
- ),
702
- 'parent' => array(
703
- 'name' => _x('Parent: ', 'Logger: Plugin ACF', 'simple-history'),
704
- ),
705
- 'key' => array(
706
- 'name' => _x('Key: ', 'Logger: Plugin ACF', 'simple-history'),
707
- ),
708
- 'label' => array(
709
- 'name' => _x('Label: ', 'Logger: Plugin ACF', 'simple-history'),
710
- ),
711
- 'type' => array(
712
- 'name' => _x('Type: ', 'Logger: Plugin ACF', 'simple-history'),
713
- ),
714
- );
715
-
716
- while (isset($context[ "acf_modified_fields_{$loopnum}_name_prev" ])) {
717
- // One modified field, with one or more changed things
718
- $strOneModifiedField = '';
719
-
720
- // Add the field name manually, if it is not among the changed field,
721
- // or we don't know what field the other changed values belongs to.
722
- /*
723
- if (empty($context["acf_modified_fields_{$loopnum}_name_new"])) {
724
- $strOneModifiedField .= sprintf(
725
- _x('Name: %1$s', 'Logger: Plugin ACF', 'simple-history'), // 1
726
- esc_html($context["acf_modified_fields_{$loopnum}_name_prev"]) // 2
727
- );
728
- }
729
- */
730
-
731
- // Add the label name manually, if it is not among the changed field,
732
- // or we don't know what field the other changed values belongs to.
733
- if (empty($context[ "acf_modified_fields_{$loopnum}_label_new" ])) {
734
- $strOneModifiedField .= sprintf(
735
- _x('Label: %1$s', 'Logger: Plugin ACF', 'simple-history'), // 1
736
- esc_html($context[ "acf_modified_fields_{$loopnum}_label_prev" ]) // 2
737
- );
738
- }
739
-
740
- // Check for other keys changed for this field
741
- foreach ($arrAddedFieldsKeysToCheck as $oneAddedFieldKeyToCheck => $oneAddedFieldKeyToCheckVals) {
742
- $newAndOldValsExists = isset($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ]) && isset($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ]);
743
- if ($newAndOldValsExists) {
744
- $strOneModifiedField .= sprintf(
745
- '
746
  %4$s
747
  %3$s
748
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%1$s</ins>
749
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%2$s</del>
750
  ',
751
- esc_html($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ]), // 1
752
- esc_html($context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_prev" ]), // 2
753
- esc_html($oneAddedFieldKeyToCheckVals['name']), // 3
754
- empty($strOneModifiedField) ? '' : '<br>' // 4 new line
755
- );
756
- }
757
- }
758
-
759
- $strOneModifiedField = trim($strOneModifiedField, ", \n\r\t");
760
-
761
- if ($strOneModifiedField) {
762
- $strModifiedFields .= sprintf(
763
- '<tr>
764
  <td>%1$s</td>
765
  <td>%2$s</td>
766
  </tr>',
767
- _x('Modified field', 'Logger: Plugin ACF', 'simple-history'), // 1
768
- $strOneModifiedField
769
- );
770
- }
771
-
772
- $loopnum++;
773
- }
774
-
775
- /*
776
- if ($strModifiedFields) {
777
- $strModifiedFields = sprintf(
778
- '<tr>
779
- <td>%1$s</td>
780
- <td>%2$s</td>
781
- </tr>',
782
- _nx('Modified field', 'Modified fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
783
- $strModifiedFields
784
- ) . $strModifiedFields;
785
- }*/
786
-
787
- $diff_table_output .= $strModifiedFields;
788
- } // if deleted fields
789
-
790
- return $diff_table_output;
791
- }
792
-
793
- /**
794
- * Append ACF data to post context.
795
- *
796
- * Called via filter `simple_history/post_logger/post_updated/context`.
797
- *
798
- * @param array $context
799
- * @param WP_Post $post
800
- */
801
- public function on_post_updated_context($context, $post)
802
- {
803
-
804
- // Only act if this is a ACF field group that is saved
805
- if ($post->post_type !== 'acf-field-group') {
806
- return $context;
807
- }
808
-
809
- // Remove some keys that we don't want,
810
- // for example the content because that's just a json string
811
- // in acf-field-group posts.
812
- unset(
813
- $context['post_prev_post_content'],
814
- $context['post_new_post_content'],
815
- $context['post_prev_post_name'],
816
- $context['post_new_post_name'],
817
- $context['post_prev_post_date'],
818
- $context['post_new_post_date'],
819
- $context['post_prev_post_date_gmt'],
820
- $context['post_new_post_date_gmt']
821
- );
822
-
823
- $acf_data_diff = array();
824
-
825
- // 'fieldGroup' fields to check.
826
- $arr_field_group_keys_to_diff = array(
827
- 'menu_order',
828
- 'position',
829
- 'style',
830
- 'label_placement',
831
- 'instruction_placement',
832
- 'active',
833
- 'description',
834
- );
835
-
836
- $fieldGroup = $this->oldAndNewFieldGroupsAndFields['fieldGroup'];
837
-
838
- foreach ($arr_field_group_keys_to_diff as $key) {
839
- if (isset($fieldGroup['old'][ $key ]) && isset($fieldGroup['new'][ $key ])) {
840
- $acf_data_diff = $this->add_diff($acf_data_diff, $key, (string) $fieldGroup['old'][ $key ], (string) $fieldGroup['new'][ $key ]);
841
- }
842
- }
843
-
844
- foreach ($acf_data_diff as $diff_key => $diff_values) {
845
- $context[ "acf_prev_{$diff_key}" ] = $diff_values['old'];
846
- $context[ "acf_new_{$diff_key}" ] = $diff_values['new'];
847
- }
848
-
849
- // Add checked or uncheckd hide on screen-items to context
850
- $arrhHideOnScreenAdded = array();
851
- $arrHideOnScreenRemoved = array();
852
-
853
- $fieldGroup['new']['hide_on_screen'] = isset($fieldGroup['new']['hide_on_screen']) && is_array($fieldGroup['new']['hide_on_screen']) ? $fieldGroup['new']['hide_on_screen'] : array();
854
- $fieldGroup['old']['hide_on_screen'] = isset($fieldGroup['old']['hide_on_screen']) && is_array($fieldGroup['old']['hide_on_screen']) ? $fieldGroup['old']['hide_on_screen'] : array();
855
-
856
- // dd($fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen']);
857
- // Act when new or old hide_on_screen is set
858
- if (! empty($fieldGroup['new']['hide_on_screen']) || ! empty($fieldGroup['old']['hide_on_screen'])) {
859
- $arrhHideOnScreenAdded = array_diff($fieldGroup['new']['hide_on_screen'], $fieldGroup['old']['hide_on_screen']);
860
- $arrHideOnScreenRemoved = array_diff($fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen']);
861
-
862
- // ddd($arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
863
- if ($arrhHideOnScreenAdded) {
864
- $context['acf_hide_on_screen_added'] = implode(',', $arrhHideOnScreenAdded);
865
- }
866
-
867
- if ($arrHideOnScreenRemoved) {
868
- $context['acf_hide_on_screen_removed'] = implode(',', $arrHideOnScreenRemoved);
869
- }
870
- }
871
-
872
- // ddd($context, $arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
873
- // Add removed fields to context
874
- if (! empty($this->oldAndNewFieldGroupsAndFields['deletedFields']) && is_array($this->oldAndNewFieldGroupsAndFields['deletedFields'])) {
875
- $loopnum = 0;
876
- foreach ($this->oldAndNewFieldGroupsAndFields['deletedFields'] as $oneDeletedField) {
877
- $context[ "acf_deleted_fields_{$loopnum}_key" ] = $oneDeletedField['key'];
878
- $context[ "acf_deleted_fields_{$loopnum}_name" ] = $oneDeletedField['name'];
879
- $context[ "acf_deleted_fields_{$loopnum}_label" ] = $oneDeletedField['label'];
880
- $context[ "acf_deleted_fields_{$loopnum}_type" ] = $oneDeletedField['type'];
881
- $loopnum++;
882
- }
883
- }
884
-
885
- // Add added fields to context
886
- if (! empty($this->oldAndNewFieldGroupsAndFields['addedFields']) && is_array($this->oldAndNewFieldGroupsAndFields['addedFields'])) {
887
- $loopnum = 0;
888
-
889
- foreach ($this->oldAndNewFieldGroupsAndFields['addedFields'] as $oneAddedField) {
890
- // Id not available here, wold be nice to have
891
- // $context["acf_added_fields_{$loopnum}_ID"] = $oneAddedField['ID'];
892
- $context[ "acf_added_fields_{$loopnum}_key" ] = $oneAddedField['key'];
893
- $context[ "acf_added_fields_{$loopnum}_name" ] = $oneAddedField['name'];
894
- $context[ "acf_added_fields_{$loopnum}_label" ] = $oneAddedField['label'];
895
- $context[ "acf_added_fields_{$loopnum}_type" ] = $oneAddedField['type'];
896
- $loopnum++;
897
- }
898
- }
899
-
900
- // Add modified fields to context
901
- // dd('on_post_updated_context', $context, $this->oldAndNewFieldGroupsAndFields);
902
- if (! empty($this->oldAndNewFieldGroupsAndFields['modifiedFields']['old']) && ! empty($this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'])) {
903
- $modifiedFields = $this->oldAndNewFieldGroupsAndFields['modifiedFields'];
904
-
905
- $arr_added_fields_keys_to_add = array(
906
- 'parent',
907
- 'key',
908
- 'label',
909
- 'name',
910
- 'type',
911
- );
912
-
913
- $loopnum = 0;
914
-
915
- foreach ($modifiedFields['old'] as $modifiedFieldId => $modifiedFieldValues) {
916
- // Both old and new values mest exist
917
- if (empty($modifiedFields['new'][ $modifiedFieldId ])) {
918
- continue;
919
- }
920
-
921
- // Always add ID, name, and lavel
922
- $context[ "acf_modified_fields_{$loopnum}_ID_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['ID'];
923
- $context[ "acf_modified_fields_{$loopnum}_name_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['name'];
924
- $context[ "acf_modified_fields_{$loopnum}_label_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['label'];
925
-
926
- foreach ($arr_added_fields_keys_to_add as $one_key_to_add) {
927
- // Check that new and old exist.
928
- $new_exists = isset($modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ]);
929
- $old_exists = isset($modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ]);
930
-
931
- if (! $new_exists || ! $old_exists) {
932
- continue;
933
- }
934
-
935
- // Only add to context if modified.
936
- if ($modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] != $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ]) {
937
- $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_prev" ] = $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ];
938
- $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_new" ] = $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ];
939
- }
940
- }
941
-
942
- $loopnum++;
943
- }
944
- }
945
-
946
- return $context;
947
- }
948
-
949
- public function add_diff($post_data_diff, $key, $old_value, $new_value)
950
- {
951
- if ($old_value != $new_value) {
952
- $post_data_diff[ $key ] = array(
953
- 'old' => $old_value,
954
- 'new' => $new_value,
955
- );
956
- }
957
-
958
- return $post_data_diff;
959
- }
960
-
961
- /**
962
- * Store a version of the field group as it was before the save
963
- * Called before field group post/values is added to db
964
- *
965
- * @param array $data Post data.
966
- * @param array $postarr Post data.
967
- */
968
- public function on_wp_insert_post_data($data, $postarr)
969
- {
970
-
971
- // Only do this if ACF field group is being saved.
972
- if ($postarr['post_type'] !== 'acf-field-group') {
973
- return $data;
974
- }
975
-
976
- if (empty($postarr['ID'])) {
977
- return $data;
978
- }
979
-
980
- if (empty($_POST['acf_field_group'])) {
981
- return $data;
982
- }
983
-
984
- $this->oldAndNewFieldGroupsAndFields['fieldGroup']['old'] = acf_get_field_group($postarr['ID']);
985
-
986
- $this->oldAndNewFieldGroupsAndFields['fieldGroup']['new'] = acf_get_valid_field_group($_POST['acf_field_group']);
987
-
988
- return $data;
989
- }
990
-
991
- /**
992
- * ACF field group is saved
993
- * Called before ACF calls its save_post filter
994
- * Here we save the new fields values and also get the old values so we can compare
995
- */
996
- public function on_transition_post_status($new_status, $old_status, $post)
997
- {
998
- static $isCalled = false;
999
-
1000
- if ($isCalled) {
1001
- return;
1002
- }
1003
-
1004
- $isCalled = true;
1005
-
1006
- $post_id = $post->ID;
1007
-
1008
- // do not act if this is an auto save routine
1009
- if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
1010
- return;
1011
- }
1012
-
1013
- // bail early if not acf-field-group
1014
- if ($post->post_type !== 'acf-field-group') {
1015
- return;
1016
- }
1017
-
1018
- // only save once! WordPress save's a revision as well.
1019
- if (wp_is_post_revision($post_id)) {
1020
- return;
1021
- }
1022
-
1023
- // Store info about fields that are going to be deleted
1024
- if (! empty($_POST['_acf_delete_fields'])) {
1025
- $deletedFieldsIDs = explode('|', $_POST['_acf_delete_fields']);
1026
- $deletedFieldsIDs = array_map('intval', $deletedFieldsIDs);
1027
-
1028
- foreach ($deletedFieldsIDs as $id) {
1029
- if (! $id) {
1030
- continue;
1031
- }
1032
-
1033
- $field_info = acf_get_field($id);
1034
-
1035
- if (! $field_info) {
1036
- continue;
1037
- }
1038
-
1039
- $this->oldAndNewFieldGroupsAndFields['deletedFields'][ $id ] = $field_info;
1040
- }
1041
- }
1042
-
1043
- // Store info about added or modified fields
1044
- if (! empty($_POST['acf_fields']) && is_array($_POST['acf_fields'])) {
1045
- foreach ($_POST['acf_fields'] as $oneFieldAddedOrUpdated) {
1046
- if (empty($oneFieldAddedOrUpdated['ID'])) {
1047
- // New fields have no id
1048
- // 'ID' => string(0) ""
1049
- $this->oldAndNewFieldGroupsAndFields['addedFields'][] = $oneFieldAddedOrUpdated;
1050
- } else {
1051
- // Existing fields have an id
1052
- // 'ID' => string(3) "383"
1053
- $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'][ $oneFieldAddedOrUpdated['ID'] ] = acf_get_field($oneFieldAddedOrUpdated['ID']);
1054
-
1055
- $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'][ $oneFieldAddedOrUpdated['ID'] ] = $oneFieldAddedOrUpdated;
1056
- }
1057
- }
1058
- }
1059
-
1060
- // We don't do anything else here, but we make the actual logging
1061
- // in filter 'acf/update_field_group' beacuse it's safer because
1062
- // ACF has done it's validation and it's after ACF has saved the fields,
1063
- // so less likely that we make some critical error
1064
- }
1065
-
1066
-
1067
- /**
1068
- * Add the post types that ACF uses for fields to the array of post types
1069
- * that the default post logger should not log. If not each field will cause one
1070
- * post update log message.
1071
- */
1072
- public function remove_acf_from_postlogger($skip_posttypes)
1073
- {
1074
- array_push(
1075
- $skip_posttypes,
1076
- 'acf-field'
1077
- );
1078
-
1079
- return $skip_posttypes;
1080
- }
1081
- }
 
1082
  } // End if().
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for the Advanced Custom Fields (ACF) plugin
9
  * @package SimpleHistory
10
  * @since 2.21
11
  */
12
+ if ( ! class_exists( 'Plugin_ACF' ) ) {
13
+
14
+ /**
15
+ * Class for ACF logging.
16
+ */
17
+ class Plugin_ACF extends SimpleLogger {
18
+
19
+
20
+ /**
21
+ * The slug for this logger.
22
+ *
23
+ * @var string $slug
24
+ */
25
+ public $slug = __CLASS__;
26
+
27
+ /**
28
+ * Will contain field groups and fields, before and after post save.
29
+ *
30
+ * @var string $oldAndNewFieldGroupsAndFields
31
+ */
32
+ private $oldAndNewFieldGroupsAndFields = array(
33
+ 'fieldGroup' => array(
34
+ 'old' => null,
35
+ 'new' => null,
36
+ ),
37
+ 'modifiedFields' => array(
38
+ 'old' => null,
39
+ 'new' => null,
40
+ ),
41
+ 'addedFields' => array(),
42
+ 'deletedFields' => array(),
43
+ );
44
+
45
+ /**
46
+ * Will contain the post data before save, i.e. the previous version of the post.
47
+ *
48
+ * @var string $oldPostData
49
+ */
50
+ private $oldPostData = array();
51
+
52
+ /**
53
+ * Get info for this logger.
54
+ *
55
+ * @return array Array with info about the logger.
56
+ */
57
+ public function getInfo() {
58
+ $arr_info = array(
59
+ 'name' => 'Plugin ACF',
60
+ 'description' => _x( 'Logs ACF stuff', 'Logger: Plugin ACF', 'simple-history' ),
61
+ 'name_via' => _x( 'Using plugin ACF', 'Logger: Plugin ACF', 'simple-history' ),
62
+ 'capability' => 'manage_options',
63
+ );
64
+
65
+ return $arr_info;
66
+ }
67
+
68
+ private function isACFInstalled() {
69
+ return defined( 'ACF' ) && ACF;
70
+ }
71
+
72
+ /**
73
+ * Method called when logger is loaded.
74
+ */
75
+ public function loaded() {
76
+
77
+ // Bail if no ACF found.
78
+ if ( ! $this->isACFInstalled() ) {
79
+ return;
80
+ }
81
+
82
+ // Remove ACF Fields from the post types that postlogger logs.
83
+ add_filter( 'simple_history/post_logger/skip_posttypes', array( $this, 'remove_acf_from_postlogger' ) );
84
+
85
+ // Get prev version of acf field group.
86
+ // This is called before transition_post_status.
87
+ add_filter( 'wp_insert_post_data', array( $this, 'on_wp_insert_post_data' ), 10, 2 );
88
+
89
+ // Store old and new field data when a post is saved.
90
+ add_action( 'transition_post_status', array( $this, 'on_transition_post_status' ), 5, 3 );
91
+
92
+ // Append ACF data to post context
93
+ add_filter( 'simple_history/post_logger/post_updated/context', array( $this, 'on_post_updated_context' ), 10, 2 );
94
+
95
+ // Add ACF diff data to activity feed detailed output.
96
+ add_filter( 'simple_history/post_logger/post_updated/diff_table_output', array( $this, 'on_diff_table_output_field_group' ), 10, 2 );
97
+
98
+ // Store prev ACF field values before new values are added.
99
+ // Called from filter admin_action_editpost that is fired at top of admin.php
100
+ add_action( 'admin_action_editpost', array( $this, 'on_admin_action_editpost' ) );
101
+
102
+ // Fired when ACF saves a post. Adds ACF context to logged row.
103
+ add_filter( 'acf/save_post', array( $this, 'on_acf_save_post' ), 50 );
104
+
105
+ // Fired after a log row is inserted. Add filter so field group save is is not logged again.
106
+ add_action( 'simple_history/log/inserted', array( $this, 'on_log_inserted' ), 10, 3 );
107
+ }
108
+
109
+ /**
110
+ * Fired after a log row is inserted.
111
+ */
112
+ public function on_log_inserted( $context, $data_parent_row, $simple_history_instance ) {
113
+ $message_key = ! empty( $context['_message_key'] ) ? $context['_message_key'] : false;
114
+ $logger = ! empty( $data_parent_row['logger'] ) ? $data_parent_row['logger'] : false;
115
+ $post_id = ! empty( $context['post_id'] ) ? $context['post_id'] : false;
116
+ $post_type = ! empty( $context['post_type'] ) ? $context['post_type'] : false;
117
+
118
+ // Bail if not all required vars are set.
119
+ if ( ! $message_key || ! $logger || ! $post_id || ! $post_type ) {
120
+ return;
121
+ }
122
+
123
+ // Only act when logger was SimplePostLogger.
124
+ if ( $logger !== 'SimplePostLogger' ) {
125
+ return;
126
+ }
127
+
128
+ // Only act when the saved type was a ACF Field Group.
129
+ if ( $post_type !== 'acf-field-group' ) {
130
+ return;
131
+ }
132
+
133
+ // Ok, a row was inserted using the log function on SimplePostLogger,
134
+ // now ACF will call save_post again and trigger
135
+ // another log of the same row. To prevent this we
136
+ // now add a filter to prevent the next log.
137
+ add_filter( 'simple_history/post_logger/post_updated/ok_to_log', array( $this, 'prevent_second_acf_field_group_post_save_log' ), 10, 4 );
138
+ }
139
+
140
+ /**
141
+ * Fired from SimpleLogger action 'simple_history/post_logger/post_updated/ok_to_log' and added only after
142
+ * a row already has been logged.
143
+ *
144
+ * This function checks if post type logged by SimplePostLogger is a ACF Field Group, and if it is
145
+ * then don't log that log. This way we prevent the post logger from logging the field group changes twice.
146
+ */
147
+ public function prevent_second_acf_field_group_post_save_log( $ok_to_log, $new_status, $old_status, $post ) {
148
+ if ( isset( $post->post_type ) && $post->post_type === 'acf-field-group' ) {
149
+ $ok_to_log = false;
150
+ }
151
+
152
+ return $ok_to_log;
153
+ }
154
+
155
+ /**
156
+ * Append info about changes in ACF fields input,
157
+ * i.e. store all info we later use to show changes that a user has done.
158
+ *
159
+ * Called when ACF saves a post.
160
+ *
161
+ * @param mixed int $post_id ID of post that is being saved. string "option" or "options" when saving an options page.
162
+ */
163
+ public function on_acf_save_post( $post_id ) {
164
+ if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
165
+ return;
166
+ }
167
+
168
+ // Only act when $post_id is numeric, can be "options" too when
169
+ // ACF saves an options page.
170
+ if ( ! is_numeric( $post_id ) ) {
171
+ return;
172
+ }
173
+
174
+ // Don't act on post revision.
175
+ if ( wp_is_post_revision( $post_id ) ) {
176
+ return;
177
+ }
178
+
179
+ /*
180
+ Meta values look like
181
+ [product_images_0_image] => 625
182
+ [_product_images_0_image] => field_59a091044812e
183
+ [product_images_0_image_caption] => Image row yes
184
+ [_product_images_0_image_caption] => field_59a0910f4812f
185
+ [product_images_0_image_related_0_related_name] => Related one
186
+ [_product_images_0_image_related_0_related_name] => field_59aaedd43ae11
187
+ [product_images_0_image_related_0_related_item_post] =>
188
+ [_product_images_0_image_related_0_related_item_post] => field_59aaede43ae12
189
+ [product_images_0_image_related_1_related_name] => Another related
190
+ [_product_images_0_image_related_1_related_name] => field_59aaedd43ae11
191
+ [product_images_0_image_related_1_related_item_post] =>
192
+ [_product_images_0_image_related_1_related_item_post] => field_59aaede43ae12
193
+ [product_images_0_image_related] => 2
194
+ [_product_images_0_image_related] => field_59aaedbc3ae10
195
+ [product_images_1_image] => 574
196
+ */
197
+ $prev_post_meta = isset( $this->oldPostData['prev_post_meta'] ) ? $this->oldPostData['prev_post_meta'] : array();
198
+
199
+ $new_post_meta = get_post_custom( $post_id );
200
+ array_walk(
201
+ $new_post_meta,
202
+ function( &$value, $key ) {
203
+ $value = reset( $value );
204
+ }
205
+ );
206
+
207
+ // New and old post meta can contain different amount of keys,
208
+ // join them so we have the name of all post meta thaf have been added, removed, or modified.
209
+ $new_and_old_post_meta = array_merge( $prev_post_meta, $new_post_meta );
210
+ ksort( $new_and_old_post_meta, SORT_REGULAR );
211
+
212
+ // array1 - The array to compare from
213
+ // array2 - An array to compare against
214
+ // Returns an array containing all the values from array1 that are not present in any of the other arrays.
215
+ // Keep only ACF fields in prev and new post meta.
216
+ $prev_post_meta = $this->keep_only_acf_stuff_in_array( $prev_post_meta, $new_and_old_post_meta );
217
+ $new_post_meta = $this->keep_only_acf_stuff_in_array( $new_post_meta, $new_and_old_post_meta );
218
+ $new_and_old_post_meta_acf_fields = array_merge( $prev_post_meta, $new_post_meta );
219
+
220
+ // Map field name with fieldkey so we can get field objects when needed.
221
+ // Final array have values like:
222
+ // [product_images_0_image] => field_59a091044812e
223
+ // [product_images_0_image_caption] => field_59a0910f4812f
224
+ // [product_images_0_image_related_0_related_name] => field_59aaedd43ae11.
225
+ $fieldnames_to_field_keys = array();
226
+ foreach ( $new_and_old_post_meta_acf_fields as $meta_key => $meta_value ) {
227
+ // $key is like [product_images_0_image_related_1_related_name].
228
+ // Get ACF fieldkey for that value. Will be in $new_and_old_post_meta
229
+ // as the same as key but with underscore first
230
+ $meta_key_to_look_for = "_{$meta_key}";
231
+ if ( isset( $new_and_old_post_meta[ $meta_key_to_look_for ] ) ) {
232
+ $fieldnames_to_field_keys[ $meta_key ] = $new_and_old_post_meta[ $meta_key_to_look_for ];
233
+ }
234
+ }
235
+
236
+ // Compare old with new = get only changed, not added, deleted are here.
237
+ $post_meta_diff1 = array_diff_assoc( $prev_post_meta, $new_post_meta );
238
+
239
+ // Compare new with old = get an diff with added and changed stuff.
240
+ $post_meta_diff2 = array_diff_assoc( $new_post_meta, $prev_post_meta );
241
+
242
+ // Compare keys, gets added fields.
243
+ $post_meta_added_fields = array_diff( array_keys( $post_meta_diff2 ), array_keys( $post_meta_diff1 ) );
244
+ $post_meta_added_fields = array_values( $post_meta_added_fields );
245
+
246
+ // Keys that exist in diff1 but not in diff2 = deleted.
247
+ $post_meta_removed_fields = array_diff_assoc( array_keys( $post_meta_diff1 ), array_keys( $post_meta_diff2 ) );
248
+ $post_meta_removed_fields = array_values( $post_meta_removed_fields );
249
+
250
+ $post_meta_changed_fields = array_keys( $post_meta_diff1 );
251
+
252
+ /*
253
+ * value is changed: added to both diff and diff2
254
+ * value is added, like in repeater: added to diff2 (not to diff)
255
+ * $diff3: contains only added things.
256
+ * Compare old and new values
257
+ * Loop all keys in $new_and_old_post_meta
258
+ * But act only on those whose keys begins with "_" and where the value begins with "field_" and ends with alphanum.
259
+ */
260
+
261
+ /*
262
+ * We have the diff, now add it to the context
263
+ * This is called after Simple History already has added its row
264
+ * So... we must add to the context late somehow
265
+ * Get the latest inserted row from the SimplePostLogger, check if that postID is
266
+ * same as the
267
+ */
268
+ $post_logger = $this->simpleHistory->getInstantiatedLoggerBySlug( 'SimplePostLogger' );
269
+
270
+ // Save ACF diff if detected post here is same as the last one used in Postlogger.
271
+ if ( isset( $post_logger->lastInsertContext['post_id'] ) && $post_id === $post_logger->lastInsertContext['post_id'] ) {
272
+ $last_insert_id = $post_logger->lastInsertID;
273
+
274
+ // Append new info to the context of history item with id $post_logger->lastInsertID.
275
+ $acf_context = array();
276
+ $acf_context = $this->add_acf_context( $acf_context, 'added', $post_meta_added_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys );
277
+ $acf_context = $this->add_acf_context( $acf_context, 'changed', $post_meta_changed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys );
278
+ $acf_context = $this->add_acf_context( $acf_context, 'removed', $post_meta_removed_fields, $prev_post_meta, $new_post_meta, $fieldnames_to_field_keys );
279
+
280
+ $post_logger->append_context( $last_insert_id, $acf_context );
281
+
282
+ // Prev and new post meta for testing.
283
+ /*
284
+ $post_logger->append_context(
285
+ $last_insert_id,
286
+ array(
287
+ 'prev_post_meta' => $prev_post_meta,
288
+ )
289
+ );
290
+ $post_logger->append_context(
291
+ $last_insert_id,
292
+ array(
293
+ 'new_post_meta' => $new_post_meta,
294
+ )
295
+ );
296
+ */
297
+ } // End if().
298
+ }
299
+
300
+ /**
301
+ * Add ACF context for added, removed, or changed fields.
302
+ *
303
+ * @param array $context Context.
304
+ * @param string $modify_type Type. added | removed | changed.
305
+ * @param array $relevant_acf_fields Fields.
306
+ * @param array $prev_post_meta Prev meta.
307
+ * @param array $new_post_meta New meta.
308
+ * @param array $fieldnames_to_field_keys Fieldnames to field keys mapping.
309
+ * @return array Modified context.
310
+ */
311
+ public function add_acf_context( $context = array(), $modify_type = '', $relevant_acf_fields = array(), $prev_post_meta = array(), $new_post_meta = array(), $fieldnames_to_field_keys = array() ) {
312
+
313
+ if ( ! is_array( $context ) || empty( $modify_type ) || empty( $relevant_acf_fields ) ) {
314
+ return $context;
315
+ }
316
+
317
+ $loopnum = 0;
318
+ foreach ( $relevant_acf_fields as $field_slug ) {
319
+ /*
320
+ Store just the names to begin with
321
+ acf_field_added_0 = url.
322
+ acf_field_added_1 = first_name.
323
+
324
+ If field slug contains a number, like in "product_images_2_image"
325
+ that probably means that that field is a repeater with name "product_images"
326
+ with a sub field called "image" and that the image is the 2:nd among it's selected sub fields.
327
+
328
+ Example of how fields can look:
329
+ acf_field_added_0 product_images_2_image
330
+ acf_field_added_1 product_images_2_image_caption
331
+ acf_field_added_2 product_images_2_image_related
332
+ acf_field_changed_0 my_field_in_acf
333
+ acf_field_changed_1 product_images
334
+ acf_field_changed_2 price
335
+ acf_field_changed_3 description
336
+ */
337
+ $context_key = "acf_field_{$modify_type}_{$loopnum}";
338
+ $context[ "{$context_key}/slug" ] = $field_slug;
339
+
340
+ /*
341
+ * Try to get som extra info, like display name and type for this field.
342
+ * For a nice context in the feed we want: parent field group name and type?
343
+ */
344
+ if ( isset( $fieldnames_to_field_keys[ $field_slug ] ) ) {
345
+ $field_key = $fieldnames_to_field_keys[ $field_slug ];
346
+ $context[ "{$context_key}/key" ] = $field_key;
347
+
348
+ // Interesting stuff in field object:
349
+ // - Label = the human readable name of the field
350
+ // - Type = the type of the field
351
+ // - Parent = id of parent field post id.
352
+ $field_object = get_field_object( $field_key );
353
+
354
+ if ( is_array( $field_object ) ) {
355
+ $context[ "{$context_key}/label" ] = $field_object['label'];
356
+ if ( ! empty( $field_object['type'] ) ) {
357
+ $context[ "{$context_key}/type" ] = $field_object['type'];
358
+ }
359
+
360
+ // If no parent just continue to next field.
361
+ if ( empty( $field_object['parent'] ) ) {
362
+ continue;
363
+ }
364
+
365
+ // We have at least one parent, get them all, including the field group
366
+ // $context[ "{$context_key}/field_parent_object" ] = $parent_field;
367
+ $field_parents = array();
368
+ $field_field_group = null;
369
+
370
+ // Begin with the direct parent.
371
+ $parent_field = $field_object;
372
+
373
+ while ( ! empty( $parent_field['parent'] ) ) {
374
+ $parentFieldParent = $parent_field['parent'];
375
+
376
+ // acf-field | acf-field-group.
377
+ $parent_field_post_type = get_post_type( $parentFieldParent );
378
+
379
+ if ( false === $parent_field_post_type ) {
380
+ break;
381
+ }
382
+
383
+ if ( 'acf-field' === $parent_field_post_type ) {
384
+ // Field is when field is for example a sub field of a repeater.
385
+ if ( function_exists( 'acf_get_field' ) ) {
386
+ // Since ACF 5.7.10 the acf_get_field() function is available.
387
+ $parent_field = acf_get_field( $parentFieldParent );
388
+ } elseif ( function_exists( '_acf_get_field_by_id' ) ) {
389
+ // ACF function _acf_get_field_by_id() is available before ACF 5.7.10.
390
+ $parent_field = _acf_get_field_by_id( $parentFieldParent );
391
+ }
392
+ } elseif ( 'acf-field-group' === $parent_field_post_type ) {
393
+ $parent_field = acf_get_field_group( $parentFieldParent );
394
+ } else {
395
+ // Unknown post type.
396
+ break;
397
+ }
398
+
399
+ if ( false === $parent_field ) {
400
+ break;
401
+ }
402
+
403
+ if ( 'acf-field' === $parent_field_post_type ) {
404
+ $field_parents[] = $parent_field;
405
+ } elseif ( 'acf-field-group' === $parent_field_post_type ) {
406
+ $field_field_group = $parent_field;
407
+ } // End if().
408
+ } // End while().
409
+
410
+ $field_parents = array_reverse( $field_parents );
411
+
412
+ // Array with info about each parent.
413
+ $arr_field_path = array();
414
+
415
+ if ( ! empty( $field_field_group['title'] ) ) {
416
+ $arr_field_path[] = array(
417
+ 'name' => $field_field_group['title'],
418
+ 'type' => 'field_group',
419
+ );
420
+ }
421
+
422
+ foreach ( $field_parents as $one_field_parent ) {
423
+ $arr_field_path[] = array(
424
+ 'name' => $one_field_parent['label'],
425
+ 'type' => 'field',
426
+ 'field_type' => $one_field_parent['type'],
427
+ );
428
+ }
429
+
430
+ if ( ! empty( $arr_field_path ) ) {
431
+ $path_loop_num = 0;
432
+ foreach ( $arr_field_path as $one_field_path ) {
433
+ $context[ "{$context_key}/path_{$path_loop_num}/name" ] = $one_field_path['name'];
434
+ $context[ "{$context_key}/path_{$path_loop_num}/type" ] = $one_field_path['type'];
435
+ if ( ! empty( $one_field_path['field_type'] ) ) {
436
+ $context[ "{$context_key}/path_{$path_loop_num}/field_type" ] = $one_field_path['field_type'];
437
+ }
438
+ $path_loop_num++;
439
+ }
440
+ }
441
+
442
+ // Add value of fields if they are not part of
443
+ // repeatable or flexible fields or similar.
444
+ // error_log( "Final parents" . print_r( $field_parents, 1 ) );
445
+ // error_log( "Final field group" . print_r( $field_field_group['title'], 1 ) );
446
+ // error_log( "context" . print_r( $context, 1 ) );
447
+ } // End if().
448
+ } // End if().
449
+
450
+ $loopnum++;
451
+ } // End foreach().
452
+
453
+ // error_log( "---------------------------" );
454
+ // error_log( "field_path_string: $field_path_string");
455
+ // error_log( "context" . print_r( $context, 1 ) );
456
+ return $context;
457
+ }
458
+
459
+ /**
460
+ * Clean array and keep only ACF related things.
461
+ *
462
+ * Remove
463
+ * - underscore fields
464
+ * - fields with value field_*
465
+ *
466
+ * Keep
467
+ * - vals that are acf
468
+ *
469
+ * @param array $arr Array.
470
+ * @param array $all_fields Array fields.
471
+ */
472
+ public function keep_only_acf_stuff_in_array( $arr, $all_fields ) {
473
+ $new_arr = array();
474
+
475
+ foreach ( $arr as $key => $val ) {
476
+ // Don't keep keys that begin with underscore "_".
477
+ if ( strpos( $key, '_' ) === 0 ) {
478
+ continue;
479
+ }
480
+
481
+ // Don't keep keys that begin with "field_".
482
+ if ( strpos( $val, 'field_' ) === 0 ) {
483
+ continue;
484
+ }
485
+
486
+ // Don't keep fields that does not have a corresponding _field value.
487
+ // Each key has both the name, for example 'color' and another
488
+ // key called '_color'. We check that the underscore version exists
489
+ // and contains 'field_'. After this check only ACF fields should exist
490
+ // in the array..
491
+ if ( ! isset( $all_fields[ "_{$key}" ] ) ) {
492
+ continue;
493
+ }
494
+
495
+ if ( strpos( $all_fields[ "_{$key}" ], 'field_' ) !== 0 ) {
496
+ continue;
497
+ }
498
+
499
+ $new_arr[ $key ] = $val;
500
+ }
501
+
502
+ return $new_arr;
503
+ }
504
+
505
+ /**
506
+ * Store prev post meta when post is saved.
507
+ * Stores data in $this->oldPostData.
508
+ */
509
+ public function on_admin_action_editpost() {
510
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
511
+ $post_ID = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
512
+
513
+ if ( ! $post_ID ) {
514
+ return;
515
+ }
516
+
517
+ $prev_post = get_post( $post_ID );
518
+
519
+ if ( is_wp_error( $prev_post ) ) {
520
+ return;
521
+ }
522
+
523
+ $post_meta = get_post_custom( $post_ID );
524
+
525
+ // Meta is array of arrays, get first value of each array value.
526
+ array_walk(
527
+ $post_meta,
528
+ function( &$value, $key ) {
529
+ $value = reset( $value );
530
+ }
531
+ );
532
+
533
+ $this->oldPostData['prev_post_meta'] = $post_meta;
534
+ }
535
+
536
+ /**
537
+ * Called from PostLogger and its diff table output using filter 'simple_history/post_logger/post_updated/diff_table_output'.
538
+ * Diff table is generated only for post type 'acf-field-group'.
539
+ *
540
+ * @param string $diff_table_output
541
+ * @param array $context
542
+ * @return string
543
+ */
544
+ public function on_diff_table_output_field_group( $diff_table_output, $context ) {
545
+ $post_type = ! empty( $context['post_type'] ) ? $context['post_type'] : false;
546
+
547
+ // Bail if not ACF Field Group.
548
+ if ( $post_type !== 'acf-field-group' ) {
549
+ return $diff_table_output;
550
+ }
551
+
552
+ // Field group fields to check for and output if found
553
+ $arrKeys = array(
554
+ 'instruction_placement' => array(
555
+ 'name' => _x( 'Instruction placement', 'Logger: Plugin ACF', 'simple-history' ),
556
+ ),
557
+ 'label_placement' => array(
558
+ 'name' => _x( 'Label placement', 'Logger: Plugin ACF', 'simple-history' ),
559
+ ),
560
+ 'description' => array(
561
+ 'name' => _x( 'Description', 'Logger: Plugin ACF', 'simple-history' ),
562
+ ),
563
+ 'menu_order' => array(
564
+ 'name' => _x( 'Menu order', 'Logger: Plugin ACF', 'simple-history' ),
565
+ ),
566
+ 'position' => array(
567
+ 'name' => _x( 'Position', 'Logger: Plugin ACF', 'simple-history' ),
568
+ ),
569
+ 'active' => array(
570
+ 'name' => _x( 'Active', 'Logger: Plugin ACF', 'simple-history' ),
571
+ ),
572
+ 'style' => array(
573
+ 'name' => _x( 'Style', 'Logger: Plugin ACF', 'simple-history' ),
574
+ ),
575
+ );
576
+
577
+ foreach ( $arrKeys as $acfKey => $acfVals ) {
578
+ if ( isset( $context[ "acf_new_$acfKey" ] ) && isset( $context[ "acf_prev_$acfKey" ] ) ) {
579
+ $diff_table_output .= sprintf(
580
+ '<tr>
 
 
 
581
  <td>%1$s</td>
582
  <td>
583
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
584
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
585
  </td>
586
  </tr>',
587
+ $acfVals['name'],
588
+ esc_html( $context[ "acf_new_$acfKey" ] ),
589
+ esc_html( $context[ "acf_prev_$acfKey" ] )
590
+ );
591
+ }
592
+ }
593
+
594
+ // If only acf_hide_on_screen_removed exists nothing is outputed.
595
+ $acf_hide_on_screen_added = empty( $context['acf_hide_on_screen_added'] ) ? null : $context['acf_hide_on_screen_added'];
596
+ $acf_hide_on_screen_removed = empty( $context['acf_hide_on_screen_removed'] ) ? null : $context['acf_hide_on_screen_removed'];
597
+
598
+ if ( $acf_hide_on_screen_added || $acf_hide_on_screen_removed ) {
599
+ $strCheckedHideOnScreen = '';
600
+ $strUncheckedHideOnScreen = '';
601
+
602
+ if ( $acf_hide_on_screen_added ) {
603
+ $strCheckedHideOnScreen = sprintf(
604
+ '%1$s %2$s',
605
+ _x( 'Checked', 'Logger: Plugin ACF', 'simple-history' ), // 1
606
+ esc_html( $acf_hide_on_screen_added ) // 2
607
+ );
608
+ }
609
+
610
+ if ( $acf_hide_on_screen_removed ) {
611
+ $strUncheckedHideOnScreen = sprintf(
612
+ '%1$s %2$s',
613
+ _x( 'Unchecked', 'Logger: Plugin ACF', 'simple-history' ), // 1
614
+ esc_html( $acf_hide_on_screen_removed ) // 2
615
+ );
616
+ }
617
+
618
+ $diff_table_output .= sprintf(
619
+ '<tr>
620
  <td>%1$s</td>
621
  <td>
622
  %2$s
623
  %3$s
624
  </td>
625
  </tr>',
626
+ _x( 'Hide on screen', 'Logger: Plugin ACF', 'simple-history' ), // 1
627
+ $strCheckedHideOnScreen, // 2
628
+ $strUncheckedHideOnScreen // 3
629
+ );
630
+ }
631
+
632
+ // Check for deleted fields.
633
+ if ( isset( $context['acf_deleted_fields_0_key'] ) ) {
634
+ // 1 or more deleted fields exist in context.
635
+ $loopnum = 0;
636
+ $strDeletedFields = '';
637
+
638
+ while ( isset( $context[ "acf_deleted_fields_{$loopnum}_key" ] ) ) {
639
+ $strDeletedFields .= sprintf(
640
+ '%1$s (%3$s), ',
641
+ esc_html( $context[ "acf_deleted_fields_{$loopnum}_label" ] ),
642
+ esc_html( $context[ "acf_deleted_fields_{$loopnum}_name" ] ),
643
+ esc_html( $context[ "acf_deleted_fields_{$loopnum}_type" ] )
644
+ );
645
+
646
+ $loopnum++;
647
+ }
648
+
649
+ $strDeletedFields = trim( $strDeletedFields, ', ' );
650
+
651
+ $diff_table_output .= sprintf(
652
+ '<tr>
653
  <td>%1$s</td>
654
  <td>%2$s</td>
655
  </tr>',
656
+ _nx( 'Deleted field', 'Deleted fields', $loopnum, 'Logger: Plugin ACF', 'simple-history' ), // 1
657
+ $strDeletedFields
658
+ );
659
+ } // if deleted fields
660
+
661
+ // Check for added fields
662
+ if ( isset( $context['acf_added_fields_0_key'] ) ) {
663
+ // 1 or more deleted fields exist in context
664
+ $loopnum = 0;
665
+ $strAddedFields = '';
666
+
667
+ while ( isset( $context[ "acf_added_fields_{$loopnum}_key" ] ) ) {
668
+ $strAddedFields .= sprintf(
669
+ '%1$s (%3$s), ',
670
+ esc_html( $context[ "acf_added_fields_{$loopnum}_label" ] ), // 1
671
+ esc_html( $context[ "acf_added_fields_{$loopnum}_name" ] ), // 2
672
+ esc_html( $context[ "acf_added_fields_{$loopnum}_type" ] ) // 3
673
+ );
674
+
675
+ $loopnum++;
676
+ }
677
+
678
+ $strAddedFields = trim( $strAddedFields, ', ' );
679
+
680
+ $diff_table_output .= sprintf(
681
+ '<tr>
682
  <td>%1$s</td>
683
  <td>%2$s</td>
684
  </tr>',
685
+ _nx( 'Added field', 'Added fields', $loopnum, 'Logger: Plugin ACF', 'simple-history' ), // 1
686
+ $strAddedFields
687
+ );
688
+ } // if deleted fields
689
+
690
+ // Check for modified fields
691
+ if ( isset( $context['acf_modified_fields_0_ID_prev'] ) ) {
692
+ // 1 or more modifiedfields exist in context
693
+ $loopnum = 0;
694
+ $strModifiedFields = '';
695
+ $arrAddedFieldsKeysToCheck = array(
696
+ 'name' => array(
697
+ 'name' => _x( 'Name: ', 'Logger: Plugin ACF', 'simple-history' ),
698
+ ),
699
+ 'parent' => array(
700
+ 'name' => _x( 'Parent: ', 'Logger: Plugin ACF', 'simple-history' ),
701
+ ),
702
+ 'key' => array(
703
+ 'name' => _x( 'Key: ', 'Logger: Plugin ACF', 'simple-history' ),
704
+ ),
705
+ 'label' => array(
706
+ 'name' => _x( 'Label: ', 'Logger: Plugin ACF', 'simple-history' ),
707
+ ),
708
+ 'type' => array(
709
+ 'name' => _x( 'Type: ', 'Logger: Plugin ACF', 'simple-history' ),
710
+ ),
711
+ );
712
+
713
+ while ( isset( $context[ "acf_modified_fields_{$loopnum}_name_prev" ] ) ) {
714
+ // One modified field, with one or more changed things
715
+ $strOneModifiedField = '';
716
+
717
+ // Add the field name manually, if it is not among the changed field,
718
+ // or we don't know what field the other changed values belongs to.
719
+ /*
720
+ if (empty($context["acf_modified_fields_{$loopnum}_name_new"])) {
721
+ $strOneModifiedField .= sprintf(
722
+ _x('Name: %1$s', 'Logger: Plugin ACF', 'simple-history'), // 1
723
+ esc_html($context["acf_modified_fields_{$loopnum}_name_prev"]) // 2
724
+ );
725
+ }
726
+ */
727
+
728
+ // Add the label name manually, if it is not among the changed field,
729
+ // or we don't know what field the other changed values belongs to.
730
+ if ( empty( $context[ "acf_modified_fields_{$loopnum}_label_new" ] ) ) {
731
+ $strOneModifiedField .= sprintf(
732
+ _x( 'Label: %1$s', 'Logger: Plugin ACF', 'simple-history' ), // 1
733
+ esc_html( $context[ "acf_modified_fields_{$loopnum}_label_prev" ] ) // 2
734
+ );
735
+ }
736
+
737
+ // Check for other keys changed for this field
738
+ foreach ( $arrAddedFieldsKeysToCheck as $oneAddedFieldKeyToCheck => $oneAddedFieldKeyToCheckVals ) {
739
+ $newAndOldValsExists = isset( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ] ) && isset( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ] );
740
+ if ( $newAndOldValsExists ) {
741
+ $strOneModifiedField .= sprintf(
742
+ '
743
  %4$s
744
  %3$s
745
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%1$s</ins>
746
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%2$s</del>
747
  ',
748
+ esc_html( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_new" ] ), // 1
749
+ esc_html( $context[ "acf_modified_fields_{$loopnum}_{$oneAddedFieldKeyToCheck}_prev" ] ), // 2
750
+ esc_html( $oneAddedFieldKeyToCheckVals['name'] ), // 3
751
+ empty( $strOneModifiedField ) ? '' : '<br>' // 4 new line
752
+ );
753
+ }
754
+ }
755
+
756
+ $strOneModifiedField = trim( $strOneModifiedField, ", \n\r\t" );
757
+
758
+ if ( $strOneModifiedField ) {
759
+ $strModifiedFields .= sprintf(
760
+ '<tr>
761
  <td>%1$s</td>
762
  <td>%2$s</td>
763
  </tr>',
764
+ _x( 'Modified field', 'Logger: Plugin ACF', 'simple-history' ), // 1
765
+ $strOneModifiedField
766
+ );
767
+ }
768
+
769
+ $loopnum++;
770
+ }
771
+
772
+ /*
773
+ if ($strModifiedFields) {
774
+ $strModifiedFields = sprintf(
775
+ '<tr>
776
+ <td>%1$s</td>
777
+ <td>%2$s</td>
778
+ </tr>',
779
+ _nx('Modified field', 'Modified fields', $loopnum, 'Logger: Plugin ACF', 'simple-history'), // 1
780
+ $strModifiedFields
781
+ ) . $strModifiedFields;
782
+ }*/
783
+
784
+ $diff_table_output .= $strModifiedFields;
785
+ } // if deleted fields
786
+
787
+ return $diff_table_output;
788
+ }
789
+
790
+ /**
791
+ * Append ACF data to post context.
792
+ *
793
+ * Called via filter `simple_history/post_logger/post_updated/context`.
794
+ *
795
+ * @param array $context
796
+ * @param WP_Post $post
797
+ */
798
+ public function on_post_updated_context( $context, $post ) {
799
+
800
+ // Only act if this is a ACF field group that is saved
801
+ if ( $post->post_type !== 'acf-field-group' ) {
802
+ return $context;
803
+ }
804
+
805
+ // Remove some keys that we don't want,
806
+ // for example the content because that's just a json string
807
+ // in acf-field-group posts.
808
+ unset(
809
+ $context['post_prev_post_content'],
810
+ $context['post_new_post_content'],
811
+ $context['post_prev_post_name'],
812
+ $context['post_new_post_name'],
813
+ $context['post_prev_post_date'],
814
+ $context['post_new_post_date'],
815
+ $context['post_prev_post_date_gmt'],
816
+ $context['post_new_post_date_gmt']
817
+ );
818
+
819
+ $acf_data_diff = array();
820
+
821
+ // 'fieldGroup' fields to check.
822
+ $arr_field_group_keys_to_diff = array(
823
+ 'menu_order',
824
+ 'position',
825
+ 'style',
826
+ 'label_placement',
827
+ 'instruction_placement',
828
+ 'active',
829
+ 'description',
830
+ );
831
+
832
+ $fieldGroup = $this->oldAndNewFieldGroupsAndFields['fieldGroup'];
833
+
834
+ foreach ( $arr_field_group_keys_to_diff as $key ) {
835
+ if ( isset( $fieldGroup['old'][ $key ] ) && isset( $fieldGroup['new'][ $key ] ) ) {
836
+ $acf_data_diff = $this->add_diff( $acf_data_diff, $key, (string) $fieldGroup['old'][ $key ], (string) $fieldGroup['new'][ $key ] );
837
+ }
838
+ }
839
+
840
+ foreach ( $acf_data_diff as $diff_key => $diff_values ) {
841
+ $context[ "acf_prev_{$diff_key}" ] = $diff_values['old'];
842
+ $context[ "acf_new_{$diff_key}" ] = $diff_values['new'];
843
+ }
844
+
845
+ // Add checked or uncheckd hide on screen-items to context
846
+ $arrhHideOnScreenAdded = array();
847
+ $arrHideOnScreenRemoved = array();
848
+
849
+ $fieldGroup['new']['hide_on_screen'] = isset( $fieldGroup['new']['hide_on_screen'] ) && is_array( $fieldGroup['new']['hide_on_screen'] ) ? $fieldGroup['new']['hide_on_screen'] : array();
850
+ $fieldGroup['old']['hide_on_screen'] = isset( $fieldGroup['old']['hide_on_screen'] ) && is_array( $fieldGroup['old']['hide_on_screen'] ) ? $fieldGroup['old']['hide_on_screen'] : array();
851
+
852
+ // dd($fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen']);
853
+ // Act when new or old hide_on_screen is set
854
+ if ( ! empty( $fieldGroup['new']['hide_on_screen'] ) || ! empty( $fieldGroup['old']['hide_on_screen'] ) ) {
855
+ $arrhHideOnScreenAdded = array_diff( $fieldGroup['new']['hide_on_screen'], $fieldGroup['old']['hide_on_screen'] );
856
+ $arrHideOnScreenRemoved = array_diff( $fieldGroup['old']['hide_on_screen'], $fieldGroup['new']['hide_on_screen'] );
857
+
858
+ // ddd($arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
859
+ if ( $arrhHideOnScreenAdded ) {
860
+ $context['acf_hide_on_screen_added'] = implode( ',', $arrhHideOnScreenAdded );
861
+ }
862
+
863
+ if ( $arrHideOnScreenRemoved ) {
864
+ $context['acf_hide_on_screen_removed'] = implode( ',', $arrHideOnScreenRemoved );
865
+ }
866
+ }
867
+
868
+ // ddd($context, $arrhHideOnScreenAdded, $arrHideOnScreenRemoved);
869
+ // Add removed fields to context
870
+ if ( ! empty( $this->oldAndNewFieldGroupsAndFields['deletedFields'] ) && is_array( $this->oldAndNewFieldGroupsAndFields['deletedFields'] ) ) {
871
+ $loopnum = 0;
872
+ foreach ( $this->oldAndNewFieldGroupsAndFields['deletedFields'] as $oneDeletedField ) {
873
+ $context[ "acf_deleted_fields_{$loopnum}_key" ] = $oneDeletedField['key'];
874
+ $context[ "acf_deleted_fields_{$loopnum}_name" ] = $oneDeletedField['name'];
875
+ $context[ "acf_deleted_fields_{$loopnum}_label" ] = $oneDeletedField['label'];
876
+ $context[ "acf_deleted_fields_{$loopnum}_type" ] = $oneDeletedField['type'];
877
+ $loopnum++;
878
+ }
879
+ }
880
+
881
+ // Add added fields to context
882
+ if ( ! empty( $this->oldAndNewFieldGroupsAndFields['addedFields'] ) && is_array( $this->oldAndNewFieldGroupsAndFields['addedFields'] ) ) {
883
+ $loopnum = 0;
884
+
885
+ foreach ( $this->oldAndNewFieldGroupsAndFields['addedFields'] as $oneAddedField ) {
886
+ // Id not available here, wold be nice to have
887
+ // $context["acf_added_fields_{$loopnum}_ID"] = $oneAddedField['ID'];
888
+ $context[ "acf_added_fields_{$loopnum}_key" ] = $oneAddedField['key'];
889
+ $context[ "acf_added_fields_{$loopnum}_name" ] = $oneAddedField['name'];
890
+ $context[ "acf_added_fields_{$loopnum}_label" ] = $oneAddedField['label'];
891
+ $context[ "acf_added_fields_{$loopnum}_type" ] = $oneAddedField['type'];
892
+ $loopnum++;
893
+ }
894
+ }
895
+
896
+ // Add modified fields to context
897
+ // dd('on_post_updated_context', $context, $this->oldAndNewFieldGroupsAndFields);
898
+ if ( ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'] ) && ! empty( $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'] ) ) {
899
+ $modifiedFields = $this->oldAndNewFieldGroupsAndFields['modifiedFields'];
900
+
901
+ $arr_added_fields_keys_to_add = array(
902
+ 'parent',
903
+ 'key',
904
+ 'label',
905
+ 'name',
906
+ 'type',
907
+ );
908
+
909
+ $loopnum = 0;
910
+
911
+ foreach ( $modifiedFields['old'] as $modifiedFieldId => $modifiedFieldValues ) {
912
+ // Both old and new values mest exist
913
+ if ( empty( $modifiedFields['new'][ $modifiedFieldId ] ) ) {
914
+ continue;
915
+ }
916
+
917
+ // Always add ID, name, and lavel
918
+ $context[ "acf_modified_fields_{$loopnum}_ID_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['ID'];
919
+ $context[ "acf_modified_fields_{$loopnum}_name_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['name'];
920
+ $context[ "acf_modified_fields_{$loopnum}_label_prev" ] = $modifiedFields['old'][ $modifiedFieldId ]['label'];
921
+
922
+ foreach ( $arr_added_fields_keys_to_add as $one_key_to_add ) {
923
+ // Check that new and old exist.
924
+ $new_exists = isset( $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] );
925
+ $old_exists = isset( $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ] );
926
+
927
+ if ( ! $new_exists || ! $old_exists ) {
928
+ continue;
929
+ }
930
+
931
+ // Only add to context if modified.
932
+ if ( $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ] != $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ] ) {
933
+ $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_prev" ] = $modifiedFields['old'][ $modifiedFieldId ][ $one_key_to_add ];
934
+ $context[ "acf_modified_fields_{$loopnum}_{$one_key_to_add}_new" ] = $modifiedFields['new'][ $modifiedFieldId ][ $one_key_to_add ];
935
+ }
936
+ }
937
+
938
+ $loopnum++;
939
+ }
940
+ }
941
+
942
+ return $context;
943
+ }
944
+
945
+ public function add_diff( $post_data_diff, $key, $old_value, $new_value ) {
946
+ if ( $old_value != $new_value ) {
947
+ $post_data_diff[ $key ] = array(
948
+ 'old' => $old_value,
949
+ 'new' => $new_value,
950
+ );
951
+ }
952
+
953
+ return $post_data_diff;
954
+ }
955
+
956
+ /**
957
+ * Store a version of the field group as it was before the save
958
+ * Called before field group post/values is added to db
959
+ *
960
+ * @param array $data Post data.
961
+ * @param array $postarr Post data.
962
+ */
963
+ public function on_wp_insert_post_data( $data, $postarr ) {
964
+
965
+ // Only do this if ACF field group is being saved.
966
+ if ( $postarr['post_type'] !== 'acf-field-group' ) {
967
+ return $data;
968
+ }
969
+
970
+ if ( empty( $postarr['ID'] ) ) {
971
+ return $data;
972
+ }
973
+
974
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
975
+ if ( empty( $_POST['acf_field_group'] ) ) {
976
+ return $data;
977
+ }
978
+
979
+ $this->oldAndNewFieldGroupsAndFields['fieldGroup']['old'] = acf_get_field_group( $postarr['ID'] );
980
+
981
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
982
+ $this->oldAndNewFieldGroupsAndFields['fieldGroup']['new'] = acf_get_valid_field_group( $_POST['acf_field_group'] );
983
+
984
+ return $data;
985
+ }
986
+
987
+ /**
988
+ * ACF field group is saved
989
+ * Called before ACF calls its save_post filter
990
+ * Here we save the new fields values and also get the old values so we can compare
991
+ */
992
+ public function on_transition_post_status( $new_status, $old_status, $post ) {
993
+ static $isCalled = false;
994
+
995
+ if ( $isCalled ) {
996
+ return;
997
+ }
998
+
999
+ $isCalled = true;
1000
+
1001
+ $post_id = $post->ID;
1002
+
1003
+ // do not act if this is an auto save routine
1004
+ if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
1005
+ return;
1006
+ }
1007
+
1008
+ // bail early if not acf-field-group
1009
+ if ( $post->post_type !== 'acf-field-group' ) {
1010
+ return;
1011
+ }
1012
+
1013
+ // only save once! WordPress save's a revision as well.
1014
+ if ( wp_is_post_revision( $post_id ) ) {
1015
+ return;
1016
+ }
1017
+
1018
+ // Store info about fields that are going to be deleted
1019
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1020
+ if ( ! empty( $_POST['_acf_delete_fields'] ) ) {
1021
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1022
+ $deletedFieldsIDs = explode( '|', (string) $_POST['_acf_delete_fields'] );
1023
+ $deletedFieldsIDs = array_map( 'intval', $deletedFieldsIDs );
1024
+
1025
+ foreach ( $deletedFieldsIDs as $id ) {
1026
+ if ( ! $id ) {
1027
+ continue;
1028
+ }
1029
+
1030
+ $field_info = acf_get_field( $id );
1031
+
1032
+ if ( ! $field_info ) {
1033
+ continue;
1034
+ }
1035
+
1036
+ $this->oldAndNewFieldGroupsAndFields['deletedFields'][ $id ] = $field_info;
1037
+ }
1038
+ }
1039
+
1040
+ // Store info about added or modified fields
1041
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1042
+ if ( ! empty( $_POST['acf_fields'] ) && is_array( $_POST['acf_fields'] ) ) {
1043
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
1044
+ foreach ( $_POST['acf_fields'] as $oneFieldAddedOrUpdated ) {
1045
+ if ( empty( $oneFieldAddedOrUpdated['ID'] ) ) {
1046
+ // New fields have no id
1047
+ // 'ID' => string(0) ""
1048
+ $this->oldAndNewFieldGroupsAndFields['addedFields'][] = $oneFieldAddedOrUpdated;
1049
+ } else {
1050
+ // Existing fields have an id
1051
+ // 'ID' => string(3) "383"
1052
+ $this->oldAndNewFieldGroupsAndFields['modifiedFields']['old'][ $oneFieldAddedOrUpdated['ID'] ] = acf_get_field( $oneFieldAddedOrUpdated['ID'] );
1053
+
1054
+ $this->oldAndNewFieldGroupsAndFields['modifiedFields']['new'][ $oneFieldAddedOrUpdated['ID'] ] = $oneFieldAddedOrUpdated;
1055
+ }
1056
+ }
1057
+ }
1058
+
1059
+ // We don't do anything else here, but we make the actual logging
1060
+ // in filter 'acf/update_field_group' beacuse it's safer because
1061
+ // ACF has done it's validation and it's after ACF has saved the fields,
1062
+ // so less likely that we make some critical error
1063
+ }
1064
+
1065
+
1066
+ /**
1067
+ * Add the post types that ACF uses for fields to the array of post types
1068
+ * that the default post logger should not log. If not each field will cause one
1069
+ * post update log message.
1070
+ */
1071
+ public function remove_acf_from_postlogger( $skip_posttypes ) {
1072
+ array_push(
1073
+ $skip_posttypes,
1074
+ 'acf-field'
1075
+ );
1076
+
1077
+ return $skip_posttypes;
1078
+ }
1079
+ }
1080
  } // End if().
loggers/Plugin_BeaverBuilder.php CHANGED
@@ -1,115 +1,112 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logger for Beaver Builder
7
  */
8
- if (!class_exists('Plugin_BeaverBuilder')) {
9
  // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
10
- class Plugin_BeaverBuilder extends SimpleLogger
11
- {
12
- public $slug = __CLASS__;
13
 
14
- public function getInfo()
15
- {
16
- $arr_info = array(
17
- 'name' => 'Plugin Beaver Builder',
18
- 'description' => _x(
19
- 'Logs various things in Beaver Builder',
20
- 'Logger: Plugin Beaver Builder',
21
- 'simple-history'
22
- ),
23
- 'name_via' => _x(
24
- 'Using plugin Beaver Builder',
25
- 'Logger: Plugin Beaver Builder',
26
- 'simple-history'
27
- ),
28
- 'capability' => 'manage_options',
29
- 'messages' => array(
30
- 'layout_saved' => __(
31
- 'Layout "{layout_name}" updated',
32
- 'simple-history'
33
- ),
34
- 'template_saved' => __(
35
- 'Template "{layout_name}" updated',
36
- 'simple-history'
37
- ),
38
- 'draft_saved' => __(
39
- 'Draft "{layout_name}" updated',
40
- 'simple-history'
41
- ),
42
- 'admin_saved' => __(
43
- 'Beaver Builder settings saved',
44
- 'simple-history'
45
- )
46
- )
47
- );
48
 
49
- return $arr_info;
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- public function loaded()
53
- {
54
- if (!class_exists('FLBuilder')) {
55
- return;
56
- }
57
 
58
- add_action(
59
- 'fl_builder_after_save_layout',
60
- array($this, 'saveLayout'),
61
- 10,
62
- 4
63
- );
64
- add_action(
65
- 'fl_builder_after_save_user_template',
66
- array($this, 'saveTemplate'),
67
- 10,
68
- 1
69
- );
70
- add_action(
71
- 'fl_builder_after_save_draft',
72
- array($this, 'saveDraft'),
73
- 10,
74
- 2
75
- );
76
- add_action('fl_builder_admin_settings_save', array(
77
- $this,
78
- 'saveAdmin'
79
- ));
80
- }
81
 
82
- public function saveTemplate($post_id)
83
- {
84
- $post = get_post($post_id);
85
- $context = array(
86
- 'layout_name' => $post->post_name
87
- );
88
- $this->noticeMessage('template_saved', $context);
89
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- public function saveDraft($post_id, $publish)
92
- {
93
- $context = array(
94
- 'layout_name' => $post_id
95
- );
96
- $this->noticeMessage('draft_saved', $context);
97
- }
98
 
99
- public function saveLayout($post_id, $publish, $data, $settings)
100
- {
101
- $post = get_post($post_id);
102
- $context = array(
103
- 'layout_name' => $post->post_name
104
- );
105
- if ($publish) {
106
- $this->noticeMessage('layout_saved', $context);
107
- }
108
- }
109
 
110
- public function saveAdmin()
111
- {
112
- $this->noticeMessage('admin_saved');
113
- }
114
- }
 
 
 
 
 
 
 
 
 
115
  } // End if().
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for Beaver Builder
7
  */
8
+ if ( ! class_exists( 'Plugin_BeaverBuilder' ) ) {
9
  // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
10
+ class Plugin_BeaverBuilder extends SimpleLogger {
 
 
11
 
12
+ public $slug = __CLASS__;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ public function getInfo() {
15
+ $arr_info = array(
16
+ 'name' => 'Plugin Beaver Builder',
17
+ 'description' => _x(
18
+ 'Logs various things in Beaver Builder',
19
+ 'Logger: Plugin Beaver Builder',
20
+ 'simple-history'
21
+ ),
22
+ 'name_via' => _x(
23
+ 'Using plugin Beaver Builder',
24
+ 'Logger: Plugin Beaver Builder',
25
+ 'simple-history'
26
+ ),
27
+ 'capability' => 'manage_options',
28
+ 'messages' => array(
29
+ 'layout_saved' => __(
30
+ 'Layout "{layout_name}" updated',
31
+ 'simple-history'
32
+ ),
33
+ 'template_saved' => __(
34
+ 'Template "{layout_name}" updated',
35
+ 'simple-history'
36
+ ),
37
+ 'draft_saved' => __(
38
+ 'Draft "{layout_name}" updated',
39
+ 'simple-history'
40
+ ),
41
+ 'admin_saved' => __(
42
+ 'Beaver Builder settings saved',
43
+ 'simple-history'
44
+ ),
45
+ ),
46
+ );
47
 
48
+ return $arr_info;
49
+ }
 
 
 
50
 
51
+ public function loaded() {
52
+ if ( ! class_exists( 'FLBuilder' ) ) {
53
+ return;
54
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
+ add_action(
57
+ 'fl_builder_after_save_layout',
58
+ array( $this, 'saveLayout' ),
59
+ 10,
60
+ 4
61
+ );
62
+ add_action(
63
+ 'fl_builder_after_save_user_template',
64
+ array( $this, 'saveTemplate' ),
65
+ 10,
66
+ 1
67
+ );
68
+ add_action(
69
+ 'fl_builder_after_save_draft',
70
+ array( $this, 'saveDraft' ),
71
+ 10,
72
+ 2
73
+ );
74
+ add_action(
75
+ 'fl_builder_admin_settings_save',
76
+ array(
77
+ $this,
78
+ 'saveAdmin',
79
+ )
80
+ );
81
+ }
82
 
83
+ public function saveTemplate( $post_id ) {
84
+ $post = get_post( $post_id );
85
+ $context = array(
86
+ 'layout_name' => $post->post_name,
87
+ );
88
+ $this->noticeMessage( 'template_saved', $context );
89
+ }
90
 
91
+ public function saveDraft( $post_id, $publish ) {
92
+ $context = array(
93
+ 'layout_name' => $post_id,
94
+ );
95
+ $this->noticeMessage( 'draft_saved', $context );
96
+ }
 
 
 
 
97
 
98
+ public function saveLayout( $post_id, $publish, $data, $settings ) {
99
+ $post = get_post( $post_id );
100
+ $context = array(
101
+ 'layout_name' => $post->post_name,
102
+ );
103
+ if ( $publish ) {
104
+ $this->noticeMessage( 'layout_saved', $context );
105
+ }
106
+ }
107
+
108
+ public function saveAdmin() {
109
+ $this->noticeMessage( 'admin_saved' );
110
+ }
111
+ }
112
  } // End if().
loggers/Plugin_DuplicatePost.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logger for the Duplicate Post plugin
@@ -9,145 +9,141 @@ defined('ABSPATH') or die();
9
  * @package SimpleHistory
10
  * @since 2.13
11
  */
12
- if (!class_exists('Plugin_DuplicatePost')) {
13
- class Plugin_DuplicatePost extends SimpleLogger
14
- {
15
- public $slug = __CLASS__;
16
-
17
- public function getInfo()
18
- {
19
- $arr_info = [
20
- 'name' => 'Plugin Duplicate Posts',
21
- 'description' => _x(
22
- 'Logs posts and pages cloned using plugin Duplicate Post',
23
- 'Logger: Plugin Duplicate Post',
24
- 'simple-history'
25
- ),
26
- 'name_via' => _x('Using plugin Duplicate Posts', 'Logger: Plugin Duplicate Post', 'simple-history'),
27
- 'capability' => 'manage_options',
28
- 'messages' => [
29
- 'post_duplicated' => _x(
30
- 'Cloned "{duplicated_post_title}" to a new post',
31
- 'Logger: Plugin Duplicate Post',
32
- 'simple-history'
33
- ),
34
- ],
35
- ];
36
-
37
- return $arr_info;
38
- }
39
-
40
- public function loaded()
41
- {
42
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
43
-
44
- $isPluginActive = is_plugin_active('duplicate-post/duplicate-post.php');
45
-
46
- if (!$isPluginActive) {
47
- return;
48
- }
49
-
50
- // When a copy have been made of a post or page
51
- // the action 'dp_duplicate_page' or 'dp_duplicate_post'
52
- // is fired with args $new_post_id, $post, $status.
53
- // We add actions with prio 20 so we probably run after
54
- // the plugins own
55
- add_action('dp_duplicate_post', [$this, 'onDpDuplicatePost'], 100, 3);
56
- add_action('dp_duplicate_page', [$this, 'onDpDuplicatePost'], 100, 3);
57
- }
58
-
59
- /**
60
- * A post or page was duplicated
61
- *
62
- * @param $new_post_id
63
- * @param $post old post that a copy was made of
64
- * @param $status
65
- */
66
- public function onDpDuplicatePost($newPostID, $post, $status)
67
- {
68
- $new_post = get_post($newPostID);
69
-
70
- $context = [
71
- 'new_post_title' => $new_post->post_title,
72
- 'new_post_id' => $new_post->ID,
73
- 'duplicated_post_title' => $post->post_title,
74
- 'duplicated_post_id' => $post->ID,
75
- // "duplicate_new_post_id" => $newPostID,
76
- // "status" => $status
77
- ];
78
-
79
- $this->infoMessage('post_duplicated', $context);
80
- }
81
-
82
- /**
83
- * Modify plain output to include link to post
84
- */
85
- public function getLogRowPlainTextOutput($row)
86
- {
87
- $context = $row->context;
88
- $new_post_id = isset($context['new_post_id']) ? $context['new_post_id'] : null;
89
- $duplicated_post_id = isset($context['duplicated_post_id']) ? $context['duplicated_post_id'] : null;
90
- $duplicated_post_title = isset($context['duplicated_post_title'])
91
- ? $context['duplicated_post_title']
92
- : null;
93
- $message_key = isset($context['_message_key']) ? $context['_message_key'] : null;
94
-
95
- $message = $row->message;
96
-
97
- // Check if post still is available
98
- // It will return a WP_Post Object if post still is in system
99
- // If post is deleted from trash (not just moved there), then null is returned
100
- $postDuplicated = get_post($duplicated_post_id);
101
- $post_is_available = is_a($postDuplicated, 'WP_Post');
102
-
103
- // Try to get singular name
104
- $post_type = isset($postDuplicated->post_type) ? $postDuplicated->post_type : '';
105
- $post_type_obj = get_post_type_object($post_type);
106
-
107
- if (!is_null($post_type_obj)) {
108
- if (!empty($post_type_obj->labels->singular_name)) {
109
- $context['duplicated_post_post_type_singular_name'] = strtolower(
110
- $post_type_obj->labels->singular_name
111
- );
112
- }
113
- }
114
-
115
- $context['duplicated_post_edit_link'] = get_edit_post_link($duplicated_post_id);
116
- $context['new_post_edit_link'] = get_edit_post_link($new_post_id);
117
-
118
- // If post is not available any longer then we can't link to it, so keep plain message then
119
- // Also keep plain format if user is not allowed to edit post (edit link is empty)
120
- if ($post_is_available && $context['duplicated_post_edit_link']) {
121
- $message = _x(
122
- 'Cloned {duplicated_post_post_type_singular_name} <a href="{duplicated_post_edit_link}">"{duplicated_post_title}"</a> to <a href="{new_post_edit_link}">a new {duplicated_post_post_type_singular_name}</a>',
123
- 'Logger: Plugin Duplicate Post',
124
- 'simple-history'
125
- );
126
- }
127
-
128
- $context['new_post_edit_link'] = isset($context['new_post_edit_link'])
129
- ? esc_html($context['new_post_edit_link'])
130
- : '';
131
-
132
- $context['duplicated_post_edit_link'] = isset($context['duplicated_post_edit_link'])
133
- ? esc_html($context['duplicated_post_edit_link'])
134
- : '';
135
-
136
- $context['duplicated_post_title'] = isset($context['duplicated_post_title'])
137
- ? esc_html($context['duplicated_post_title'])
138
- : '';
139
-
140
- $context['duplicated_post_title'] = isset($context['duplicated_post_title'])
141
- ? esc_html($context['duplicated_post_title'])
142
- : '';
143
-
144
- $context['duplicated_post_post_type_singular_name'] = isset(
145
- $context['duplicated_post_post_type_singular_name']
146
- )
147
- ? esc_html($context['duplicated_post_post_type_singular_name'])
148
- : '';
149
-
150
- return $this->interpolate($message, $context, $row);
151
- }
152
- }
153
  } // End if().
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for the Duplicate Post plugin
9
  * @package SimpleHistory
10
  * @since 2.13
11
  */
12
+ if ( ! class_exists( 'Plugin_DuplicatePost' ) ) {
13
+ class Plugin_DuplicatePost extends SimpleLogger {
14
+
15
+ public $slug = __CLASS__;
16
+
17
+ public function getInfo() {
18
+ $arr_info = array(
19
+ 'name' => 'Plugin Duplicate Posts',
20
+ 'description' => _x(
21
+ 'Logs posts and pages cloned using plugin Duplicate Post',
22
+ 'Logger: Plugin Duplicate Post',
23
+ 'simple-history'
24
+ ),
25
+ 'name_via' => _x( 'Using plugin Duplicate Posts', 'Logger: Plugin Duplicate Post', 'simple-history' ),
26
+ 'capability' => 'manage_options',
27
+ 'messages' => array(
28
+ 'post_duplicated' => _x(
29
+ 'Cloned "{duplicated_post_title}" to a new post',
30
+ 'Logger: Plugin Duplicate Post',
31
+ 'simple-history'
32
+ ),
33
+ ),
34
+ );
35
+
36
+ return $arr_info;
37
+ }
38
+
39
+ public function loaded() {
40
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
41
+
42
+ $isPluginActive = is_plugin_active( 'duplicate-post/duplicate-post.php' );
43
+
44
+ if ( ! $isPluginActive ) {
45
+ return;
46
+ }
47
+
48
+ // When a copy have been made of a post or page
49
+ // the action 'dp_duplicate_page' or 'dp_duplicate_post'
50
+ // is fired with args $new_post_id, $post, $status.
51
+ // We add actions with prio 20 so we probably run after
52
+ // the plugins own
53
+ add_action( 'dp_duplicate_post', array( $this, 'onDpDuplicatePost' ), 100, 3 );
54
+ add_action( 'dp_duplicate_page', array( $this, 'onDpDuplicatePost' ), 100, 3 );
55
+ }
56
+
57
+ /**
58
+ * A post or page was duplicated
59
+ *
60
+ * @param $new_post_id
61
+ * @param $post old post that a copy was made of
62
+ * @param $status
63
+ */
64
+ public function onDpDuplicatePost( $newPostID, $post, $status ) {
65
+ $new_post = get_post( $newPostID );
66
+
67
+ $context = array(
68
+ 'new_post_title' => $new_post->post_title,
69
+ 'new_post_id' => $new_post->ID,
70
+ 'duplicated_post_title' => $post->post_title,
71
+ 'duplicated_post_id' => $post->ID,
72
+ // "duplicate_new_post_id" => $newPostID,
73
+ // "status" => $status
74
+ );
75
+
76
+ $this->infoMessage( 'post_duplicated', $context );
77
+ }
78
+
79
+ /**
80
+ * Modify plain output to include link to post
81
+ */
82
+ public function getLogRowPlainTextOutput( $row ) {
83
+ $context = $row->context;
84
+ $new_post_id = isset( $context['new_post_id'] ) ? $context['new_post_id'] : null;
85
+ $duplicated_post_id = isset( $context['duplicated_post_id'] ) ? $context['duplicated_post_id'] : null;
86
+ $duplicated_post_title = isset( $context['duplicated_post_title'] )
87
+ ? $context['duplicated_post_title']
88
+ : null;
89
+ $message_key = isset( $context['_message_key'] ) ? $context['_message_key'] : null;
90
+
91
+ $message = $row->message;
92
+
93
+ // Check if post still is available
94
+ // It will return a WP_Post Object if post still is in system
95
+ // If post is deleted from trash (not just moved there), then null is returned
96
+ $postDuplicated = get_post( $duplicated_post_id );
97
+ $post_is_available = is_a( $postDuplicated, 'WP_Post' );
98
+
99
+ // Try to get singular name
100
+ $post_type = isset( $postDuplicated->post_type ) ? $postDuplicated->post_type : '';
101
+ $post_type_obj = get_post_type_object( $post_type );
102
+
103
+ if ( ! is_null( $post_type_obj ) ) {
104
+ if ( ! empty( $post_type_obj->labels->singular_name ) ) {
105
+ $context['duplicated_post_post_type_singular_name'] = strtolower(
106
+ $post_type_obj->labels->singular_name
107
+ );
108
+ }
109
+ }
110
+
111
+ $context['duplicated_post_edit_link'] = get_edit_post_link( $duplicated_post_id );
112
+ $context['new_post_edit_link'] = get_edit_post_link( $new_post_id );
113
+
114
+ // If post is not available any longer then we can't link to it, so keep plain message then
115
+ // Also keep plain format if user is not allowed to edit post (edit link is empty)
116
+ if ( $post_is_available && $context['duplicated_post_edit_link'] ) {
117
+ $message = _x(
118
+ 'Cloned {duplicated_post_post_type_singular_name} <a href="{duplicated_post_edit_link}">"{duplicated_post_title}"</a> to <a href="{new_post_edit_link}">a new {duplicated_post_post_type_singular_name}</a>',
119
+ 'Logger: Plugin Duplicate Post',
120
+ 'simple-history'
121
+ );
122
+ }
123
+
124
+ $context['new_post_edit_link'] = isset( $context['new_post_edit_link'] )
125
+ ? esc_html( $context['new_post_edit_link'] )
126
+ : '';
127
+
128
+ $context['duplicated_post_edit_link'] = isset( $context['duplicated_post_edit_link'] )
129
+ ? esc_html( $context['duplicated_post_edit_link'] )
130
+ : '';
131
+
132
+ $context['duplicated_post_title'] = isset( $context['duplicated_post_title'] )
133
+ ? esc_html( $context['duplicated_post_title'] )
134
+ : '';
135
+
136
+ $context['duplicated_post_title'] = isset( $context['duplicated_post_title'] )
137
+ ? esc_html( $context['duplicated_post_title'] )
138
+ : '';
139
+
140
+ $context['duplicated_post_post_type_singular_name'] = isset(
141
+ $context['duplicated_post_post_type_singular_name']
142
+ )
143
+ ? esc_html( $context['duplicated_post_post_type_singular_name'] )
144
+ : '';
145
+
146
+ return $this->interpolate( $message, $context, $row );
147
+ }
148
+ }
 
 
 
 
149
  } // End if().
loggers/Plugin_LimitLoginAttempts.php CHANGED
@@ -1,231 +1,228 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logger for the (old but still) very popular plugin Limit Login Attempts
7
  * https://sv.wordpress.org/plugins/limit-login-attempts/
8
  */
9
- if (! class_exists('Plugin_LimitLoginAttempts')) {
10
- class Plugin_LimitLoginAttempts extends SimpleLogger
11
- {
12
-
13
- public $slug = __CLASS__;
14
-
15
- public function getInfo()
16
- {
17
-
18
- $arr_info = array(
19
- 'name' => 'Plugin Limit Login Attempts',
20
- 'description' => _x('Logs failed login attempts, lockouts, and configuration changes made in the plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
21
- 'name_via' => _x('Using plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
22
- 'capability' => 'manage_options',
23
- 'messages' => array(
24
- // 'user_locked_out' => _x( 'User locked out', "Logger: Plugin Limit Login Attempts", "simple-history" ),
25
- 'failed_login_whitelisted' => _x('Failed login attempt from whitelisted IP', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
26
- 'failed_login' => _x('Was locked out because too many failed login attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
27
- 'cleared_ip_log' => _x('Cleared IP log', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
28
- 'reseted_lockout_count' => _x('Reseted lockout count', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
29
- 'cleared_current_lockouts' => _x('Cleared current lockouts', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
30
- 'updated_options' => _x('Updated options', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
31
- ),
32
- /*
33
- "labels" => array(
34
- "search" => array(
35
- "label" => _x( "Limit Login Attempts", "Logger: Plugin Limit Login Attempts", "simple-history" ),
36
- "options" => array(
37
- _x( "xxxPages not found", "User logger: 404", "simple-history" ) => array(
38
- "page_not_found",
39
- ),
40
- ),
41
- ), // end search
42
- ),*/ // end labels
43
- );
44
-
45
- return $arr_info;
46
- }
47
-
48
- public function loaded()
49
- {
50
-
51
- require_once(ABSPATH . 'wp-admin/includes/plugin.php');
52
-
53
- $pluginFilePath = 'limit-login-attempts/limit-login-attempts.php';
54
- $isPluginActive = is_plugin_active($pluginFilePath);
55
-
56
- // Only continue to add filters if plugin is active.
57
- // This minimise the risk of plugin errors, because plugin
58
- // has been forked to new versions.
59
- if (! $isPluginActive) {
60
- return;
61
- }
62
-
63
- add_filter('pre_option_limit_login_lockouts_total', array( $this, 'on_option_limit_login_lockouts_total' ), 10, 1);
64
-
65
- add_action('load-settings_page_limit-login-attempts', array( $this, 'on_load_settings_page' ), 10, 1);
66
- }
67
-
68
- /**
69
- * Fired when plugin options screen is loaded
70
- */
71
- public function on_load_settings_page($a)
72
- {
73
-
74
- if ($_POST && wp_verify_nonce($_POST['_wpnonce'], 'limit-login-attempts-options')) {
75
- // Settings saved
76
- if (isset($_POST['clear_log'])) {
77
- $this->noticeMessage('cleared_ip_log');
78
- }
79
-
80
- if (isset($_POST['reset_total'])) {
81
- $this->noticeMessage('reseted_lockout_count');
82
- }
83
-
84
- if (isset($_POST['reset_current'])) {
85
- $this->noticeMessage('cleared_current_lockouts');
86
- }
87
-
88
- if (isset($_POST['update_options'])) {
89
- $options = array(
90
- 'client_type' => sanitize_text_field($_POST['client_type']),
91
- 'allowed_retries' => sanitize_text_field($_POST['allowed_retries']),
92
- 'lockout_duration' => sanitize_text_field($_POST['lockout_duration']) * 60,
93
- 'valid_duration' => sanitize_text_field($_POST['valid_duration']) * 3600,
94
- 'allowed_lockouts' => sanitize_text_field($_POST['allowed_lockouts']),
95
- 'long_duration' => sanitize_text_field($_POST['long_duration']) * 3600,
96
- 'email_after' => sanitize_text_field($_POST['email_after']),
97
- 'cookies' => (isset($_POST['cookies']) && $_POST['cookies'] == '1') ? 'yes' : 'no',
98
- );
99
-
100
- $v = array();
101
- if (isset($_POST['lockout_notify_log'])) {
102
- $v[] = 'log';
103
- }
104
- if (isset($_POST['lockout_notify_email'])) {
105
- $v[] = 'email';
106
- }
107
- $lockout_notify = implode(',', $v);
108
- $options['lockout_notify'] = $lockout_notify;
109
-
110
- $this->noticeMessage('updated_options', array(
111
- 'options' => $options,
112
- ));
113
- }
114
- }// End if().
115
- }
116
-
117
- /**
118
- * When option value is updated
119
- * do same checks as plugin itself does
120
- * and log if we match something
121
- */
122
- public function on_option_limit_login_lockouts_total($value)
123
- {
124
-
125
- global $limit_login_just_lockedout;
126
-
127
- if (! $limit_login_just_lockedout) {
128
- return $value;
129
- }
130
-
131
- $ip = limit_login_get_address();
132
- $whitelisted = is_limit_login_ip_whitelisted($ip);
133
-
134
- $retries = get_option('limit_login_retries');
135
- if (! is_array($retries)) {
136
- $retries = array();
137
- }
138
-
139
- if (isset($retries[ $ip ]) && ( ( $retries[ $ip ] / limit_login_option('allowed_retries') ) % limit_login_option('notify_email_after') ) != 0) {
140
- // $this->notice( "user locked out but don't log" );
141
- // return;
142
- }
143
-
144
- /* Format message. First current lockout duration */
145
- $lockout_type = '';
146
- if (! isset($retries[ $ip ])) {
147
- /* longer lockout */
148
- $lockout_type = 'longer';
149
- $count = limit_login_option('allowed_retries') * limit_login_option('allowed_lockouts');
150
- $lockouts = limit_login_option('allowed_lockouts');
151
- $time = round(limit_login_option('long_duration') / 3600);
152
- // $when = sprintf( _n( '%d hour', '%d hours', $time, "Logger: Plugin Limit Login Attempts", 'limit-login-attempts' ), $time );
153
- } else {
154
- /* normal lockout */
155
- $lockout_type = 'normal';
156
- $count = $retries[ $ip ];
157
- $lockouts = floor($count / limit_login_option('allowed_retries'));
158
- $time = round(limit_login_option('lockout_duration') / 60);
159
- // $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts' ), $time );
160
- }
161
-
162
- if ($whitelisted) {
163
- // $subject = __( "Failed login attempts from whitelisted IP", 'limit-login-attempts' );
164
- $message_key = 'failed_login_whitelisted';
165
- } else {
166
- // $subject = __( "Too many failed login attempts", 'limit-login-attempts' );
167
- $message_key = 'failed_login';
168
- }
169
-
170
- $this->noticeMessage($message_key, array(
171
- '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
172
- 'value' => $value,
173
- 'limit_login_just_lockedout' => $limit_login_just_lockedout,
174
- // "retries" => $retries,
175
- // "whitelisted" => $whitelisted, // bool, true | false
176
- // "subject" => $subject,
177
- // "message" => $message,
178
- 'count' => $count, // num of failed login attempts before block
179
- 'time' => $time, // duration in minutes for block
180
- 'lockouts' => $lockouts,
181
- 'ip' => $ip,
182
- 'lockout_type' => $lockout_type,
183
- ));
184
-
185
- return $value;
186
- }
187
-
188
-
189
- /**
190
- * Add some extra info
191
- */
192
- public function getLogRowDetailsOutput($row)
193
- {
194
-
195
- $output = '';
196
-
197
- $context = isset($row->context) ? $row->context : array();
198
-
199
- $message_key = $row->context_message_key;
200
-
201
- if ('failed_login' == $message_key) {
202
- $count = $context['count'];
203
- $lockouts = $context['lockouts'];
204
- $ip = $context['ip'];
205
- // $whitelisted = $context["whitelisted"];
206
- $lockout_type = $context['lockout_type'];
207
- $time = $context['time'];
208
-
209
- $output .= sprintf(
210
- '<p>' . _x('%1$d failed login attempts (%2$d lockout(s)) from IP: %3$s', 'Logger: Plugin Limit Login Attempts', 'simple-history') . '</p>',
211
- $count, // 1
212
- $lockouts, // 2
213
- $ip // 3
214
- );
215
-
216
- if ('longer' == $lockout_type) {
217
- $when = sprintf(_nx('%d hour', '%d hours', $time, 'Logger: Plugin Limit Login Attempts', 'limit-login-attempts'), $time);
218
- } elseif ('normal' == $lockout_type) {
219
- $when = sprintf(_nx('%d minute', '%d minutes', $time, 'Logger: Plugin Limit Login Attempts', 'limit-login-attempts'), $time);
220
- }
221
-
222
- $output .= '<p>' . sprintf(
223
- _x('IP was blocked for %1$s', 'Logger: Plugin Limit Login Attempts', 'simple-history'),
224
- $when // 1
225
- ) . '</p>';
226
- }
227
-
228
- return $output;
229
- }
230
- }
231
  } // End if().
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for the (old but still) very popular plugin Limit Login Attempts
7
  * https://sv.wordpress.org/plugins/limit-login-attempts/
8
  */
9
+ if ( ! class_exists( 'Plugin_LimitLoginAttempts' ) ) {
10
+ class Plugin_LimitLoginAttempts extends SimpleLogger {
11
+
12
+
13
+ public $slug = __CLASS__;
14
+
15
+ public function getInfo() {
16
+
17
+ $arr_info = array(
18
+ 'name' => 'Plugin Limit Login Attempts',
19
+ 'description' => _x( 'Logs failed login attempts, lockouts, and configuration changes made in the plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
20
+ 'name_via' => _x( 'Using plugin Limit Login Attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
21
+ 'capability' => 'manage_options',
22
+ 'messages' => array(
23
+ // 'user_locked_out' => _x( 'User locked out', "Logger: Plugin Limit Login Attempts", "simple-history" ),
24
+ 'failed_login_whitelisted' => _x( 'Failed login attempt from whitelisted IP', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
25
+ 'failed_login' => _x( 'Was locked out because too many failed login attempts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
26
+ 'cleared_ip_log' => _x( 'Cleared IP log', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
27
+ 'reseted_lockout_count' => _x( 'Reseted lockout count', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
28
+ 'cleared_current_lockouts' => _x( 'Cleared current lockouts', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
29
+ 'updated_options' => _x( 'Updated options', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
30
+ ),
31
+ /*
32
+ "labels" => array(
33
+ "search" => array(
34
+ "label" => _x( "Limit Login Attempts", "Logger: Plugin Limit Login Attempts", "simple-history" ),
35
+ "options" => array(
36
+ _x( "xxxPages not found", "User logger: 404", "simple-history" ) => array(
37
+ "page_not_found",
38
+ ),
39
+ ),
40
+ ), // end search
41
+ ),*/ // end labels
42
+ );
43
+
44
+ return $arr_info;
45
+ }
46
+
47
+ public function loaded() {
48
+
49
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
50
+
51
+ $pluginFilePath = 'limit-login-attempts/limit-login-attempts.php';
52
+ $isPluginActive = is_plugin_active( $pluginFilePath );
53
+
54
+ // Only continue to add filters if plugin is active.
55
+ // This minimise the risk of plugin errors, because plugin
56
+ // has been forked to new versions.
57
+ if ( ! $isPluginActive ) {
58
+ return;
59
+ }
60
+
61
+ add_filter( 'pre_option_limit_login_lockouts_total', array( $this, 'on_option_limit_login_lockouts_total' ), 10, 1 );
62
+
63
+ add_action( 'load-settings_page_limit-login-attempts', array( $this, 'on_load_settings_page' ), 10, 1 );
64
+ }
65
+
66
+ /**
67
+ * Fired when plugin options screen is loaded
68
+ */
69
+ public function on_load_settings_page( $a ) {
70
+
71
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
72
+ if ( $_POST && wp_verify_nonce( $_POST['_wpnonce'], 'limit-login-attempts-options' ) ) {
73
+ // Settings saved
74
+ if ( isset( $_POST['clear_log'] ) ) {
75
+ $this->noticeMessage( 'cleared_ip_log' );
76
+ }
77
+
78
+ if ( isset( $_POST['reset_total'] ) ) {
79
+ $this->noticeMessage( 'reseted_lockout_count' );
80
+ }
81
+
82
+ if ( isset( $_POST['reset_current'] ) ) {
83
+ $this->noticeMessage( 'cleared_current_lockouts' );
84
+ }
85
+
86
+ if ( isset( $_POST['update_options'] ) ) {
87
+ $options = array(
88
+ 'client_type' => sanitize_text_field( $_POST['client_type'] ),
89
+ 'allowed_retries' => sanitize_text_field( $_POST['allowed_retries'] ),
90
+ 'lockout_duration' => sanitize_text_field( $_POST['lockout_duration'] ) * 60,
91
+ 'valid_duration' => sanitize_text_field( $_POST['valid_duration'] ) * 3600,
92
+ 'allowed_lockouts' => sanitize_text_field( $_POST['allowed_lockouts'] ),
93
+ 'long_duration' => sanitize_text_field( $_POST['long_duration'] ) * 3600,
94
+ 'email_after' => sanitize_text_field( $_POST['email_after'] ),
95
+ 'cookies' => ( isset( $_POST['cookies'] ) && $_POST['cookies'] == '1' ) ? 'yes' : 'no',
96
+ );
97
+
98
+ $v = array();
99
+ if ( isset( $_POST['lockout_notify_log'] ) ) {
100
+ $v[] = 'log';
101
+ }
102
+ if ( isset( $_POST['lockout_notify_email'] ) ) {
103
+ $v[] = 'email';
104
+ }
105
+ $lockout_notify = implode( ',', $v );
106
+ $options['lockout_notify'] = $lockout_notify;
107
+
108
+ $this->noticeMessage(
109
+ 'updated_options',
110
+ array(
111
+ 'options' => $options,
112
+ )
113
+ );
114
+ }
115
+ }// End if().
116
+ }
117
+
118
+ /**
119
+ * When option value is updated
120
+ * do same checks as plugin itself does
121
+ * and log if we match something
122
+ */
123
+ public function on_option_limit_login_lockouts_total( $value ) {
124
+
125
+ global $limit_login_just_lockedout;
126
+
127
+ if ( ! $limit_login_just_lockedout ) {
128
+ return $value;
129
+ }
130
+
131
+ $ip = limit_login_get_address();
132
+ $whitelisted = is_limit_login_ip_whitelisted( $ip );
133
+
134
+ $retries = get_option( 'limit_login_retries' );
135
+ if ( ! is_array( $retries ) ) {
136
+ $retries = array();
137
+ }
138
+
139
+ /* Format message. First current lockout duration */
140
+ $lockout_type = '';
141
+ if ( ! isset( $retries[ $ip ] ) ) {
142
+ /* longer lockout */
143
+ $lockout_type = 'longer';
144
+ $count = limit_login_option( 'allowed_retries' ) * limit_login_option( 'allowed_lockouts' );
145
+ $lockouts = limit_login_option( 'allowed_lockouts' );
146
+ $time = round( limit_login_option( 'long_duration' ) / 3600 );
147
+ // $when = sprintf( _n( '%d hour', '%d hours', $time, "Logger: Plugin Limit Login Attempts", 'limit-login-attempts' ), $time );
148
+ } else {
149
+ /* normal lockout */
150
+ $lockout_type = 'normal';
151
+ $count = $retries[ $ip ];
152
+ $lockouts = floor( $count / limit_login_option( 'allowed_retries' ) );
153
+ $time = round( limit_login_option( 'lockout_duration' ) / 60 );
154
+ // $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts' ), $time );
155
+ }
156
+
157
+ if ( $whitelisted ) {
158
+ // $subject = __( "Failed login attempts from whitelisted IP", 'limit-login-attempts' );
159
+ $message_key = 'failed_login_whitelisted';
160
+ } else {
161
+ // $subject = __( "Too many failed login attempts", 'limit-login-attempts' );
162
+ $message_key = 'failed_login';
163
+ }
164
+
165
+ $this->noticeMessage(
166
+ $message_key,
167
+ array(
168
+ '_initiator' => SimpleLoggerLogInitiators::WEB_USER,
169
+ 'value' => $value,
170
+ 'limit_login_just_lockedout' => $limit_login_just_lockedout,
171
+ // "retries" => $retries,
172
+ // "whitelisted" => $whitelisted, // bool, true | false
173
+ // "subject" => $subject,
174
+ // "message" => $message,
175
+ 'count' => $count, // num of failed login attempts before block
176
+ 'time' => $time, // duration in minutes for block
177
+ 'lockouts' => $lockouts,
178
+ 'ip' => $ip,
179
+ 'lockout_type' => $lockout_type,
180
+ )
181
+ );
182
+
183
+ return $value;
184
+ }
185
+
186
+
187
+ /**
188
+ * Add some extra info
189
+ */
190
+ public function getLogRowDetailsOutput( $row ) {
191
+
192
+ $output = '';
193
+
194
+ $context = isset( $row->context ) ? $row->context : array();
195
+
196
+ $message_key = $row->context_message_key;
197
+
198
+ if ( 'failed_login' == $message_key ) {
199
+ $count = $context['count'];
200
+ $lockouts = $context['lockouts'];
201
+ $ip = $context['ip'];
202
+ // $whitelisted = $context["whitelisted"];
203
+ $lockout_type = $context['lockout_type'];
204
+ $time = $context['time'];
205
+
206
+ $output .= sprintf(
207
+ '<p>' . _x( '%1$d failed login attempts (%2$d lockout(s)) from IP: %3$s', 'Logger: Plugin Limit Login Attempts', 'simple-history' ) . '</p>',
208
+ $count, // 1
209
+ $lockouts, // 2
210
+ $ip // 3
211
+ );
212
+
213
+ if ( 'longer' == $lockout_type ) {
214
+ $when = sprintf( _nx( '%d hour', '%d hours', $time, 'Logger: Plugin Limit Login Attempts', 'simple-history' ), $time );
215
+ } elseif ( 'normal' == $lockout_type ) {
216
+ $when = sprintf( _nx( '%d minute', '%d minutes', $time, 'Logger: Plugin Limit Login Attempts', 'simple-history' ), $time );
217
+ }
218
+
219
+ $output .= '<p>' . sprintf(
220
+ _x( 'IP was blocked for %1$s', 'Logger: Plugin Limit Login Attempts', 'simple-history' ),
221
+ $when // 1
222
+ ) . '</p>';
223
+ }
224
+
225
+ return $output;
226
+ }
227
+ }
 
 
 
228
  } // End if().
loggers/Plugin_Redirection.php CHANGED
@@ -1,410 +1,398 @@
1
  <?php
2
 
3
- defined('ABSPATH') || die();
4
 
5
  /**
6
  * Logger for the Redirection plugin
7
  * https://wordpress.org/plugins/redirection/
8
  */
9
- if (! class_exists('Plugin_Redirection')) {
10
- /**
11
- * Class to log things from the Redirection plugin.
12
- */
13
- class Plugin_Redirection extends SimpleLogger
14
- {
15
-
16
- /**
17
- * Logger slug.
18
- *
19
- * @var string
20
- */
21
- public $slug = __CLASS__;
22
-
23
- /**
24
- * Return info about logger.
25
- *
26
- * @return array Array with plugin info.
27
- */
28
- public function getInfo()
29
- {
30
-
31
- $arr_info = array(
32
- 'name' => 'Redirection',
33
- 'description' => _x('Text', 'Logger: Redirection', 'simple-history'),
34
- 'name_via' => _x('In plugin Redirection', 'Logger: Redirection', 'simple-history'),
35
- 'capability' => 'manage_options',
36
- 'messages' => array(
37
- 'redirection_redirection_added' => _x('Added a redirection for URL "{source_url}"', 'Logger: Redirection', 'simple-history'),
38
- 'redirection_redirection_edited' => _x('Edited redirection for URL "{prev_source_url}"', 'Logger: Redirection', 'simple-history'),
39
- 'redirection_redirection_enabled' => _x('Enabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history'),
40
- 'redirection_redirection_disabled' => _x('Disabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history'),
41
- 'redirection_redirection_deleted' => _x('Deleted redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history'),
42
- 'redirection_options_saved' => _x('Updated redirection options', 'Logger: Redirection', 'simple-history'),
43
- 'redirection_options_removed_all' => _x('Removed all redirection options and deactivated plugin', 'Logger: Redirection', 'simple-history'),
44
- 'redirection_group_added' => _x('Added redirection group "{group_name}"', 'Logger: Redirection', 'simple-history'),
45
- 'redirection_group_enabled' => _x('Enabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history'),
46
- 'redirection_group_disabled' => _x('Disabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history'),
47
- 'redirection_group_deleted' => _x('Deleted {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history'),
48
- ),
49
-
50
- /*
51
- "labels" => array(
52
- "search" => array(
53
- "label" => _x("Plugin Redirection", "Logger: Redirection", "simple-history"),
54
- "label_all" => _x("All posts & pages activity", "Logger: Redirection", "simple-history"),
55
- "options" => array(
56
- _x("Posts created", "Logger: Redirection", "simple-history") => array(
57
- "post_created"
58
- ),
59
- _x("Posts updated", "Logger: Redirection", "simple-history") => array(
60
- "post_updated"
61
- ),
62
- _x("Posts trashed", "Logger: Redirection", "simple-history") => array(
63
- "post_trashed"
64
- ),
65
- _x("Posts deleted", "Logger: Redirection", "simple-history") => array(
66
- "post_deleted"
67
- ),
68
- _x("Posts restored", "Logger: Redirection", "simple-history") => array(
69
- "post_restored"
70
- ),
71
- )
72
- ) // end search array
73
- ) // end labels
74
- */
75
- );
76
-
77
- return $arr_info;
78
- }
79
-
80
- /**
81
- * Called when logger is loaded.
82
- */
83
- public function loaded()
84
- {
85
- // Redirection plugin uses the WP REST API, so catch when requests do the API is done.
86
- // We use filter *_before_callbacks so we can access the old title
87
- // of the Redirection object, i.e. before new values are saved.
88
- add_filter('rest_request_before_callbacks', array( $this, 'on_rest_request_before_callbacks' ), 10, 3);
89
- }
90
-
91
- /**
92
- * Fired when WP REST API call is done.
93
- *
94
- * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
95
- * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
96
- * @param WP_REST_Request $request Request used to generate the response.
97
- *
98
- * @return WP_HTTP_Response $response
99
- */
100
- public function on_rest_request_before_callbacks($response, $handler, $request)
101
- {
102
- // Callback must be set.
103
- if (! isset($handler['callback'])) {
104
- return $response;
105
- }
106
-
107
- $callback = $handler['callback'];
108
-
109
- $callable_name = sh_get_callable_name($callback);
110
-
111
- $ok_redirection_api_callable_names = array(
112
- 'Redirection_Api_Redirect::route_bulk',
113
- 'Redirection_Api_Redirect::route_create',
114
- 'Redirection_Api_Redirect::route_update',
115
- 'Redirection_Api_Group::route_create',
116
- 'Redirection_Api_Group::route_bulk',
117
- 'Redirection_Api_Settings::route_save_settings',
118
- );
119
-
120
- // Bail directly if this is not a Redirection API call.
121
- if (! in_array($callable_name, $ok_redirection_api_callable_names)) {
122
- return $response;
123
- }
124
-
125
- if ('Redirection_Api_Redirect::route_create' === $callable_name) {
126
- $this->log_redirection_add($request);
127
- } elseif ('Redirection_Api_Redirect::route_update' === $callable_name) {
128
- $this->log_redirection_edit($request);
129
- } elseif ('Redirection_Api_Redirect::route_bulk' === $callable_name) {
130
- $bulk_action = $request->get_param('bulk');
131
- $bulk_items = $request->get_param('items');
132
-
133
- if (!is_array($bulk_items)) {
134
- $bulk_items = explode(',', $bulk_items);
135
- }
136
-
137
- if (is_array($bulk_items)) {
138
- $bulk_items = array_map('intval', $bulk_items);
139
- }
140
-
141
- if (empty($bulk_items)) {
142
- return $response;
143
- }
144
-
145
- if ('enable' === $bulk_action) {
146
- $this->log_redirection_enable_or_disable($request, $bulk_items);
147
- } elseif ('disable' === $bulk_action) {
148
- $this->log_redirection_enable_or_disable($request, $bulk_items);
149
- } elseif ('delete' === $bulk_action) {
150
- $this->log_redirection_delete($request, $bulk_items);
151
- }
152
- } elseif ('Redirection_Api_Group::route_create' === $callable_name) {
153
- $this->log_group_add($request);
154
- } elseif ('Redirection_Api_Group::route_bulk' === $callable_name) {
155
- $bulk_action = $request->get_param('bulk');
156
-
157
- $bulk_items = $request->get_param('items');
158
- $bulk_items = explode(',', $bulk_items);
159
-
160
- if (is_array($bulk_items)) {
161
- $bulk_items = array_map('intval', $bulk_items);
162
- }
163
-
164
- if (empty($bulk_items)) {
165
- return $response;
166
- }
167
-
168
- if ('enable' === $bulk_action) {
169
- $this->log_group_enable_or_disable($request, $bulk_items);
170
- } elseif ('disable' === $bulk_action) {
171
- $this->log_group_enable_or_disable($request, $bulk_items);
172
- } elseif ('delete' === $bulk_action) {
173
- $this->log_group_delete($request, $bulk_items);
174
- }
175
- } elseif ('Redirection_Api_Settings::route_save_settings' == $callable_name) {
176
- $this->log_options_save($request);
177
- }
178
-
179
- return $response;
180
- }
181
-
182
- /**
183
- * Log when a Redirection group is deleted.
184
- *
185
- * @param object $req Request.
186
- * @param array $bulk_items Array with item ids.
187
- */
188
- public function log_group_delete($req, $bulk_items)
189
- {
190
- $context = array(
191
- 'items' => $bulk_items,
192
- 'items_count' => count($bulk_items),
193
- );
194
-
195
- $this->infoMessage(
196
- 'redirection_group_deleted',
197
- $context
198
- );
199
- }
200
-
201
- /**
202
- * Log when a Redirection grouop is added
203
- *
204
- * @param WP_REST_Request $req Request.
205
- */
206
- public function log_group_add($req)
207
- {
208
- $group_name = $req->get_param('name');
209
-
210
- if (! $group_name) {
211
- return;
212
- }
213
-
214
- $context = array(
215
- 'group_name' => $group_name,
216
- );
217
-
218
- $this->infoMessage(
219
- 'redirection_group_added',
220
- $context
221
- );
222
- }
223
-
224
- /**
225
- * Log enabling and disabling of redirection groups.
226
- *
227
- * @param object $req Request.
228
- * @param array $bulk_items Array with item ids.
229
- */
230
- public function log_group_enable_or_disable($req, $bulk_items)
231
- {
232
- $bulk_action = $req->get_param('bulk');
233
-
234
- $message_key = 'enable' === $bulk_action ? 'redirection_group_enabled' : 'redirection_group_disabled';
235
-
236
- $context = array(
237
- 'items' => $bulk_items,
238
- 'items_count' => count($bulk_items),
239
- );
240
-
241
- $this->infoMessage(
242
- $message_key,
243
- $context
244
- );
245
- }
246
-
247
- /**
248
- * Log when options are saved.
249
- *
250
- * @param object $req Request.
251
- */
252
- protected function log_options_save($req)
253
- {
254
- $this->infoMessage('redirection_options_saved');
255
- }
256
-
257
- /**
258
- * Log the deletion of a redirection.
259
- *
260
- * @param object $req Request.
261
- * @param array $bulk_items Array with item ids.
262
- */
263
- protected function log_redirection_delete($req, $bulk_items)
264
- {
265
- $context = array(
266
- 'items' => $bulk_items,
267
- 'items_count' => count($bulk_items),
268
- );
269
-
270
- $message_key = 'redirection_redirection_deleted';
271
-
272
- $this->infoMessage(
273
- $message_key,
274
- $context
275
- );
276
- }
277
-
278
- /**
279
- * Log enable or disable of items.
280
- *
281
- * @param Object $req Req.
282
- * @param Array $bulk_items Array.
283
- */
284
- protected function log_redirection_enable_or_disable($req, $bulk_items)
285
- {
286
- $bulk_action = $req->get_param('bulk');
287
-
288
- $message_key = 'enable' === $bulk_action ? 'redirection_redirection_enabled' : 'redirection_redirection_disabled';
289
-
290
- $context = array(
291
- 'items' => $bulk_items,
292
- 'items_count' => count($bulk_items),
293
- );
294
-
295
- $this->infoMessage(
296
- $message_key,
297
- $context
298
- );
299
- }
300
-
301
- /**
302
- * Log when a Redirection is added.
303
- *
304
- * @param WP_REST_Request $req Request.
305
- */
306
- protected function log_redirection_add($req)
307
- {
308
- $action_data = $req->get_param('action_data');
309
-
310
- if (! $action_data || ! is_array($action_data)) {
311
- return false;
312
- }
313
-
314
- $context = array(
315
- 'source_url' => $req->get_param('url'),
316
- 'target_url' => $action_data['url'],
317
- );
318
-
319
- $this->infoMessage('redirection_redirection_added', $context);
320
- }
321
-
322
- /**
323
- * Log when a Redirection is changed.
324
- *
325
- * @param WP_REST_Request $req Request.
326
- */
327
- protected function log_redirection_edit($req)
328
- {
329
- $action_data = $req->get_param('action_data');
330
-
331
- if (! $action_data || ! is_array($action_data)) {
332
- return false;
333
- }
334
-
335
- $message_key = 'redirection_redirection_edited';
336
-
337
- $redirection_id = $req->get_param('id');
338
-
339
- $context = array(
340
- 'new_source_url' => $req->get_param('url'),
341
- 'new_target' => $action_data,
342
- 'redirection_id' => $redirection_id,
343
- );
344
-
345
- // Get old values.
346
- $redirection_item = Red_Item::get_by_id($redirection_id);
347
-
348
- if (false !== $redirection_item) {
349
- $context['prev_source_url'] = $redirection_item->get_url();
350
- $context['prev_target'] = maybe_unserialize($redirection_item->get_action_data());
351
- }
352
-
353
- $this->infoMessage(
354
- $message_key,
355
- $context
356
- );
357
- }
358
-
359
- /**
360
- * Return more info about an logged redirection event.
361
- *
362
- * @param array $row Row with info.
363
- */
364
- public function getLogRowDetailsOutput($row)
365
- {
366
- $context = $row->context;
367
- $message_key = $context['_message_key'];
368
-
369
- $out = '';
370
-
371
- if ('redirection_redirection_edited' === $message_key) {
372
- if ($context['new_source_url'] !== $context['prev_source_url']) {
373
- $diff_table_output = sprintf(
374
- '<tr>
375
  <td>%1$s</td>
376
  <td>
377
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
378
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
379
  </td>
380
  </tr>',
381
- esc_html_x('Source URL', 'Logger: Redirection', 'simple-history'), // 1
382
- esc_html($context['new_source_url']), // 2
383
- esc_html($context['prev_source_url']) // 3
384
- );
385
 
386
- $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
387
- }
388
 
389
- if ($context['new_target'] !== $context['prev_target']) {
390
- $diff_table_output = sprintf(
391
- '<tr>
392
  <td>%1$s</td>
393
  <td>
394
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
395
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
396
  </td>
397
  </tr>',
398
- esc_html_x('Target', 'Logger: Redirection', 'simple-history'), // 1
399
- esc_html($context['new_target']), // 2
400
- esc_html($context['prev_target']) // 3
401
- );
402
-
403
- $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
404
- }
405
- }
406
-
407
- return $out;
408
- }
409
- }
410
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logger for the Redirection plugin
7
  * https://wordpress.org/plugins/redirection/
8
  */
9
+ if ( ! class_exists( 'Plugin_Redirection' ) ) {
10
+ /**
11
+ * Class to log things from the Redirection plugin.
12
+ */
13
+ class Plugin_Redirection extends SimpleLogger {
14
+
15
+
16
+ /**
17
+ * Logger slug.
18
+ *
19
+ * @var string
20
+ */
21
+ public $slug = __CLASS__;
22
+
23
+ /**
24
+ * Return info about logger.
25
+ *
26
+ * @return array Array with plugin info.
27
+ */
28
+ public function getInfo() {
29
+
30
+ $arr_info = array(
31
+ 'name' => 'Redirection',
32
+ 'description' => _x( 'Text', 'Logger: Redirection', 'simple-history' ),
33
+ 'name_via' => _x( 'In plugin Redirection', 'Logger: Redirection', 'simple-history' ),
34
+ 'capability' => 'manage_options',
35
+ 'messages' => array(
36
+ 'redirection_redirection_added' => _x( 'Added a redirection for URL "{source_url}"', 'Logger: Redirection', 'simple-history' ),
37
+ 'redirection_redirection_edited' => _x( 'Edited redirection for URL "{prev_source_url}"', 'Logger: Redirection', 'simple-history' ),
38
+ 'redirection_redirection_enabled' => _x( 'Enabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history' ),
39
+ 'redirection_redirection_disabled' => _x( 'Disabled redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history' ),
40
+ 'redirection_redirection_deleted' => _x( 'Deleted redirection for {items_count} URL(s)', 'Logger: Redirection', 'simple-history' ),
41
+ 'redirection_options_saved' => _x( 'Updated redirection options', 'Logger: Redirection', 'simple-history' ),
42
+ 'redirection_options_removed_all' => _x( 'Removed all redirection options and deactivated plugin', 'Logger: Redirection', 'simple-history' ),
43
+ 'redirection_group_added' => _x( 'Added redirection group "{group_name}"', 'Logger: Redirection', 'simple-history' ),
44
+ 'redirection_group_enabled' => _x( 'Enabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history' ),
45
+ 'redirection_group_disabled' => _x( 'Disabled {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history' ),
46
+ 'redirection_group_deleted' => _x( 'Deleted {items_count} redirection group(s)', 'Logger: Redirection', 'simple-history' ),
47
+ ),
48
+
49
+ /*
50
+ "labels" => array(
51
+ "search" => array(
52
+ "label" => _x("Plugin Redirection", "Logger: Redirection", "simple-history"),
53
+ "label_all" => _x("All posts & pages activity", "Logger: Redirection", "simple-history"),
54
+ "options" => array(
55
+ _x("Posts created", "Logger: Redirection", "simple-history") => array(
56
+ "post_created"
57
+ ),
58
+ _x("Posts updated", "Logger: Redirection", "simple-history") => array(
59
+ "post_updated"
60
+ ),
61
+ _x("Posts trashed", "Logger: Redirection", "simple-history") => array(
62
+ "post_trashed"
63
+ ),
64
+ _x("Posts deleted", "Logger: Redirection", "simple-history") => array(
65
+ "post_deleted"
66
+ ),
67
+ _x("Posts restored", "Logger: Redirection", "simple-history") => array(
68
+ "post_restored"
69
+ ),
70
+ )
71
+ ) // end search array
72
+ ) // end labels
73
+ */
74
+ );
75
+
76
+ return $arr_info;
77
+ }
78
+
79
+ /**
80
+ * Called when logger is loaded.
81
+ */
82
+ public function loaded() {
83
+ // Redirection plugin uses the WP REST API, so catch when requests do the API is done.
84
+ // We use filter *_before_callbacks so we can access the old title
85
+ // of the Redirection object, i.e. before new values are saved.
86
+ add_filter( 'rest_request_before_callbacks', array( $this, 'on_rest_request_before_callbacks' ), 10, 3 );
87
+ }
88
+
89
+ /**
90
+ * Fired when WP REST API call is done.
91
+ *
92
+ * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
93
+ * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
94
+ * @param WP_REST_Request $request Request used to generate the response.
95
+ *
96
+ * @return WP_HTTP_Response $response
97
+ */
98
+ public function on_rest_request_before_callbacks( $response, $handler, $request ) {
99
+ // Callback must be set.
100
+ if ( ! isset( $handler['callback'] ) ) {
101
+ return $response;
102
+ }
103
+
104
+ $callback = $handler['callback'];
105
+
106
+ $callable_name = sh_get_callable_name( $callback );
107
+
108
+ $ok_redirection_api_callable_names = array(
109
+ 'Redirection_Api_Redirect::route_bulk',
110
+ 'Redirection_Api_Redirect::route_create',
111
+ 'Redirection_Api_Redirect::route_update',
112
+ 'Redirection_Api_Group::route_create',
113
+ 'Redirection_Api_Group::route_bulk',
114
+ 'Redirection_Api_Settings::route_save_settings',
115
+ );
116
+
117
+ // Bail directly if this is not a Redirection API call.
118
+ if ( ! in_array( $callable_name, $ok_redirection_api_callable_names ) ) {
119
+ return $response;
120
+ }
121
+
122
+ if ( 'Redirection_Api_Redirect::route_create' === $callable_name ) {
123
+ $this->log_redirection_add( $request );
124
+ } elseif ( 'Redirection_Api_Redirect::route_update' === $callable_name ) {
125
+ $this->log_redirection_edit( $request );
126
+ } elseif ( 'Redirection_Api_Redirect::route_bulk' === $callable_name ) {
127
+ $bulk_action = $request->get_param( 'bulk' );
128
+ $bulk_items = $request->get_param( 'items' );
129
+
130
+ if ( ! is_array( $bulk_items ) ) {
131
+ $bulk_items = explode( ',', $bulk_items );
132
+ }
133
+
134
+ if ( is_array( $bulk_items ) ) {
135
+ $bulk_items = array_map( 'intval', $bulk_items );
136
+ }
137
+
138
+ if ( empty( $bulk_items ) ) {
139
+ return $response;
140
+ }
141
+
142
+ if ( 'enable' === $bulk_action ) {
143
+ $this->log_redirection_enable_or_disable( $request, $bulk_items );
144
+ } elseif ( 'disable' === $bulk_action ) {
145
+ $this->log_redirection_enable_or_disable( $request, $bulk_items );
146
+ } elseif ( 'delete' === $bulk_action ) {
147
+ $this->log_redirection_delete( $request, $bulk_items );
148
+ }
149
+ } elseif ( 'Redirection_Api_Group::route_create' === $callable_name ) {
150
+ $this->log_group_add( $request );
151
+ } elseif ( 'Redirection_Api_Group::route_bulk' === $callable_name ) {
152
+ $bulk_action = $request->get_param( 'bulk' );
153
+
154
+ $bulk_items = $request->get_param( 'items' );
155
+ $bulk_items = explode( ',', $bulk_items );
156
+
157
+ if ( is_array( $bulk_items ) ) {
158
+ $bulk_items = array_map( 'intval', $bulk_items );
159
+ }
160
+
161
+ if ( empty( $bulk_items ) ) {
162
+ return $response;
163
+ }
164
+
165
+ if ( 'enable' === $bulk_action ) {
166
+ $this->log_group_enable_or_disable( $request, $bulk_items );
167
+ } elseif ( 'disable' === $bulk_action ) {
168
+ $this->log_group_enable_or_disable( $request, $bulk_items );
169
+ } elseif ( 'delete' === $bulk_action ) {
170
+ $this->log_group_delete( $request, $bulk_items );
171
+ }
172
+ } elseif ( 'Redirection_Api_Settings::route_save_settings' == $callable_name ) {
173
+ $this->log_options_save( $request );
174
+ }
175
+
176
+ return $response;
177
+ }
178
+
179
+ /**
180
+ * Log when a Redirection group is deleted.
181
+ *
182
+ * @param object $req Request.
183
+ * @param array $bulk_items Array with item ids.
184
+ */
185
+ public function log_group_delete( $req, $bulk_items ) {
186
+ $context = array(
187
+ 'items' => $bulk_items,
188
+ 'items_count' => count( $bulk_items ),
189
+ );
190
+
191
+ $this->infoMessage(
192
+ 'redirection_group_deleted',
193
+ $context
194
+ );
195
+ }
196
+
197
+ /**
198
+ * Log when a Redirection grouop is added
199
+ *
200
+ * @param WP_REST_Request $req Request.
201
+ */
202
+ public function log_group_add( $req ) {
203
+ $group_name = $req->get_param( 'name' );
204
+
205
+ if ( ! $group_name ) {
206
+ return;
207
+ }
208
+
209
+ $context = array(
210
+ 'group_name' => $group_name,
211
+ );
212
+
213
+ $this->infoMessage(
214
+ 'redirection_group_added',
215
+ $context
216
+ );
217
+ }
218
+
219
+ /**
220
+ * Log enabling and disabling of redirection groups.
221
+ *
222
+ * @param object $req Request.
223
+ * @param array $bulk_items Array with item ids.
224
+ */
225
+ public function log_group_enable_or_disable( $req, $bulk_items ) {
226
+ $bulk_action = $req->get_param( 'bulk' );
227
+
228
+ $message_key = 'enable' === $bulk_action ? 'redirection_group_enabled' : 'redirection_group_disabled';
229
+
230
+ $context = array(
231
+ 'items' => $bulk_items,
232
+ 'items_count' => count( $bulk_items ),
233
+ );
234
+
235
+ $this->infoMessage(
236
+ $message_key,
237
+ $context
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Log when options are saved.
243
+ *
244
+ * @param object $req Request.
245
+ */
246
+ protected function log_options_save( $req ) {
247
+ $this->infoMessage( 'redirection_options_saved' );
248
+ }
249
+
250
+ /**
251
+ * Log the deletion of a redirection.
252
+ *
253
+ * @param object $req Request.
254
+ * @param array $bulk_items Array with item ids.
255
+ */
256
+ protected function log_redirection_delete( $req, $bulk_items ) {
257
+ $context = array(
258
+ 'items' => $bulk_items,
259
+ 'items_count' => count( $bulk_items ),
260
+ );
261
+
262
+ $message_key = 'redirection_redirection_deleted';
263
+
264
+ $this->infoMessage(
265
+ $message_key,
266
+ $context
267
+ );
268
+ }
269
+
270
+ /**
271
+ * Log enable or disable of items.
272
+ *
273
+ * @param Object $req Req.
274
+ * @param Array $bulk_items Array.
275
+ */
276
+ protected function log_redirection_enable_or_disable( $req, $bulk_items ) {
277
+ $bulk_action = $req->get_param( 'bulk' );
278
+
279
+ $message_key = 'enable' === $bulk_action ? 'redirection_redirection_enabled' : 'redirection_redirection_disabled';
280
+
281
+ $context = array(
282
+ 'items' => $bulk_items,
283
+ 'items_count' => count( $bulk_items ),
284
+ );
285
+
286
+ $this->infoMessage(
287
+ $message_key,
288
+ $context
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Log when a Redirection is added.
294
+ *
295
+ * @param WP_REST_Request $req Request.
296
+ */
297
+ protected function log_redirection_add( $req ) {
298
+ $action_data = $req->get_param( 'action_data' );
299
+
300
+ if ( ! $action_data || ! is_array( $action_data ) ) {
301
+ return false;
302
+ }
303
+
304
+ $context = array(
305
+ 'source_url' => $req->get_param( 'url' ),
306
+ 'target_url' => $action_data['url'],
307
+ );
308
+
309
+ $this->infoMessage( 'redirection_redirection_added', $context );
310
+ }
311
+
312
+ /**
313
+ * Log when a Redirection is changed.
314
+ *
315
+ * @param WP_REST_Request $req Request.
316
+ */
317
+ protected function log_redirection_edit( $req ) {
318
+ $action_data = $req->get_param( 'action_data' );
319
+
320
+ if ( ! $action_data || ! is_array( $action_data ) ) {
321
+ return false;
322
+ }
323
+
324
+ $message_key = 'redirection_redirection_edited';
325
+
326
+ $redirection_id = $req->get_param( 'id' );
327
+
328
+ $context = array(
329
+ 'new_source_url' => $req->get_param( 'url' ),
330
+ 'new_target' => $action_data,
331
+ 'redirection_id' => $redirection_id,
332
+ );
333
+
334
+ // Get old values.
335
+ $redirection_item = Red_Item::get_by_id( $redirection_id );
336
+
337
+ if ( false !== $redirection_item ) {
338
+ $context['prev_source_url'] = $redirection_item->get_url();
339
+ $context['prev_target'] = maybe_unserialize( $redirection_item->get_action_data() );
340
+ }
341
+
342
+ $this->infoMessage(
343
+ $message_key,
344
+ $context
345
+ );
346
+ }
347
+
348
+ /**
349
+ * Return more info about an logged redirection event.
350
+ *
351
+ * @param object $row Row with info.
352
+ */
353
+ public function getLogRowDetailsOutput( $row ) {
354
+ $context = $row->context;
355
+ $message_key = $context['_message_key'];
356
+
357
+ $out = '';
358
+
359
+ if ( 'redirection_redirection_edited' === $message_key ) {
360
+ if ( $context['new_source_url'] !== $context['prev_source_url'] ) {
361
+ $diff_table_output = sprintf(
362
+ '<tr>
 
 
 
 
 
 
 
 
 
 
 
 
363
  <td>%1$s</td>
364
  <td>
365
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
366
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
367
  </td>
368
  </tr>',
369
+ esc_html_x( 'Source URL', 'Logger: Redirection', 'simple-history' ), // 1
370
+ esc_html( $context['new_source_url'] ), // 2
371
+ esc_html( $context['prev_source_url'] ) // 3
372
+ );
373
 
374
+ $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
375
+ }
376
 
377
+ if ( $context['new_target'] !== $context['prev_target'] ) {
378
+ $diff_table_output = sprintf(
379
+ '<tr>
380
  <td>%1$s</td>
381
  <td>
382
  <ins class="SimpleHistoryLogitem__keyValueTable__addedThing">%2$s</ins>
383
  <del class="SimpleHistoryLogitem__keyValueTable__removedThing">%3$s</del>
384
  </td>
385
  </tr>',
386
+ esc_html_x( 'Target', 'Logger: Redirection', 'simple-history' ), // 1
387
+ esc_html( $context['new_target'] ), // 2
388
+ esc_html( $context['prev_target'] ) // 3
389
+ );
390
+
391
+ $out .= '<table class="SimpleHistoryLogitem__keyValueTable">' . $diff_table_output . '</table>';
392
+ }
393
+ }
394
+
395
+ return $out;
396
+ }
397
+ }
398
  }
loggers/Plugin_UltimateMembers_Logger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  *
@@ -8,48 +8,48 @@ defined('ABSPATH') or die();
8
  *
9
  * @since 2.2
10
  */
11
- class Plugin_UltimateMembers_Logger extends SimpleLogger
12
- {
13
- public $slug = __CLASS__;
14
-
15
- /**
16
- * Get array with information about this logger
17
- *
18
- * @return array
19
- */
20
- public function getInfo()
21
- {
22
- $arr_info = array(
23
- 'name' => _x('Ultimate Members Logger', 'PluginUltimateMembersLogger', 'simple-history'),
24
- 'description' => _x('Logs actions from the Ultimate Members plugin', 'PluginUltimateMembersLogger', 'simple-history'),
25
- 'capability' => 'edit_users',
26
- 'messages' => array(
27
- 'logged_in' => _x('Logged in', 'PluginUltimateMembersLogger', 'simple-history'),
28
- ),
29
- );
30
-
31
- return $arr_info;
32
- }
33
-
34
- public function loaded()
35
- {
36
-
37
- // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
38
- add_action('um_on_login_before_redirect', array( $this, 'on_um_on_login_before_redirect' ), 10, 1);
39
- }
40
-
41
- public function on_um_on_login_before_redirect($user_id)
42
- {
43
-
44
- $this->infoMessage('logged_in', array(
45
- // "user_id" => $user_id,
46
- /*
47
- "get" => $_GET,
48
- "post" => $_POST,
49
- "files" => $_FILES,
50
- "old_attachment_post" => $prev_attachment_post,
51
- "old_attachment_meta" => $prev_attachment_meta
52
- */
53
- ));
54
- }
55
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  *
8
  *
9
  * @since 2.2
10
  */
11
+ class Plugin_UltimateMembers_Logger extends SimpleLogger {
12
+
13
+ public $slug = __CLASS__;
14
+
15
+ /**
16
+ * Get array with information about this logger
17
+ *
18
+ * @return array
19
+ */
20
+ public function getInfo() {
21
+ $arr_info = array(
22
+ 'name' => _x( 'Ultimate Members Logger', 'PluginUltimateMembersLogger', 'simple-history' ),
23
+ 'description' => _x( 'Logs actions from the Ultimate Members plugin', 'PluginUltimateMembersLogger', 'simple-history' ),
24
+ 'capability' => 'edit_users',
25
+ 'messages' => array(
26
+ 'logged_in' => _x( 'Logged in', 'PluginUltimateMembersLogger', 'simple-history' ),
27
+ ),
28
+ );
29
+
30
+ return $arr_info;
31
+ }
32
+
33
+ public function loaded() {
34
+
35
+ // Action that is called when Enable Media Replace loads it's admin options page (both when viewing and when posting new file to it)
36
+ add_action( 'um_on_login_before_redirect', array( $this, 'on_um_on_login_before_redirect' ), 10, 1 );
37
+ }
38
+
39
+ public function on_um_on_login_before_redirect( $user_id ) {
40
+
41
+ $this->infoMessage(
42
+ 'logged_in',
43
+ array(
44
+ // "user_id" => $user_id,
45
+ /*
46
+ "get" => $_GET,
47
+ "post" => $_POST,
48
+ "files" => $_FILES,
49
+ "old_attachment_post" => $prev_attachment_post,
50
+ "old_attachment_meta" => $prev_attachment_meta
51
+ */
52
+ )
53
+ );
54
+ }
55
  }
loggers/SimpleCategoriesLogger.php CHANGED
@@ -1,323 +1,314 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs changes to categories and tags and taxonomies
7
  */
8
- class SimpleCategoriesLogger extends SimpleLogger
9
- {
10
-
11
- public $slug = __CLASS__;
12
-
13
- /**
14
- * Get array with information about this logger
15
- *
16
- * @return array
17
- */
18
- public function getInfo()
19
- {
20
-
21
- $arr_info = array(
22
- 'name' => __('Categories Logger', 'simple-history'),
23
- 'description' => 'Logs changes to categories, tags, and taxonomies',
24
- 'messages' => array(
25
- 'created_term' => __('Added term "{term_name}" in taxonomy "{term_taxonomy}"', 'simple-history'),
26
- 'deleted_term' => __('Deleted term "{term_name}" from taxonomy "{term_taxonomy}"', 'simple-history'),
27
- 'edited_term' => __('Edited term "{to_term_name}" in taxonomy "{to_term_taxonomy}"', 'simple-history'),
28
- ),
29
- 'labels' => array(
30
- 'search' => array(
31
- 'label' => _x('Categories', 'Categories logger: search', 'simple-history'),
32
- 'label_all' => _x('All category activity', 'Category logger: search', 'simple-history'),
33
- 'options' => array(
34
- _x('Term created', 'Category logger: search', 'simple-history') => array(
35
- 'created_term'
36
- ),
37
- _x('Term deleted', 'Category logger: search', 'simple-history') => array(
38
- 'deleted_term'
39
- ),
40
- _x('Term edited', 'Category logger: search', 'simple-history') => array(
41
- 'edited_term'
42
- ),
43
- )
44
- ) // end search array
45
- ) // end labels
46
- );
47
-
48
- return $arr_info;
49
- }
50
-
51
- /**
52
- * Called when the logger is loaded.
53
- */
54
- public function loaded()
55
- {
56
- // Fires after a new term is created, and after the term cache has been cleaned..
57
- add_action('created_term', array( $this, 'on_created_term' ), 10, 3);
58
-
59
- // Hook to this filter to see if it will cause a hierarchy loop.
60
- add_action('delete_term', array( $this, 'on_delete_term' ), 10, 4);
61
-
62
- // Filter the term parent.
63
- add_action('wp_update_term_parent', array( $this, 'on_wp_update_term_parent' ), 10, 5);
64
- }
65
-
66
- /**
67
- * Filter the term parent.
68
- * Only way for Simple History to get both old and new term name.
69
- * For example 'edited_term' does not contain enough info to know what the term was called before the update.
70
- *
71
- * @param int $parent ID of the parent term.
72
- * @param int $term_id Term ID.
73
- * @param string $taxonomy Taxonomy slug.
74
- * @param array $parsed_args An array of potentially altered update arguments for the given term.
75
- * @param array $term_update_args An array of update arguments for the given term.
76
- */
77
- public function on_wp_update_term_parent($parent = null, $term_id = null, $taxonomy = null, $parsed_args = null, $term_update_args = null)
78
- {
79
-
80
- $term_before_edited = get_term_by('id', $term_id, $taxonomy);
81
-
82
- if (! $term_before_edited || empty($term_update_args)) {
83
- return $parent;
84
- }
85
-
86
- $term_id = $term_before_edited->term_id;
87
-
88
- $from_term_name = $term_before_edited->name;
89
- $from_term_taxonomy = $term_before_edited->taxonomy;
90
- $from_term_slug = $term_before_edited->slug;
91
- $from_term_description = $term_before_edited->description;
92
-
93
- $to_term_name = $term_update_args['name'];
94
- $to_term_taxonomy = $term_update_args['taxonomy'];
95
- $to_term_slug = $term_update_args['slug'];
96
- $to_term_description = $term_update_args['description'];
97
-
98
- $do_log_term = $this->ok_to_log_taxonomy($from_term_taxonomy);
99
-
100
- if (! $do_log_term) {
101
- return $parent;
102
- }
103
-
104
- $this->infoMessage(
105
- 'edited_term',
106
- array(
107
- 'term_id' => $term_id,
108
- 'from_term_name' => $from_term_name,
109
- 'from_term_taxonomy' => $from_term_taxonomy,
110
- 'from_term_slug' => $from_term_slug,
111
- 'from_term_description' => $from_term_description,
112
- 'to_term_name' => $to_term_name,
113
- 'to_term_taxonomy' => $to_term_taxonomy,
114
- 'to_term_slug' => $to_term_slug,
115
- 'to_term_description' => $to_term_description,
116
- )
117
- );
118
-
119
- return $parent;
120
- }
121
-
122
- /**
123
- * Check if it's ok to log a taxonomy.
124
- * We skip some taxonomies, for example Polylang translation terms that fill the log with
125
- * messages like 'Edited term "pll_5a3643a142c80" in taxonomy "post_translations"' otherwise.
126
- *
127
- * @since 2.21
128
- * @param string $from_term_taxonomy Slug of taxonomy.
129
- * @return bool True or false.
130
- */
131
- public function ok_to_log_taxonomy($from_term_taxonomy = '')
132
- {
133
- if (empty($from_term_taxonomy)) {
134
- return false;
135
- }
136
-
137
- $skip_taxonomies = $this->get_skip_taxonomies();
138
-
139
- $do_log = ! in_array($from_term_taxonomy, $skip_taxonomies, true);
140
-
141
- return $do_log;
142
- }
143
-
144
- /**
145
- * Get taxonomies to skip.
146
- *
147
- * @since 2.21
148
- * @return array Array with taxonomies.
149
- */
150
- public function get_skip_taxonomies()
151
- {
152
-
153
- $taxonomies_to_skip = array(
154
- // Polylang taxonomies used to store translation mappings.
155
- 'post_translations',
156
- 'term_translations',
157
- );
158
-
159
- $taxonomies_to_skip = apply_filters('simple_history/categories_logger/skip_taxonomies', $taxonomies_to_skip);
160
-
161
- return $taxonomies_to_skip;
162
- }
163
-
164
- /*
165
- * Fires after a new term is created, and after the term cache has been cleaned.
166
- *
167
- * @since 2.3.0
168
- *
169
- * @param int $term_id Term ID.
170
- * @param int $tt_id Term taxonomy ID.
171
- * @param string $taxonomy Taxonomy slug.
172
- */
173
- public function on_created_term($term_id = null, $tt_id = null, $taxonomy = null)
174
- {
175
-
176
- $term = get_term_by('id', $term_id, $taxonomy);
177
-
178
- if (! $term) {
179
- return;
180
- }
181
-
182
- $term_name = $term->name;
183
- $term_taxonomy = $term->taxonomy;
184
- $term_id = $term->term_id;
185
-
186
- $do_log_term = $this->ok_to_log_taxonomy($term_taxonomy);
187
-
188
- if (! $do_log_term) {
189
- return;
190
- }
191
-
192
- $this->infoMessage(
193
- 'created_term',
194
- array(
195
- 'term_id' => $term_id,
196
- 'term_name' => $term_name,
197
- 'term_taxonomy' => $term_taxonomy,
198
- )
199
- );
200
- }
201
-
202
-
203
- /**
204
- * Fires after a term is deleted from the database and the cache is cleaned.
205
- *
206
- * @param int $term Term ID.
207
- * @param int $tt_id Term taxonomy ID.
208
- * @param string $taxonomy Taxonomy slug.
209
- * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
210
- * by the parent function. WP_Error otherwise.
211
- */
212
- public function on_delete_term($term = null, $tt_id = null, $taxonomy = null, $deleted_term = null)
213
- {
214
-
215
- if (is_wp_error($deleted_term)) {
216
- return;
217
- }
218
-
219
- $term_name = $deleted_term->name;
220
- $term_taxonomy = $deleted_term->taxonomy;
221
- $term_id = $deleted_term->term_id;
222
-
223
- $do_log_term = $this->ok_to_log_taxonomy($term_taxonomy);
224
-
225
- if (! $do_log_term) {
226
- return;
227
- }
228
-
229
- $this->infoMessage(
230
- 'deleted_term',
231
- array(
232
- 'term_id' => $term_id,
233
- 'term_name' => $term_name,
234
- 'term_taxonomy' => $term_taxonomy,
235
- )
236
- );
237
- }
238
-
239
- /**
240
- * Modify plain output to include link to term and taxonomy.
241
- *
242
- * @param array $row Row data.
243
- */
244
- public function getLogRowPlainTextOutput($row)
245
- {
246
- $context = $row->context;
247
- $message_key = isset($context['_message_key']) ? $context['_message_key'] : null;
248
-
249
- // Default to original log message.
250
- $message = $row->message;
251
-
252
- // Get term that was created, edited, or removed.
253
- $term_id = isset($context['term_id']) ? (int) $context['term_id'] : null;
254
-
255
- // Get taxonomy for term.
256
- if ('created_term' === $message_key || 'deleted_term' === $message_key) {
257
- $term_taxonomy = isset($context['term_taxonomy']) ? (string) $context['term_taxonomy'] : null;
258
- } elseif ('edited_term' === $message_key) {
259
- $term_taxonomy = isset($context['from_term_taxonomy']) ? (string) $context['from_term_taxonomy'] : null;
260
- }
261
-
262
- $tax_edit_link = add_query_arg(
263
- array(
264
- 'taxonomy' => $term_taxonomy,
265
- ),
266
- admin_url('term.php')
267
- );
268
-
269
- $context['tax_edit_link'] = $tax_edit_link;
270
-
271
- $term_object = get_term($term_id, $term_taxonomy);
272
-
273
- if (is_wp_error($term_object)) {
274
- return $this->interpolate($message, $context, $row);
275
- }
276
-
277
- $term_edit_link = isset($term_object) ? get_edit_tag_link($term_id, $term_object->taxonomy) : null;
278
- $context['term_edit_link'] = $term_edit_link;
279
-
280
- // Get taxonomy name to use in log but fall back to taxonomy slug if
281
- // taxonomy has been deleted.
282
- $context['termTaxonomySlugOrName'] = isset($context['term_taxonomy']) ? $context['term_taxonomy'] : null;
283
- $context['toTermTaxonomySlugOrName'] = isset($context['to_term_taxonomy']) ? $context['to_term_taxonomy'] : null;
284
-
285
- if (isset($context['term_taxonomy']) && $context['term_taxonomy']) {
286
- $termTaxonomyObject = get_taxonomy($context['term_taxonomy']);
287
- if (is_a($termTaxonomyObject, 'WP_Taxonomy')) {
288
- $termTaxonomyObjectLabels = get_taxonomy_labels($termTaxonomyObject);
289
- $context['termTaxonomySlugOrName'] = $termTaxonomyObjectLabels->singular_name;
290
- }
291
- }
292
-
293
- if (isset($context['to_term_taxonomy']) && $context['to_term_taxonomy']) {
294
- $termTaxonomyObject = get_taxonomy($context['to_term_taxonomy']);
295
- if (is_a($termTaxonomyObject, 'WP_Taxonomy')) {
296
- $termTaxonomyObjectLabels = get_taxonomy_labels($termTaxonomyObject);
297
- $context['toTermTaxonomySlugOrName'] = $termTaxonomyObjectLabels->singular_name;
298
- }
299
- }
300
-
301
- if ('created_term' === $message_key && ! empty($term_edit_link) && ! empty($tax_edit_link)) {
302
- $message = _x(
303
- 'Added term <a href="{term_edit_link}">"{term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{termTaxonomySlugOrName}"</a>',
304
- 'Categories logger: detailed plain text output for created term',
305
- 'simple-history'
306
- );
307
- } elseif ('deleted_term' === $message_key && ! empty($tax_edit_link)) {
308
- $message = _x(
309
- 'Deleted term "{term_name}" from taxonomy <a href="{tax_edit_link}">"{termTaxonomySlugOrName}"</a>',
310
- 'Categories logger: detailed plain text output for deleted term',
311
- 'simple-history'
312
- );
313
- } elseif ('edited_term' === $message_key && ! empty($term_edit_link) && ! empty($tax_edit_link)) {
314
- $message = _x(
315
- 'Edited term <a href="{term_edit_link}">"{to_term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{toTermTaxonomySlugOrName}"</a>',
316
- 'Categories logger: detailed plain text output for edited term',
317
- 'simple-history'
318
- );
319
- }
320
-
321
- return $this->interpolate($message, $context, $row);
322
- }
323
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs changes to categories and tags and taxonomies
7
  */
8
+ class SimpleCategoriesLogger extends SimpleLogger {
9
+
10
+ public $slug = __CLASS__;
11
+
12
+ /**
13
+ * Get array with information about this logger
14
+ *
15
+ * @return array
16
+ */
17
+ public function getInfo() {
18
+
19
+ $arr_info = array(
20
+ 'name' => __( 'Categories Logger', 'simple-history' ),
21
+ 'description' => 'Logs changes to categories, tags, and taxonomies',
22
+ 'messages' => array(
23
+ 'created_term' => __( 'Added term "{term_name}" in taxonomy "{term_taxonomy}"', 'simple-history' ),
24
+ 'deleted_term' => __( 'Deleted term "{term_name}" from taxonomy "{term_taxonomy}"', 'simple-history' ),
25
+ 'edited_term' => __( 'Edited term "{to_term_name}" in taxonomy "{to_term_taxonomy}"', 'simple-history' ),
26
+ ),
27
+ 'labels' => array(
28
+ 'search' => array(
29
+ 'label' => _x( 'Categories', 'Categories logger: search', 'simple-history' ),
30
+ 'label_all' => _x( 'All category activity', 'Category logger: search', 'simple-history' ),
31
+ 'options' => array(
32
+ _x( 'Term created', 'Category logger: search', 'simple-history' ) => array(
33
+ 'created_term',
34
+ ),
35
+ _x( 'Term deleted', 'Category logger: search', 'simple-history' ) => array(
36
+ 'deleted_term',
37
+ ),
38
+ _x( 'Term edited', 'Category logger: search', 'simple-history' ) => array(
39
+ 'edited_term',
40
+ ),
41
+ ),
42
+ ), // end search array
43
+ ), // end labels
44
+ );
45
+
46
+ return $arr_info;
47
+ }
48
+
49
+ /**
50
+ * Called when the logger is loaded.
51
+ */
52
+ public function loaded() {
53
+ // Fires after a new term is created, and after the term cache has been cleaned..
54
+ add_action( 'created_term', array( $this, 'on_created_term' ), 10, 3 );
55
+
56
+ // Hook to this filter to see if it will cause a hierarchy loop.
57
+ add_action( 'delete_term', array( $this, 'on_delete_term' ), 10, 4 );
58
+
59
+ // Filter the term parent.
60
+ add_action( 'wp_update_term_parent', array( $this, 'on_wp_update_term_parent' ), 10, 5 );
61
+ }
62
+
63
+ /**
64
+ * Filter the term parent.
65
+ * Only way for Simple History to get both old and new term name.
66
+ * For example 'edited_term' does not contain enough info to know what the term was called before the update.
67
+ *
68
+ * @param int $parent ID of the parent term.
69
+ * @param int $term_id Term ID.
70
+ * @param string $taxonomy Taxonomy slug.
71
+ * @param array $parsed_args An array of potentially altered update arguments for the given term.
72
+ * @param array $term_update_args An array of update arguments for the given term.
73
+ */
74
+ public function on_wp_update_term_parent( $parent = null, $term_id = null, $taxonomy = null, $parsed_args = null, $term_update_args = null ) {
75
+
76
+ $term_before_edited = get_term_by( 'id', $term_id, $taxonomy );
77
+
78
+ if ( ! $term_before_edited || empty( $term_update_args ) ) {
79
+ return $parent;
80
+ }
81
+
82
+ $term_id = $term_before_edited->term_id;
83
+
84
+ $from_term_name = $term_before_edited->name;
85
+ $from_term_taxonomy = $term_before_edited->taxonomy;
86
+ $from_term_slug = $term_before_edited->slug;
87
+ $from_term_description = $term_before_edited->description;
88
+
89
+ $to_term_name = $term_update_args['name'];
90
+ $to_term_taxonomy = $term_update_args['taxonomy'];
91
+ $to_term_slug = $term_update_args['slug'];
92
+ $to_term_description = $term_update_args['description'];
93
+
94
+ $do_log_term = $this->ok_to_log_taxonomy( $from_term_taxonomy );
95
+
96
+ if ( ! $do_log_term ) {
97
+ return $parent;
98
+ }
99
+
100
+ $this->infoMessage(
101
+ 'edited_term',
102
+ array(
103
+ 'term_id' => $term_id,
104
+ 'from_term_name' => $from_term_name,
105
+ 'from_term_taxonomy' => $from_term_taxonomy,
106
+ 'from_term_slug' => $from_term_slug,
107
+ 'from_term_description' => $from_term_description,
108
+ 'to_term_name' => $to_term_name,
109
+ 'to_term_taxonomy' => $to_term_taxonomy,
110
+ 'to_term_slug' => $to_term_slug,
111
+ 'to_term_description' => $to_term_description,
112
+ )
113
+ );
114
+
115
+ return $parent;
116
+ }
117
+
118
+ /**
119
+ * Check if it's ok to log a taxonomy.
120
+ * We skip some taxonomies, for example Polylang translation terms that fill the log with
121
+ * messages like 'Edited term "pll_5a3643a142c80" in taxonomy "post_translations"' otherwise.
122
+ *
123
+ * @since 2.21
124
+ * @param string $from_term_taxonomy Slug of taxonomy.
125
+ * @return bool True or false.
126
+ */
127
+ public function ok_to_log_taxonomy( $from_term_taxonomy = '' ) {
128
+ if ( empty( $from_term_taxonomy ) ) {
129
+ return false;
130
+ }
131
+
132
+ $skip_taxonomies = $this->get_skip_taxonomies();
133
+
134
+ $do_log = ! in_array( $from_term_taxonomy, $skip_taxonomies, true );
135
+
136
+ return $do_log;
137
+ }
138
+
139
+ /**
140
+ * Get taxonomies to skip.
141
+ *
142
+ * @since 2.21
143
+ * @return array Array with taxonomies.
144
+ */
145
+ public function get_skip_taxonomies() {
146
+
147
+ $taxonomies_to_skip = array(
148
+ // Polylang taxonomies used to store translation mappings.
149
+ 'post_translations',
150
+ 'term_translations',
151
+ );
152
+
153
+ $taxonomies_to_skip = apply_filters( 'simple_history/categories_logger/skip_taxonomies', $taxonomies_to_skip );
154
+
155
+ return $taxonomies_to_skip;
156
+ }
157
+
158
+ /*
159
+ * Fires after a new term is created, and after the term cache has been cleaned.
160
+ *
161
+ * @since 2.3.0
162
+ *
163
+ * @param int $term_id Term ID.
164
+ * @param int $tt_id Term taxonomy ID.
165
+ * @param string $taxonomy Taxonomy slug.
166
+ */
167
+ public function on_created_term( $term_id = null, $tt_id = null, $taxonomy = null ) {
168
+
169
+ $term = get_term_by( 'id', $term_id, $taxonomy );
170
+
171
+ if ( ! $term ) {
172
+ return;
173
+ }
174
+
175
+ $term_name = $term->name;
176
+ $term_taxonomy = $term->taxonomy;
177
+ $term_id = $term->term_id;
178
+
179
+ $do_log_term = $this->ok_to_log_taxonomy( $term_taxonomy );
180
+
181
+ if ( ! $do_log_term ) {
182
+ return;
183
+ }
184
+
185
+ $this->infoMessage(
186
+ 'created_term',
187
+ array(
188
+ 'term_id' => $term_id,
189
+ 'term_name' => $term_name,
190
+ 'term_taxonomy' => $term_taxonomy,
191
+ )
192
+ );
193
+ }
194
+
195
+
196
+ /**
197
+ * Fires after a term is deleted from the database and the cache is cleaned.
198
+ *
199
+ * @param int $term Term ID.
200
+ * @param int $tt_id Term taxonomy ID.
201
+ * @param string $taxonomy Taxonomy slug.
202
+ * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
203
+ * by the parent function. WP_Error otherwise.
204
+ */
205
+ public function on_delete_term( $term = null, $tt_id = null, $taxonomy = null, $deleted_term = null ) {
206
+
207
+ if ( is_wp_error( $deleted_term ) ) {
208
+ return;
209
+ }
210
+
211
+ $term_name = $deleted_term->name;
212
+ $term_taxonomy = $deleted_term->taxonomy;
213
+ $term_id = $deleted_term->term_id;
214
+
215
+ $do_log_term = $this->ok_to_log_taxonomy( $term_taxonomy );
216
+
217
+ if ( ! $do_log_term ) {
218
+ return;
219
+ }
220
+
221
+ $this->infoMessage(
222
+ 'deleted_term',
223
+ array(
224
+ 'term_id' => $term_id,
225
+ 'term_name' => $term_name,
226
+ 'term_taxonomy' => $term_taxonomy,
227
+ )
228
+ );
229
+ }
230
+
231
+ /**
232
+ * Modify plain output to include link to term and taxonomy.
233
+ *
234
+ * @param object $row Row data.
235
+ */
236
+ public function getLogRowPlainTextOutput( $row ) {
237
+ $context = $row->context;
238
+ $message_key = isset( $context['_message_key'] ) ? $context['_message_key'] : null;
239
+
240
+ // Default to original log message.
241
+ $message = $row->message;
242
+
243
+ // Get term that was created, edited, or removed.
244
+ $term_id = isset( $context['term_id'] ) ? (int) $context['term_id'] : null;
245
+
246
+ // Get taxonomy for term.
247
+ if ( 'created_term' === $message_key || 'deleted_term' === $message_key ) {
248
+ $term_taxonomy = isset( $context['term_taxonomy'] ) ? (string) $context['term_taxonomy'] : null;
249
+ } elseif ( 'edited_term' === $message_key ) {
250
+ $term_taxonomy = isset( $context['from_term_taxonomy'] ) ? (string) $context['from_term_taxonomy'] : null;
251
+ }
252
+
253
+ $tax_edit_link = add_query_arg(
254
+ array(
255
+ 'taxonomy' => $term_taxonomy,
256
+ ),
257
+ admin_url( 'term.php' )
258
+ );
259
+
260
+ $context['tax_edit_link'] = $tax_edit_link;
261
+
262
+ $term_object = get_term( $term_id, $term_taxonomy );
263
+
264
+ if ( is_wp_error( $term_object ) ) {
265
+ return $this->interpolate( $message, $context, $row );
266
+ }
267
+
268
+ $term_edit_link = isset( $term_object ) ? get_edit_tag_link( $term_id, $term_object->taxonomy ) : null;
269
+ $context['term_edit_link'] = $term_edit_link;
270
+
271
+ // Get taxonomy name to use in log but fall back to taxonomy slug if
272
+ // taxonomy has been deleted.
273
+ $context['termTaxonomySlugOrName'] = isset( $context['term_taxonomy'] ) ? $context['term_taxonomy'] : null;
274
+ $context['toTermTaxonomySlugOrName'] = isset( $context['to_term_taxonomy'] ) ? $context['to_term_taxonomy'] : null;
275
+
276
+ if ( isset( $context['term_taxonomy'] ) && $context['term_taxonomy'] ) {
277
+ $termTaxonomyObject = get_taxonomy( $context['term_taxonomy'] );
278
+ if ( is_a( $termTaxonomyObject, 'WP_Taxonomy' ) ) {
279
+ $termTaxonomyObjectLabels = get_taxonomy_labels( $termTaxonomyObject );
280
+ $context['termTaxonomySlugOrName'] = $termTaxonomyObjectLabels->singular_name;
281
+ }
282
+ }
283
+
284
+ if ( isset( $context['to_term_taxonomy'] ) && $context['to_term_taxonomy'] ) {
285
+ $termTaxonomyObject = get_taxonomy( $context['to_term_taxonomy'] );
286
+ if ( is_a( $termTaxonomyObject, 'WP_Taxonomy' ) ) {
287
+ $termTaxonomyObjectLabels = get_taxonomy_labels( $termTaxonomyObject );
288
+ $context['toTermTaxonomySlugOrName'] = $termTaxonomyObjectLabels->singular_name;
289
+ }
290
+ }
291
+
292
+ if ( 'created_term' === $message_key && ! empty( $term_edit_link ) && ! empty( $tax_edit_link ) ) {
293
+ $message = _x(
294
+ 'Added term <a href="{term_edit_link}">"{term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{termTaxonomySlugOrName}"</a>',
295
+ 'Categories logger: detailed plain text output for created term',
296
+ 'simple-history'
297
+ );
298
+ } elseif ( 'deleted_term' === $message_key && ! empty( $tax_edit_link ) ) {
299
+ $message = _x(
300
+ 'Deleted term "{term_name}" from taxonomy <a href="{tax_edit_link}">"{termTaxonomySlugOrName}"</a>',
301
+ 'Categories logger: detailed plain text output for deleted term',
302
+ 'simple-history'
303
+ );
304
+ } elseif ( 'edited_term' === $message_key && ! empty( $term_edit_link ) && ! empty( $tax_edit_link ) ) {
305
+ $message = _x(
306
+ 'Edited term <a href="{term_edit_link}">"{to_term_name}"</a> in taxonomy <a href="{tax_edit_link}">"{toTermTaxonomySlugOrName}"</a>',
307
+ 'Categories logger: detailed plain text output for edited term',
308
+ 'simple-history'
309
+ );
310
+ }
311
+
312
+ return $this->interpolate( $message, $context, $row );
313
+ }
 
 
 
 
 
 
 
 
 
314
  }
loggers/SimpleCommentsLogger.php CHANGED
@@ -1,52 +1,51 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs things related to comments
7
  */
8
- class SimpleCommentsLogger extends SimpleLogger
9
- {
10
- public $slug = __CLASS__;
11
-
12
- public function __construct($sh)
13
- {
14
- parent::__construct($sh);
15
-
16
- // Add option to not show spam comments, because to much things getting logged
17
- // add_filter("simple_history/log_query_sql_where", array($this, "maybe_modify_log_query_sql_where"));
18
- add_filter('simple_history/log_query_inner_where', array( $this, 'maybe_modify_log_query_sql_where' ));
19
- add_filter('simple_history/quick_stats_where', array( $this, 'maybe_modify_log_query_sql_where' ));
20
- }
21
-
22
- /**
23
- * Modify sql query to exclude comments of type spam
24
- *
25
- * @param string $where sql query where
26
- */
27
- public function maybe_modify_log_query_sql_where($where)
28
- {
29
-
30
- // since 19 sept 2016 we do include spam, to skip the subquery
31
- // spam comments should not be logged anyway since some time
32
- $include_spam = true;
33
-
34
- /**
35
- * Filter option to include spam or not in the gui
36
- * By default spam is not included, because it can fill the log
37
- * with too much events
38
- *
39
- * @since 2.0
40
- *
41
- * @param bool $include_spam Default false
42
- */
43
- $include_spam = apply_filters('simple_history/comments_logger/include_spam', $include_spam);
44
-
45
- if ($include_spam) {
46
- return $where;
47
- }
48
-
49
- $where .= sprintf('
50
  AND id NOT IN (
51
 
52
  SELECT id
@@ -73,707 +72,702 @@ class SimpleCommentsLogger extends SimpleLogger
73
  WHERE logger = "%3$s"
74
 
75
  )
76
- ', $this->db_table, $this->db_table_contexts, $this->slug);
77
-
78
- // echo $where;
79
- return $where;
80
- }
81
-
82
- /**
83
- * Get array with information about this logger
84
- *
85
- * @return array
86
- */
87
- public function getInfo()
88
- {
89
-
90
- $arr_info = array(
91
- 'name' => 'Comments Logger',
92
- 'description' => 'Logs comments, and modifications to them',
93
- 'capability' => 'moderate_comments',
94
- 'messages' => array(
95
-
96
- // Comments
97
- 'anon_comment_added' => _x(
98
- 'Added a comment to {comment_post_type} "{comment_post_title}"',
99
- 'A comment was added to the database by a non-logged in internet user',
100
- 'simple-history'
101
- ),
102
-
103
- 'user_comment_added' => _x(
104
- 'Added a comment to {comment_post_type} "{comment_post_title}"',
105
- 'A comment was added to the database by a logged in user',
106
- 'simple-history'
107
- ),
108
-
109
- 'comment_status_approve' => _x(
110
- 'Approved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
111
- 'A comment was approved',
112
- 'simple-history'
113
- ),
114
-
115
- 'comment_status_hold' => _x(
116
- 'Unapproved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
117
- 'A comment was was unapproved',
118
- 'simple-history'
119
- ),
120
-
121
- 'comment_status_spam' => _x(
122
- 'Marked a comment to post "{comment_post_title}" as spam',
123
- 'A comment was marked as spam',
124
- 'simple-history'
125
- ),
126
-
127
- 'comment_status_trash' => _x(
128
- 'Trashed a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
129
- 'A comment was marked moved to the trash',
130
- 'simple-history'
131
- ),
132
-
133
- 'comment_untrashed' => _x(
134
- 'Restored a comment to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
135
- 'A comment was restored from the trash',
136
- 'simple-history'
137
- ),
138
-
139
- 'comment_deleted' => _x(
140
- 'Deleted a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
141
- 'A comment was deleted',
142
- 'simple-history'
143
- ),
144
-
145
- 'comment_edited' => _x(
146
- 'Edited a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
147
- 'A comment was edited',
148
- 'simple-history'
149
- ),
150
-
151
- // Trackbacks
152
- 'anon_trackback_added' => _x(
153
- 'Added a trackback to {comment_post_type} "{comment_post_title}"',
154
- 'A trackback was added to the database by a non-logged in internet user',
155
- 'simple-history'
156
- ),
157
-
158
- 'user_trackback_added' => _x(
159
- 'Added a trackback to {comment_post_type} "{comment_post_title}"',
160
- 'A trackback was added to the database by a logged in user',
161
- 'simple-history'
162
- ),
163
-
164
- 'trackback_status_approve' => _x(
165
- 'Approved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
166
- 'A trackback was approved',
167
- 'simple-history'
168
- ),
169
-
170
- 'trackback_status_hold' => _x(
171
- 'Unapproved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
172
- 'A trackback was was unapproved',
173
- 'simple-history'
174
- ),
175
-
176
- 'trackback_status_spam' => _x(
177
- 'Marked a trackback to post "{comment_post_title}" as spam',
178
- 'A trackback was marked as spam',
179
- 'simple-history'
180
- ),
181
-
182
- 'trackback_status_trash' => _x(
183
- 'Trashed a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
184
- 'A trackback was marked moved to the trash',
185
- 'simple-history'
186
- ),
187
-
188
- 'trackback_untrashed' => _x(
189
- 'Restored a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
190
- 'A trackback was restored from the trash',
191
- 'simple-history'
192
- ),
193
-
194
- 'trackback_deleted' => _x(
195
- 'Deleted a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
196
- 'A trackback was deleted',
197
- 'simple-history'
198
- ),
199
-
200
- 'trackback_edited' => _x(
201
- 'Edited a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
202
- 'A trackback was edited',
203
- 'simple-history'
204
- ),
205
-
206
- // Pingbacks
207
- 'anon_pingback_added' => _x(
208
- 'Added a pingback to {comment_post_type} "{comment_post_title}"',
209
- 'A trackback was added to the database by a non-logged in internet user',
210
- 'simple-history'
211
- ),
212
-
213
- 'user_pingback_added' => _x(
214
- 'Added a pingback to {comment_post_type} "{comment_post_title}"',
215
- 'A pingback was added to the database by a logged in user',
216
- 'simple-history'
217
- ),
218
-
219
- 'pingback_status_approve' => _x(
220
- 'Approved a pingback to "{comment_post_title}" by "{comment_author}"" ({comment_author_email})',
221
- 'A pingback was approved',
222
- 'simple-history'
223
- ),
224
-
225
- 'pingback_status_hold' => _x(
226
- 'Unapproved a pingback to "{comment_post_title}" by "{comment_author}" ({comment_author_email})',
227
- 'A pingback was was unapproved',
228
- 'simple-history'
229
- ),
230
-
231
- 'pingback_status_spam' => _x(
232
- 'Marked a pingback to post "{comment_post_title}" as spam',
233
- 'A pingback was marked as spam',
234
- 'simple-history'
235
- ),
236
-
237
- 'pingback_status_trash' => _x(
238
- 'Trashed a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
239
- 'A pingback was marked moved to the trash',
240
- 'simple-history'
241
- ),
242
-
243
- 'pingback_untrashed' => _x(
244
- 'Restored a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
245
- 'A pingback was restored from the trash',
246
- 'simple-history'
247
- ),
248
-
249
- 'pingback_deleted' => _x(
250
- 'Deleted a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
251
- 'A pingback was deleted',
252
- 'simple-history'
253
- ),
254
-
255
- 'pingback_edited' => _x(
256
- 'Edited a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
257
- 'A pingback was edited',
258
- 'simple-history'
259
- ),
260
-
261
- ), // end messages
262
-
263
- 'labels' => array(
264
-
265
- 'search' => array(
266
- 'label' => _x('Comments', 'Comments logger: search', 'simple-history'),
267
- 'label_all' => _x('All comments activity', 'Comments logger: search', 'simple-history'),
268
- 'options' => array(
269
- _x('Added comments', 'Comments logger: search', 'simple-history') => array(
270
- 'anon_comment_added',
271
- 'user_comment_added',
272
- 'anon_trackback_added',
273
- 'user_trackback_added',
274
- 'anon_pingback_added',
275
- 'user_pingback_added',
276
- ),
277
- _x('Edited comments', 'Comments logger: search', 'simple-history') => array(
278
- 'comment_edited',
279
- 'trackback_edited',
280
- 'pingback_edited',
281
- ),
282
- _x('Approved comments', 'Comments logger: search', 'simple-history') => array(
283
- 'comment_status_approve',
284
- 'trackback_status_approve',
285
- 'pingback_status_approve',
286
- ),
287
- _x('Held comments', 'Comments logger: search', 'simple-history') => array(
288
- 'comment_status_hold',
289
- 'trackback_status_hold',
290
- 'pingback_status_hold',
291
- ),
292
- _x('Comments status changed to spam', 'Comments logger: search', 'simple-history') => array(
293
- 'comment_status_spam',
294
- 'trackback_status_spam',
295
- 'pingback_status_spam',
296
- ),
297
- _x('Trashed comments', 'Comments logger: search', 'simple-history') => array(
298
- 'comment_status_trash',
299
- 'trackback_status_trash',
300
- 'pingback_status_trash',
301
- ),
302
- _x('Untrashed comments', 'Comments logger: search', 'simple-history') => array(
303
- 'comment_untrashed',
304
- 'trackback_untrashed',
305
- 'pingback_untrashed',
306
- ),
307
- _x('Deleted comments', 'Comments logger: search', 'simple-history') => array(
308
- 'comment_deleted',
309
- 'trackback_deleted',
310
- 'pingback_deleted',
311
- ),
312
- ),
313
- ),// end search
314
-
315
- ),// labels
316
-
317
- );
318
-
319
- return $arr_info;
320
- }
321
-
322
- public function loaded()
323
- {
324
-
325
- /**
326
- * Fires immediately after a comment is inserted into the database.
327
- */
328
- add_action('comment_post', array( $this, 'on_comment_post' ), 10, 2);
329
-
330
- /**
331
- * Fires after a comment status has been updated in the database.
332
- * The hook also fires immediately before comment status transition hooks are fired.
333
- */
334
- add_action('wp_set_comment_status', array( $this, 'on_wp_set_comment_status' ), 10, 2);
335
-
336
- /**
337
- *Fires immediately after a comment is restored from the Trash.
338
- */
339
- add_action('untrashed_comment', array( $this, 'on_untrashed_comment' ), 10, 1);
340
-
341
- /**
342
- * Fires immediately before a comment is deleted from the database.
343
- */
344
- add_action('delete_comment', array( $this, 'on_delete_comment' ), 10, 1);
345
-
346
- /**
347
- * Fires immediately after a comment is updated in the database.
348
- * The hook also fires immediately before comment status transition hooks are fired.
349
- */
350
- add_action('edit_comment', array( $this, 'on_edit_comment' ), 10, 1);
351
- }
352
-
353
- /**
354
- * Get comments context
355
- *
356
- * @param int $comment_ID
357
- * @return mixed array with context if comment found, false if comment not found
358
- */
359
- public function get_context_for_comment($comment_ID)
360
- {
361
-
362
- // get_comment passes comment_ID by reference, so it can be unset by that function
363
- $comment_ID_original = $comment_ID;
364
- $comment_data = get_comment($comment_ID);
365
-
366
- if (is_null($comment_data)) {
367
- return false;
368
- }
369
-
370
- $comment_parent_post = get_post($comment_data->comment_post_ID);
371
-
372
- $context = array(
373
- 'comment_ID' => $comment_ID_original,
374
- 'comment_author' => $comment_data->comment_author,
375
- 'comment_author_email' => $comment_data->comment_author_email,
376
- 'comment_author_url' => $comment_data->comment_author_url,
377
- 'comment_author_IP' => $comment_data->comment_author_IP,
378
- 'comment_content' => $comment_data->comment_content,
379
- 'comment_approved' => $comment_data->comment_approved,
380
- 'comment_agent' => $comment_data->comment_agent,
381
- 'comment_type' => $comment_data->comment_type,
382
- 'comment_parent' => $comment_data->comment_parent,
383
- 'comment_post_ID' => $comment_data->comment_post_ID,
384
- 'comment_post_title' => $comment_parent_post->post_title,
385
- 'comment_post_type' => $comment_parent_post->post_type,
386
- );
387
-
388
- // Note: comment type is empty for normal comments
389
- if (empty($context['comment_type'])) {
390
- $context['comment_type'] = 'comment';
391
- }
392
-
393
- return $context;
394
- }
395
-
396
- public function on_edit_comment($comment_ID)
397
- {
398
-
399
- $context = $this->get_context_for_comment($comment_ID);
400
- if (! $context) {
401
- return;
402
- }
403
-
404
- $this->infoMessage(
405
- "{$context["comment_type"]}_edited",
406
- $context
407
- );
408
- }
409
-
410
- public function on_delete_comment($comment_ID)
411
- {
412
-
413
- $context = $this->get_context_for_comment($comment_ID);
414
-
415
- if (! $context) {
416
- return;
417
- }
418
-
419
- $comment_data = get_comment($comment_ID);
420
-
421
- // add occasions if comment was considered spam
422
- // if not added, spam comments can easily flood the log
423
- // Deletions of spam easiy flood log
424
- if (isset($comment_data->comment_approved) && 'spam' === $comment_data->comment_approved) {
425
- // since 2.5.5: don't log deletion of spam comments
426
- return;
427
- // $context["_occasionsID"] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_deleted/type:spam";
428
- }
429
-
430
- $this->infoMessage(
431
- "{$context["comment_type"]}_deleted",
432
- $context
433
- );
434
- }
435
-
436
- public function on_untrashed_comment($comment_ID)
437
- {
438
-
439
- $context = $this->get_context_for_comment($comment_ID);
440
- if (! $context) {
441
- return;
442
- }
443
-
444
- $this->infoMessage(
445
- "{$context["comment_type"]}_untrashed",
446
- $context
447
- );
448
- }
449
-
450
- /**
451
- * Fires after a comment status has been updated in the database.
452
- * The hook also fires immediately before comment status transition hooks are fired.
453
- *
454
- * @param int $comment_id The comment ID.
455
- * @param string|bool $comment_status The comment status. Possible values include 'hold',
456
- * 'approve', 'spam', 'trash', or false.
457
- * do_action( 'wp_set_comment_status', $comment_id, $comment_status );
458
- */
459
- public function on_wp_set_comment_status($comment_ID, $comment_status)
460
- {
461
-
462
- $context = $this->get_context_for_comment($comment_ID);
463
-
464
- if (! $context) {
465
- return;
466
- }
467
-
468
- /*
469
- $comment_status:
470
- approve
471
- comment was approved
472
- spam
473
- comment was marked as spam
474
- trash
475
- comment was trashed
476
- hold
477
- comment was un-approved
478
- */
479
- $message = "{$context["comment_type"]}_status_{$comment_status}";
480
-
481
- $this->infoMessage(
482
- $message,
483
- $context
484
- );
485
- }
486
-
487
- /**
488
- * Fires immediately after a comment is inserted into the database.
489
- */
490
- public function on_comment_post($comment_ID, $comment_approved)
491
- {
492
-
493
- $context = $this->get_context_for_comment($comment_ID);
494
-
495
- if (! $context) {
496
- return;
497
- }
498
-
499
- // since 2.5.5: no more logging of spam comments
500
- if (isset($comment_approved) && 'spam' === $comment_approved) {
501
- return;
502
- }
503
-
504
- $comment_data = get_comment($comment_ID);
505
-
506
- $message = '';
507
-
508
- if ($comment_data->user_id) {
509
- // comment was from a logged in user
510
- $message = "user_{$context["comment_type"]}_added";
511
- } else {
512
- // comment was from a non-logged in user
513
- $message = "anon_{$context["comment_type"]}_added";
514
- $context['_initiator'] = SimpleLoggerLogInitiators::WEB_USER;
515
-
516
- // add occasions if comment is considered spam
517
- // if not added, spam comments can easily flood the log
518
- if (isset($comment_data->comment_approved) && 'spam' === $comment_data->comment_approved) {
519
- $context['_occasionsID'] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_added/type:spam";
520
- }
521
- }
522
-
523
- $this->infoMessage(
524
- $message,
525
- $context
526
- );
527
- }
528
-
529
-
530
- /**
531
- * Modify plain output to inlcude link to post
532
- * and link to comment
533
- */
534
- public function getLogRowPlainTextOutput($row)
535
- {
536
-
537
- $message = $row->message;
538
- $context = $row->context;
539
- $message_key = $context['_message_key'];
540
-
541
- // Message is untranslated here, so get translated text
542
- // Can't call parent __FUNCTION__ because it will interpolate too, which we don't want
543
- if (! empty($message_key)) {
544
- $message = $this->messages[ $message_key ]['translated_text'];
545
- }
546
-
547
- // Wrap links around {comment_post_title}
548
- $comment_post_ID = isset($context['comment_post_ID']) ? (int) $context['comment_post_ID'] : null;
549
- if ($comment_post_ID && $comment_post = get_post($comment_post_ID)) {
550
- $edit_post_link = get_edit_post_link($comment_post_ID);
551
-
552
- if ($edit_post_link) {
553
- $message = str_replace(
554
- '"{comment_post_title}"',
555
- "<a href='{$edit_post_link}'>\"{comment_post_title}\"</a>",
556
- $message
557
- );
558
- }
559
- }
560
-
561
- return $this->interpolate($message, $context, $row);
562
- }
563
-
564
-
565
- /**
566
- * Get output for detailed log section
567
- */
568
- public function getLogRowDetailsOutput($row)
569
- {
570
-
571
- $context = $row->context;
572
- $message_key = $context['_message_key'];
573
- $output = '';
574
- // print_r($row);exit;
575
- /*
576
- if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
577
- if ( '0' == $commentdata['comment_approved'] ) { // comment not spam, but not auto-approved
578
- wp_notify_moderator( $comment_ID );
579
- */
580
- /*
581
- if ( isset( $context["comment_approved"] ) && $context["comment_approved"] == '0' ) {
582
- $output .= "<br>comment was automatically approved";
583
- } else {
584
- $output .= "<br>comment was not automatically approved";
585
- }*/
586
-
587
- $comment_text = '';
588
- if (isset($context['comment_content']) && $context['comment_content']) {
589
- $comment_text = $context['comment_content'];
590
- $comment_text = wp_trim_words($comment_text, 20);
591
- $comment_text = wpautop($comment_text);
592
- }
593
-
594
- // Keys to show
595
- $arr_plugin_keys = array();
596
- $comment_type = isset($context['comment_type']) ? $context['comment_type'] : '';
597
-
598
- switch ($comment_type) {
599
- case 'trackback':
600
- $arr_plugin_keys = array(
601
- 'trackback_status' => _x('Status', 'comments logger - detailed output comment status', 'simple-history'),
602
- // "trackback_type" => _x("Trackback type", "comments logger - detailed output comment type", "simple-history"),
603
- 'trackback_author' => _x('Name', 'comments logger - detailed output author', 'simple-history'),
604
- 'trackback_author_email' => _x('Email', 'comments logger - detailed output email', 'simple-history'),
605
- 'trackback_content' => _x('Content', 'comments logger - detailed output content', 'simple-history'),
606
- );
607
-
608
- break;
609
- case 'pingback':
610
- $arr_plugin_keys = array(
611
- 'pingback_status' => _x('Status', 'comments logger - detailed output comment status', 'simple-history'),
612
- // "pingback_type" => _x("Pingback type", "comments logger - detailed output comment type", "simple-history"),
613
- 'pingback_author' => _x('Name', 'comments logger - detailed output author', 'simple-history'),
614
- 'pingback_author_email' => _x('Email', 'comments logger - detailed output email', 'simple-history'),
615
- 'pingback_content' => _x('Content', 'comments logger - detailed output content', 'simple-history'),
616
-
617
- );
618
-
619
- break;
620
- case 'comment':
621
- default:
622
- $arr_plugin_keys = array(
623
- 'comment_status' => _x('Status', 'comments logger - detailed output comment status', 'simple-history'),
624
- // "comment_type" => _x("Comment type", "comments logger - detailed output comment type", "simple-history"),
625
- 'comment_author' => _x('Name', 'comments logger - detailed output author', 'simple-history'),
626
- 'comment_author_email' => _x('Email', 'comments logger - detailed output email', 'simple-history'),
627
- 'comment_content' => _x('Comment', 'comments logger - detailed output content', 'simple-history'),
628
- );
629
-
630
- break;
631
-
632
- // "comment_author_url" => _x("Author URL", "comments logger - detailed output author", "simple-history"),
633
- // "comment_author_IP" => _x("IP number", "comments logger - detailed output IP", "simple-history"),
634
- }// End switch().
635
-
636
- $arr_plugin_keys = apply_filters('simple_history/comments_logger/row_details_plugin_info_keys', $arr_plugin_keys);
637
-
638
- // Start output of plugin meta data table
639
- $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
640
-
641
- foreach ($arr_plugin_keys as $key => $desc) {
642
- switch ($key) {
643
- case 'comment_content':
644
- case 'trackback_content':
645
- case 'pingback_content':
646
- $desc_output = $comment_text;
647
- break;
648
-
649
- case 'comment_author':
650
- case 'trackback_author':
651
- case 'pingback_author':
652
- $desc_output = '';
653
-
654
- if (isset($context[ $key ])) {
655
- $desc_output .= esc_html($context[ $key ]);
656
- }
657
-
658
- /*
659
- if ( isset( $context["comment_author_email"] ) ) {
660
-
661
- $gravatar_email = $context["comment_author_email"];
662
- $avatar = $this->simpleHistory->get_avatar( $gravatar_email, 14, "blank" );
663
- $desc_output .= "<span class='SimpleCommentsLogger__gravatar'>{$avatar}</span>";
664
-
665
- }
666
- */
667
-
668
- break;
669
-
670
- case 'comment_status':
671
- case 'trackback_status':
672
- case 'pingback_status':
673
- if (isset($context['comment_approved'])) {
674
- if ($context['comment_approved'] === 'spam') {
675
- $desc_output = __('Spam', 'simple-history');
676
- } elseif ($context['comment_approved'] == 1) {
677
- $desc_output = __('Approved', 'simple-history');
678
- } elseif ($context['comment_approved'] == 0) {
679
- $desc_output = __('Pending', 'simple-history');
680
- }
681
- }
682
-
683
- break;
684
-
685
- case 'comment_type':
686
- case 'trackback_type':
687
- case 'pingback_type':
688
- if (isset($context['comment_type'])) {
689
- if ($context['comment_type'] === 'trackback') {
690
- $desc_output = __('Trackback', 'simple-history');
691
- } elseif ($context['comment_type'] === 'pingback') {
692
- $desc_output = __('Pingback', 'simple-history');
693
- } elseif ($context['comment_type'] === 'comment') {
694
- $desc_output = __('Comment', 'simple-history');
695
- } else {
696
- $desc_output = '';
697
- }
698
- }
699
-
700
- break;
701
-
702
- default:
703
- if (isset($context[ $key ])) {
704
- $desc_output = esc_html($context[ $key ]);
705
- }
706
-
707
- break;
708
- }// End switch().
709
-
710
- // Skip empty rows
711
- if (empty($desc_output)) {
712
- continue;
713
- }
714
-
715
- $output .= sprintf(
716
- '
717
  <tr>
718
  <td>%1$s</td>
719
  <td>%2$s</td>
720
  </tr>
721
  ',
722
- esc_html($desc),
723
- $desc_output
724
- );
725
- }// End foreach().
726
-
727
- // Add link to edit comment
728
- $comment_ID = isset($context['comment_ID']) && is_numeric($context['comment_ID']) ? (int) $context['comment_ID'] : false;
729
-
730
- if ($comment_ID) {
731
- $comment = get_comment($comment_ID);
732
-
733
- if ($comment) {
734
- // http://site.local/wp/wp-admin/comment.php?action=editcomment&c=
735
- $edit_comment_link = get_edit_comment_link($comment_ID);
736
-
737
- // Edit link sometimes does not contain comment ID
738
- // Probably because comment has been removed or something
739
- // So only continue if link does not end with "=""
740
- if ($edit_comment_link && $edit_comment_link[ strlen($edit_comment_link) - 1 ] !== '=') {
741
- $output .= sprintf(
742
- '
743
  <tr>
744
  <td></td>
745
  <td><a href="%2$s">%1$s</a></td>
746
  </tr>
747
  ',
748
- _x('View/Edit', 'comments logger - edit comment', 'simple-history'),
749
- $edit_comment_link
750
- );
751
- }
752
- }
753
- } // End if().
754
-
755
- // End table
756
- $output .= '</table>';
757
-
758
- return $output;
759
- }
760
-
761
- public function adminCSS()
762
- {
763
- ?>
764
- <style>
765
- .SimpleCommentsLogger__gravatar {
766
- line-height: 1;
767
- border-radius: 50%;
768
- overflow: hidden;
769
- margin-right: .5em;
770
- margin-left: .5em;
771
- display: inline-block;
772
- }
773
- .SimpleCommentsLogger__gravatar img {
774
- display: block;
775
- }
776
- </style>
777
- <?php
778
- }
779
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs things related to comments
7
  */
8
+ class SimpleCommentsLogger extends SimpleLogger {
9
+
10
+ public $slug = __CLASS__;
11
+
12
+ public function __construct( $sh ) {
13
+ parent::__construct( $sh );
14
+
15
+ // Add option to not show spam comments, because to much things getting logged
16
+ // add_filter("simple_history/log_query_sql_where", array($this, "maybe_modify_log_query_sql_where"));
17
+ add_filter( 'simple_history/log_query_inner_where', array( $this, 'maybe_modify_log_query_sql_where' ) );
18
+ add_filter( 'simple_history/quick_stats_where', array( $this, 'maybe_modify_log_query_sql_where' ) );
19
+ }
20
+
21
+ /**
22
+ * Modify sql query to exclude comments of type spam
23
+ *
24
+ * @param string $where sql query where
25
+ */
26
+ public function maybe_modify_log_query_sql_where( $where ) {
27
+
28
+ // since 19 sept 2016 we do include spam, to skip the subquery
29
+ // spam comments should not be logged anyway since some time
30
+ $include_spam = true;
31
+
32
+ /**
33
+ * Filter option to include spam or not in the gui
34
+ * By default spam is not included, because it can fill the log
35
+ * with too much events
36
+ *
37
+ * @since 2.0
38
+ *
39
+ * @param bool $include_spam Default false
40
+ */
41
+ $include_spam = apply_filters( 'simple_history/comments_logger/include_spam', $include_spam );
42
+
43
+ if ( $include_spam ) {
44
+ return $where;
45
+ }
46
+
47
+ $where .= sprintf(
48
+ '
 
49
  AND id NOT IN (
50
 
51
  SELECT id
72
  WHERE logger = "%3$s"
73
 
74
  )
75
+ ',
76
+ $this->db_table,
77
+ $this->db_table_contexts,
78
+ $this->slug
79
+ );
80
+
81
+ // echo $where;
82
+ return $where;
83
+ }
84
+
85
+ /**
86
+ * Get array with information about this logger
87
+ *
88
+ * @return array
89
+ */
90
+ public function getInfo() {
91
+
92
+ $arr_info = array(
93
+ 'name' => 'Comments Logger',
94
+ 'description' => 'Logs comments, and modifications to them',
95
+ 'capability' => 'moderate_comments',
96
+ 'messages' => array(
97
+
98
+ // Comments
99
+ 'anon_comment_added' => _x(
100
+ 'Added a comment to {comment_post_type} "{comment_post_title}"',
101
+ 'A comment was added to the database by a non-logged in internet user',
102
+ 'simple-history'
103
+ ),
104
+
105
+ 'user_comment_added' => _x(
106
+ 'Added a comment to {comment_post_type} "{comment_post_title}"',
107
+ 'A comment was added to the database by a logged in user',
108
+ 'simple-history'
109
+ ),
110
+
111
+ 'comment_status_approve' => _x(
112
+ 'Approved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
113
+ 'A comment was approved',
114
+ 'simple-history'
115
+ ),
116
+
117
+ 'comment_status_hold' => _x(
118
+ 'Unapproved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
119
+ 'A comment was was unapproved',
120
+ 'simple-history'
121
+ ),
122
+
123
+ 'comment_status_spam' => _x(
124
+ 'Marked a comment to post "{comment_post_title}" as spam',
125
+ 'A comment was marked as spam',
126
+ 'simple-history'
127
+ ),
128
+
129
+ 'comment_status_trash' => _x(
130
+ 'Trashed a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
131
+ 'A comment was marked moved to the trash',
132
+ 'simple-history'
133
+ ),
134
+
135
+ 'comment_untrashed' => _x(
136
+ 'Restored a comment to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
137
+ 'A comment was restored from the trash',
138
+ 'simple-history'
139
+ ),
140
+
141
+ 'comment_deleted' => _x(
142
+ 'Deleted a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
143
+ 'A comment was deleted',
144
+ 'simple-history'
145
+ ),
146
+
147
+ 'comment_edited' => _x(
148
+ 'Edited a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
149
+ 'A comment was edited',
150
+ 'simple-history'
151
+ ),
152
+
153
+ // Trackbacks
154
+ 'anon_trackback_added' => _x(
155
+ 'Added a trackback to {comment_post_type} "{comment_post_title}"',
156
+ 'A trackback was added to the database by a non-logged in internet user',
157
+ 'simple-history'
158
+ ),
159
+
160
+ 'user_trackback_added' => _x(
161
+ 'Added a trackback to {comment_post_type} "{comment_post_title}"',
162
+ 'A trackback was added to the database by a logged in user',
163
+ 'simple-history'
164
+ ),
165
+
166
+ 'trackback_status_approve' => _x(
167
+ 'Approved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
168
+ 'A trackback was approved',
169
+ 'simple-history'
170
+ ),
171
+
172
+ 'trackback_status_hold' => _x(
173
+ 'Unapproved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
174
+ 'A trackback was was unapproved',
175
+ 'simple-history'
176
+ ),
177
+
178
+ 'trackback_status_spam' => _x(
179
+ 'Marked a trackback to post "{comment_post_title}" as spam',
180
+ 'A trackback was marked as spam',
181
+ 'simple-history'
182
+ ),
183
+
184
+ 'trackback_status_trash' => _x(
185
+ 'Trashed a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
186
+ 'A trackback was marked moved to the trash',
187
+ 'simple-history'
188
+ ),
189
+
190
+ 'trackback_untrashed' => _x(
191
+ 'Restored a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
192
+ 'A trackback was restored from the trash',
193
+ 'simple-history'
194
+ ),
195
+
196
+ 'trackback_deleted' => _x(
197
+ 'Deleted a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
198
+ 'A trackback was deleted',
199
+ 'simple-history'
200
+ ),
201
+
202
+ 'trackback_edited' => _x(
203
+ 'Edited a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
204
+ 'A trackback was edited',
205
+ 'simple-history'
206
+ ),
207
+
208
+ // Pingbacks
209
+ 'anon_pingback_added' => _x(
210
+ 'Added a pingback to {comment_post_type} "{comment_post_title}"',
211
+ 'A trackback was added to the database by a non-logged in internet user',
212
+ 'simple-history'
213
+ ),
214
+
215
+ 'user_pingback_added' => _x(
216
+ 'Added a pingback to {comment_post_type} "{comment_post_title}"',
217
+ 'A pingback was added to the database by a logged in user',
218
+ 'simple-history'
219
+ ),
220
+
221
+ 'pingback_status_approve' => _x(
222
+ 'Approved a pingback to "{comment_post_title}" by "{comment_author}"" ({comment_author_email})',
223
+ 'A pingback was approved',
224
+ 'simple-history'
225
+ ),
226
+
227
+ 'pingback_status_hold' => _x(
228
+ 'Unapproved a pingback to "{comment_post_title}" by "{comment_author}" ({comment_author_email})',
229
+ 'A pingback was was unapproved',
230
+ 'simple-history'
231
+ ),
232
+
233
+ 'pingback_status_spam' => _x(
234
+ 'Marked a pingback to post "{comment_post_title}" as spam',
235
+ 'A pingback was marked as spam',
236
+ 'simple-history'
237
+ ),
238
+
239
+ 'pingback_status_trash' => _x(
240
+ 'Trashed a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
241
+ 'A pingback was marked moved to the trash',
242
+ 'simple-history'
243
+ ),
244
+
245
+ 'pingback_untrashed' => _x(
246
+ 'Restored a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
247
+ 'A pingback was restored from the trash',
248
+ 'simple-history'
249
+ ),
250
+
251
+ 'pingback_deleted' => _x(
252
+ 'Deleted a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
253
+ 'A pingback was deleted',
254
+ 'simple-history'
255
+ ),
256
+
257
+ 'pingback_edited' => _x(
258
+ 'Edited a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
259
+ 'A pingback was edited',
260
+ 'simple-history'
261
+ ),
262
+
263
+ ), // end messages
264
+
265
+ 'labels' => array(
266
+
267
+ 'search' => array(
268
+ 'label' => _x( 'Comments', 'Comments logger: search', 'simple-history' ),
269
+ 'label_all' => _x( 'All comments activity', 'Comments logger: search', 'simple-history' ),
270
+ 'options' => array(
271
+ _x( 'Added comments', 'Comments logger: search', 'simple-history' ) => array(
272
+ 'anon_comment_added',
273
+ 'user_comment_added',
274
+ 'anon_trackback_added',
275
+ 'user_trackback_added',
276
+ 'anon_pingback_added',
277
+ 'user_pingback_added',
278
+ ),
279
+ _x( 'Edited comments', 'Comments logger: search', 'simple-history' ) => array(
280
+ 'comment_edited',
281
+ 'trackback_edited',
282
+ 'pingback_edited',
283
+ ),
284
+ _x( 'Approved comments', 'Comments logger: search', 'simple-history' ) => array(
285
+ 'comment_status_approve',
286
+ 'trackback_status_approve',
287
+ 'pingback_status_approve',
288
+ ),
289
+ _x( 'Held comments', 'Comments logger: search', 'simple-history' ) => array(
290
+ 'comment_status_hold',
291
+ 'trackback_status_hold',
292
+ 'pingback_status_hold',
293
+ ),
294
+ _x( 'Comments status changed to spam', 'Comments logger: search', 'simple-history' ) => array(
295
+ 'comment_status_spam',
296
+ 'trackback_status_spam',
297
+ 'pingback_status_spam',
298
+ ),
299
+ _x( 'Trashed comments', 'Comments logger: search', 'simple-history' ) => array(
300
+ 'comment_status_trash',
301
+ 'trackback_status_trash',
302
+ 'pingback_status_trash',
303
+ ),
304
+ _x( 'Untrashed comments', 'Comments logger: search', 'simple-history' ) => array(
305
+ 'comment_untrashed',
306
+ 'trackback_untrashed',
307
+ 'pingback_untrashed',
308
+ ),
309
+ _x( 'Deleted comments', 'Comments logger: search', 'simple-history' ) => array(
310
+ 'comment_deleted',
311
+ 'trackback_deleted',
312
+ 'pingback_deleted',
313
+ ),
314
+ ),
315
+ ), // end search
316
+
317
+ ), // labels
318
+
319
+ );
320
+
321
+ return $arr_info;
322
+ }
323
+
324
+ public function loaded() {
325
+
326
+ /**
327
+ * Fires immediately after a comment is inserted into the database.
328
+ */
329
+ add_action( 'comment_post', array( $this, 'on_comment_post' ), 10, 2 );
330
+
331
+ /**
332
+ * Fires after a comment status has been updated in the database.
333
+ * The hook also fires immediately before comment status transition hooks are fired.
334
+ */
335
+ add_action( 'wp_set_comment_status', array( $this, 'on_wp_set_comment_status' ), 10, 2 );
336
+
337
+ /**
338
+ *Fires immediately after a comment is restored from the Trash.
339
+ */
340
+ add_action( 'untrashed_comment', array( $this, 'on_untrashed_comment' ), 10, 1 );
341
+
342
+ /**
343
+ * Fires immediately before a comment is deleted from the database.
344
+ */
345
+ add_action( 'delete_comment', array( $this, 'on_delete_comment' ), 10, 1 );
346
+
347
+ /**
348
+ * Fires immediately after a comment is updated in the database.
349
+ * The hook also fires immediately before comment status transition hooks are fired.
350
+ */
351
+ add_action( 'edit_comment', array( $this, 'on_edit_comment' ), 10, 1 );
352
+ }
353
+
354
+ /**
355
+ * Get comments context
356
+ *
357
+ * @param int $comment_ID
358
+ * @return mixed array with context if comment found, false if comment not found
359
+ */
360
+ public function get_context_for_comment( $comment_ID ) {
361
+
362
+ // get_comment passes comment_ID by reference, so it can be unset by that function
363
+ $comment_ID_original = $comment_ID;
364
+ $comment_data = get_comment( $comment_ID );
365
+
366
+ if ( is_null( $comment_data ) ) {
367
+ return false;
368
+ }
369
+
370
+ $comment_parent_post = get_post( $comment_data->comment_post_ID );
371
+
372
+ $context = array(
373
+ 'comment_ID' => $comment_ID_original,
374
+ 'comment_author' => $comment_data->comment_author,
375
+ 'comment_author_email' => $comment_data->comment_author_email,
376
+ 'comment_author_url' => $comment_data->comment_author_url,
377
+ 'comment_author_IP' => $comment_data->comment_author_IP,
378
+ 'comment_content' => $comment_data->comment_content,
379
+ 'comment_approved' => $comment_data->comment_approved,
380
+ 'comment_agent' => $comment_data->comment_agent,
381
+ 'comment_type' => $comment_data->comment_type,
382
+ 'comment_parent' => $comment_data->comment_parent,
383
+ 'comment_post_ID' => $comment_data->comment_post_ID,
384
+ 'comment_post_title' => $comment_parent_post->post_title,
385
+ 'comment_post_type' => $comment_parent_post->post_type,
386
+ );
387
+
388
+ // Note: comment type is empty for normal comments
389
+ if ( empty( $context['comment_type'] ) ) {
390
+ $context['comment_type'] = 'comment';
391
+ }
392
+
393
+ return $context;
394
+ }
395
+
396
+ public function on_edit_comment( $comment_ID ) {
397
+
398
+ $context = $this->get_context_for_comment( $comment_ID );
399
+ if ( ! $context ) {
400
+ return;
401
+ }
402
+
403
+ $this->infoMessage(
404
+ "{$context["comment_type"]}_edited",
405
+ $context
406
+ );
407
+ }
408
+
409
+ public function on_delete_comment( $comment_ID ) {
410
+
411
+ $context = $this->get_context_for_comment( $comment_ID );
412
+
413
+ if ( ! $context ) {
414
+ return;
415
+ }
416
+
417
+ $comment_data = get_comment( $comment_ID );
418
+
419
+ // add occasions if comment was considered spam
420
+ // if not added, spam comments can easily flood the log
421
+ // Deletions of spam easiy flood log
422
+ if ( isset( $comment_data->comment_approved ) && 'spam' === $comment_data->comment_approved ) {
423
+ // since 2.5.5: don't log deletion of spam comments
424
+ return;
425
+ // $context["_occasionsID"] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_deleted/type:spam";
426
+ }
427
+
428
+ $this->infoMessage(
429
+ "{$context["comment_type"]}_deleted",
430
+ $context
431
+ );
432
+ }
433
+
434
+ public function on_untrashed_comment( $comment_ID ) {
435
+
436
+ $context = $this->get_context_for_comment( $comment_ID );
437
+ if ( ! $context ) {
438
+ return;
439
+ }
440
+
441
+ $this->infoMessage(
442
+ "{$context["comment_type"]}_untrashed",
443
+ $context
444
+ );
445
+ }
446
+
447
+ /**
448
+ * Fires after a comment status has been updated in the database.
449
+ * The hook also fires immediately before comment status transition hooks are fired.
450
+ *
451
+ * @param int $comment_id The comment ID.
452
+ * @param string|bool $comment_status The comment status. Possible values include 'hold',
453
+ * 'approve', 'spam', 'trash', or false.
454
+ * do_action( 'wp_set_comment_status', $comment_id, $comment_status );
455
+ */
456
+ public function on_wp_set_comment_status( $comment_ID, $comment_status ) {
457
+
458
+ $context = $this->get_context_for_comment( $comment_ID );
459
+
460
+ if ( ! $context ) {
461
+ return;
462
+ }
463
+
464
+ /*
465
+ $comment_status:
466
+ approve
467
+ comment was approved
468
+ spam
469
+ comment was marked as spam
470
+ trash
471
+ comment was trashed
472
+ hold
473
+ comment was un-approved
474
+ */
475
+ $message = "{$context["comment_type"]}_status_{$comment_status}";
476
+
477
+ $this->infoMessage(
478
+ $message,
479
+ $context
480
+ );
481
+ }
482
+
483
+ /**
484
+ * Fires immediately after a comment is inserted into the database.
485
+ */
486
+ public function on_comment_post( $comment_ID, $comment_approved ) {
487
+
488
+ $context = $this->get_context_for_comment( $comment_ID );
489
+
490
+ if ( ! $context ) {
491
+ return;
492
+ }
493
+
494
+ // since 2.5.5: no more logging of spam comments
495
+ if ( isset( $comment_approved ) && 'spam' === $comment_approved ) {
496
+ return;
497
+ }
498
+
499
+ $comment_data = get_comment( $comment_ID );
500
+
501
+ $message = '';
502
+
503
+ if ( $comment_data->user_id ) {
504
+ // comment was from a logged in user
505
+ $message = "user_{$context["comment_type"]}_added";
506
+ } else {
507
+ // comment was from a non-logged in user
508
+ $message = "anon_{$context["comment_type"]}_added";
509
+ $context['_initiator'] = SimpleLoggerLogInitiators::WEB_USER;
510
+
511
+ // add occasions if comment is considered spam
512
+ // if not added, spam comments can easily flood the log
513
+ if ( isset( $comment_data->comment_approved ) && 'spam' === $comment_data->comment_approved ) {
514
+ $context['_occasionsID'] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_added/type:spam";
515
+ }
516
+ }
517
+
518
+ $this->infoMessage(
519
+ $message,
520
+ $context
521
+ );
522
+ }
523
+
524
+
525
+ /**
526
+ * Modify plain output to inlcude link to post
527
+ * and link to comment
528
+ *
529
+ * @param object $row
530
+ */
531
+ public function getLogRowPlainTextOutput( $row ) {
532
+
533
+ $message = $row->message;
534
+ $context = $row->context;
535
+ $message_key = $context['_message_key'];
536
+
537
+ // Message is untranslated here, so get translated text
538
+ // Can't call parent __FUNCTION__ because it will interpolate too, which we don't want
539
+ if ( ! empty( $message_key ) ) {
540
+ $message = $this->messages[ $message_key ]['translated_text'];
541
+ }
542
+
543
+ // Wrap links around {comment_post_title}
544
+ $comment_post_ID = isset( $context['comment_post_ID'] ) ? (int) $context['comment_post_ID'] : null;
545
+ if ( $comment_post_ID ) {
546
+ $edit_post_link = get_edit_post_link( $comment_post_ID );
547
+
548
+ if ( $edit_post_link ) {
549
+ $message = str_replace(
550
+ '"{comment_post_title}"',
551
+ "<a href='{$edit_post_link}'>\"{comment_post_title}\"</a>",
552
+ $message
553
+ );
554
+ }
555
+ }
556
+
557
+ return $this->interpolate( $message, $context, $row );
558
+ }
559
+
560
+
561
+ /**
562
+ * Get output for detailed log section
563
+ */
564
+ public function getLogRowDetailsOutput( $row ) {
565
+
566
+ $context = $row->context;
567
+ $message_key = $context['_message_key'];
568
+ $output = '';
569
+ // print_r($row);exit;
570
+ /*
571
+ if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
572
+ if ( '0' == $commentdata['comment_approved'] ) { // comment not spam, but not auto-approved
573
+ wp_notify_moderator( $comment_ID );
574
+ */
575
+ /*
576
+ if ( isset( $context["comment_approved"] ) && $context["comment_approved"] == '0' ) {
577
+ $output .= "<br>comment was automatically approved";
578
+ } else {
579
+ $output .= "<br>comment was not automatically approved";
580
+ }*/
581
+
582
+ $comment_text = '';
583
+ if ( isset( $context['comment_content'] ) && $context['comment_content'] ) {
584
+ $comment_text = $context['comment_content'];
585
+ $comment_text = wp_trim_words( $comment_text, 20 );
586
+ $comment_text = wpautop( $comment_text );
587
+ }
588
+
589
+ // Keys to show
590
+ $arr_plugin_keys = array();
591
+ $comment_type = isset( $context['comment_type'] ) ? $context['comment_type'] : '';
592
+
593
+ switch ( $comment_type ) {
594
+ case 'trackback':
595
+ $arr_plugin_keys = array(
596
+ 'trackback_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ),
597
+ // "trackback_type" => _x("Trackback type", "comments logger - detailed output comment type", "simple-history"),
598
+ 'trackback_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ),
599
+ 'trackback_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ),
600
+ 'trackback_content' => _x( 'Content', 'comments logger - detailed output content', 'simple-history' ),
601
+ );
602
+
603
+ break;
604
+ case 'pingback':
605
+ $arr_plugin_keys = array(
606
+ 'pingback_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ),
607
+ // "pingback_type" => _x("Pingback type", "comments logger - detailed output comment type", "simple-history"),
608
+ 'pingback_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ),
609
+ 'pingback_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ),
610
+ 'pingback_content' => _x( 'Content', 'comments logger - detailed output content', 'simple-history' ),
611
+
612
+ );
613
+
614
+ break;
615
+ case 'comment':
616
+ default:
617
+ $arr_plugin_keys = array(
618
+ 'comment_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ),
619
+ // "comment_type" => _x("Comment type", "comments logger - detailed output comment type", "simple-history"),
620
+ 'comment_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ),
621
+ 'comment_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ),
622
+ 'comment_content' => _x( 'Comment', 'comments logger - detailed output content', 'simple-history' ),
623
+ );
624
+
625
+ break;
626
+
627
+ // "comment_author_url" => _x("Author URL", "comments logger - detailed output author", "simple-history"),
628
+ // "comment_author_IP" => _x("IP number", "comments logger - detailed output IP", "simple-history"),
629
+ }// End switch().
630
+
631
+ $arr_plugin_keys = apply_filters( 'simple_history/comments_logger/row_details_plugin_info_keys', $arr_plugin_keys );
632
+
633
+ // Start output of plugin meta data table
634
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
635
+
636
+ foreach ( $arr_plugin_keys as $key => $desc ) {
637
+ switch ( $key ) {
638
+ case 'comment_content':
639
+ case 'trackback_content':
640
+ case 'pingback_content':
641
+ $desc_output = $comment_text;
642
+ break;
643
+
644
+ case 'comment_author':
645
+ case 'trackback_author':
646
+ case 'pingback_author':
647
+ $desc_output = '';
648
+
649
+ if ( isset( $context[ $key ] ) ) {
650
+ $desc_output .= esc_html( $context[ $key ] );
651
+ }
652
+
653
+ /*
654
+ if ( isset( $context["comment_author_email"] ) ) {
655
+
656
+ $gravatar_email = $context["comment_author_email"];
657
+ $avatar = $this->simpleHistory->get_avatar( $gravatar_email, 14, "blank" );
658
+ $desc_output .= "<span class='SimpleCommentsLogger__gravatar'>{$avatar}</span>";
659
+
660
+ }
661
+ */
662
+
663
+ break;
664
+
665
+ case 'comment_status':
666
+ case 'trackback_status':
667
+ case 'pingback_status':
668
+ if ( isset( $context['comment_approved'] ) ) {
669
+ if ( $context['comment_approved'] === 'spam' ) {
670
+ $desc_output = __( 'Spam', 'simple-history' );
671
+ } elseif ( $context['comment_approved'] == 1 ) {
672
+ $desc_output = __( 'Approved', 'simple-history' );
673
+ } elseif ( $context['comment_approved'] == 0 ) {
674
+ $desc_output = __( 'Pending', 'simple-history' );
675
+ }
676
+ }
677
+
678
+ break;
679
+
680
+ case 'comment_type':
681
+ case 'trackback_type':
682
+ case 'pingback_type':
683
+ if ( isset( $context['comment_type'] ) ) {
684
+ if ( $context['comment_type'] === 'trackback' ) {
685
+ $desc_output = __( 'Trackback', 'simple-history' );
686
+ } elseif ( $context['comment_type'] === 'pingback' ) {
687
+ $desc_output = __( 'Pingback', 'simple-history' );
688
+ } elseif ( $context['comment_type'] === 'comment' ) {
689
+ $desc_output = __( 'Comment', 'simple-history' );
690
+ } else {
691
+ $desc_output = '';
692
+ }
693
+ }
694
+
695
+ break;
696
+
697
+ default:
698
+ if ( isset( $context[ $key ] ) ) {
699
+ $desc_output = esc_html( $context[ $key ] );
700
+ }
701
+
702
+ break;
703
+ }// End switch().
704
+
705
+ // Skip empty rows
706
+ if ( empty( $desc_output ) ) {
707
+ continue;
708
+ }
709
+
710
+ $output .= sprintf(
711
+ '
 
 
 
 
712
  <tr>
713
  <td>%1$s</td>
714
  <td>%2$s</td>
715
  </tr>
716
  ',
717
+ esc_html( $desc ),
718
+ $desc_output
719
+ );
720
+ }// End foreach().
721
+
722
+ // Add link to edit comment
723
+ $comment_ID = isset( $context['comment_ID'] ) && is_numeric( $context['comment_ID'] ) ? (int) $context['comment_ID'] : false;
724
+
725
+ if ( $comment_ID ) {
726
+ $comment = get_comment( $comment_ID );
727
+
728
+ if ( $comment ) {
729
+ // http://site.local/wp/wp-admin/comment.php?action=editcomment&c=
730
+ $edit_comment_link = get_edit_comment_link( $comment_ID );
731
+
732
+ // Edit link sometimes does not contain comment ID
733
+ // Probably because comment has been removed or something
734
+ // So only continue if link does not end with "=""
735
+ if ( $edit_comment_link && $edit_comment_link[ strlen( $edit_comment_link ) - 1 ] !== '=' ) {
736
+ $output .= sprintf(
737
+ '
738
  <tr>
739
  <td></td>
740
  <td><a href="%2$s">%1$s</a></td>
741
  </tr>
742
  ',
743
+ _x( 'View/Edit', 'comments logger - edit comment', 'simple-history' ),
744
+ $edit_comment_link
745
+ );
746
+ }
747
+ }
748
+ } // End if().
749
+
750
+ // End table
751
+ $output .= '</table>';
752
+
753
+ return $output;
754
+ }
755
+
756
+ public function adminCSS() {
757
+ ?>
758
+ <style>
759
+ .SimpleCommentsLogger__gravatar {
760
+ line-height: 1;
761
+ border-radius: 50%;
762
+ overflow: hidden;
763
+ margin-right: .5em;
764
+ margin-left: .5em;
765
+ display: inline-block;
766
+ }
767
+ .SimpleCommentsLogger__gravatar img {
768
+ display: block;
769
+ }
770
+ </style>
771
+ <?php
772
+ }
 
773
  }
loggers/SimpleCoreUpdatesLogger.php CHANGED
@@ -1,116 +1,111 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs WordPress core updates
7
  */
8
- class SimpleCoreUpdatesLogger extends SimpleLogger
9
- {
10
-
11
- public $slug = __CLASS__;
12
-
13
- public function loaded()
14
- {
15
-
16
- add_action('_core_updated_successfully', array( $this, 'on_core_updated' ));
17
- add_action('update_feedback', array( $this, 'on_update_feedback' ));
18
-
19
- // Can't log db updates at the moment, because loaded() is not called yet when the action fires
20
- // add_action( 'wp_upgrade', array( $this, "on_wp_upgrade" ), 10, 2 );
21
- }
22
-
23
- /**
24
- * Fires after a site is fully upgraded.
25
- * The database, that is.
26
- *
27
- * @param int $wp_db_version The new $wp_db_version.
28
- * @param int $wp_current_db_version The old (current) $wp_db_version.
29
- */
30
- public function on_wp_upgrade($wp_db_version, $wp_current_db_version)
31
- {
32
-
33
- $this->debugMessage(
34
- 'core_db_version_updated',
35
- array(
36
- 'new_version' => $wp_db_version,
37
- 'prev_version' => $wp_current_db_version,
38
- )
39
- );
40
- }
41
-
42
- /**
43
- * We need to store the WordPress version we are updating from.
44
- * 'update_feedback' is a suitable filter.
45
- */
46
- public function on_update_feedback()
47
- {
48
-
49
- if (! empty($GLOBALS['wp_version']) && ! isset($GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ])) {
50
- $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] = $GLOBALS['wp_version'];
51
- }
52
- }
53
-
54
- /**
55
- * Get array with information about this logger
56
- *
57
- * @return array
58
- */
59
- public function getInfo()
60
- {
61
-
62
- $arr_info = array(
63
- 'name' => 'Core Updates Logger',
64
- 'description' => 'Logs the update of WordPress (manual and automatic updates)',
65
- 'capability' => 'update_core',
66
- 'messages' => array(
67
- 'core_updated' => __('Updated WordPress to {new_version} from {prev_version}', 'simple-history'),
68
- 'core_auto_updated' => __('WordPress auto-updated to {new_version} from {prev_version}', 'simple-history'),
69
- 'core_db_version_updated' => __('WordPress database version updated to {new_version} from {prev_version}', 'simple-history'),
70
- ),
71
- 'labels' => array(
72
- 'search' => array(
73
- 'label' => _x('WordPress Core', 'User logger: search', 'simple-history'),
74
- 'options' => array(
75
- _x('WordPress core updates', 'User logger: search', 'simple-history') => array(
76
- 'core_updated',
77
- 'core_auto_updated',
78
- ),
79
- ),
80
- ),// end search array
81
- ),// end labels
82
- );
83
-
84
- return $arr_info;
85
- }
86
-
87
- /**
88
- * Called when WordPress is updated
89
- *
90
- * @param string $new_wp_version
91
- */
92
- public function on_core_updated($new_wp_version)
93
- {
94
-
95
- $old_wp_version = empty($GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ]) ? $GLOBALS['wp_version'] : $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ];
96
-
97
- $auto_update = true;
98
- if ($GLOBALS['pagenow'] == 'update-core.php') {
99
- $auto_update = false;
100
- }
101
-
102
- if ($auto_update) {
103
- $message = 'core_auto_updated';
104
- } else {
105
- $message = 'core_updated';
106
- }
107
-
108
- $this->noticeMessage(
109
- $message,
110
- array(
111
- 'prev_version' => $old_wp_version,
112
- 'new_version' => $new_wp_version,
113
- )
114
- );
115
- }
116
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs WordPress core updates
7
  */
8
+ class SimpleCoreUpdatesLogger extends SimpleLogger {
9
+
10
+
11
+ public $slug = __CLASS__;
12
+
13
+ public function loaded() {
14
+
15
+ add_action( '_core_updated_successfully', array( $this, 'on_core_updated' ) );
16
+ add_action( 'update_feedback', array( $this, 'on_update_feedback' ) );
17
+
18
+ // Can't log db updates at the moment, because loaded() is not called yet when the action fires
19
+ // add_action( 'wp_upgrade', array( $this, "on_wp_upgrade" ), 10, 2 );
20
+ }
21
+
22
+ /**
23
+ * Fires after a site is fully upgraded.
24
+ * The database, that is.
25
+ *
26
+ * @param int $wp_db_version The new $wp_db_version.
27
+ * @param int $wp_current_db_version The old (current) $wp_db_version.
28
+ */
29
+ public function on_wp_upgrade( $wp_db_version, $wp_current_db_version ) {
30
+
31
+ $this->debugMessage(
32
+ 'core_db_version_updated',
33
+ array(
34
+ 'new_version' => $wp_db_version,
35
+ 'prev_version' => $wp_current_db_version,
36
+ )
37
+ );
38
+ }
39
+
40
+ /**
41
+ * We need to store the WordPress version we are updating from.
42
+ * 'update_feedback' is a suitable filter.
43
+ */
44
+ public function on_update_feedback() {
45
+
46
+ if ( ! empty( $GLOBALS['wp_version'] ) && ! isset( $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] ) ) {
47
+ $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] = $GLOBALS['wp_version'];
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Get array with information about this logger
53
+ *
54
+ * @return array
55
+ */
56
+ public function getInfo() {
57
+
58
+ $arr_info = array(
59
+ 'name' => 'Core Updates Logger',
60
+ 'description' => 'Logs the update of WordPress (manual and automatic updates)',
61
+ 'capability' => 'update_core',
62
+ 'messages' => array(
63
+ 'core_updated' => __( 'Updated WordPress to {new_version} from {prev_version}', 'simple-history' ),
64
+ 'core_auto_updated' => __( 'WordPress auto-updated to {new_version} from {prev_version}', 'simple-history' ),
65
+ 'core_db_version_updated' => __( 'WordPress database version updated to {new_version} from {prev_version}', 'simple-history' ),
66
+ ),
67
+ 'labels' => array(
68
+ 'search' => array(
69
+ 'label' => _x( 'WordPress Core', 'User logger: search', 'simple-history' ),
70
+ 'options' => array(
71
+ _x( 'WordPress core updates', 'User logger: search', 'simple-history' ) => array(
72
+ 'core_updated',
73
+ 'core_auto_updated',
74
+ ),
75
+ ),
76
+ ), // end search array
77
+ ), // end labels
78
+ );
79
+
80
+ return $arr_info;
81
+ }
82
+
83
+ /**
84
+ * Called when WordPress is updated
85
+ *
86
+ * @param string $new_wp_version
87
+ */
88
+ public function on_core_updated( $new_wp_version ) {
89
+
90
+ $old_wp_version = empty( $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ] ) ? $GLOBALS['wp_version'] : $GLOBALS[ 'simple_history_' . $this->slug . '_wp_version' ];
91
+
92
+ $auto_update = true;
93
+ if ( $GLOBALS['pagenow'] == 'update-core.php' ) {
94
+ $auto_update = false;
95
+ }
96
+
97
+ if ( $auto_update ) {
98
+ $message = 'core_auto_updated';
99
+ } else {
100
+ $message = 'core_updated';
101
+ }
102
+
103
+ $this->noticeMessage(
104
+ $message,
105
+ array(
106
+ 'prev_version' => $old_wp_version,
107
+ 'new_version' => $new_wp_version,
108
+ )
109
+ );
110
+ }
 
 
 
 
 
111
  }
loggers/SimpleExportLogger.php CHANGED
@@ -1,56 +1,53 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * Logs WordPress exports
7
  */
8
- class SimpleExportLogger extends SimpleLogger
9
- {
10
- public $slug = __CLASS__;
11
-
12
- /**
13
- * Get array with information about this logger
14
- *
15
- * @return array
16
- */
17
- public function getInfo()
18
- {
19
- $arr_info = array(
20
- 'name' => __('Export Logger', 'simple-history'),
21
- 'description' => __('Logs updates to WordPress export', 'simple-history'),
22
- 'capability' => 'export',
23
- 'messages' => array(
24
- 'created_export' => __('Created XML export', 'simple-history'),
25
- ),
26
- 'labels' => array(
27
- 'search' => array(
28
- 'label' => _x('Export', 'Export logger: search', 'simple-history'),
29
- 'options' => array(
30
- _x('Created exports', 'Export logger: search', 'simple-history') => array(
31
- 'created_export'
32
- ),
33
- ),
34
- ),// end search array
35
- ),// end labels
36
- );
37
-
38
- return $arr_info;
39
- }
40
-
41
- public function loaded()
42
- {
43
-
44
- add_action('export_wp', array( $this, 'on_export_wp' ), 10, 1);
45
- }
46
-
47
- public function on_export_wp($args)
48
- {
49
- $this->infoMessage(
50
- 'created_export',
51
- array(
52
- 'args' => $this->simpleHistory->json_encode($args),
53
- )
54
- );
55
- }
56
  }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * Logs WordPress exports
7
  */
8
+ class SimpleExportLogger extends SimpleLogger {
9
+
10
+ public $slug = __CLASS__;
11
+
12
+ /**
13
+ * Get array with information about this logger
14
+ *
15
+ * @return array
16
+ */
17
+ public function getInfo() {
18
+ $arr_info = array(
19
+ 'name' => __( 'Export Logger', 'simple-history' ),
20
+ 'description' => __( 'Logs updates to WordPress export', 'simple-history' ),
21
+ 'capability' => 'export',
22
+ 'messages' => array(
23
+ 'created_export' => __( 'Created XML export', 'simple-history' ),
24
+ ),
25
+ 'labels' => array(
26
+ 'search' => array(
27
+ 'label' => _x( 'Export', 'Export logger: search', 'simple-history' ),
28
+ 'options' => array(
29
+ _x( 'Created exports', 'Export logger: search', 'simple-history' ) => array(
30
+ 'created_export',
31
+ ),
32
+ ),
33
+ ), // end search array
34
+ ), // end labels
35
+ );
36
+
37
+ return $arr_info;
38
+ }
39
+
40
+ public function loaded() {
41
+
42
+ add_action( 'export_wp', array( $this, 'on_export_wp' ), 10, 1 );
43
+ }
44
+
45
+ public function on_export_wp( $args ) {
46
+ $this->infoMessage(
47
+ 'created_export',
48
+ array(
49
+ 'args' => $this->simpleHistory->json_encode( $args ),
50
+ )
51
+ );
52
+ }
 
 
 
53
  }
loggers/SimpleLegacyLogger.php DELETED
@@ -1,104 +0,0 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- /**
6
- * Logger for events stored earlier than v2
7
- * and for events added via simple_history_add
8
- *
9
- * @since 2.0
10
- */
11
- class SimpleLegacyLogger extends SimpleLogger
12
- {
13
-
14
- /**
15
- * Unique slug for this logger
16
- * Will be saved in DB and used to associate each log row with its logger
17
- */
18
- public $slug = 'SimpleLegacyLogger';
19
-
20
- public function __construct()
21
- {
22
-
23
- // $this->info(__CLASS__ . " construct()");
24
- }
25
-
26
- /**
27
- * Get array with information about this logger
28
- *
29
- * @return array
30
- */
31
- public function getInfo()
32
- {
33
-
34
- $arr_info = array(
35
- 'name' => 'Legacy Logger',
36
- 'description' => 'Formats old events',
37
- 'capability' => 'edit_pages',
38
- 'messages' => array(),
39
- /*
40
- "labels" => array(
41
- "search" => array(
42
- "label" => _x("Export", "Export logger: search", "simple-history"),
43
- "options" => array(
44
- _x("Exports created", "Core updates logger: search", "simple-history") => array(
45
- "created_export"
46
- ),
47
- )
48
- ) // end search array
49
- ) // end labels
50
- */
51
-
52
- );
53
-
54
- return $arr_info;
55
- }
56
-
57
- public function getLogRowPlainTextOutput($row)
58
- {
59
-
60
- $message = $row->message;
61
- $context = $row->context;
62
-
63
- $out = '';
64
-
65
- global $wpdb;
66
-
67
- // Get old columns for this event
68
- $sql = sprintf(
69
- '
70
- SELECT * FROM %1$s
71
- WHERE id = %2$d
72
- ',
73
- $wpdb->prefix . SimpleHistory::DBTABLE,
74
- $row->id
75
- );
76
-
77
- $one_item = $wpdb->get_row($sql);
78
-
79
- // $out .= print_r($row, true);
80
- // Code mostly from version 1.x
81
- $object_type = ucwords($one_item->object_type);
82
- $object_name = esc_html($one_item->object_name);
83
- $user = get_user_by('id', $one_item->user_id);
84
- $user_nicename = esc_html(@$user->user_nicename);
85
- $user_email = esc_html(@$user->user_email);
86
- $description = '';
87
-
88
- if ($user_nicename) {
89
- $description .= sprintf(__('By %s', 'simple-history'), $user_nicename);
90
- }
91
-
92
- if (isset($one_item->occasions)) {
93
- $description .= sprintf(__('%d occasions', 'simple-history'), sizeof($one_item->occasions));
94
- }
95
-
96
- $item_title = esc_html($object_type) . ' "' . esc_html($object_name) . "\" {$one_item->action}";
97
- $item_title = html_entity_decode($item_title, ENT_COMPAT, 'UTF-8');
98
-
99
- $out .= "$item_title";
100
- $out .= "<br>$description";
101
-
102
- return $out;
103
- }
104
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
loggers/SimpleLogger.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- defined('ABSPATH') or die();
4
 
5
  /**
6
  * A PSR-3 inspired logger class
@@ -10,1726 +10,1690 @@ defined('ABSPATH') or die();
10
  *
11
  * @link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md PSR-3 specification
12
  */
13
- class SimpleLogger
14
- {
15
- /**
16
- * Unique slug for this logger
17
- * Will be saved in DB and used to associate each log row with its logger
18
- */
19
- public $slug = __CLASS__;
20
-
21
- /**
22
- * Will contain the untranslated messages from getInfo()
23
- *
24
- * By adding your messages here they will be stored both translated and non-translated
25
- * You then log something like this:
26
- * <code>
27
- * $this->info( $this->messages["POST_UPDATED"] );
28
- * </code>
29
- * or with the shortcut
30
- * <code>
31
- * $this->infoMessage("POST_UPDATED");
32
- * </code>
33
- * which results in the original, untranslated, string being added to the log and database
34
- * the translated string are then only used when showing the log in the GUI
35
- */
36
- public $messages;
37
-
38
- /**
39
- * ID of last inserted row
40
- *
41
- * @var int $lastInsertID Database row primary key.
42
- */
43
- public $lastInsertID;
44
-
45
- /**
46
- * Context of last inserted row.
47
- *
48
- * @var int $lastInsertContext Context used for the last insert.
49
- * @since 2.2x
50
- */
51
- public $lastInsertContext;
52
-
53
- /**
54
- * Simple History instance.
55
- *
56
- * @var object $simpleHistory Simple history instance.
57
- */
58
- public $simpleHistory;
59
-
60
- /**
61
- * Constructor. Remember to call this as parent constructor if making a childlogger
62
- *
63
- * @param $simpleHistory history class objectinstance
64
- */
65
- public function __construct($simpleHistory = null)
66
- {
67
- global $wpdb;
68
-
69
- $this->db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
70
- $this->db_table_contexts =
71
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
72
-
73
- $this->simpleHistory = $simpleHistory;
74
- }
75
-
76
- /**
77
- * Method that is called automagically when logger is loaded by Simple History
78
- * Add your init stuff here
79
- */
80
- public function loaded()
81
- {
82
- }
83
-
84
- /**
85
- * Get array with information about this logger
86
- *
87
- * @return array
88
- */
89
- public function getInfo()
90
- {
91
- $arr_info = array(
92
- // The logger slug. Defaulting to the class name is nice and logical I think
93
- 'slug' => __CLASS__,
94
-
95
- // Shown on the info-tab in settings, use these fields to tell
96
- // an admin what your logger is used for
97
- 'name' => 'SimpleLogger',
98
- 'description' => 'The built in logger for Simple History',
99
-
100
- // Capability required to view log entries from this logger
101
- 'capability' => 'edit_pages',
102
- 'messages' => array(
103
- // No pre-defined variants
104
- // when adding messages __() or _x() must be used
105
- )
106
- );
107
-
108
- return $arr_info;
109
- }
110
-
111
- /**
112
- * Return single array entry from the array in getInfo()
113
- * Returns the value of the key if value exists, or null
114
- *
115
- * @since 2.5.4
116
- * @return Mixed
117
- */
118
- public function getInfoValueByKey($key)
119
- {
120
- $arr_info = $this->getInfo();
121
-
122
- return isset($arr_info[$key]) ? $arr_info[$key] : null;
123
- }
124
-
125
- /**
126
- * Returns the capability required to read log rows from this logger
127
- *
128
- * @return $string capability
129
- */
130
- public function getCapability()
131
- {
132
- $arr_info = $this->getInfo();
133
-
134
- $capability = 'manage_options';
135
-
136
- if (!empty($arr_info['capability'])) {
137
- $capability = $arr_info['capability'];
138
- }
139
-
140
- return $capability;
141
- }
142
-
143
- /**
144
- * Interpolates context values into the message placeholders.
145
- *
146
- * @param string $message
147
- * @param array $context
148
- * @param array $row Currently not always passed, because loggers need to be updated to support this...
149
- */
150
- public function interpolate($message, $context = array(), $row = null)
151
- {
152
- if (!is_array($context)) {
153
- return $message;
154
- }
155
-
156
- /**
157
- * Filter the context used to create the message from the message template
158
- *
159
- * @since 2.2.4
160
- */
161
- $context = apply_filters(
162
- 'simple_history/logger/interpolate/context',
163
- $context,
164
- $message,
165
- $row
166
- );
167
-
168
- // Build a replacement array with braces around the context keys
169
- $replace = array();
170
- foreach ($context as $key => $val) {
171
- // Both key and val must be strings or number (for vals)
172
- if (is_string($key) || is_numeric($key)) {
173
- // key ok
174
- }
175
-
176
- if (is_string($val) || is_numeric($val)) {
177
- // val ok
178
- } else {
179
- // not a value we can replace
180
- continue;
181
- }
182
-
183
- $replace['{' . $key . '}'] = $val;
184
- }
185
-
186
- // Interpolate replacement values into the message and return
187
- return strtr($message, $replace);
188
- }
189
-
190
- /**
191
- * @param object $row
192
- * @return string HTML
193
- */
194
- public function getLogRowHeaderInitiatorOutput($row)
195
- {
196
- $initiator_html = '';
197
- $initiator = $row->initiator;
198
- $context = $row->context;
199
-
200
- switch ($initiator) {
201
- case 'wp':
202
- $initiator_html .=
203
- '<strong class="SimpleHistoryLogitem__inlineDivided">WordPress</strong> ';
204
- break;
205
-
206
- case 'wp_cli':
207
- $initiator_html .=
208
- '<strong class="SimpleHistoryLogitem__inlineDivided">WP-CLI</strong> ';
209
- break;
210
-
211
- // wp_user = wordpress uses, but user may have been deleted since log entry was added
212
- case 'wp_user':
213
- $user_id = isset($row->context['_user_id'])
214
- ? $row->context['_user_id']
215
- : null;
216
-
217
- if ($user_id > 0 && ($user = get_user_by('id', $user_id))) {
218
- // Sender is user and user still exists
219
- $is_current_user =
220
- get_current_user_id() == $user_id ? true : false;
221
-
222
- // get user role, as done in user-edit.php
223
- $wp_roles = $GLOBALS['wp_roles'];
224
- $user_roles = array_intersect(
225
- array_values((array) $user->roles),
226
- array_keys((array) $wp_roles->roles)
227
- );
228
- $user_role = array_shift($user_roles);
229
-
230
- $user_display_name = $user->display_name;
231
-
232
- /*
233
- * If user who logged this is the currently logged in user
234
- * skip name and email and use just "You"
235
- *
236
- * @param bool If you should be used
237
- * @since 2.1
238
- */
239
- $use_you = apply_filters(
240
- 'simple_history/header_initiator_use_you',
241
- true
242
- );
243
-
244
- if ($use_you && $is_current_user) {
245
- $tmpl_initiator_html = '
246
  <a href="%6$s" class="SimpleHistoryLogitem__headerUserProfileLink">
247
  <strong class="SimpleHistoryLogitem__inlineDivided">%5$s</strong>
248
  </a>
249
  ';
250
- } else {
251
- $tmpl_initiator_html = '
252
  <a href="%6$s" class="SimpleHistoryLogitem__headerUserProfileLink">
253
  <strong class="SimpleHistoryLogitem__inlineDivided">%3$s</strong>
254
  <span class="SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__headerEmail">%2$s</span>
255
  </a>
256
  ';
257
- }
258
-
259
- /**
260
- * Filter the format for the user output
261
- *
262
- * @since 2.0
263
- *
264
- * @param string $format.
265
- */
266
- $tmpl_initiator_html = apply_filters(
267
- 'simple_history/header_initiator_html_existing_user',
268
- $tmpl_initiator_html
269
- );
270
-
271
- $initiator_html .= sprintf(
272
- $tmpl_initiator_html,
273
- esc_html($user->user_login), // 1
274
- esc_html($user->user_email), // 2
275
- esc_html($user_display_name), // 3
276
- $user_role, // 4
277
- _x(
278
- 'You',
279
- 'header output when initiator is the currently logged in user',
280
- 'simple-history'
281
- ), // 5
282
- get_edit_user_link($user_id) // 6
283
- );
284
- } elseif ($user_id > 0) {
285
- // Sender was a user, but user is deleted now
286
- // output all info we have
287
- // _user_id
288
- // _username
289
- // _user_login
290
- // _user_email
291
- $initiator_html .= sprintf(
292
- '<strong class="SimpleHistoryLogitem__inlineDivided">' .
293
- __(
294
- 'Deleted user (had id %1$s, email %2$s, login %3$s)',
295
- 'simple-history'
296
- ) .
297
- '</strong>',
298
- esc_html($context['_user_id']), // 1
299
- esc_html($context['_user_email']), // 2
300
- esc_html($context['_user_login']) // 3
301
- );
302
- } // End if().
303
-
304
- break;
305
-
306
- case 'web_user':
307
- /*
308
- Note: server_remote_addr may not show visiting/attacking ip, if server is behind...stuff..
309
- Can be behind varnish cache, or browser can for example use compression in chrome mobile
310
- then the real ip is behind _server_http_x_forwarded_for_0 or similar
311
- _server_remote_addr 66.249.81.222
312
- _server_http_x_forwarded_for_0 5.35.187.212
313
- */
314
-
315
- // Check if additional IP addresses are stored, from http_x_forwarded_for and so on.
316
- $arr_found_additional_ip_headers = $this->get_event_ip_number_headers(
317
- $row
318
- );
319
-
320
- $initiator_html .=
321
- "<strong class='SimpleHistoryLogitem__inlineDivided'>" .
322
- __('Anonymous web user', 'simple-history') .
323
- '</strong> ';
324
-
325
- break;
326
-
327
- case 'other':
328
- $initiator_html .=
329
- "<strong class='SimpleHistoryLogitem__inlineDivided'>" .
330
- _x(
331
- 'Other',
332
- 'Event header output, when initiator is unknown',
333
- 'simple-history'
334
- ) .
335
- '</strong>';
336
- break;
337
-
338
- // no initiator
339
- case null:
340
- // $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided'>Null</strong>";
341
- break;
342
-
343
- default:
344
- $initiator_html .=
345
- "<strong class='SimpleHistoryLogitem__inlineDivided'>" .
346
- esc_html($initiator) .
347
- '</strong>';
348
- } // End switch().
349
-
350
- /**
351
- * Filter generated html for the initiator row header html
352
- *
353
- * @since 2.0
354
- *
355
- * @param string $initiator_html
356
- * @param object $row Log row
357
- */
358
- $initiator_html = apply_filters(
359
- 'simple_history/row_header_initiator_output',
360
- $initiator_html,
361
- $row
362
- );
363
-
364
- return $initiator_html;
365
- }
366
-
367
- public function getLogRowHeaderDateOutput($row)
368
- {
369
- // HTML for date
370
- // Date (should...) always exist
371
- // http://developers.whatwg.org/text-level-semantics.html#the-time-element
372
- $date_html = '';
373
- $str_when = '';
374
-
375
- // $row->date is in GMT
376
- $date_datetime = new DateTime($row->date, new DateTimeZone('GMT'));
377
-
378
- // Current datetime in GMT
379
- $time_current = strtotime(current_time('mysql', 1));
380
-
381
- /**
382
- * Filter how many seconds as most that can pass since an
383
- * event occured to show "nn minutes ago" (human diff time-format) instead of exact date
384
- *
385
- * @since 2.0
386
- *
387
- * @param int $time_ago_max_time Seconds
388
- */
389
- $time_ago_max_time = DAY_IN_SECONDS * 2;
390
- $time_ago_max_time = apply_filters(
391
- 'simple_history/header_time_ago_max_time',
392
- $time_ago_max_time
393
- );
394
-
395
- /**
396
- * Filter how many seconds as most that can pass since an
397
- * event occured to show "just now" instead of exact date
398
- *
399
- * @since 2.0
400
- *
401
- * @param int $time_ago_max_time Seconds
402
- */
403
- $time_ago_just_now_max_time = 30;
404
- $time_ago_just_now_max_time = apply_filters(
405
- 'simple_history/header_just_now_max_time',
406
- $time_ago_just_now_max_time
407
- );
408
-
409
- $date_format = get_option('date_format');
410
- $time_format = get_option('time_format');
411
- $date_and_time_format = $date_format . ' - ' . $time_format;
412
-
413
- // Show local time as hours an minutes when event is recent.
414
- $local_date_format = $time_format;
415
-
416
- // Show local time as date and hours when event is a bit older.
417
- if (
418
- $time_current - HOUR_IN_SECONDS * 6 >
419
- $date_datetime->getTimestamp()
420
- ) {
421
- $local_date_format = $date_and_time_format;
422
- }
423
-
424
- if (
425
- $time_current - $date_datetime->getTimestamp() <=
426
- $time_ago_just_now_max_time
427
- ) {
428
- // Show "just now" if event is very recent.
429
- $str_when = __('Just now', 'simple-history');
430
- } elseif (
431
- $time_current - $date_datetime->getTimestamp() >
432
- $time_ago_max_time
433
- ) {
434
- /* Translators: Date format for log row header, see http://php.net/date */
435
- $datef = __('M j, Y \a\t G:i', 'simple-history');
436
- $str_when = date_i18n(
437
- $datef,
438
- strtotime(get_date_from_gmt($row->date))
439
- );
440
- } else {
441
- // Show "nn minutes ago" when event is xx seconds ago or earlier
442
- $date_human_time_diff = human_time_diff(
443
- $date_datetime->getTimestamp(),
444
- $time_current
445
- );
446
- /* Translators: 1: last modified date and time in human time diff-format */
447
- $str_when = sprintf(
448
- __('%1$s ago', 'simple-history'),
449
- $date_human_time_diff
450
- );
451
- }
452
-
453
- $item_permalink = admin_url(apply_filters('simple_history/admin_location', 'index') . '.php?page=simple_history_page');
454
- if (! empty($row->id)) {
455
- $item_permalink .= "#item/{$row->id}";
456
- }
457
-
458
- // Datetime attribute on <time> element.
459
- $str_datetime_title = sprintf(
460
- __('%1$s local time %3$s (%2$s GMT time)', 'simple-history'),
461
- get_date_from_gmt(
462
- $date_datetime->format('Y-m-d H:i:s'),
463
- $date_and_time_format
464
- ), // 1 local time
465
- $date_datetime->format($date_and_time_format), // GMT time
466
- PHP_EOL // 3, new line
467
- );
468
-
469
- // Time and date before live updated relative date.
470
- $str_datetime_local = sprintf(
471
- '%1$s',
472
- get_date_from_gmt(
473
- $date_datetime->format('Y-m-d H:i:s'),
474
- $local_date_format
475
- ) // 1 local time
476
- );
477
-
478
- // HTML for whole span with date info.
479
- $date_html =
480
- "<span class='SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided'>";
481
- $date_html .= "<a class='' href='{$item_permalink}'>";
482
- $date_html .= sprintf(
483
- '<span title="%1$s">%4$s (<time datetime="%3$s" class="SimpleHistoryLogitem__when__liveRelative">%2$s</time>)</span>',
484
- esc_attr($str_datetime_title), // 1 datetime attribute
485
- esc_html($str_when), // 2 date text, visible in log, but overridden by JS relative date script.
486
- $date_datetime->format(DateTime::RFC3339), // 3
487
- esc_html($str_datetime_local) // 4
488
- );
489
- $date_html .= '</a>';
490
- $date_html .= '</span>';
491
-
492
- /**
493
- * Filter the output of the date section of the header.
494
- *
495
- * @since 2.5.1
496
- *
497
- * @param String $date_html
498
- * @param array $row
499
- */
500
- $date_html = apply_filters(
501
- 'simple_history/row_header_date_output',
502
- $date_html,
503
- $row
504
- );
505
-
506
- return $date_html;
507
- }
508
-
509
- public function getLogRowHeaderUsingPluginOutput($row)
510
- {
511
- // Logger "via" info in header, i.e. output some extra
512
- // info next to the time to make it more clear what plugin etc.
513
- // that "caused" this event
514
- $via_html = '';
515
- $logger_name_via = $this->getInfoValueByKey('name_via');
516
-
517
- if ($logger_name_via) {
518
- $via_html =
519
- "<span class='SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__via'>";
520
- $via_html .= $logger_name_via;
521
- $via_html .= '</span>';
522
- }
523
-
524
- return $via_html;
525
- }
526
-
527
- /**
528
- * Context for IP Addresses can contain multiple entries.
529
- *
530
- * - "_server_remote_addr" with value for example "172.17.0.0" is the main entry.
531
- * It usually contains the IP address of the visitor.
532
- *
533
- * - Then zero or one or multiple entries can exist if web server is for example behind proxy.
534
- * Entries that can exist are the one with keys is get_ip_number_header_keys(),
535
- * Also each key can exist multiple times.
536
- * Final key name will be like "_server_http_x_forwarded_for_0", "_server_http_x_forwarded_for_1" and so on.
537
- *
538
- * @param mixed $row
539
- * @return string
540
- */
541
- public function getLogRowHeaderIPAddressOutput($row)
542
- {
543
-
544
- /**
545
- * Filter if IP Address should be added to header row.
546
- *
547
- * @since 2.x
548
- *
549
- * @param bool True to show IP address, false to hide it. Defaults to false.
550
- * @param object $row Row data
551
- */
552
- $show_ip_address = apply_filters(
553
- 'simple_history/row_header_output/display_ip_address',
554
- false,
555
- $row
556
- );
557
-
558
- if (!$show_ip_address) {
559
- return '';
560
- }
561
-
562
- $context = $row->context;
563
- $html = "<span class='SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__anonUserWithIp'>";
564
-
565
- $arr_ip_addresses = [];
566
-
567
- // Look for additional ip addresses.
568
- $arr_found_additional_ip_headers = $this->get_event_ip_number_headers($row);
569
-
570
- $arr_ip_addresses = array_merge(
571
- // Remote addr always exists.
572
- ['_server_remote_addr' => $context['_server_remote_addr']],
573
- $arr_found_additional_ip_headers
574
- );
575
-
576
- // if ( sizeof( $arr_found_additional_ip_headers ) ) {
577
- // $iplookup_link = sprintf('https://ipinfo.io/%1$s', esc_attr($context["_server_remote_addr"]));
578
- // $ip_numbers_joined = wp_sprintf_l('%l', array("_server_remote_addr" => $context["_server_remote_addr"]) + $arr_found_additional_ip_headers);
579
- /*
580
- $html .= sprintf(
581
- __('Anonymous user with multiple IP addresses detected: %1$s', "simple-history"),
582
- "<a target='_blank' href={$iplookup_link} class='SimpleHistoryLogitem__anonUserWithIp__theIp'>" . esc_html( $ip_numbers_joined ) . "</a>"
583
- );*/
584
-
585
- /*
586
- print_r($arr_found_additional_ip_headers);
587
- Array
588
- (
589
- [_server_http_x_forwarded_for_0] => 5.35.187.212
590
- [_server_http_x_forwarded_for_1] => 83.251.97.21
591
- )
592
- */
593
-
594
- // } else {
595
-
596
- $first_ip_address = reset($arr_ip_addresses);
597
-
598
- // Output single or plural text.
599
- if (sizeof($arr_ip_addresses) === 1) {
600
- // Single ip address
601
- $iplookup_link = sprintf(
602
- 'https://ipinfo.io/%1$s',
603
- esc_attr($first_ip_address)
604
- );
605
-
606
- $html .= sprintf(
607
- __('IP Address %1$s', 'simple-history'),
608
- "<a target='_blank' href='{$iplookup_link}' class='SimpleHistoryLogitem__anonUserWithIp__theIp' data-ip-address='" . esc_attr($first_ip_address) . "'>" .
609
- esc_html($first_ip_address) .
610
- '</a>'
611
- );
612
- } elseif (sizeof($arr_ip_addresses) > 1) {
613
- $ip_addresses_html = '';
614
-
615
- foreach ($arr_ip_addresses as $ip_address_header => $ip_address) {
616
- $iplookup_link = sprintf(
617
- 'https://ipinfo.io/%1$s',
618
- esc_attr($ip_address)
619
- );
620
-
621
- $ip_addresses_html .= sprintf(
622
- '<a target="_blank" href="%3$s" class="SimpleHistoryLogitem__anonUserWithIp__theIp" data-ip-address="%4$s">%1$s</a>, ',
623
- esc_html($ip_address), // 1
624
- esc_html($ip_address_header), // 2
625
- $iplookup_link, // 3
626
- esc_attr($ip_address) // 4
627
- );
628
- }
629
-
630
- // Remove trailing comma.
631
- $ip_addresses_html = rtrim($ip_addresses_html, ', ');
632
-
633
- $html .= sprintf(
634
- __('IP Addresses %1$s', 'simple-history'),
635
- $ip_addresses_html
636
- );
637
- }
638
-
639
- // } // multiple ip
640
- $html .= '</span> ';
641
-
642
- // $initiator_html .= "<strong>" . __("<br><br>Unknown user from {$context["_server_remote_addr"]}") . "</strong>";
643
- // $initiator_html .= "<strong>" . __("<br><br>{$context["_server_remote_addr"]}") . "</strong>";
644
- // $initiator_html .= "<strong>" . __("<br><br>User from IP {$context["_server_remote_addr"]}") . "</strong>";
645
- // $initiator_html .= "<strong>" . __("<br><br>Non-logged in user from IP {$context["_server_remote_addr"]}") . "</strong>";
646
- // } // End if().
647
- return $html;
648
- }
649
-
650
- /**
651
- * Returns header output for a log row.
652
- *
653
- * Format should be common for all log rows and should be like:
654
- * Username (user role) · Date · IP Address · Via plugin abc
655
- * I.e.:
656
- * Initiator * Date/time * IP Address * Via logger
657
- *
658
- * @param object $row Row data
659
- * @return string HTML
660
- */
661
- public function getLogRowHeaderOutput($row)
662
- {
663
- $initiator_html = $this->getLogRowHeaderInitiatorOutput($row);
664
- $date_html = $this->getLogRowHeaderDateOutput($row);
665
- $via_html = $this->getLogRowHeaderUsingPluginOutput($row);
666
- $ip_address_html = $this->getLogRowHeaderIPAddressOutput($row);
667
-
668
- // Template to combine header parts.
669
- $template = '
670
  %1$s
671
  %2$s
672
  %3$s
673
  %4$s
674
  ';
675
 
676
- /**
677
- * Filter template used to glue together markup the log row header.
678
- *
679
- * @since 2.0
680
- *
681
- * @param string $template
682
- * @param object $row Log row
683
- */
684
- $template = apply_filters(
685
- 'simple_history/row_header_output/template',
686
- $template,
687
- $row
688
- );
689
-
690
- // Glue together final result.
691
- $html = sprintf(
692
- $template,
693
- $initiator_html, // 1
694
- $date_html, // 2
695
- $via_html, // 3
696
- $ip_address_html // 4
697
- );
698
-
699
- /**
700
- * Filter generated html for the log row header.
701
- *
702
- * @since 2.0
703
- *
704
- * @param string $html
705
- * @param object $row Log row
706
- */
707
- $html = apply_filters('simple_history/row_header_output', $html, $row);
708
-
709
- return $html;
710
- }
711
-
712
- /**
713
- * Returns the plain text version of this entry
714
- * Used in for example CSV-exports.
715
- * Defaults to log message with context interpolated.
716
- * Keep format as plain and simple as possible.
717
- * Links are ok, for example to link to users or posts.
718
- * Tags will be stripped when text is used for CSV-exports and so on.
719
- * Keep it on a single line. No <p> or <br> and so on.
720
- *
721
- * Example output:
722
- * Edited post "About the company"
723
- *
724
- * Message should sound like it's coming from the user.
725
- * Image that the name of the user is added in front of the text:
726
- * Jessie James: Edited post "About the company"
727
- */
728
- public function getLogRowPlainTextOutput($row)
729
- {
730
- $message = $row->message;
731
- $message_key = isset($row->context['_message_key'])
732
- ? $row->context['_message_key']
733
- : null;
734
-
735
- // Message is translated here, but translation must be added in
736
- // plain text before
737
- if (empty($message_key)) {
738
- // Message key did not exist, so check if we should translate using textdomain
739
- if (!empty($row->context['_gettext_domain'])) {
740
- $message = __($message, $row->context['_gettext_domain']);
741
- }
742
- } else {
743
- // Check that messages does exist
744
- // If we for example disable a Logger we may have references
745
- // to message keys that are unavailable. If so then fallback to message.
746
- if (isset($this->messages[$message_key]['translated_text'])) {
747
- $message = $this->messages[$message_key]['translated_text'];
748
- } else {
749
- // Not message exists for message key. Just keep using message.
750
- }
751
- }
752
-
753
- $html = $this->interpolate($message, $row->context, $row);
754
-
755
- // All messages are escaped by default.
756
- // If you need unescaped output override this method
757
- // in your own logger
758
- $html = esc_html($html);
759
-
760
- /**
761
- * Filter generated output for plain text output
762
- *
763
- * @since 2.0
764
- *
765
- * @param string $html
766
- * @param object $row Log row
767
- */
768
- $html = apply_filters(
769
- 'simple_history/row_plain_text_output',
770
- $html,
771
- $row
772
- );
773
-
774
- return $html;
775
- }
776
-
777
- /**
778
- * Get output for image
779
- * Image can be for example gravar if sender is user,
780
- * or other images if sender i system, wordpress, and so on
781
- */
782
- public function getLogRowSenderImageOutput($row)
783
- {
784
- $sender_image_html = '';
785
- $sender_image_size = 32;
786
-
787
- $initiator = $row->initiator;
788
-
789
- switch ($initiator) {
790
- // wp_user = wordpress uses, but user may have been deleted since log entry was added
791
- case 'wp_user':
792
- $user_id = isset($row->context['_user_id'])
793
- ? $row->context['_user_id']
794
- : null;
795
-
796
- if ($user_id > 0 && ($user = get_user_by('id', $user_id))) {
797
- // Sender was user
798
- $sender_image_html = $this->simpleHistory->get_avatar(
799
- $user->user_email,
800
- $sender_image_size
801
- );
802
- } elseif ($user_id > 0) {
803
- // Sender was a user, but user is deleted now
804
- $sender_image_html = $this->simpleHistory->get_avatar(
805
- '',
806
- $sender_image_size
807
- );
808
- } else {
809
- $sender_image_html = $this->simpleHistory->get_avatar(
810
- '',
811
- $sender_image_size
812
- );
813
- }
814
-
815
- break;
816
- }
817
-
818
- /**
819
- * Filter generated output for row image (sender image)
820
- *
821
- * @since 2.0
822
- *
823
- * @param string $sender_image_html
824
- * @param object $row Log row
825
- */
826
- $sender_image_html = apply_filters(
827
- 'simple_history/row_sender_image_output',
828
- $sender_image_html,
829
- $row
830
- );
831
-
832
- return $sender_image_html;
833
- }
834
-
835
- /**
836
- * Use this method to output detailed output for a log row
837
- * Example usage is if a user has uploaded an image then a
838
- * thumbnail of that image can bo outputed here
839
- *
840
- * @param object $row
841
- * @return string HTML-formatted output
842
- */
843
- public function getLogRowDetailsOutput($row)
844
- {
845
- $html = '';
846
-
847
- /**
848
- * Filter generated output for details
849
- *
850
- * @since 2.0
851
- *
852
- * @param string $html
853
- * @param object $row Log row
854
- */
855
- $html = apply_filters('simple_history/row_details_output', $html, $row);
856
-
857
- return $html;
858
- }
859
-
860
- /**
861
- * System is unusable.
862
- *
863
- * @param string $message
864
- * @param array $context
865
- * @return null
866
- */
867
- public function emergency($message, array $context = array())
868
- {
869
- return $this->log(SimpleLoggerLogLevels::EMERGENCY, $message, $context);
870
- }
871
-
872
- /**
873
- * System is unusable.
874
- *
875
- * @param string $message key from getInfo messages array
876
- * @param array $context
877
- * @return null
878
- */
879
- public function emergencyMessage($message, array $context = array())
880
- {
881
- return $this->logByMessageKey(
882
- SimpleLoggerLogLevels::EMERGENCY,
883
- $message,
884
- $context
885
- );
886
- }
887
-
888
- /**
889
- * Log with message
890
- * Called from infoMessage(), errorMessage(), and so on
891
- *
892
- * Call like this:
893
- *
894
- * return $this->logByMessageKey(SimpleLoggerLogLevels::EMERGENCY, $message, $context);
895
- */
896
- private function logByMessageKey(
897
- $SimpleLoggerLogLevelsLevel,
898
- $messageKey,
899
- $context
900
- ) {
901
- // When logging by message then the key must exist
902
- if (!isset($this->messages[$messageKey]['untranslated_text'])) {
903
- return;
904
- }
905
-
906
- /**
907
- * Filter so plugins etc. can shortut logging
908
- *
909
- * @since 2.0.20
910
- *
911
- * @param true yes, we default to do the logging
912
- * @param string logger slug
913
- * @param string messageKey
914
- * @param string log level
915
- * @param array context
916
- * @return bool false to abort logging
917
- */
918
- $doLog = apply_filters(
919
- 'simple_history/simple_logger/log_message_key',
920
- true,
921
- $this->slug,
922
- $messageKey,
923
- $SimpleLoggerLogLevelsLevel,
924
- $context
925
- );
926
-
927
- if (!$doLog) {
928
- return;
929
- }
930
-
931
- $context['_message_key'] = $messageKey;
932
- $message = $this->messages[$messageKey]['untranslated_text'];
933
-
934
- $this->log($SimpleLoggerLogLevelsLevel, $message, $context);
935
- }
936
-
937
- /**
938
- * Action must be taken immediately.
939
- *
940
- * @param string $message
941
- * @param array $context
942
- * @return null
943
- */
944
- public function alert($message, array $context = array())
945
- {
946
- return $this->log(SimpleLoggerLogLevels::ALERT, $message, $context);
947
- }
948
-
949
- /**
950
- * Action must be taken immediately.
951
- *
952
- * @param string $message key from getInfo messages array
953
- * @param array $context
954
- * @return null
955
- */
956
- public function alertMessage($message, array $context = array())
957
- {
958
- return $this->logByMessageKey(
959
- SimpleLoggerLogLevels::ALERT,
960
- $message,
961
- $context
962
- );
963
- }
964
-
965
- /**
966
- * Critical conditions.
967
- *
968
- * Example: Application component unavailable, unexpected exception.
969
- *
970
- * @param string $message
971
- * @param array $context
972
- * @return null
973
- */
974
- public function critical($message, array $context = array())
975
- {
976
- return $this->log(SimpleLoggerLogLevels::CRITICAL, $message, $context);
977
- }
978
-
979
- /**
980
- * Critical conditions.
981
- *
982
- * @param string $message key from getInfo messages array
983
- * @param array $context
984
- * @return null
985
- */
986
- public function criticalMessage($message, array $context = array())
987
- {
988
- if (!isset($this->messages[$message]['untranslated_text'])) {
989
- return;
990
- }
991
-
992
- $context['_message_key'] = $message;
993
- $message = $this->messages[$message]['untranslated_text'];
994
-
995
- $this->log(SimpleLoggerLogLevels::CRITICAL, $message, $context);
996
- }
997
-
998
- /**
999
- * Runtime errors that do not require immediate action but should typically
1000
- * be logged and monitored.
1001
- *
1002
- * @param string $message
1003
- * @param array $context
1004
- * @return null
1005
- */
1006
- public function error($message, array $context = array())
1007
- {
1008
- return $this->log(SimpleLoggerLogLevels::ERROR, $message, $context);
1009
- }
1010
-
1011
- /**
1012
- * Runtime errors that do not require immediate action but should typically
1013
- * be logged and monitored.
1014
- *
1015
- * @param string $message key from getInfo messages array
1016
- * @param array $context
1017
- * @return null
1018
- */
1019
- public function errorMessage($message, array $context = array())
1020
- {
1021
- return $this->logByMessageKey(
1022
- SimpleLoggerLogLevels::ERROR,
1023
- $message,
1024
- $context
1025
- );
1026
- }
1027
-
1028
- /**
1029
- * Exceptional occurrences that are not errors.
1030
- *
1031
- * Example: Use of deprecated APIs, poor use of an API, undesirable things
1032
- * that are not necessarily wrong.
1033
- *
1034
- * @param string $message
1035
- * @param array $context
1036
- * @return null
1037
- */
1038
- public function warning($message, array $context = array())
1039
- {
1040
- return $this->log(SimpleLoggerLogLevels::WARNING, $message, $context);
1041
- }
1042
-
1043
- /**
1044
- * Exceptional occurrences that are not errors.
1045
- *
1046
- * @param string $message key from getInfo messages array
1047
- * @param array $context
1048
- * @return null
1049
- */
1050
- public function warningMessage($message, array $context = array())
1051
- {
1052
- return $this->logByMessageKey(
1053
- SimpleLoggerLogLevels::WARNING,
1054
- $message,
1055
- $context
1056
- );
1057
- }
1058
-
1059
- /**
1060
- * Normal but significant events.
1061
- *
1062
- * @param string $message
1063
- * @param array $context
1064
- * @return null
1065
- */
1066
- public function notice($message, array $context = array())
1067
- {
1068
- return $this->log(SimpleLoggerLogLevels::NOTICE, $message, $context);
1069
- }
1070
-
1071
- /**
1072
- * Normal but significant events.
1073
- *
1074
- * @param string $message key from getInfo messages array
1075
- * @param array $context
1076
- * @return null
1077
- */
1078
- public function noticeMessage($message, array $context = array())
1079
- {
1080
- return $this->logByMessageKey(
1081
- SimpleLoggerLogLevels::NOTICE,
1082
- $message,
1083
- $context
1084
- );
1085
- }
1086
-
1087
- /**
1088
- * Interesting events.
1089
- *
1090
- * Example: User logs in, SQL logs.
1091
- *
1092
- * @param string $message
1093
- * @param array $context
1094
- * @return null
1095
- */
1096
- public function info($message, array $context = array())
1097
- {
1098
- return $this->log(SimpleLoggerLogLevels::INFO, $message, $context);
1099
- }
1100
-
1101
- /**
1102
- * Interesting events.
1103
- *
1104
- * Example: User logs in, SQL logs.
1105
- *
1106
- * @param string $message key from getInfo messages array
1107
- * @param array $context
1108
- * @return null
1109
- */
1110
- public function infoMessage($message, array $context = array())
1111
- {
1112
- return $this->logByMessageKey(
1113
- SimpleLoggerLogLevels::INFO,
1114
- $message,
1115
- $context
1116
- );
1117
- }
1118
-
1119
- /**
1120
- * Detailed debug information.
1121
- *
1122
- * @param string $message
1123
- * @param array $context
1124
- * @return null
1125
- */
1126
- public function debug($message, array $context = array())
1127
- {
1128
- return $this->log(SimpleLoggerLogLevels::DEBUG, $message, $context);
1129
- }
1130
-
1131
- /**
1132
- * Detailed debug information.
1133
- *
1134
- * @param string $message key from getInfo messages array
1135
- * @param array $context
1136
- * @return null
1137
- */
1138
- public function debugMessage($message, array $context = array())
1139
- {
1140
- return $this->logByMessageKey(
1141
- SimpleLoggerLogLevels::DEBUG,
1142
- $message,
1143
- $context
1144
- );
1145
- }
1146
-
1147
- /**
1148
- * Logs with an arbitrary level.
1149
- *
1150
- * @param mixed $level The log level. Default "info".
1151
- * @param string $message The log message. Default "".
1152
- * @param array $context The log context. Default empty array.
1153
- * @return class SimpleLogger instance
1154
- */
1155
- public function log($level = 'info', $message = '', $context = array())
1156
- {
1157
- global $wpdb;
1158
-
1159
- // Check that passed args are of correct types.
1160
- if (!is_string($level) || !is_string($message)) {
1161
- return $this;
1162
- }
1163
-
1164
- // Context must be array, but can be passed as null and so on.
1165
- if (!is_array($context)) {
1166
- $context = array();
1167
- }
1168
-
1169
- // Don't go on if message is empty.
1170
- if (empty($message)) {
1171
- return $this;
1172
- }
1173
-
1174
- /**
1175
- * Filter that makes it possible to shortcut this log.
1176
- * Return bool false to cancel.
1177
- *
1178
- * @since 2.3.1
1179
- */
1180
- $do_log = apply_filters(
1181
- 'simple_history/log/do_log',
1182
- true,
1183
- $level,
1184
- $message,
1185
- $context,
1186
- $this
1187
- );
1188
- if (false === $do_log) {
1189
- return $this;
1190
- }
1191
-
1192
- /**
1193
- * Easy shortcut method to disable logging of messages from
1194
- * a specific logger.
1195
- *
1196
- * Example filter name:
1197
- * simple_history/log/do_log/SimpleUserLogger
1198
- * simple_history/log/do_log/SimplePostLogger
1199
- *
1200
- * Example to disable logging of any user login/logout/failed login activity:
1201
- * add_filter('simple_history/log/do_log/SimpleUserLogger', '__return_false')
1202
- *
1203
- * @since 2.nn
1204
- */
1205
- $do_log = apply_filters(
1206
- "simple_history/log/do_log/{$this->slug}",
1207
- true
1208
- );
1209
- if (false === $do_log) {
1210
- return $this;
1211
- }
1212
-
1213
- /**
1214
- * Easy shortcut method to disable logging of messages from
1215
- * a specific logger and message.
1216
- *
1217
- * Example filter name:
1218
- * simple_history/log/do_log/SimpleUserLogger/user_logged_in
1219
- * simple_history/log/do_log/SimplePostLogger/post_updated
1220
- *
1221
- * @since 2.nn
1222
- */
1223
- $message_key = isset($context['_message_key'])
1224
- ? $context['_message_key']
1225
- : null;
1226
- $do_log = apply_filters(
1227
- "simple_history/log/do_log/{$this->slug}/{$message_key}",
1228
- true
1229
- );
1230
- if (false === $do_log) {
1231
- return $this;
1232
- }
1233
-
1234
- // Check if $message is a translated message, and if so then fetch original
1235
- $sh_latest_translations =
1236
- $this->simpleHistory->gettextLatestTranslations;
1237
-
1238
- if (!empty($sh_latest_translations)) {
1239
- if (isset($sh_latest_translations[$message])) {
1240
- // Translation of this phrase was found, so use original phrase instead of translated one
1241
- // Store textdomain since it's required to translate
1242
- $context['_gettext_domain'] =
1243
- $sh_latest_translations[$message]['domain'];
1244
-
1245
- // These are good to keep when debugging
1246
- // $context["_gettext_org_message"] = $sh_latest_translations[$message]["text"];
1247
- // $context["_gettext_translated_message"] = $sh_latest_translations[$message]["translation"];
1248
- $message = $sh_latest_translations[$message]['text'];
1249
- }
1250
- }
1251
-
1252
- /**
1253
- * Filter arguments passed to log funtion
1254
- *
1255
- * @since 2.0
1256
- *
1257
- * @param string $level
1258
- * @param string $message
1259
- * @param array $context
1260
- * @param object SimpleLogger object
1261
- */
1262
- apply_filters(
1263
- 'simple_history/log_arguments',
1264
- $level,
1265
- $message,
1266
- $context,
1267
- $this
1268
- );
1269
- $context = apply_filters(
1270
- 'simple_history/log_argument/context',
1271
- $context,
1272
- $level,
1273
- $message,
1274
- $this
1275
- );
1276
- $level = apply_filters(
1277
- 'simple_history/log_argument/level',
1278
- $level,
1279
- $context,
1280
- $message,
1281
- $this
1282
- );
1283
- $message = apply_filters(
1284
- 'simple_history/log_argument/message',
1285
- $message,
1286
- $level,
1287
- $context,
1288
- $this
1289
- );
1290
-
1291
- /*
1292
- Store date as GMT date, i.e. not local date/time
1293
- * Some info here:
1294
- * http://www.skyverge.com/blog/down-the-rabbit-hole-wordpress-and-timezones/
1295
- */
1296
- $localtime = current_time('mysql', 1);
1297
-
1298
- $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
1299
-
1300
- /**
1301
- * Filter db table used for simple history events
1302
- *
1303
- * @since 2.0
1304
- *
1305
- * @param string $db_table
1306
- */
1307
- $db_table = apply_filters('simple_history/db_table', $db_table);
1308
-
1309
- $data = array(
1310
- 'logger' => $this->slug,
1311
- 'level' => $level,
1312
- 'date' => $localtime,
1313
- 'message' => $message
1314
- );
1315
-
1316
- // Allow date to be overriden.
1317
- // Date must be in format 'Y-m-d H:i:s'.
1318
- if (isset($context['_date'])) {
1319
- $data['date'] = $context['_date'];
1320
- unset($context['_date']);
1321
- }
1322
-
1323
- // Add occasions id.
1324
- $occasions_id = null;
1325
- if (isset($context['_occasionsID'])) {
1326
- // Minimize risk of similar loggers logging same messages and such and resulting in same occasions id
1327
- // by always adding logger slug.
1328
- $occasions_data = array(
1329
- '_occasionsID' => $context['_occasionsID'],
1330
- '_loggerSlug' => $this->slug
1331
- );
1332
- $occasions_id = md5(json_encode($occasions_data));
1333
- unset($context['_occasionsID']);
1334
- } else {
1335
- // No occasions id specified, create one bases on the data array.
1336
- $occasions_data = $data + $context;
1337
-
1338
- // Don't include date in context data.
1339
- unset($occasions_data['date']);
1340
-
1341
- $occasions_id = md5(json_encode($occasions_data));
1342
- }
1343
-
1344
- $data['occasionsID'] = $occasions_id;
1345
-
1346
- // Log initiator, defaults to current user if exists, or other if not user exist
1347
- if (isset($context['_initiator'])) {
1348
- // Manually set in context
1349
- $data['initiator'] = $context['_initiator'];
1350
- unset($context['_initiator']);
1351
- } else {
1352
- // No initiator set, try to determine
1353
- // Default to other
1354
- $data['initiator'] = SimpleLoggerLogInitiators::OTHER;
1355
-
1356
- // Check if user is responsible.
1357
- if (function_exists('wp_get_current_user')) {
1358
- $current_user = wp_get_current_user();
1359
-
1360
- if (isset($current_user->ID) && $current_user->ID) {
1361
- $data['initiator'] = SimpleLoggerLogInitiators::WP_USER;
1362
- $context['_user_id'] = $current_user->ID;
1363
- $context['_user_login'] = $current_user->user_login;
1364
- $context['_user_email'] = $current_user->user_email;
1365
- }
1366
- }
1367
-
1368
- // If cron then set WordPress as responsible
1369
- if (defined('DOING_CRON') && DOING_CRON) {
1370
- // Seems to be wp cron running and doing this.
1371
- $data['initiator'] = SimpleLoggerLogInitiators::WORDPRESS;
1372
- $context['_wp_cron_running'] = true;
1373
-
1374
- // To aid debugging we log the current filter and a list of all filters.
1375
- global $wp_current_filter;
1376
- $context['_wp_cron_current_filter'] = current_filter();
1377
- }
1378
-
1379
- // If running as CLI and WP_CLI_PHP_USED is set then it is WP CLI that is doing it
1380
- // How to log this? Is this a user, is it WordPress, or what?
1381
- // I'm thinking:
1382
- // - it is a user that is manually doing this, on purpose, with intent, so not auto wordpress
1383
- // - it is a specific user, but we don't know who
1384
- // - sounds like a special case, set initiator to wp_cli
1385
- // Can be used by plugins/themes to check if WP-CLI is running or not
1386
- if (defined('WP_CLI') && WP_CLI) {
1387
- $data['initiator'] = SimpleLoggerLogInitiators::WP_CLI;
1388
- }
1389
- } // End if().
1390
-
1391
- // Detect XML-RPC calls and append to context, if not already there.
1392
- if (
1393
- defined('XMLRPC_REQUEST') &&
1394
- XMLRPC_REQUEST &&
1395
- !isset($context['_xmlrpc_request'])
1396
- ) {
1397
- $context['_xmlrpc_request'] = true;
1398
- }
1399
-
1400
- // Detect REST calls and append to context, if not already there.
1401
- $isRestApiRequest =
1402
- (defined('REST_API_REQUEST') && REST_API_REQUEST) ||
1403
- (defined('REST_REQUEST') && REST_REQUEST);
1404
- if ($isRestApiRequest) {
1405
- $context['_rest_api_request'] = true;
1406
- }
1407
-
1408
- // Trim message.
1409
- $data['message'] = trim($data['message']);
1410
-
1411
- /**
1412
- * Filter data to be saved to db.
1413
- *
1414
- * @since 2.0
1415
- *
1416
- * @param array $data
1417
- */
1418
- $data = apply_filters('simple_history/log_insert_data', $data);
1419
-
1420
- // Insert data into db.
1421
- $result = $wpdb->insert($db_table, $data);
1422
-
1423
- $data_parent_row = null;
1424
-
1425
- // Only save context if able to store row.
1426
- if (false === $result) {
1427
- $history_inserted_id = null;
1428
- } else {
1429
- $history_inserted_id = $wpdb->insert_id;
1430
-
1431
- $db_table_contexts =
1432
- $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
1433
-
1434
- /**
1435
- * Filter table name for contexts.
1436
- *
1437
- * @since 2.0
1438
- *
1439
- * @param string $db_table_contexts
1440
- */
1441
- $db_table_contexts = apply_filters(
1442
- 'simple_history/logger_db_table_contexts',
1443
- $db_table_contexts
1444
- );
1445
-
1446
- if (!is_array($context)) {
1447
- $context = array();
1448
- }
1449
-
1450
- // Append user id to context, if not already added.
1451
- if (!isset($context['_user_id'])) {
1452
- // wp_get_current_user is not available early.
1453
- // http://codex.wordpress.org/Function_Reference/wp_get_current_user
1454
- // https://core.trac.wordpress.org/ticket/14024
1455
- if (function_exists('wp_get_current_user')) {
1456
- $current_user = wp_get_current_user();
1457
-
1458
- if (isset($current_user->ID) && $current_user->ID) {
1459
- $context['_user_id'] = $current_user->ID;
1460
- $context['_user_login'] = $current_user->user_login;
1461
- $context['_user_email'] = $current_user->user_email;
1462
- }
1463
- }
1464
- }
1465
-
1466
- // Add remote addr to context.
1467
- if (!isset($context['_server_remote_addr'])) {
1468
- $remote_addr = empty($_SERVER['REMOTE_ADDR'])
1469
- ? ''
1470
- : wp_unslash($_SERVER['REMOTE_ADDR']);
1471
-
1472
- /**
1473
- * Filter to control if ip addresses should be anonymized or not.
1474
- *
1475
- * @since 2.22
1476
- *
1477
- * @param bool true to anonymize ip address, false to keep original ip address.
1478
- * @return bool
1479
- */
1480
- $anonymize_ip_address = apply_filters(
1481
- 'simple_history/privacy/anonymize_ip_address',
1482
- true
1483
- );
1484
-
1485
- if (
1486
- $anonymize_ip_address &&
1487
- function_exists('wp_privacy_anonymize_ip')
1488
- ) {
1489
- $remote_addr = wp_privacy_anonymize_ip($remote_addr);
1490
- }
1491
-
1492
- $context['_server_remote_addr'] = $remote_addr;
1493
-
1494
- // If web server is behind a load balancer then the ip address will always be the same
1495
- // See bug report: https://wordpress.org/support/topic/use-x-forwarded-for-http-header-when-logging-remote_addr?replies=1#post-6422981
1496
- // Note that the x-forwarded-for header can contain multiple ips, comma separated
1497
- // Also note that the header can be faked
1498
- // Ref: http://stackoverflow.com/questions/753645/how-do-i-get-the-correct-ip-from-http-x-forwarded-for-if-it-contains-multiple-ip
1499
- // Ref: http://blackbe.lt/advanced-method-to-obtain-the-client-ip-in-php/
1500
- // Check for IP in lots of headers
1501
- // Based on code found here:
1502
- // http://blackbe.lt/advanced-method-to-obtain-the-client-ip-in-php/
1503
- $ip_keys = $this->get_ip_number_header_keys();
1504
-
1505
- foreach ($ip_keys as $key) {
1506
- if (array_key_exists($key, $_SERVER) === true) {
1507
- // Loop through all IPs.
1508
- $ip_loop_num = 0;
1509
- foreach (explode(',', $_SERVER[$key]) as $ip) {
1510
- // trim for safety measures.
1511
- $ip = trim($ip);
1512
-
1513
- // attempt to validate IP.
1514
- if ($this->validate_ip($ip)) {
1515
- // valid, add to context, with loop index appended so we can store many IPs.
1516
- $key_lower = strtolower($key);
1517
-
1518
- if (
1519
- $anonymize_ip_address &&
1520
- function_exists('wp_privacy_anonymize_ip')
1521
- ) {
1522
- $ip = wp_privacy_anonymize_ip($ip);
1523
- }
1524
-
1525
- $context[
1526
- "_server_{$key_lower}_{$ip_loop_num}"
1527
- ] = $ip;
1528
- }
1529
-
1530
- $ip_loop_num++;
1531
- }
1532
- }
1533
- }
1534
- } // End if().
1535
-
1536
- // Append http referer.
1537
- if (
1538
- !isset($context['_server_http_referer']) &&
1539
- isset($_SERVER['HTTP_REFERER'])
1540
- ) {
1541
- $context['_server_http_referer'] = $_SERVER['HTTP_REFERER'];
1542
- }
1543
-
1544
- /**
1545
- * Filter the context to store for this event/row
1546
- *
1547
- * @since 2.0.29
1548
- *
1549
- * @param array $context Array with all context data to store. Modify and return this.
1550
- * @param array $data Array with data used for parent row.
1551
- * @param array $this Reference to this logger instance.
1552
- */
1553
- $context = apply_filters(
1554
- 'simple_history/log_insert_context',
1555
- $context,
1556
- $data,
1557
- $this
1558
- );
1559
- $data_parent_row = $data;
1560
-
1561
- // Insert all context values into db.
1562
- $this->append_context($history_inserted_id, $context);
1563
- } // End if().
1564
-
1565
- $this->lastInsertID = $history_inserted_id;
1566
- $this->lastInsertContext = $context;
1567
-
1568
- $this->simpleHistory->get_cache_incrementor(true);
1569
-
1570
- /**
1571
- * Action that is called after an event has been logged
1572
- *
1573
- * @since 2.5.1
1574
- *
1575
- * @param array $context Array with all context data that was used to log event.
1576
- * @param array $data Array with data used for parent row.
1577
- * @param array $this Reference to this logger instance
1578
- */
1579
- do_action(
1580
- 'simple_history/log/inserted',
1581
- $context,
1582
- $data_parent_row,
1583
- $this
1584
- );
1585
-
1586
- // Return $this so we can chain methods.
1587
- return $this;
1588
- }
1589
-
1590
- /**
1591
- * Append new info to the context of history item with id $post_logger->lastInsertID.
1592
- *
1593
- * @param int $history_id The id of the history row to add context to.
1594
- * @param array $context Context to append to existing context for the row.
1595
- * @return bool True if context was added, false if not (beacuse row_id or context is empty).
1596
- */
1597
- public function append_context($history_id, $context)
1598
- {
1599
- if (empty($history_id) || empty($context)) {
1600
- return false;
1601
- }
1602
-
1603
- global $wpdb;
1604
-
1605
- $db_table_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
1606
-
1607
- foreach ($context as $key => $value) {
1608
- // Everything except strings should be json_encoded, ie. arrays and objects.
1609
- if (!is_string($value)) {
1610
- $value = simpleHistory::json_encode($value);
1611
- }
1612
-
1613
- $data = array(
1614
- 'history_id' => $history_id,
1615
- 'key' => $key,
1616
- 'value' => $value
1617
- );
1618
-
1619
- $wpdb->insert($db_table_contexts, $data);
1620
- }
1621
-
1622
- return true;
1623
- }
1624
-
1625
- /**
1626
- * Returns array with headers that may contain user IP
1627
- *
1628
- * @since 2.0.29
1629
- */
1630
- public function get_ip_number_header_keys()
1631
- {
1632
- $arr = array(
1633
- 'HTTP_CLIENT_IP',
1634
- 'HTTP_X_FORWARDED_FOR',
1635
- 'HTTP_X_FORWARDED',
1636
- 'HTTP_X_CLUSTER_CLIENT_IP',
1637
- 'HTTP_FORWARDED_FOR',
1638
- 'HTTP_FORWARDED'
1639
- );
1640
-
1641
- return $arr;
1642
- }
1643
-
1644
- /**
1645
- * Returns additional headers with ip number from context
1646
- *
1647
- * @since 2.0.29
1648
- * @param object $row Row with info.
1649
- * @return array Headers
1650
- */
1651
- public function get_event_ip_number_headers($row)
1652
- {
1653
- $ip_keys = $this->get_ip_number_header_keys();
1654
- $arr_found_additional_ip_headers = array();
1655
- $context = $row->context;
1656
-
1657
- foreach ($ip_keys as $one_ip_header_key) {
1658
- $one_ip_header_key_lower = strtolower($one_ip_header_key);
1659
-
1660
- foreach ($context as $context_key => $context_val) {
1661
- // $key_check_for = "_server_" . strtolower($one_ip_header_key) . "_0";
1662
- $match = preg_match(
1663
- "/^_server_{$one_ip_header_key_lower}_[\d+]/",
1664
- $context_key,
1665
- $matches
1666
- );
1667
- if ($match) {
1668
- $arr_found_additional_ip_headers[
1669
- $context_key
1670
- ] = $context_val;
1671
- }
1672
- }
1673
- } // End foreach().
1674
-
1675
- return $arr_found_additional_ip_headers;
1676
- }
1677
-
1678
- /**
1679
- * Ensures an ip address is both a valid IP and does not fall within
1680
- * a private network range.
1681
- *
1682
- * @param string $ip IP number.
1683
- * @return bool
1684
- */
1685
- public function validate_ip($ip)
1686
- {
1687
- if (
1688
- filter_var(
1689
- $ip,
1690
- FILTER_VALIDATE_IP,
1691
- FILTER_FLAG_IPV4 |
1692
- FILTER_FLAG_NO_PRIV_RANGE |
1693
- FILTER_FLAG_NO_RES_RANGE
1694
- ) === false
1695
- ) {
1696
- return false;
1697
- }
1698
-
1699
- return true;
1700
- }
1701
-
1702
- /**
1703
- * Override this to add CSS in <head> for your logger.
1704
- * The CSS that you output will only be outputed
1705
- * on pages where Simple History is used.
1706
- */
1707
- public function adminCSS()
1708
- {
1709
- /*
1710
- ?>
1711
- <style>
1712
- body {
1713
- border: 2px solid red;
1714
- }
1715
- </style>
1716
- <?php
1717
- */
1718
- }
1719
-
1720
- /**
1721
- * Override this to add JavaScript in the footer for your logger.
1722
- * The JS that you output will only be outputed
1723
- * on pages where Simple History is used.
1724
- */
1725
- public function adminJS()
1726
- {
1727
- /*
1728
- ?>
1729
- <script>
1730
- console.log("This is outputed in the footer");
1731
- </script>
1732
- <?php
1733
- */
1734
- }
1
  <?php
2
 
3
+ defined( 'ABSPATH' ) || die();
4
 
5
  /**
6
  * A PSR-3 inspired logger class
10
  *
11
  * @link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md PSR-3 specification
12
  */
13
+ class SimpleLogger {
14
+
15
+ /**
16
+ * Unique slug for this logger
17
+ * Will be saved in DB and used to associate each log row with its logger
18
+ */
19
+ public $slug = __CLASS__;
20
+
21
+ /**
22
+ * Will contain the untranslated messages from getInfo()
23
+ *
24
+ * By adding your messages here they will be stored both translated and non-translated
25
+ * You then log something like this:
26
+ * <code>
27
+ * $this->info( $this->messages["POST_UPDATED"] );
28
+ * </code>
29
+ * or with the shortcut
30
+ * <code>
31
+ * $this->infoMessage("POST_UPDATED");
32
+ * </code>
33
+ * which results in the original, untranslated, string being added to the log and database
34
+ * the translated string are then only used when showing the log in the GUI
35
+ */
36
+ public $messages;
37
+
38
+ /**
39
+ * ID of last inserted row
40
+ *
41
+ * @var int $lastInsertID Database row primary key.
42
+ */
43
+ public $lastInsertID;
44
+
45
+ /**
46
+ * Context of last inserted row.
47
+ *
48
+ * @var int $lastInsertContext Context used for the last insert.
49
+ * @since 2.2x
50
+ */
51
+ public $lastInsertContext;
52
+
53
+ /**
54
+ * Simple History instance.
55
+ *
56
+ * @var object $simpleHistory Simple history instance.
57
+ */
58
+ public $simpleHistory;
59
+
60
+ /**
61
+ * Constructor. Remember to call this as parent constructor if making a childlogger
62
+ *
63
+ * @param $simpleHistory history class objectinstance
64
+ */
65
+ public function __construct( $simpleHistory = null ) {
66
+ global $wpdb;
67
+
68
+ $this->db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
69
+ $this->db_table_contexts =
70
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
71
+
72
+ $this->simpleHistory = $simpleHistory;
73
+ }
74
+
75
+ /**
76
+ * Method that is called automagically when logger is loaded by Simple History
77
+ * Add your init stuff here
78
+ */
79
+ public function loaded() {
80
+ }
81
+
82
+ /**
83
+ * Get array with information about this logger.
84
+ *
85
+ * @return array
86
+ */
87
+ public function getInfo() {
88
+ $arr_info = array(
89
+ // The logger slug. Defaulting to the class name is nice and logical I think.
90
+ 'slug' => __CLASS__,
91
+
92
+ // Shown on the info-tab in settings, use these fields to tell
93
+ // an admin what your logger is used for.
94
+ 'name' => 'SimpleLogger',
95
+ 'description' => 'The built in logger for Simple History',
96
+
97
+ // Capability required to view log entries from this logger
98
+ 'capability' => 'edit_pages',
99
+ 'messages' => array(
100
+ // No pre-defined variants
101
+ // when adding messages __() or _x() must be used
102
+ ),
103
+ );
104
+
105
+ return $arr_info;
106
+ }
107
+
108
+ /**
109
+ * Return single array entry from the array in getInfo()
110
+ * Returns the value of the key if value exists, or null
111
+ *
112
+ * @since 2.5.4
113
+ * @return Mixed
114
+ */
115
+ public function getInfoValueByKey( $key ) {
116
+ $arr_info = $this->getInfo();
117
+
118
+ return isset( $arr_info[ $key ] ) ? $arr_info[ $key ] : null;
119
+ }
120
+
121
+ /**
122
+ * Returns the capability required to read log rows from this logger
123
+ *
124
+ * @return $string capability
125
+ */
126
+ public function getCapability() {
127
+ $arr_info = $this->getInfo();
128
+
129
+ $capability = 'manage_options';
130
+
131
+ if ( ! empty( $arr_info['capability'] ) ) {
132
+ $capability = $arr_info['capability'];
133
+ }
134
+
135
+ return $capability;
136
+ }
137
+
138
+ /**
139
+ * Interpolates context values into the message placeholders.
140
+ *
141
+ * @param string $message
142
+ * @param array $context
143
+ * @param array $row Currently not always passed, because loggers need to be updated to support this...
144
+ */
145
+ public function interpolate( $message, $context = array(), $row = null ) {
146
+ if ( ! is_array( $context ) ) {
147
+ return $message;
148
+ }
149
+
150
+ /**
151
+ * Filter the context used to create the message from the message template
152
+ *
153
+ * @since 2.2.4
154
+ */
155
+ $context = apply_filters(
156
+ 'simple_history/logger/interpolate/context',
157
+ $context,
158
+ $message,
159
+ $row
160
+ );
161
+
162
+ // Build a replacement array with braces around the context keys
163
+ $replace = array();
164
+ foreach ( $context as $key => $val ) {
165
+ // Both key and val must be strings or number (for vals)
166
+ // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
167
+ if ( is_string( $key ) || is_numeric( $key ) ) {
168
+ // key ok
169
+ }
170
+
171
+ // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
172
+ if ( is_string( $val ) || is_numeric( $val ) ) {
173
+ // val ok
174
+ } else {
175
+ // not a value we can replace
176
+ continue;
177
+ }
178
+
179
+ $replace[ '{' . $key . '}' ] = $val;
180
+ }
181
+
182
+ // Interpolate replacement values into the message and return
183
+ return strtr( $message, $replace );
184
+ }
185
+
186
+ /**
187
+ * @param object $row
188
+ * @return string HTML
189
+ */
190
+ public function getLogRowHeaderInitiatorOutput( $row ) {
191
+ $initiator_html = '';
192
+ $initiator = $row->initiator;
193
+ $context = $row->context;
194
+
195
+ switch ( $initiator ) {
196
+ case 'wp':
197
+ $initiator_html .=
198
+ '<strong class="SimpleHistoryLogitem__inlineDivided">WordPress</strong> ';
199
+ break;
200
+
201
+ case 'wp_cli':
202
+ $initiator_html .=
203
+ '<strong class="SimpleHistoryLogitem__inlineDivided">WP-CLI</strong> ';
204
+ break;
205
+
206
+ // wp_user = wordpress uses, but user may have been deleted since log entry was added
207
+ case 'wp_user':
208
+ $user_id = isset( $row->context['_user_id'] )
209
+ ? $row->context['_user_id']
210
+ : null;
211
+
212
+ $user = get_user_by( 'id', $user_id );
213
+ if ( $user_id > 0 && ( $user ) ) {
214
+ // Sender is user and user still exists.
215
+ $is_current_user =
216
+ get_current_user_id() == $user_id ? true : false;
217
+
218
+ // get user role, as done in user-edit.php
219
+ $wp_roles = $GLOBALS['wp_roles'];
220
+ $user_roles = array_intersect(
221
+ array_values( (array) $user->roles ),
222
+ array_keys( (array) $wp_roles->roles )
223
+ );
224
+ $user_role = array_shift( $user_roles );
225
+
226
+ $user_display_name = $user->display_name;
227
+
228
+ /*
229
+ * If user who logged this is the currently logged in user
230
+ * skip name and email and use just "You"
231
+ *
232
+ * @param bool If you should be used
233
+ * @since 2.1
234
+ */
235
+ $use_you = apply_filters(
236
+ 'simple_history/header_initiator_use_you',
237
+ true
238
+ );
239
+
240
+ if ( $use_you && $is_current_user ) {
241
+ $tmpl_initiator_html = '
 
 
 
 
242
  <a href="%6$s" class="SimpleHistoryLogitem__headerUserProfileLink">
243
  <strong class="SimpleHistoryLogitem__inlineDivided">%5$s</strong>
244
  </a>
245
  ';
246
+ } else {
247
+ $tmpl_initiator_html = '
248
  <a href="%6$s" class="SimpleHistoryLogitem__headerUserProfileLink">
249
  <strong class="SimpleHistoryLogitem__inlineDivided">%3$s</strong>
250
  <span class="SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__headerEmail">%2$s</span>
251
  </a>
252
  ';
253
+ }
254
+
255
+ /**
256
+ * Filter the format for the user output
257
+ *
258
+ * @since 2.0
259
+ *
260
+ * @param string $format.
261
+ */
262
+ $tmpl_initiator_html = apply_filters(
263
+ 'simple_history/header_initiator_html_existing_user',
264
+ $tmpl_initiator_html
265
+ );
266
+
267
+ $initiator_html .= sprintf(
268
+ $tmpl_initiator_html,
269
+ esc_html( $user->user_login ), // 1
270
+ esc_html( $user->user_email ), // 2
271
+ esc_html( $user_display_name ), // 3
272
+ $user_role, // 4
273
+ _x(
274
+ 'You',
275
+ 'header output when initiator is the currently logged in user',
276
+ 'simple-history'
277
+ ), // 5
278
+ get_edit_user_link( $user_id ) // 6
279
+ );
280
+ } elseif ( $user_id > 0 ) {
281
+ // Sender was a user, but user is deleted now
282
+ // output all info we have
283
+ // _user_id
284
+ // _username
285
+ // _user_login
286
+ // _user_email
287
+ $initiator_html .= sprintf(
288
+ '<strong class="SimpleHistoryLogitem__inlineDivided">' .
289
+ __(
290
+ 'Deleted user (had id %1$s, email %2$s, login %3$s)',
291
+ 'simple-history'
292
+ ) .
293
+ '</strong>',
294
+ esc_html( $context['_user_id'] ), // 1
295
+ esc_html( $context['_user_email'] ), // 2
296
+ esc_html( $context['_user_login'] ) // 3
297
+ );
298
+ } // End if().
299
+
300
+ break;
301
+
302
+ case 'web_user':
303
+ /*
304
+ Note: server_remote_addr may not show visiting/attacking ip, if server is behind...stuff..
305
+ Can be behind varnish cache, or browser can for example use compression in chrome mobile
306
+ then the real ip is behind _server_http_x_forwarded_for_0 or similar
307
+ _server_remote_addr 66.249.81.222
308
+ _server_http_x_forwarded_for_0 5.35.187.212
309
+ */
310
+
311
+ // Check if additional IP addresses are stored, from http_x_forwarded_for and so on.
312
+ $arr_found_additional_ip_headers = $this->get_event_ip_number_headers(
313
+ $row
314
+ );
315
+
316
+ $initiator_html .=
317
+ "<strong class='SimpleHistoryLogitem__inlineDivided'>" .
318
+ __( 'Anonymous web user', 'simple-history' ) .
319
+ '</strong> ';
320
+
321
+ break;
322
+
323
+ case 'other':
324
+ $initiator_html .=
325
+ "<strong class='SimpleHistoryLogitem__inlineDivided'>" .
326
+ _x(
327
+ 'Other',
328
+ 'Event header output, when initiator is unknown',
329
+ 'simple-history'
330
+ ) .
331
+ '</strong>';
332
+ break;
333
+
334
+ // no initiator
335
+ case null:
336
+ // $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided'>Null</strong>";
337
+ break;
338
+
339
+ default:
340
+ $initiator_html .=
341
+ "<strong class='SimpleHistoryLogitem__inlineDivided'>" .
342
+ esc_html( $initiator ) .
343
+ '</strong>';
344
+ } // End switch().
345
+
346
+ /**
347
+ * Filter generated html for the initiator row header html
348
+ *
349
+ * @since 2.0
350
+ *
351
+ * @param string $initiator_html
352
+ * @param object $row Log row
353
+ */
354
+ $initiator_html = apply_filters(
355
+ 'simple_history/row_header_initiator_output',
356
+ $initiator_html,
357
+ $row
358
+ );
359
+
360
+ return $initiator_html;
361
+ }
362
+
363
+ public function getLogRowHeaderDateOutput( $row ) {
364
+ // HTML for date
365
+ // Date (should...) always exist
366
+ // http://developers.whatwg.org/text-level-semantics.html#the-time-element
367
+ $date_html = '';
368
+ $str_when = '';
369
+
370
+ // $row->date is in GMT
371
+ $date_datetime = new DateTime( $row->date, new DateTimeZone( 'GMT' ) );
372
+
373
+ // Current datetime in GMT
374
+ $time_current = strtotime( current_time( 'mysql', 1 ) );
375
+
376
+ /**
377
+ * Filter how many seconds as most that can pass since an
378
+ * event occured to show "nn minutes ago" (human diff time-format) instead of exact date
379
+ *
380
+ * @since 2.0
381
+ *
382
+ * @param int $time_ago_max_time Seconds
383
+ */
384
+ $time_ago_max_time = DAY_IN_SECONDS * 2;
385
+ $time_ago_max_time = apply_filters(
386
+ 'simple_history/header_time_ago_max_time',
387
+ $time_ago_max_time
388
+ );
389
+
390
+ /**
391
+ * Filter how many seconds as most that can pass since an
392
+ * event occured to show "just now" instead of exact date
393
+ *
394
+ * @since 2.0
395
+ *
396
+ * @param int $time_ago_max_time Seconds
397
+ */
398
+ $time_ago_just_now_max_time = 30;
399
+ $time_ago_just_now_max_time = apply_filters(
400
+ 'simple_history/header_just_now_max_time',
401
+ $time_ago_just_now_max_time
402
+ );
403
+
404
+ $date_format = get_option( 'date_format' );
405
+ $time_format = get_option( 'time_format' );
406
+ $date_and_time_format = $date_format . ' - ' . $time_format;
407
+
408
+ // Show local time as hours an minutes when event is recent.
409
+ $local_date_format = $time_format;
410
+
411
+ // Show local time as date and hours when event is a bit older.
412
+ if (
413
+ $time_current - HOUR_IN_SECONDS * 6 >
414
+ $date_datetime->getTimestamp()
415
+ ) {
416
+ $local_date_format = $date_and_time_format;
417
+ }
418
+
419
+ if (
420
+ $time_current - $date_datetime->getTimestamp() <=
421
+ $time_ago_just_now_max_time
422
+ ) {
423
+ // Show "just now" if event is very recent.
424
+ $str_when = __( 'Just now', 'simple-history' );
425
+ } elseif (
426
+ $time_current - $date_datetime->getTimestamp() >
427
+ $time_ago_max_time
428
+ ) {
429
+ /* Translators: Date format for log row header, see http://php.net/date */
430
+ $datef = __( 'M j, Y \a\t G:i', 'simple-history' );
431
+ $str_when = date_i18n(
432
+ $datef,
433
+ strtotime( get_date_from_gmt( $row->date ) )
434
+ );
435
+ } else {
436
+ // Show "nn minutes ago" when event is xx seconds ago or earlier
437
+ $date_human_time_diff = human_time_diff(
438
+ $date_datetime->getTimestamp(),
439
+ $time_current
440
+ );
441
+ /* Translators: 1: last modified date and time in human time diff-format */
442
+ $str_when = sprintf(
443
+ __( '%1$s ago', 'simple-history' ),
444
+ $date_human_time_diff
445
+ );
446
+ }
447
+
448
+ $item_permalink = admin_url( apply_filters( 'simple_history/admin_location', 'index' ) . '.php?page=simple_history_page' );
449
+ if ( ! empty( $row->id ) ) {
450
+ $item_permalink .= "#item/{$row->id}";
451
+ }
452
+
453
+ // Datetime attribute on <time> element.
454
+ $str_datetime_title = sprintf(
455
+ __( '%1$s local time %3$s (%2$s GMT time)', 'simple-history' ),
456
+ get_date_from_gmt(
457
+ $date_datetime->format( 'Y-m-d H:i:s' ),
458
+ $date_and_time_format
459
+ ), // 1 local time
460
+ $date_datetime->format( $date_and_time_format ), // GMT time
461
+ PHP_EOL // 3, new line
462
+ );
463
+
464
+ // Time and date before live updated relative date.
465
+ $str_datetime_local = sprintf(
466
+ '%1$s',
467
+ get_date_from_gmt(
468
+ $date_datetime->format( 'Y-m-d H:i:s' ),
469
+ $local_date_format
470
+ ) // 1 local time
471
+ );
472
+
473
+ // HTML for whole span with date info.
474
+ $date_html =
475
+ "<span class='SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided'>";
476
+ $date_html .= "<a class='' href='{$item_permalink}'>";
477
+ $date_html .= sprintf(
478
+ '<span title="%1$s">%4$s (<time datetime="%3$s" class="SimpleHistoryLogitem__when__liveRelative">%2$s</time>)</span>',
479
+ esc_attr( $str_datetime_title ), // 1 datetime attribute
480
+ esc_html( $str_when ), // 2 date text, visible in log, but overridden by JS relative date script.
481
+ $date_datetime->format( DateTime::RFC3339 ), // 3
482
+ esc_html( $str_datetime_local ) // 4
483
+ );
484
+ $date_html .= '</a>';
485
+ $date_html .= '</span>';
486
+
487
+ /**
488
+ * Filter the output of the date section of the header.
489
+ *
490
+ * @since 2.5.1
491
+ *
492
+ * @param string $date_html
493
+ * @param object $row
494
+ */
495
+ $date_html = apply_filters(
496
+ 'simple_history/row_header_date_output',
497
+ $date_html,
498
+ $row
499
+ );
500
+
501
+ return $date_html;
502
+ }
503
+
504
+ public function getLogRowHeaderUsingPluginOutput( $row ) {
505
+ // Logger "via" info in header, i.e. output some extra
506
+ // info next to the time to make it more clear what plugin etc.
507
+ // that "caused" this event
508
+ $via_html = '';
509
+ $logger_name_via = $this->getInfoValueByKey( 'name_via' );
510
+
511
+ if ( $logger_name_via ) {
512
+ $via_html =
513
+ "<span class='SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__via'>";
514
+ $via_html .= $logger_name_via;
515
+ $via_html .= '</span>';
516
+ }
517
+
518
+ return $via_html;
519
+ }
520
+
521
+ /**
522
+ * Context for IP Addresses can contain multiple entries.
523
+ *
524
+ * - "_server_remote_addr" with value for example "172.17.0.0" is the main entry.
525
+ * It usually contains the IP address of the visitor.
526
+ *
527
+ * - Then zero or one or multiple entries can exist if web server is for example behind proxy.
528
+ * Entries that can exist are the one with keys is get_ip_number_header_keys(),
529
+ * Also each key can exist multiple times.
530
+ * Final key name will be like "_server_http_x_forwarded_for_0", "_server_http_x_forwarded_for_1" and so on.
531
+ *
532
+ * @param mixed $row
533
+ * @return string
534
+ */
535
+ public function getLogRowHeaderIPAddressOutput( $row ) {
536
+
537
+ /**
538
+ * Filter if IP Address should be added to header row.
539
+ *
540
+ * @since 2.x
541
+ *
542
+ * @param bool True to show IP address, false to hide it. Defaults to false.
543
+ * @param object $row Row data
544
+ */
545
+ $show_ip_address = apply_filters(
546
+ 'simple_history/row_header_output/display_ip_address',
547
+ false,
548
+ $row
549
+ );
550
+
551
+ if ( ! $show_ip_address ) {
552
+ return '';
553
+ }
554
+
555
+ $context = $row->context;
556
+ $html = "<span class='SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__anonUserWithIp'>";
557
+
558
+ $arr_ip_addresses = array();
559
+
560
+ // Look for additional ip addresses.
561
+ $arr_found_additional_ip_headers = $this->get_event_ip_number_headers( $row );
562
+
563
+ $arr_ip_addresses = array_merge(
564
+ // Remote addr always exists.
565
+ array( '_server_remote_addr' => $context['_server_remote_addr'] ),
566
+ $arr_found_additional_ip_headers
567
+ );
568
+
569
+ // if ( count( $arr_found_additional_ip_headers ) ) {
570
+ // $iplookup_link = sprintf('https://ipinfo.io/%1$s', esc_attr($context["_server_remote_addr"]));
571
+ // $ip_numbers_joined = wp_sprintf_l('%l', array("_server_remote_addr" => $context["_server_remote_addr"]) + $arr_found_additional_ip_headers);
572
+ /*
573
+ $html .= sprintf(
574
+ __('Anonymous user with multiple IP addresses detected: %1$s', "simple-history"),
575
+ "<a target='_blank' href={$iplookup_link} class='SimpleHistoryLogitem__anonUserWithIp__theIp'>" . esc_html( $ip_numbers_joined ) . "</a>"
576
+ );*/
577
+
578
+ /*
579
+ print_r($arr_found_additional_ip_headers);
580
+ Array
581
+ (
582
+ [_server_http_x_forwarded_for_0] => 5.35.187.212
583
+ [_server_http_x_forwarded_for_1] => 83.251.97.21
584
+ )
585
+ */
586
+
587
+ // } else {
588
+
589
+ $first_ip_address = reset( $arr_ip_addresses );
590
+
591
+ // Output single or plural text.
592
+ if ( count( $arr_ip_addresses ) === 1 ) {
593
+ // Single ip address
594
+ $iplookup_link = sprintf(
595
+ 'https://ipinfo.io/%1$s',
596
+ esc_attr( $first_ip_address )
597
+ );
598
+
599
+ $html .= sprintf(
600
+ __( 'IP Address %1$s', 'simple-history' ),
601
+ "<a target='_blank' href='{$iplookup_link}' class='SimpleHistoryLogitem__anonUserWithIp__theIp' data-ip-address='" . esc_attr( $first_ip_address ) . "'>" .
602
+ esc_html( $first_ip_address ) .
603
+ '</a>'
604
+ );
605
+ } elseif ( count( $arr_ip_addresses ) > 1 ) {
606
+ $ip_addresses_html = '';
607
+
608
+ foreach ( $arr_ip_addresses as $ip_address_header => $ip_address ) {
609
+ $iplookup_link = sprintf(
610
+ 'https://ipinfo.io/%1$s',
611
+ esc_attr( $ip_address )
612
+ );
613
+
614
+ $ip_addresses_html .= sprintf(
615
+ '<a target="_blank" href="%3$s" class="SimpleHistoryLogitem__anonUserWithIp__theIp" data-ip-address="%4$s">%1$s</a>, ',
616
+ esc_html( $ip_address ), // 1
617
+ esc_html( $ip_address_header ), // 2
618
+ $iplookup_link, // 3
619
+ esc_attr( $ip_address ) // 4
620
+ );
621
+ }
622
+
623
+ // Remove trailing comma.
624
+ $ip_addresses_html = rtrim( $ip_addresses_html, ', ' );
625
+
626
+ $html .= sprintf(
627
+ __( 'IP Addresses %1$s', 'simple-history' ),
628
+ $ip_addresses_html
629
+ );
630
+ }
631
+
632
+ // } // multiple ip
633
+ $html .= '</span> ';
634
+
635
+ // $initiator_html .= "<strong>" . __("<br><br>Unknown user from {$context["_server_remote_addr"]}") . "</strong>";
636
+ // $initiator_html .= "<strong>" . __("<br><br>{$context["_server_remote_addr"]}") . "</strong>";
637
+ // $initiator_html .= "<strong>" . __("<br><br>User from IP {$context["_server_remote_addr"]}") . "</strong>";
638
+ // $initiator_html .= "<strong>" . __("<br><br>Non-logged in user from IP {$context["_server_remote_addr"]}") . "</strong>";
639
+ // } // End if().
640
+ return $html;
641
+ }
642
+
643
+ /**
644
+ * Returns header output for a log row.
645
+ *
646
+ * Format should be common for all log rows and should be like:
647
+ * Username (user role) · Date · IP Address · Via plugin abc
648
+ * I.e.:
649
+ * Initiator * Date/time * IP Address * Via logger
650
+ *
651
+ * @param object $row Row data
652
+ * @return string HTML
653
+ */
654
+ public function getLogRowHeaderOutput( $row ) {
655
+ $initiator_html = $this->getLogRowHeaderInitiatorOutput( $row );
656
+ $date_html = $this->getLogRowHeaderDateOutput( $row );
657
+ $via_html = $this->getLogRowHeaderUsingPluginOutput( $row );
658
+ $ip_address_html = $this->getLogRowHeaderIPAddressOutput( $row );
659
+
660
+ // Template to combine header parts.
661
+ $template = '
 
 
 
 
662
  %1$s
663
  %2$s
664
  %3$s
665
  %4$s
666
  ';
667
 
668
+ /**
669
+ * Filter template used to glue together markup the log row header.
670
+ *
671
+ * @since 2.0
672
+ *
673
+ * @param string $template
674
+ * @param object $row Log row
675
+ */
676
+ $template = apply_filters(
677
+ 'simple_history/row_header_output/template',
678
+ $template,
679
+ $row
680
+ );
681
+
682
+ // Glue together final result.
683
+ $html = sprintf(
684
+ $template,
685
+ $initiator_html, // 1
686
+ $date_html, // 2
687
+ $via_html, // 3
688
+ $ip_address_html // 4
689
+ );
690
+
691
+ /**
692
+ * Filter generated html for the log row header.
693
+ *
694
+ * @since 2.0
695
+ *
696
+ * @param string $html
697
+ * @param object $row Log row
698
+ */
699
+ $html = apply_filters( 'simple_history/row_header_output', $html, $row );
700
+
701
+ return $html;
702
+ }
703
+
704
+ /**
705
+ * Returns the plain text version of this entry
706
+ * Used in for example CSV-exports.
707
+ * Defaults to log message with context interpolated.
708
+ * Keep format as plain and simple as possible.
709
+ * Links are ok, for example to link to users or posts.
710
+ * Tags will be stripped when text is used for CSV-exports and so on.
711
+ * Keep it on a single line. No <p> or <br> and so on.
712
+ *
713
+ * Example output:
714
+ * Edited post "About the company"
715
+ *
716
+ * Message should sound like it's coming from the user.
717
+ * Image that the name of the user is added in front of the text:
718
+ * Jessie James: Edited post "About the company"
719
+ */
720
+ public function getLogRowPlainTextOutput( $row ) {
721
+ $message = $row->message;
722
+ $message_key = isset( $row->context['_message_key'] )
723
+ ? $row->context['_message_key']
724
+ : null;
725
+
726
+ // Message is translated here, but translation must be added in
727
+ // plain text before
728
+ if ( empty( $message_key ) ) {
729
+ // Message key did not exist, so check if we should translate using textdomain
730
+ if ( ! empty( $row->context['_gettext_domain'] ) ) {
731
+ // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.NonSingularStringLiteralText
732
+ $message = __( $message, $row->context['_gettext_domain'] );
733
+ }
734
+ } else {
735
+ // Check that messages does exist
736
+ // If we for example disable a Logger we may have references
737
+ // to message keys that are unavailable. If so then fallback to message.
738
+ if ( isset( $this->messages[ $message_key ]['translated_text'] ) ) {
739
+ $message = $this->messages[ $message_key ]['translated_text'];
740
+ } else { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedElse
741
+ // Not message exists for message key. Just keep using message.
742
+ }
743
+ }
744
+
745
+ $html = $this->interpolate( $message, $row->context, $row );
746
+
747
+ // All messages are escaped by default.
748
+ // If you need unescaped output override this method
749
+ // in your own logger
750
+ $html = esc_html( $html );
751
+
752
+ /**
753
+ * Filter generated output for plain text output
754
+ *
755
+ * @since 2.0
756
+ *
757
+ * @param string $html
758
+ * @param object $row Log row
759
+ */
760
+ $html = apply_filters(
761
+ 'simple_history/row_plain_text_output',
762
+ $html,
763
+ $row
764
+ );
765
+
766
+ return $html;
767
+ }
768
+
769
+ /**
770
+ * Get output for image
771
+ * Image can be for example gravar if sender is user,
772
+ * or other images if sender i system, wordpress, and so on
773
+ */
774
+ public function getLogRowSenderImageOutput( $row ) {
775
+ $sender_image_html = '';
776
+ $sender_image_size = 32;
777
+
778
+ $initiator = $row->initiator;
779
+
780
+ switch ( $initiator ) {
781
+ // wp_user = wordpress uses, but user may have been deleted since log entry was added
782
+ case 'wp_user':
783
+ $user_id = isset( $row->context['_user_id'] )
784
+ ? $row->context['_user_id']
785
+ : null;
786
+
787
+ $user = get_user_by( 'id', $user_id );
788
+ if ( $user_id > 0 && ( $user ) ) {
789
+ // Sender was user
790
+ $sender_image_html = $this->simpleHistory->get_avatar(
791
+ $user->user_email,
792
+ $sender_image_size
793
+ );
794
+ } elseif ( $user_id > 0 ) {
795
+ // Sender was a user, but user is deleted now
796
+ $sender_image_html = $this->simpleHistory->get_avatar(
797
+ '',
798
+ $sender_image_size
799
+ );
800
+ } else {
801
+ $sender_image_html = $this->simpleHistory->get_avatar(
802
+ '',
803
+ $sender_image_size
804
+ );
805
+ }
806
+
807
+ break;
808
+ }
809
+
810
+ /**
811
+ * Filter generated output for row image (sender image)
812
+ *
813
+ * @since 2.0
814
+ *
815
+ * @param string $sender_image_html
816
+ * @param object $row Log row
817
+ */
818
+ $sender_image_html = apply_filters(
819
+ 'simple_history/row_sender_image_output',
820
+ $sender_image_html,
821
+ $row
822
+ );
823
+
824
+ return $sender_image_html;
825
+ }
826
+
827
+ /**
828
+ * Use this method to output detailed output for a log row
829
+ * Example usage is if a user has uploaded an image then a
830
+ * thumbnail of that image can bo outputed here
831
+ *
832
+ * @param object $row
833
+ * @return string HTML-formatted output
834
+ */
835
+ public function getLogRowDetailsOutput( $row ) {
836
+ $html = '';
837
+
838
+ /**
839
+ * Filter generated output for details
840
+ *
841
+ * @since 2.0
842
+ *
843
+ * @param string $html
844
+ * @param object $row Log row
845
+ */
846
+ $html = apply_filters( 'simple_history/row_details_output', $html, $row );
847
+
848
+ return $html;
849
+ }
850
+
851
+ /**
852
+ * System is unusable.
853
+ *
854
+ * @param string $message
855
+ * @param array $context
856
+ * @return null
857
+ */
858
+ public function emergency( $message, array $context = array() ) {
859
+ return $this->log( SimpleLoggerLogLevels::EMERGENCY, $message, $context );
860
+ }
861
+
862
+ /**
863
+ * System is unusable.
864
+ *
865
+ * @param string $message key from getInfo messages array
866
+ * @param array $context
867
+ * @return null
868
+ */
869
+ public function emergencyMessage( $message, array $context = array() ) {
870
+ return $this->logByMessageKey(
871
+ SimpleLoggerLogLevels::EMERGENCY,
872
+ $message,
873
+ $context
874
+ );
875
+ }
876
+
877
+ /**
878
+ * Log with message
879
+ * Called from infoMessage(), errorMessage(), and so on
880
+ *
881
+ * Call like this:
882
+ *
883
+ * return $this->logByMessageKey(SimpleLoggerLogLevels::EMERGENCY, $message, $context);
884
+ */
885
+ private function logByMessageKey(
886
+ $SimpleLoggerLogLevelsLevel,
887
+ $messageKey,
888
+ $context
889
+ ) {
890
+ // When logging by message then the key must exist
891
+ if ( ! isset( $this->messages[ $messageKey ]['untranslated_text'] ) ) {
892
+ return;
893
+ }
894
+
895
+ /**
896
+ * Filter so plugins etc. can shortut logging
897
+ *
898
+ * @since 2.0.20
899
+ *
900
+ * @param true yes, we default to do the logging
901
+ * @param string logger slug
902
+ * @param string messageKey
903
+ * @param string log level
904
+ * @param array context
905
+ * @return bool false to abort logging
906
+ */
907
+ $doLog = apply_filters(
908
+ 'simple_history/simple_logger/log_message_key',
909
+ true,
910
+ $this->slug,
911
+ $messageKey,
912
+ $SimpleLoggerLogLevelsLevel,
913
+ $context
914
+ );
915
+
916
+ if ( ! $doLog ) {
917
+ return;
918
+ }
919
+
920
+ $context['_message_key'] = $messageKey;
921
+ $message = $this->messages[ $messageKey ]['untranslated_text'];
922
+
923
+ $this->log( $SimpleLoggerLogLevelsLevel, $message, $context );
924
+ }
925
+
926
+ /**
927
+ * Action must be taken immediately.
928
+ *
929
+ * @param string $message
930
+ * @param array $context
931
+ * @return null
932
+ */
933
+ public function alert( $message, array $context = array() ) {
934
+ return $this->log( SimpleLoggerLogLevels::ALERT, $message, $context );
935
+ }
936
+
937
+ /**
938
+ * Action must be taken immediately.
939
+ *
940
+ * @param string $message key from getInfo messages array
941
+ * @param array $context
942
+ * @return null
943
+ */
944
+ public function alertMessage( $message, array $context = array() ) {
945
+ return $this->logByMessageKey(
946
+ SimpleLoggerLogLevels::ALERT,
947
+ $message,
948
+ $context
949
+ );
950
+ }
951
+
952
+ /**
953
+ * Critical conditions.
954
+ *
955
+ * Example: Application component unavailable, unexpected exception.
956
+ *
957
+ * @param string $message
958
+ * @param array $context
959
+ * @return null
960
+ */
961
+ public function critical( $message, array $context = array() ) {
962
+ return $this->log( SimpleLoggerLogLevels::CRITICAL, $message, $context );
963
+ }
964
+
965
+ /**
966
+ * Critical conditions.
967
+ *
968
+ * @param string $message key from getInfo messages array
969
+ * @param array $context
970
+ * @return null
971
+ */
972
+ public function criticalMessage( $message, array $context = array() ) {
973
+ if ( ! isset( $this->messages[ $message ]['untranslated_text'] ) ) {
974
+ return;
975
+ }
976
+
977
+ $context['_message_key'] = $message;
978
+ $message = $this->messages[ $message ]['untranslated_text'];
979
+
980
+ $this->log( SimpleLoggerLogLevels::CRITICAL, $message, $context );
981
+ }
982
+
983
+ /**
984
+ * Runtime errors that do not require immediate action but should typically
985
+ * be logged and monitored.
986
+ *
987
+ * @param string $message
988
+ * @param array $context
989
+ * @return null
990
+ */
991
+ public function error( $message, array $context = array() ) {
992
+ return $this->log( SimpleLoggerLogLevels::ERROR, $message, $context );
993
+ }
994
+
995
+ /**
996
+ * Runtime errors that do not require immediate action but