Simple History - Version 2.0

Version Description

(november 2014) =

Major update - Simple History is now better and nicer than ever before! :) I've spend hundreds of hours making this update, so if you use it and like it please donate to keep my spirit up or give it a nice review.

  • Code cleanup and modularization
  • Support for log contexts
  • Kinda PSR-3-compatible :)
  • Can handle larger logs (doesn't load whole log into memory any more)
  • Use nonces at more places
  • More filters and hooks to make it easier to customize
  • Better looking! well, at least I think so ;)
  • Much better logging system to make it much easier to create new loggers and to translate logs into different languages
  • Features as plugins: more things are moved into modules/its own file
  • Users see different logs depending on their capability, for example an administrator will see what plugins have been installed, but an editor will not see any plugin related logs
  • Much much more.
Download this release

Release Info

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

Code changes from version 1.3.11 to 2.0

Files changed (127) hide show
  1. README.md +37 -11
  2. SimpleHistory.php +2111 -0
  3. SimpleHistoryFunctions.php +38 -0
  4. SimpleHistoryLogQuery.php +742 -0
  5. css/styles.css +783 -0
  6. dropins/SimpleHistoryDonateDropin.php +83 -0
  7. dropins/SimpleHistoryFilterDropin.css +37 -0
  8. dropins/SimpleHistoryFilterDropin.js +200 -0
  9. dropins/SimpleHistoryFilterDropin.php +237 -0
  10. dropins/SimpleHistoryIpInfoDropin.css +7 -0
  11. dropins/SimpleHistoryIpInfoDropin.js +33 -0
  12. dropins/SimpleHistoryIpInfoDropin.php +37 -0
  13. dropins/SimpleHistoryNewRowsNotifier.php +92 -0
  14. dropins/SimpleHistoryNewRowsNotifierDropin.css +40 -0
  15. dropins/SimpleHistoryNewRowsNotifierDropin.js +101 -0
  16. dropins/SimpleHistoryRSSDropin.php +348 -0
  17. dropins/SimpleHistorySettingsLogtestDropin.php +292 -0
  18. dropins/SimpleHistorySettingsStatsDropin.css +11 -0
  19. dropins/SimpleHistorySettingsStatsDropin.php +113 -0
  20. examples.php +96 -0
  21. gruntfile.js +36 -0
  22. index.php +59 -2094
  23. js/scripts.js +674 -0
  24. js/select2/LICENSE +18 -0
  25. js/select2/README.md +99 -0
  26. js/select2/bower.json +8 -0
  27. js/select2/component.json +66 -0
  28. js/select2/composer.json +29 -0
  29. js/select2/package.json +20 -0
  30. js/select2/release.sh +79 -0
  31. js/select2/select2-bootstrap.css +87 -0
  32. js/select2/select2-spinner.gif +0 -0
  33. js/select2/select2.css +704 -0
  34. js/select2/select2.jquery.json +36 -0
  35. js/select2/select2.js +3508 -0
  36. js/select2/select2.min.js +23 -0
  37. js/select2/select2.png +0 -0
  38. js/select2/select2_locale_ar.js +19 -0
  39. js/select2/select2_locale_az.js +20 -0
  40. js/select2/select2_locale_bg.js +20 -0
  41. js/select2/select2_locale_ca.js +19 -0
  42. js/select2/select2_locale_cs.js +51 -0
  43. js/select2/select2_locale_da.js +19 -0
  44. js/select2/select2_locale_de.js +18 -0
  45. js/select2/select2_locale_el.js +19 -0
  46. js/select2/select2_locale_en.js.template +20 -0
  47. js/select2/select2_locale_es.js +17 -0
  48. js/select2/select2_locale_et.js +19 -0
  49. js/select2/select2_locale_eu.js +45 -0
  50. js/select2/select2_locale_fa.js +21 -0
  51. js/select2/select2_locale_fi.js +30 -0
  52. js/select2/select2_locale_fr.js +18 -0
  53. js/select2/select2_locale_gl.js +45 -0
  54. js/select2/select2_locale_he.js +19 -0
  55. js/select2/select2_locale_hr.js +24 -0
  56. js/select2/select2_locale_hu.js +17 -0
  57. js/select2/select2_locale_id.js +19 -0
  58. js/select2/select2_locale_is.js +17 -0
  59. js/select2/select2_locale_it.js +17 -0
  60. js/select2/select2_locale_ja.js +17 -0
  61. js/select2/select2_locale_ka.js +19 -0
  62. js/select2/select2_locale_ko.js +19 -0
  63. js/select2/select2_locale_lt.js +26 -0
  64. js/select2/select2_locale_lv.js +19 -0
  65. js/select2/select2_locale_mk.js +19 -0
  66. js/select2/select2_locale_ms.js +19 -0
  67. js/select2/select2_locale_nl.js +17 -0
  68. js/select2/select2_locale_no.js +20 -0
  69. js/select2/select2_locale_pl.js +25 -0
  70. js/select2/select2_locale_pt-BR.js +17 -0
  71. js/select2/select2_locale_pt-PT.js +17 -0
  72. js/select2/select2_locale_ro.js +17 -0
  73. js/select2/select2_locale_rs.js +19 -0
  74. js/select2/select2_locale_ru.js +23 -0
  75. js/select2/select2_locale_sk.js +50 -0
  76. js/select2/select2_locale_sv.js +19 -0
  77. js/select2/select2_locale_th.js +19 -0
  78. js/select2/select2_locale_tr.js +19 -0
  79. js/select2/select2_locale_ug-CN.js +16 -0
  80. js/select2/select2_locale_uk.js +25 -0
  81. js/select2/select2_locale_vi.js +20 -0
  82. js/select2/select2_locale_zh-CN.js +16 -0
  83. js/select2/select2_locale_zh-TW.js +16 -0
  84. js/select2/select2x2.png +0 -0
  85. languages/simple-history-sv_SE.mo +0 -0
  86. languages/simple-history-sv_SE.po +1315 -347
  87. languages/simple-history.pot +880 -358
  88. loggers/SimpleCommentsLogger.php +789 -0
  89. loggers/SimpleCoreUpdatesLogger.php +79 -0
  90. loggers/SimpleExportLogger.php +73 -0
  91. loggers/SimpleLegacyLogger.php +109 -0
  92. loggers/SimpleLogger.php +1111 -0
  93. loggers/SimpleMediaLogger.php +283 -0
  94. loggers/SimpleMenuLogger.php +382 -0
  95. loggers/SimpleOptionsLogger.php +299 -0
  96. loggers/SimplePluginLogger.php +967 -0
  97. loggers/SimplePostLogger.php +259 -0
  98. loggers/SimpleThemeLogger.php +940 -0
  99. loggers/SimpleUserLogger.php +385 -0
  100. package.json +14 -0
  101. readme.txt +44 -119
  102. screenshot-1.png +0 -0
  103. screenshot-2.png +0 -0
  104. screenshot-3.png +0 -0
  105. scripts.js +0 -315
  106. simple-history-extender/class.simple-history-extend.php +0 -303
  107. simple-history-extender/languages/sh-extender-de_DE.mo +0 -0
  108. simple-history-extender/languages/sh-extender-de_DE.po +0 -413
  109. simple-history-extender/languages/sh-extender-nl_NL.mo +0 -0
  110. simple-history-extender/languages/sh-extender-nl_NL.po +0 -415
  111. simple-history-extender/languages/sh-extender.pot +0 -396
  112. simple-history-extender/modules/bbpress.php +0 -400
  113. simple-history-extender/modules/gravityforms.php +0 -225
  114. simple-history-extender/modules/widgets.php +0 -104
  115. simple-history-extender/simple-history-extender.php +0 -303
  116. styles.css +0 -419
  117. templates/settings-general.php +15 -0
  118. templates/settings-log.php +22 -0
  119. templates/settings-statsForGeeks.php +171 -0
  120. templates/settings-statsInitiators.php +42 -0
  121. templates/settings-statsIntro.php +23 -0
  122. templates/settings-statsLogLevels.php +114 -0
  123. templates/settings-statsLoggers.php +133 -0
  124. templates/settings-statsRowsPerDay.php +148 -0
  125. templates/settings-statsUsers.php +111 -0
  126. templates/settings-style-example.php +186 -0
  127. uninstall.php +1 -0
README.md CHANGED
@@ -1,20 +1,46 @@
1
- WordPress Simple History
2
- ========================
3
 
4
- Think of it as **Syslog for WordPress**
5
- – a plugin for viewing changes made in WordPress the admin by the users of the system.
6
 
7
- Download from WordPress.org:
8
  http://wordpress.org/extend/plugins/simple-history/
9
 
10
  # Screenshots
11
 
12
- **The log with different kind of items logged**
13
- ![Simple History screenshot 1](http://eskapism.se/external/simple-history/screenshot-1.png)
14
 
15
- **The setings page**
16
- ![Simple History screenshot 1](http://eskapism.se/external/simple-history/screenshot-2.png)
 
17
 
18
- **Example of RSS feed with history items**
19
- ![Simple History screenshot 1](http://eskapism.se/external/simple-history/screenshot-3.png)
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simple History 2 – a simple, lightweight, extendable logger for WordPress
 
2
 
3
+ Simple History is a WordPress plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
 
4
 
5
+ Download from WordPress.org:
6
  http://wordpress.org/extend/plugins/simple-history/
7
 
8
  # Screenshots
9
 
10
+ ## Viewing history events
 
11
 
12
+ This screenshot show the log view + it also shows the filter function in use: the log only shows event that
13
+ are of type post and pages and media (i.e. images & other uploads), and only events
14
+ initiated by a specific user.
15
 
16
+ ![Simple History screenshot](https://raw.githubusercontent.com/bonny/WordPress-Simple-History/v2/screenshot-1.png)
 
17
 
18
+ ## Events with different severity
19
+
20
+ Simple History uses the log levels specified in the [PHP PSR-3 standard](http://www.php-fig.org/psr/psr-3/).
21
+
22
+ ![Simple History screenshot](https://raw.githubusercontent.com/bonny/WordPress-Simple-History/v2/screenshot-2.png)
23
+
24
+ ## Events have context with extra details
25
+
26
+ Each logged event can include useful rich formatted extra information. For example: a plugin install can contain author info and a the url to the plugin, and an uploaded image can contain a thumbnail of the image.
27
+
28
+ ![Simple History screenshot](https://raw.githubusercontent.com/bonny/WordPress-Simple-History/v2/screenshot-3.png)
29
+
30
+ # Plugin API
31
+
32
+ Developers can easily log their own things using a simple API:
33
+
34
+ ```php
35
+ <?php
36
+ // Add events to the log
37
+ SimpleLogger()->info("This is a message sent to the log");
38
+
39
+ // Add events of different severity
40
+ SimpleLogger()->info("User admin edited page 'About our company'");
41
+ SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
42
+ SimpleLogger()->debug("Ok, cron job is running!");
43
+
44
+ ```
45
+
46
+ See more examples at [simple-history.com/docs](http://simple-history.com/docs).
SimpleHistory.php ADDED
@@ -0,0 +1,2111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Main class for Simple History
5
+ */
6
+ class SimpleHistory {
7
+
8
+ const NAME = "Simple History";
9
+ const VERSION = "2.0";
10
+
11
+ /**
12
+ * Capability required to view the history log
13
+ */
14
+ private $view_history_capability;
15
+
16
+ /**
17
+ * Capability required to view and edit the settings page
18
+ */
19
+ private $view_settings_capability;
20
+
21
+ /**
22
+ * Array with all instantiated loggers
23
+ */
24
+ private $instantiatedLoggers;
25
+
26
+ /**
27
+ * Array with all instantiated dropins
28
+ */
29
+ private $instantiatedDropins;
30
+
31
+ public $pluginBasename;
32
+
33
+ /**
34
+ * Bool if gettext filter function should be active
35
+ * Should only be active during the load of a logger
36
+ */
37
+ private $doFilterGettext = false;
38
+
39
+ /**
40
+ * Used by gettext filter to temporarily store current logger
41
+ */
42
+ private $doFilterGettext_currentLogger = null;
43
+
44
+ /**
45
+ * All registered settings tabs
46
+ */
47
+ private $arr_settings_tabs = array();
48
+
49
+ const DBTABLE = "simple_history";
50
+ const DBTABLE_CONTEXTS = "simple_history_contexts";
51
+
52
+ /** Slug for the settings menu */
53
+ const SETTINGS_MENU_SLUG = "simple_history_settings_menu_slug";
54
+
55
+ /** ID for the general settings section */
56
+ const SETTINGS_SECTION_GENERAL_ID = "simple_history_settings_section_general";
57
+
58
+ function __construct() {
59
+
60
+ /**
61
+ * Fires before Simple History does it's init stuff
62
+ *
63
+ * @since 2.0
64
+ *
65
+ * @param SimpleHistory $SimpleHistory This class.
66
+ */
67
+ do_action( "simple_history/before_init", $this );
68
+
69
+ $this->setupVariables();
70
+
71
+ // Actions and filters, ordered by order specified in codex: http://codex.wordpress.org/Plugin_API/Action_Reference
72
+ add_action( 'plugins_loaded', array($this, 'load_plugin_textdomain') );
73
+ add_action( 'plugins_loaded', array($this, 'add_default_settings_tabs') );
74
+ add_action( 'plugins_loaded', array($this, 'loadLoggers') );
75
+ add_action( 'plugins_loaded', array($this, 'loadDropins') );
76
+
77
+ // Run before loading of loggers and before menu items are added
78
+ add_action( 'plugins_loaded', array($this, 'check_for_upgrade'), 5 );
79
+
80
+ add_action( 'admin_menu', array($this, 'add_admin_pages') );
81
+ add_action( 'admin_menu', array($this, 'add_settings') );
82
+
83
+ add_action( 'admin_footer', array( $this, "add_js_templates" ) );
84
+
85
+ add_action( 'wp_dashboard_setup', array($this, 'add_dashboard_widget') );
86
+
87
+ add_action( 'admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
88
+
89
+ add_action( 'admin_head', array( $this, "onAdminHead" ) );
90
+
91
+ // Filters and actions not called during regular boot
92
+ add_filter("gettext", array($this, 'filter_gettext'), 20, 3);
93
+ add_filter("gettext_with_context", array($this, 'filter_gettext_with_context'), 20, 4);
94
+
95
+ add_action( 'simple_history/history_page/before_gui', array( $this, "output_quick_stats" ) );
96
+ add_action( 'simple_history/dashboard/before_gui', array( $this, "output_quick_stats" ) );
97
+
98
+ add_action( 'wp_ajax_simple_history_ajax', array($this, 'ajax') );
99
+ add_action( 'wp_ajax_simple_history_api', array($this, 'api') );
100
+
101
+ add_filter( 'plugin_action_links_simple-history/index.php', array($this, 'plugin_action_links'), 10, 4);
102
+
103
+ /**
104
+ * Fires after Simple History has done it's init stuff
105
+ *
106
+ * @since 2.0
107
+ *
108
+ * @param SimpleHistory $SimpleHistory This class.
109
+ */
110
+ do_action( "simple_history/after_init", $this );
111
+
112
+ #add_action("init", array($this, "testlog_old"));
113
+
114
+
115
+ }
116
+
117
+ public function testlog_old() {
118
+
119
+ # Log that an email has been sent
120
+ simple_history_add(array(
121
+ "object_type" => "Email",
122
+ "object_name" => "Hi there",
123
+ "action" => "was sent"
124
+ ));
125
+
126
+ # Will show “Plugin your_plugin_name Edited” in the history log
127
+ simple_history_add("action=edited&object_type=plugin&object_name=your_plugin_name");
128
+
129
+ # Will show the history item "Starship USS Enterprise repaired"
130
+ simple_history_add("action=repaired&object_type=Starship&object_name=USS Enterprise");
131
+
132
+ # Log with some extra details about the email
133
+ simple_history_add(array(
134
+ "object_type" => "Email",
135
+ "object_name" => "Hi there",
136
+ "action" => "was sent",
137
+ "description" => "The database query to generate the email took .3 seconds. This is email number 4 that is sent to this user"
138
+ ));
139
+
140
+
141
+ }
142
+
143
+ public function onAdminHead() {
144
+
145
+ if ( $this->is_on_our_own_pages() ) {
146
+
147
+ do_action( "simple_history/admin_head", $this );
148
+
149
+ }
150
+
151
+ }
152
+
153
+ /**
154
+ * Output JS templated into footer
155
+ */
156
+ public function add_js_templates($hook) {
157
+
158
+ if ( $this->is_on_our_own_pages() ) {
159
+
160
+ ?>
161
+
162
+ <script type="text/html" id="tmpl-simple-history-base">
163
+
164
+ <div class="SimpleHistory__waitingForFirstLoad">
165
+ <img src="<?php echo admin_url("/images/spinner.gif");?>" alt="" width="20" height="20">
166
+ <?php echo _x("Loading history...", "Message visible while waiting for log to load from server the first time", "simple-history") ?>
167
+ </div>
168
+
169
+ <div class="SimpleHistoryLogitemsWrap">
170
+ <div class="SimpleHistoryLogitems__beforeTopPagination"></div>
171
+ <div class="SimpleHistoryLogitems__above"></div>
172
+ <ul class="SimpleHistoryLogitems"></ul>
173
+ <div class="SimpleHistoryLogitems__below"></div>
174
+ <div class="SimpleHistoryLogitems__pagination"></div>
175
+ <div class="SimpleHistoryLogitems__afterBottomPagination"></div>
176
+ </div>
177
+
178
+ <div class="SimpleHistoryLogitems__debug"></div>
179
+
180
+ </script>
181
+
182
+ <script type="text/html" id="tmpl-simple-history-logitems-pagination">
183
+
184
+ <!-- this uses the (almost) the same html as WP does -->
185
+ <div class="SimpleHistoryPaginationPages">
186
+ <!--
187
+ <%= page_rows_from %>–<%= page_rows_to %>
188
+ <span class="SimpleHistoryPaginationDisplayNum"> of <%= total_row_count %></span>
189
+ -->
190
+ <span class="SimpleHistoryPaginationLinks">
191
+ <a
192
+ data-direction="first"
193
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--firstPage <% if ( api_args.paged <= 1 ) { %> disabled <% } %>"
194
+ title="<%= strings.goToTheFirstPage %>"
195
+ href="#">«</a>
196
+ <a
197
+ data-direction="prev"
198
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--prevPage <% if ( api_args.paged <= 1 ) { %> disabled <% } %>"
199
+ title="<%= strings.goToThePrevPage %>"
200
+ href="#">‹</a>
201
+ <span class="SimpleHistoryPaginationInput">
202
+ <input class="SimpleHistoryPaginationCurrentPage" title="<%= strings.currentPage %>" type="text" name="paged" value="<%= api_args.paged %>" size="4">
203
+ <?php _x("of", "page n of n", "simple-history") ?>
204
+ <span class="total-pages"><%= pages_count %></span>
205
+ </span>
206
+ <a
207
+ data-direction="next"
208
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--nextPage <% if ( api_args.paged >= pages_count ) { %> disabled <% } %>"
209
+ title="<%= strings.goToTheNextPage %>"
210
+ href="#">›</a>
211
+ <a
212
+ data-direction="last"
213
+ class="button SimpleHistoryPaginationLink SimpleHistoryPaginationLink--lastPage <% if ( api_args.paged >= pages_count ) { %> disabled <% } %>"
214
+ title="<%= strings.goToTheLastPage %>"
215
+ href="#">»</a>
216
+ </span>
217
+ </div>
218
+
219
+ </script>
220
+
221
+ <script type="text/html" id="tmpl-simple-history-logitems-modal">
222
+
223
+ <div class="SimpleHistory-modal">
224
+ <div class="SimpleHistory-modal__background"></div>
225
+ <div class="SimpleHistory-modal__content">
226
+ <div class="SimpleHistory-modal__contentInner">
227
+ <img class="SimpleHistory-modal__contentSpinner" src="<?php echo admin_url("/images/spinner.gif");?>" alt="">
228
+ </div>
229
+ <div class="SimpleHistory-modal__contentClose">
230
+ <button class="button">✕</button>
231
+ </div>
232
+ </div>
233
+ </div>
234
+
235
+ </script>
236
+
237
+ <?php
238
+
239
+ // Call plugins so they can add their js
240
+ foreach ( $this->instantiatedLoggers as $one_logger ) {
241
+ if( method_exists($one_logger["instance"], "adminJS" ) ) {
242
+ $one_logger["instance"]->adminJS();
243
+ }
244
+ }
245
+
246
+ }
247
+
248
+ }
249
+
250
+ /**
251
+ * Base url is:
252
+ * /wp-admin/admin-ajax.php?action=simple_history_api
253
+ *
254
+ * Examples:
255
+ * http://playground-root.ep/wp-admin/admin-ajax.php?action=simple_history_api&posts_per_page=5&paged=1&format=html
256
+ *
257
+ */
258
+ public function api() {
259
+
260
+ global $wpdb;
261
+
262
+ // Fake slow answers
263
+ //sleep(2);
264
+ //sleep(rand(0,3));
265
+ $args = $_GET;
266
+ unset($args["action"]);
267
+
268
+ // Type = overview | ...
269
+ $type = isset( $_GET["type"] ) ? $_GET["type"] : null;
270
+
271
+ if ( empty( $args ) || ! $type ) {
272
+
273
+ wp_send_json_error( array(
274
+ _x("Not enough args specified", "API: not enought arguments passed", "simple-history")
275
+ ) );
276
+
277
+ }
278
+
279
+ if (isset($args["id"])) {
280
+ $args["post__in"] = array(
281
+ $args["id"]
282
+ );
283
+ }
284
+
285
+
286
+ $data = array();
287
+
288
+ switch ($type) {
289
+
290
+ case "overview":
291
+ case "occasions":
292
+ case "single":
293
+
294
+ // API use SimpleHistoryLogQuery, so simply pass args on to that
295
+ $logQuery = new SimpleHistoryLogQuery();
296
+ $data = $logQuery->query($args);
297
+
298
+ $data["api_args"] = $args;
299
+
300
+ // Output can be array or HMTL
301
+ if ( isset( $args["format"] ) && "html" === $args["format"] ) {
302
+
303
+ $data["log_rows_raw"] = array();
304
+
305
+ foreach ($data["log_rows"] as $key => $oneLogRow) {
306
+
307
+ $args = array();
308
+ if ($type == "single") {
309
+ $args["type"] = "single";
310
+ }
311
+
312
+ $data["log_rows"][$key] = $this->getLogRowHTMLOutput( $oneLogRow, $args);
313
+
314
+ }
315
+
316
+ } else {
317
+
318
+ $data["logRows"] = $logRows;
319
+ }
320
+
321
+ break;
322
+
323
+
324
+ default:
325
+ $data[] = "Nah.";
326
+
327
+ }
328
+
329
+ wp_send_json_success( $data );
330
+
331
+ }
332
+
333
+ /**
334
+ * During the load of info for a logger we want to get a reference
335
+ * to the untranslated text too, because that's the version we want to store
336
+ * in the database.
337
+ */
338
+ public function filter_gettext( $translated_text, $untranslated_text, $domain ) {
339
+
340
+ if ( isset( $this->doFilterGettext ) && $this->doFilterGettext ) {
341
+
342
+ $this->doFilterGettext_currentLogger->messages[] = array(
343
+ "untranslated_text" => $untranslated_text,
344
+ "translated_text" => $translated_text,
345
+ "domain" => $domain,
346
+ "context" => null,
347
+ );
348
+
349
+ }
350
+
351
+ return $translated_text;
352
+
353
+ }
354
+
355
+ /**
356
+ * Store messages with context
357
+ */
358
+ public function filter_gettext_with_context( $translated_text, $untranslated_text, $context, $domain ) {
359
+
360
+ if ( isset( $this->doFilterGettext ) && $this->doFilterGettext ) {
361
+
362
+ $this->doFilterGettext_currentLogger->messages[] = array(
363
+ "untranslated_text" => $untranslated_text,
364
+ "translated_text" => $translated_text,
365
+ "domain" => $domain,
366
+ "context" => $context,
367
+ );
368
+
369
+ }
370
+
371
+ return $translated_text;
372
+
373
+ }
374
+
375
+ /**
376
+ * Load language files.
377
+ * Uses the method described here:
378
+ * http://geertdedeckere.be/article/loading-wordpress-language-files-the-right-way
379
+ *
380
+ * @since 2.0
381
+ */
382
+ public function load_plugin_textdomain() {
383
+
384
+ $domain = 'simple-history';
385
+
386
+ // The "plugin_locale" filter is also used in load_plugin_textdomain()
387
+ $locale = apply_filters('plugin_locale', get_locale(), $domain);
388
+
389
+ load_textdomain($domain, WP_LANG_DIR.'/simple-history/'.$domain.'-'.$locale.'.mo');
390
+ load_plugin_textdomain($domain, FALSE, dirname( $this->plugin_basename ).'/languages/');
391
+
392
+ }
393
+
394
+ /**
395
+ * Setup variables and things
396
+ */
397
+ public function setupVariables() {
398
+
399
+ // Capability required to view history = for who will the History page be added
400
+ $this->view_history_capability = "edit_pages";
401
+ $this->view_history_capability = apply_filters("simple_history_view_history_capability", $this->view_history_capability);
402
+ $this->view_history_capability = apply_filters("simple_history/view_history_capability", $this->view_history_capability);
403
+
404
+ // Capability required to view settings
405
+ $this->view_settings_capability = "manage_options";
406
+ $this->view_settings_capability = apply_filters("simple_history_view_settings_capability", $this->view_settings_capability);
407
+ $this->view_settings_capability = apply_filters("simple_history/view_settings_capability", $this->view_settings_capability);
408
+
409
+ $this->plugin_basename = plugin_basename(__DIR__ . "/index.php");
410
+
411
+ }
412
+
413
+ /**
414
+ * Adds default tabs to settings
415
+ */
416
+ public function add_default_settings_tabs() {
417
+
418
+ // Add default settings tabs
419
+ $this->arr_settings_tabs = array(
420
+
421
+ array(
422
+ "slug" => "settings",
423
+ "name" => __("Settings", "simple-history"),
424
+ "function" => array($this, "settings_output_general")
425
+ ),
426
+
427
+ );
428
+
429
+ if ( defined("SIMPLE_HISTORY_DEV") && SIMPLE_HISTORY_DEV ) {
430
+
431
+ $arr_dev_tabs = array(
432
+ array(
433
+ "slug" => "log",
434
+ "name" => __("Log (debug)", "simple-history"),
435
+ "function" => array($this, "settings_output_log")
436
+ ),
437
+ array(
438
+ "slug" => "styles-example",
439
+ "name" => __("Styles example (debug)", "simple-history"),
440
+ "function" => array($this, "settings_output_styles_example")
441
+ )
442
+
443
+ );
444
+
445
+ $this->arr_settings_tabs = array_merge( $this->arr_settings_tabs, $arr_dev_tabs );
446
+
447
+ }
448
+
449
+ }
450
+
451
+ /**
452
+ * Load built in loggers from all files in /loggers
453
+ * and instantiates them
454
+ */
455
+ public function loadLoggers() {
456
+
457
+ $loggersDir = __DIR__ . "/loggers/";
458
+
459
+ /**
460
+ * Filter the directory to load loggers from
461
+ *
462
+ * @since 2.0
463
+ *
464
+ * @param string $loggersDir Full directory path
465
+ */
466
+ $loggersDir = apply_filters("simple_history/loggers_dir", $loggersDir);
467
+
468
+ $loggersFiles = glob( $loggersDir . "*.php");
469
+
470
+ // SimpleLogger.php must be loaded first since the other loggers extend it
471
+ require_once($loggersDir . "SimpleLogger.php");
472
+
473
+ /**
474
+ * Filter the array with absolute paths to files as returned by glob function.
475
+ * Each file will be loaded and will be assumed to be a logger with a classname
476
+ * the same as the filename.
477
+ *
478
+ * @since 2.0
479
+ *
480
+ * @param array $loggersFiles Array with filenames
481
+ */
482
+ $loggersFiles = apply_filters("simple_history/loggers_files", $loggersFiles);
483
+
484
+ $arrLoggersToInstantiate = array();
485
+ foreach ( $loggersFiles as $oneLoggerFile) {
486
+
487
+ if ( basename($oneLoggerFile) == "SimpleLogger.php" ) {
488
+
489
+ // SimpleLogger is already loaded
490
+
491
+ } else {
492
+
493
+ include_once($oneLoggerFile);
494
+
495
+ }
496
+
497
+ $arrLoggersToInstantiate[] = basename($oneLoggerFile, ".php");
498
+
499
+ }
500
+
501
+ /**
502
+ * Filter the array with names of loggers to instantiate.
503
+ *
504
+ * @since 2.0
505
+ *
506
+ * @param array $arrLoggersToInstantiate Array with class names
507
+ */
508
+ $arrLoggersToInstantiate = apply_filters("simple_history/loggers_to_instantiate", $arrLoggersToInstantiate);
509
+ // Instantiate each logger
510
+ foreach ($arrLoggersToInstantiate as $oneLoggerName ) {
511
+
512
+ if ( ! class_exists($oneLoggerName) ) {
513
+ continue;
514
+ }
515
+
516
+ $loggerInstance = new $oneLoggerName($this);
517
+
518
+ if ( ! is_subclass_of($loggerInstance, "SimpleLogger") && ! is_a($loggerInstance, "SimpleLogger") ) {
519
+ continue;
520
+ }
521
+
522
+ $loggerInstance->loaded();
523
+
524
+ // Tell gettext-filter to add untranslated messages
525
+ $this->doFilterGettext = true;
526
+ $this->doFilterGettext_currentLogger = $loggerInstance;
527
+
528
+ $loggerInfo = $loggerInstance->getInfo();
529
+
530
+ // Un-tell gettext filter
531
+ $this->doFilterGettext = false;
532
+ $this->doFilterGettext_currentLogger = null;
533
+
534
+ // LoggerInfo contains all messages, both translated an not, by key.
535
+ // Add messages to the loggerInstance
536
+ $loopNum = 0;
537
+ foreach ( $loggerInfo["messages"] as $message_key => $message ) {
538
+
539
+ $loggerInstance->messages[ $message_key ] = $loggerInstance->messages[ $loopNum ];
540
+ $loopNum++;
541
+
542
+ }
543
+
544
+ // Remove index keys, only keeping slug keys
545
+ if (is_array($loggerInstance->messages)) {
546
+ foreach ( $loggerInstance->messages as $key => $val ) {
547
+ if ( is_int($key) ) {
548
+ unset( $loggerInstance->messages[$key] );
549
+ }
550
+ }
551
+ }
552
+
553
+ // Add logger to array of loggers
554
+ $this->instantiatedLoggers[ $loggerInstance->slug ] = array(
555
+ "name" => $loggerInfo["name"],
556
+ "instance" => $loggerInstance
557
+ );
558
+
559
+ }
560
+
561
+ do_action( "simple_history/loggers_loaded" );
562
+
563
+ #sf_d($this->instantiatedLoggers);exit;
564
+
565
+ }
566
+
567
+ /**
568
+ * Load built in dropins from all files in /dropins
569
+ * and instantiates them
570
+ */
571
+ public function loadDropins() {
572
+
573
+ $dropinsDir = __DIR__ . "/dropins/";
574
+
575
+ /**
576
+ * Filter the directory to load loggers from
577
+ *
578
+ * @since 2.0
579
+ *
580
+ * @param string $dropinsDir Full directory path
581
+ */
582
+ $dropinsDir = apply_filters("simple_history/dropins_dir", $dropinsDir);
583
+
584
+ $dropinsFiles = glob( $dropinsDir . "*.php");
585
+
586
+ /**
587
+ * Filter the array with absolute paths to files as returned by glob function.
588
+ * Each file will be loaded and will be assumed to be a dropin with a classname
589
+ * the same as the filename.
590
+ *
591
+ * @since 2.0
592
+ *
593
+ * @param array $dropinsFiles Array with filenames
594
+ */
595
+ $dropinsFiles = apply_filters("simple_history/dropins_files", $dropinsFiles);
596
+
597
+ $arrDropinsToInstantiate = array();
598
+
599
+ foreach ( $dropinsFiles as $oneDropinFile) {
600
+
601
+ include_once($oneDropinFile);
602
+
603
+ $arrDropinsToInstantiate[] = basename($oneDropinFile, ".php");
604
+
605
+ }
606
+
607
+ /**
608
+ * Filter the array with names of dropin to instantiate.
609
+ *
610
+ * @since 2.0
611
+ *
612
+ * @param array $arrDropinsToInstantiate Array with class names
613
+ */
614
+ $arrDropinsToInstantiate = apply_filters("simple_history/dropins_to_instantiate", $arrDropinsToInstantiate);
615
+
616
+ // Instantiate each dropin
617
+ foreach ($arrDropinsToInstantiate as $oneDropinName ) {
618
+
619
+ if ( ! class_exists( $oneDropinName ) ) {
620
+ continue;
621
+ }
622
+
623
+ $this->instantiatedDropins[$oneDropinName] = array(
624
+ "name" => $oneDropinName,
625
+ "instance" => new $oneDropinName( $this )
626
+ );
627
+ }
628
+
629
+ }
630
+
631
+ /**
632
+ * Gets the pager size,
633
+ * i.e. the number of items to show on each page in the history
634
+ *
635
+ * @return int
636
+ */
637
+ function get_pager_size() {
638
+
639
+ $pager_size = get_option("simple_history_pager_size", 5);
640
+
641
+ /**
642
+ * Filter the pager size setting
643
+ *
644
+ * @since 2.0
645
+ *
646
+ * @param int $pager_size
647
+ */
648
+ $pager_size = apply_filters("simple_history/pager_size", $pager_size);
649
+
650
+ return $pager_size;
651
+
652
+ }
653
+
654
+
655
+ /**
656
+ * Show a link to our settings page on the Plugins -> Installed Plugins screen
657
+ */
658
+ function plugin_action_links($actions, $b, $c, $d) {
659
+
660
+ // Only add link if user has the right to view the settings page
661
+ if ( ! current_user_can($this->view_settings_capability) ) {
662
+ return $actions;
663
+ }
664
+
665
+ $settings_page_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
666
+
667
+ $actions[] = "<a href='$settings_page_url'>" . __("Settings", "simple-history") . "</a>";
668
+
669
+ return $actions;
670
+
671
+ }
672
+
673
+ /**
674
+ * Maybe add a dashboard widget,
675
+ * requires current user to have view history capability
676
+ * and a setting to show dashboard to be set
677
+ */
678
+ function add_dashboard_widget() {
679
+
680
+ if ( $this->setting_show_on_dashboard() && current_user_can($this->view_history_capability) ) {
681
+
682
+ wp_add_dashboard_widget("simple_history_dashboard_widget", __("History", 'simple-history'), array($this, "dashboard_widget_output"));
683
+
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Output html for the dashboard widget
689
+ */
690
+ function dashboard_widget_output() {
691
+
692
+ $pager_size = $this->get_pager_size();
693
+
694
+ /**
695
+ * Filter the pager size setting for the dashboard
696
+ *
697
+ * @since 2.0
698
+ *
699
+ * @param int $pager_size
700
+ */
701
+ $pager_size = apply_filters("simple_history/dashboard_pager_size", $pager_size);
702
+
703
+ do_action( "simple_history/dashboard/before_gui", $this );
704
+
705
+ ?>
706
+ <div class="SimpleHistoryGui"
707
+ data-pager-size='<?php echo $pager_size ?>'
708
+ ></div>
709
+ <?php
710
+
711
+ }
712
+
713
+ function is_on_our_own_pages($hook = "") {
714
+
715
+ $current_screen = get_current_screen();
716
+
717
+ if ( $current_screen && $current_screen->base == "settings_page_" . SimpleHistory::SETTINGS_MENU_SLUG ) {
718
+
719
+ return true;
720
+
721
+ } else if ( $current_screen && $current_screen->base == "dashboard_page_simple_history_page" ) {
722
+
723
+ return true;
724
+
725
+ } else if ( ($hook == "settings_page_" . SimpleHistory::SETTINGS_MENU_SLUG) || ($this->setting_show_on_dashboard() && $hook == "index.php") || ($this->setting_show_as_page() && $hook == "dashboard_page_simple_history_page")) {
726
+
727
+ return true;
728
+
729
+ } else if ( $current_screen && $current_screen->base == "dashboard" && $this->setting_show_on_dashboard() ) {
730
+
731
+ return true;
732
+
733
+ }
734
+
735
+ return false;
736
+ }
737
+
738
+ /**
739
+ * Enqueue styles and scripts for Simple History but only to our own pages.
740
+ *
741
+ * Only adds scripts to pages where the log is shown or the settings page.
742
+ */
743
+ function enqueue_admin_scripts($hook) {
744
+
745
+ if ( $this->is_on_our_own_pages() ) {
746
+
747
+ add_thickbox();
748
+
749
+ $plugin_url = plugin_dir_url(__FILE__);
750
+ wp_enqueue_style( "simple_history_styles", $plugin_url . "css/styles.css", false, SimpleHistory::VERSION );
751
+ wp_enqueue_script("simple_history_script", $plugin_url . "js/scripts.js", array("jquery", "backbone"), SimpleHistory::VERSION, true);
752
+
753
+ wp_enqueue_script("select2", $plugin_url . "/js/select2/select2.min.js", array("jquery"));
754
+ wp_enqueue_style("select2", $plugin_url . "/js/select2/select2.css");
755
+
756
+ // Translations that we use in JavaScript
757
+ wp_localize_script('simple_history_script', 'simple_history_script_vars', array(
758
+ 'settingsConfirmClearLog' => __("Remove all log items?", 'simple-history'),
759
+ 'pagination' => array(
760
+ 'goToTheFirstPage' => __("Go to the first page", 'simple-history'),
761
+ 'goToThePrevPage' => __("Go to the previous page", 'simple-history'),
762
+ 'goToTheNextPage' => __("Go to the next page", 'simple-history'),
763
+ 'goToTheLastPage' => __("Go to the last page", 'simple-history'),
764
+ 'currentPage' => __("Current page", 'simple-history'),
765
+ ),
766
+ "loadLogAPIError" => __("Oups, the log could not be loaded right now.", 'simple-history'),
767
+ "logNoHits" => __("Your search did not match any history events.", "simple-history")
768
+ ));
769
+
770
+ // Call plugins adminCSS-method, so they can add their CSS
771
+ foreach ( $this->instantiatedLoggers as $one_logger ) {
772
+ if ( method_exists($one_logger["instance"], "adminCSS" ) ) {
773
+ $one_logger["instance"]->adminCSS();
774
+ }
775
+ }
776
+
777
+ /**
778
+ * Fires when the admin scripts have been enqueued.
779
+ * Only fires on any of the pages where Simple History is used
780
+ *
781
+ * @since 2.0
782
+ *
783
+ * @param SimpleHistory $SimpleHistory This class.
784
+ */
785
+ do_action("simple_history/enqueue_admin_scripts", $this);
786
+
787
+ }
788
+
789
+ }
790
+
791
+ function filter_option_page_capability($capability) {
792
+ return $capability;
793
+ }
794
+
795
+ /**
796
+ * Check if plugin version have changed, i.e. has been upgraded
797
+ * If upgrade is detected then maybe modify database and so on for that version
798
+ */
799
+ function check_for_upgrade() {
800
+
801
+ global $wpdb;
802
+
803
+ $db_version = get_option("simple_history_db_version");
804
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
805
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
806
+ $first_install = false;
807
+
808
+ // If no db_version is set then this
809
+ // is a version of Simple History < 0.4
810
+ // or it's a first install
811
+ // Fix database not using UTF-8
812
+ if ( false === $db_version ) {
813
+
814
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
815
+
816
+ // Table creation, used to be in register_activation_hook
817
+ /*
818
+ $sql = "CREATE TABLE " . $table_name . " (
819
+ id int(10) NOT NULL AUTO_INCREMENT,
820
+ date datetime NOT NULL,
821
+ action varchar(255) NOT NULL COLLATE utf8_general_ci,
822
+ object_type varchar(255) NOT NULL COLLATE utf8_general_ci,
823
+ object_subtype VARCHAR(255) NOT NULL COLLATE utf8_general_ci,
824
+ user_id int(10) NOT NULL,
825
+ object_id int(10) NOT NULL,
826
+ object_name varchar(255) NOT NULL COLLATE utf8_general_ci,
827
+ action_description longtext,
828
+ PRIMARY KEY (id)
829
+ ) CHARACTER SET=utf8;";
830
+ dbDelta($sql);
831
+ */
832
+
833
+ // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
834
+ // This table is missing action_description, but we add that later on
835
+ $sql = "CREATE TABLE " . $table_name . " (
836
+ id bigint(20) NOT NULL AUTO_INCREMENT,
837
+ date datetime NOT NULL,
838
+ action VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
839
+ object_type VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
840
+ object_subtype VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
841
+ user_id int(10) NOT NULL,
842
+ object_id int(10) NOT NULL,
843
+ object_name VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
844
+ PRIMARY KEY (id)
845
+ ) CHARACTER SET=utf8;";
846
+
847
+ // Upgrade db / fix utf for varchars
848
+ dbDelta($sql);
849
+
850
+ // Fix UTF-8 for table
851
+ $sql = sprintf('alter table %1$s charset=utf8;', $table_name);
852
+ $wpdb->query($sql);
853
+
854
+ $db_version_prev = $db_version;
855
+ $db_version = 1;
856
+
857
+ /*SimpleLogger()->debug(
858
+ "Simple History updated its database from version {from_version} to {to_version}",
859
+ array(
860
+ "from_version" => $db_version_prev,
861
+ "to_version" => $db_version
862
+ )
863
+ );*/
864
+
865
+ update_option("simple_history_db_version", $db_version);
866
+
867
+ // We are not 100% sure that this is a first install,
868
+ // but it is at least a very old version that is being updated
869
+ $first_install = true;
870
+
871
+ } // done pre db ver 1 things
872
+
873
+
874
+ // If db version is 1 then upgrade to 2
875
+ // Version 2 added the action_description column
876
+ if ( 1 == intval($db_version) ) {
877
+
878
+ // Add column for action description in non-translateable free text
879
+ $sql = "ALTER TABLE {$table_name} ADD COLUMN action_description longtext";
880
+ $wpdb->query($sql);
881
+
882
+ $db_version_prev = $db_version;
883
+ $db_version = 2;
884
+
885
+ update_option("simple_history_db_version", $db_version);
886
+
887
+ }
888
+
889
+ // Check that all options we use are set to their defaults, if they miss value
890
+ // Each option that is missing a value will make a sql call otherwise = unnecessary
891
+ $arr_options = array(
892
+ array(
893
+ "name" => "simple_history_show_as_page",
894
+ "default_value" => 1
895
+ ),
896
+ array(
897
+ "name" => "simple_history_show_on_dashboard",
898
+ "default_value" => 1
899
+ )
900
+ );
901
+
902
+ foreach ($arr_options as $one_option) {
903
+
904
+ if ( false === ($option_value = get_option( $one_option["name"] ) ) ) {
905
+
906
+ // Value is not set in db, so set it to a default
907
+ update_option( $one_option["name"], $one_option["default_value"] );
908
+
909
+ }
910
+ }
911
+
912
+ /**
913
+ * If db_version is 2 then upgrade to 3:
914
+ * - Add some fields to existing table wp_simple_history_contexts
915
+ * - Add all new table wp_simple_history_contexts
916
+ *
917
+ * @since 2.0
918
+ */
919
+ if ( 2 == intval($db_version) ) {
920
+
921
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
922
+
923
+ // Update old table
924
+ $sql = "
925
+ CREATE TABLE {$table_name} (
926
+ id bigint(20) NOT NULL AUTO_INCREMENT,
927
+ date datetime NOT NULL,
928
+ logger varchar(30) DEFAULT NULL,
929
+ level varchar(20) DEFAULT NULL,
930
+ message varchar(255) DEFAULT NULL,
931
+ occasionsID varchar(32) DEFAULT NULL,
932
+ type varchar(16) DEFAULT NULL,
933
+ initiator varchar(16) DEFAULT NULL,
934
+ action varchar(255) NOT NULL,
935
+ object_type varchar(255) NOT NULL,
936
+ object_subtype varchar(255) NOT NULL,
937
+ user_id int(10) NOT NULL,
938
+ object_id int(10) NOT NULL,
939
+ object_name varchar(255) NOT NULL,
940
+ action_description longtext,
941
+ PRIMARY KEY (id),
942
+ KEY date (date),
943
+ KEY loggerdate (logger, date)
944
+ ) CHARSET=utf8;";
945
+
946
+ dbDelta($sql);
947
+
948
+ // Add context table
949
+ $sql = "
950
+ CREATE TABLE {$table_name_contexts} (
951
+ context_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
952
+ history_id bigint(20) unsigned NOT NULL,
953
+ `key` varchar(255) DEFAULT NULL,
954
+ value longtext,
955
+ PRIMARY KEY (context_id),
956
+ KEY history_id (history_id),
957
+ KEY `key` (`key`)
958
+ ) CHARSET=utf8;
959
+ ";
960
+
961
+ $wpdb->query($sql);
962
+
963
+ $db_version_prev = $db_version;
964
+ $db_version = 3;
965
+ update_option("simple_history_db_version", $db_version);
966
+
967
+ // Update old items to use SimpleLegacyLogger
968
+ $sql = sprintf('
969
+ UPDATE %1$s
970
+ SET
971
+ logger = "SimpleLegacyLogger",
972
+ level = "info"
973
+ WHERE logger IS NULL
974
+ ',
975
+ $table_name
976
+ );
977
+
978
+ $wpdb->query( $sql );
979
+
980
+ // Say welcome, however loggers are not added this early so we need to
981
+ // use a filter to load it later
982
+ add_action("simple_history/loggers_loaded", array( $this, "addWelcomeLogMessage" ));
983
+
984
+ } // db version 2 » 3
985
+
986
+ /**
987
+ * If db version = 3
988
+ * then we need to update database to allow null values for some old columns
989
+ * that used to work in pre wp 4.1 beta, but since 4.1 wp uses STRICT_ALL_TABLES
990
+ * WordPress Commit: https://github.com/WordPress/WordPress/commit/f17d168a0f72211a9bfd9d3fa680713069871bb6
991
+ *
992
+ * @since 2.0
993
+ */
994
+ if ( 3 == intval($db_version) ) {
995
+
996
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
997
+
998
+ $sql = sprintf('
999
+ ALTER TABLE %1$s
1000
+ MODIFY `action` varchar(255) NULL,
1001
+ MODIFY `object_type` varchar(255) NULL,
1002
+ MODIFY `object_subtype` varchar(255) NULL,
1003
+ MODIFY `user_id` int(10) NULL,
1004
+ MODIFY `object_id` int(10) NULL,
1005
+ MODIFY `object_name` varchar(255) NULL
1006
+ ',
1007
+ $table_name
1008
+ );
1009
+ $wpdb->query( $sql );
1010
+
1011
+ $db_version_prev = $db_version;
1012
+ $db_version = 4;
1013
+
1014
+ update_option("simple_history_db_version", $db_version);
1015
+
1016
+ } // end db version 3 » 4
1017
+
1018
+
1019
+ } // end check_for_upgrade
1020
+
1021
+ /**
1022
+ * Greet users to version 2!
1023
+ */
1024
+ public function addWelcomeLogMessage() {
1025
+
1026
+ SimpleLogger()->info(
1027
+ "Welcome to Simple History 2! Hope you will enjoy this plugin.
1028
+ Found bugs? Got great ideas? Send them to the plugin developer at par.thernstrom@gmail.com.",
1029
+ array(
1030
+ "_initiator" => SimpleLoggerLogInitiators::WORDPRESS
1031
+ )
1032
+ );
1033
+
1034
+ }
1035
+
1036
+ public function registerSettingsTab($arr_tab_settings) {
1037
+
1038
+ $this->arr_settings_tabs[] = $arr_tab_settings;
1039
+
1040
+ }
1041
+
1042
+ public function getSettingsTabs() {
1043
+
1044
+ return $this->arr_settings_tabs;
1045
+
1046
+ }
1047
+
1048
+ /**
1049
+ * Output HTML for the settings page
1050
+ * Called from add_options_page
1051
+ */
1052
+ function settings_page_output() {
1053
+
1054
+ $arr_settings_tabs = $this->getSettingsTabs();
1055
+
1056
+ ?>
1057
+ <div class="wrap">
1058
+
1059
+ <h2 class="SimpleHistoryPageHeadline">
1060
+ <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1061
+ <?php _e("Simple History Settings", "simple-history") ?>
1062
+ </h2>
1063
+
1064
+ <?php
1065
+ $active_tab = isset( $_GET["selected-tab"] ) ? $_GET["selected-tab"] : "settings";
1066
+ $settings_base_url = menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0);
1067
+ ?>
1068
+
1069
+ <h3 class="nav-tab-wrapper">
1070
+ <?php
1071
+ foreach ( $arr_settings_tabs as $one_tab ) {
1072
+
1073
+ $tab_slug = $one_tab["slug"];
1074
+
1075
+ printf(
1076
+ '<a href="%3$s" class="nav-tab %4$s">%1$s</a>',
1077
+ $one_tab["name"], // 1
1078
+ $tab_slug, // 2
1079
+ add_query_arg("selected-tab", $tab_slug, $settings_base_url), // 3
1080
+ $active_tab == $tab_slug ? "nav-tab-active" : "" // 4
1081
+ );
1082
+
1083
+ }
1084
+ ?>
1085
+ </h3>
1086
+
1087
+ <?php
1088
+
1089
+ // Output contents for selected tab
1090
+ $arr_active_tab = wp_filter_object_list( $arr_settings_tabs, array("slug" => $active_tab));
1091
+ $arr_active_tab = current($arr_active_tab);
1092
+
1093
+ // We must have found an active tab and it must have a callable function
1094
+ if ( ! $arr_active_tab || ! is_callable( $arr_active_tab["function"] ) ) {
1095
+ wp_die( __("No valid callback found", "simple-history") );
1096
+ }
1097
+
1098
+ $args = array(
1099
+ "arr_active_tab" => $arr_active_tab
1100
+ );
1101
+
1102
+ call_user_func_array( $arr_active_tab["function"], $args );
1103
+
1104
+ ?>
1105
+
1106
+ </div>
1107
+ <?php
1108
+
1109
+ }
1110
+
1111
+ public function settings_output_log() {
1112
+
1113
+ include( __DIR__ . "/templates/settings-log.php" );
1114
+
1115
+ }
1116
+
1117
+ public function settings_output_general() {
1118
+
1119
+ include( __DIR__ . "/templates/settings-general.php" );
1120
+
1121
+ }
1122
+
1123
+ public function settings_output_styles_example() {
1124
+
1125
+ include( __DIR__ . "/templates/settings-style-example.php" );
1126
+
1127
+ }
1128
+
1129
+
1130
+ /**
1131
+ * Content for section intro. Leave it be, even if empty.
1132
+ * Called from add_sections_setting.
1133
+ */
1134
+ function settings_section_output() {
1135
+
1136
+ }
1137
+
1138
+
1139
+ /**
1140
+ * Add pages (history page and settings page)
1141
+ */
1142
+ function add_admin_pages() {
1143
+
1144
+ // Add a history page as a sub-page below the Dashboard menu item
1145
+ if ( $this->setting_show_as_page() ) {
1146
+
1147
+ add_dashboard_page(
1148
+ SimpleHistory::NAME,
1149
+ _x("Simple History", 'dashboard menu name', 'simple-history'),
1150
+ $this->view_history_capability,
1151
+ "simple_history_page",
1152
+ array($this, "history_page_output")
1153
+ );
1154
+
1155
+ }
1156
+
1157
+ // Add a settings page
1158
+ $show_settings_page = true;
1159
+ $show_settings_page = apply_filters("simple_history_show_settings_page", $show_settings_page);
1160
+ $show_settings_page = apply_filters("simple_history/show_settings_page", $show_settings_page);
1161
+ if ($show_settings_page) {
1162
+
1163
+ add_options_page(
1164
+ __('Simple History Settings', "simple-history"),
1165
+ SimpleHistory::NAME,
1166
+ $this->view_settings_capability,
1167
+ SimpleHistory::SETTINGS_MENU_SLUG,
1168
+ array($this, 'settings_page_output')
1169
+ );
1170
+
1171
+ }
1172
+
1173
+ }
1174
+
1175
+ /*
1176
+ * Add setting sections and settings for the settings page
1177
+ * Also maybe save some settings before outputing them
1178
+ */
1179
+ function add_settings() {
1180
+
1181
+ // Clear the log if clear button was clicked in settings
1182
+ if ( isset( $_GET["simple_history_clear_log_nonce"] ) && wp_verify_nonce( $_GET["simple_history_clear_log_nonce"], 'simple_history_clear_log')) {
1183
+
1184
+ $this->clear_log();
1185
+ $msg = __("Cleared database", 'simple-history');
1186
+ add_settings_error( "simple_history_rss_feed_regenerate_secret", "simple_history_rss_feed_regenerate_secret", $msg, "updated" );
1187
+ set_transient('settings_errors', get_settings_errors(), 30);
1188
+
1189
+ $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
1190
+ wp_redirect( $goback );
1191
+ exit;
1192
+
1193
+ }
1194
+
1195
+ // Section for general options
1196
+ // Will contain settings like where to show simple history and number of items
1197
+ $settings_section_general_id = self::SETTINGS_SECTION_GENERAL_ID;
1198
+ add_settings_section(
1199
+ $settings_section_general_id,
1200
+ "", // No title __("General", "simple-history"),
1201
+ array($this, "settings_section_output"),
1202
+ SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
1203
+ );
1204
+
1205
+ // Settings for the general settings section
1206
+ // Each setting = one row in the settings section
1207
+ // add_settings_field( $id, $title, $callback, $page, $section, $args );
1208
+
1209
+ // Checkboxes for where to show simple history
1210
+ add_settings_field(
1211
+ "simple_history_show_where",
1212
+ __("Show history", "simple-history"),
1213
+ array($this, "settings_field_where_to_show"),
1214
+ SimpleHistory::SETTINGS_MENU_SLUG,
1215
+ $settings_section_general_id
1216
+ );
1217
+
1218
+ // Nonces for show where inputs
1219
+ register_setting("simple_history_settings_group", "simple_history_show_on_dashboard");
1220
+ register_setting("simple_history_settings_group", "simple_history_show_as_page");
1221
+
1222
+ // Dropdown number if items to show
1223
+ add_settings_field(
1224
+ "simple_history_number_of_items",
1225
+ __("Number of items per page", "simple-history"),
1226
+ array($this, "settings_field_number_of_items"),
1227
+ SimpleHistory::SETTINGS_MENU_SLUG,
1228
+ $settings_section_general_id
1229
+ );
1230
+
1231
+ // Nonces for number of items inputs
1232
+ register_setting("simple_history_settings_group", "simple_history_pager_size");
1233
+
1234
+ // Link to clear log
1235
+ add_settings_field(
1236
+ "simple_history_clear_log",
1237
+ __("Clear log", "simple-history"),
1238
+ array($this, "settings_field_clear_log"),
1239
+ SimpleHistory::SETTINGS_MENU_SLUG,
1240
+ $settings_section_general_id
1241
+ );
1242
+
1243
+ }
1244
+
1245
+
1246
+ /**
1247
+ * Output for page with the history
1248
+ */
1249
+ function history_page_output() {
1250
+
1251
+ //global $simple_history;
1252
+
1253
+ //$this->purge_db();
1254
+
1255
+ global $wpdb;
1256
+
1257
+ $pager_size = $this->get_pager_size();
1258
+
1259
+ /**
1260
+ * Filter the pager size setting for the history page
1261
+ *
1262
+ * @since 2.0
1263
+ *
1264
+ * @param int $pager_size
1265
+ */
1266
+ $pager_size = apply_filters("simple_history/page_pager_size", $pager_size);
1267
+
1268
+ ?>
1269
+
1270
+ <div class="wrap SimpleHistoryWrap">
1271
+
1272
+ <h2 class="SimpleHistoryPageHeadline">
1273
+ <div class="dashicons dashicons-backup SimpleHistoryPageHeadline__icon"></div>
1274
+ <?php echo _x("Simple History", 'history page headline', 'simple-history') ?>
1275
+ </h2>
1276
+
1277
+ <?php
1278
+ /**
1279
+ * Fires before the gui div
1280
+ *
1281
+ * @since 2.0
1282
+ *
1283
+ * @param SimpleHistory $SimpleHistory This class.
1284
+ */
1285
+ do_action( "simple_history/history_page/before_gui", $this );
1286
+ ?>
1287
+
1288
+ <div class="SimpleHistoryGuiWrap">
1289
+
1290
+ <div class="SimpleHistoryGui"
1291
+ data-pager-size='<?php echo $pager_size ?>'
1292
+ ></div>
1293
+
1294
+ <?php
1295
+
1296
+ /**
1297
+ * Fires after the gui div
1298
+ *
1299
+ * @since 2.0
1300
+ *
1301
+ * @param SimpleHistory $SimpleHistory This class.
1302
+ */
1303
+ do_action( "simple_history/history_page/after_gui", $this );
1304
+
1305
+ ?>
1306
+
1307
+ </div>
1308
+
1309
+ </div>
1310
+
1311
+ <?php
1312
+
1313
+ }
1314
+
1315
+ /**
1316
+ * Get history from ajax
1317
+ */
1318
+ /*
1319
+ function ajax() {
1320
+
1321
+ global $simple_history;
1322
+
1323
+ $type = isset($_POST["type"]) ? $_POST["type"] : "";
1324
+ $subtype = isset($_POST["subtype"]) ? $_POST["subtype"] : "";
1325
+
1326
+ // We get users by username, so get username from id
1327
+ $user_id = (int) $_POST["user_id"];
1328
+ if (empty($user_id)) {
1329
+ $user = "";
1330
+ } else {
1331
+ $user_obj = new WP_User($user_id);
1332
+ if ( ! $user_obj->exists() ) exit;
1333
+ $user = $user_obj->user_login;
1334
+ };
1335
+
1336
+ // page to show. 1 = first page.
1337
+ $page = 0;
1338
+ if (isset($_POST["page"])) {
1339
+ $page = (int) $_POST["page"];
1340
+ }
1341
+
1342
+ // number of items to get
1343
+ $items = (int) (isset($_POST["items"])) ? $_POST["items"] : $simple_history->get_pager_size();
1344
+
1345
+ // number of prev added items = number of items to skip before starting to add $items num of new items
1346
+ $num_added = (int) (isset($_POST["num_added"])) ? $_POST["num_added"] : $simple_history->get_pager_size();
1347
+
1348
+ $search = (isset($_POST["search"])) ? $_POST["search"] : "";
1349
+
1350
+ $filter_type = $type . "/" . $subtype;
1351
+
1352
+ $args = array(
1353
+ "is_ajax" => true,
1354
+ "filter_type" => $filter_type,
1355
+ "filter_user" => $user,
1356
+ "page" => $page,
1357
+ "items" => $items,
1358
+ "num_added" => $num_added,
1359
+ "search" => $search
1360
+ );
1361
+
1362
+ $arr_json = array(
1363
+ "status" => "ok",
1364
+ "error" => "",
1365
+ "items_li" => "",
1366
+ "filtered_items_total_count" => 0,
1367
+ "filtered_items_total_count_string" => "",
1368
+ "filtered_items_total_pages" => 0
1369
+ );
1370
+
1371
+ // ob_start();
1372
+ $return = simple_history_print_history($args);
1373
+ // $return = ob_get_clean();
1374
+ if ("noMoreItems" == $return) {
1375
+ $arr_json["status"] = "error";
1376
+ $arr_json["error"] = "noMoreItems";
1377
+ } else {
1378
+ $arr_json["items_li"] = $return;
1379
+ // total number of event. really bad way since we get them all again. need to fix this :/
1380
+ $args["items"] = "all";
1381
+ $all_items = simple_history_get_items_array($args);
1382
+ $arr_json["filtered_items_total_count"] = sizeof($all_items);
1383
+ $arr_json["filtered_items_total_count_string"] = sprintf(_n('One item', '%1$d items', sizeof($all_items), "simple-history"), sizeof($all_items));
1384
+ $arr_json["filtered_items_total_pages"] = ceil($arr_json["filtered_items_total_count"] / $simple_history->get_pager_size());
1385
+ }
1386
+
1387
+ header("Content-type: application/json");
1388
+ echo json_encode($arr_json);
1389
+
1390
+ exit;
1391
+
1392
+ }
1393
+ */
1394
+
1395
+ /**
1396
+ * Get setting if plugin should be visible on dasboard.
1397
+ * Defaults to true
1398
+ *
1399
+ * @return bool
1400
+ */
1401
+ function setting_show_on_dashboard() {
1402
+
1403
+ $show_on_dashboard = get_option("simple_history_show_on_dashboard", 1);
1404
+ $show_on_dashboard = apply_filters("simple_history_show_on_dashboard", $show_on_dashboard);
1405
+ return (bool) $show_on_dashboard;
1406
+
1407
+ }
1408
+
1409
+ /**
1410
+ * Should simple history be shown as a page
1411
+ * Defaults to true
1412
+ *
1413
+ * @return bool
1414
+ */
1415
+ function setting_show_as_page() {
1416
+
1417
+ $setting = get_option("simple_history_show_as_page", 1);
1418
+ $setting = apply_filters("simple_history_show_as_page", $setting);
1419
+ return (bool) $setting;
1420
+
1421
+ }
1422
+
1423
+ /**
1424
+ * Settings field for how many rows/items to show in log
1425
+ */
1426
+ function settings_field_number_of_items() {
1427
+
1428
+ $current_pager_size = $this->get_pager_size();
1429
+
1430
+ ?>
1431
+ <select name="simple_history_pager_size">
1432
+ <option <?php echo $current_pager_size == 5 ? "selected" : "" ?> value="5">5</option>
1433
+ <option <?php echo $current_pager_size == 10 ? "selected" : "" ?> value="10">10</option>
1434
+ <option <?php echo $current_pager_size == 15 ? "selected" : "" ?> value="15">15</option>
1435
+ <option <?php echo $current_pager_size == 20 ? "selected" : "" ?> value="20">20</option>
1436
+ <option <?php echo $current_pager_size == 25 ? "selected" : "" ?> value="25">25</option>
1437
+ <option <?php echo $current_pager_size == 30 ? "selected" : "" ?> value="30">30</option>
1438
+ <option <?php echo $current_pager_size == 40 ? "selected" : "" ?> value="40">40</option>
1439
+ <option <?php echo $current_pager_size == 50 ? "selected" : "" ?> value="50">50</option>
1440
+ <option <?php echo $current_pager_size == 75 ? "selected" : "" ?> value="75">75</option>
1441
+ <option <?php echo $current_pager_size == 100 ? "selected" : "" ?> value="100">100</option>
1442
+ </select>
1443
+ <?php
1444
+
1445
+ }
1446
+
1447
+ /**
1448
+ * Settings field for where to show the log, page or dashboard
1449
+ */
1450
+ function settings_field_where_to_show() {
1451
+
1452
+ $show_on_dashboard = $this->setting_show_on_dashboard();
1453
+ $show_as_page = $this->setting_show_as_page();
1454
+ ?>
1455
+
1456
+ <input <?php echo $show_on_dashboard ? "checked='checked'" : "" ?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
1457
+ <label for="simple_history_show_on_dashboard"><?php _e("on the dashboard", 'simple-history') ?></label>
1458
+
1459
+ <br />
1460
+
1461
+ <input <?php echo $show_as_page ? "checked='checked'" : "" ?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
1462
+ <label for="simple_history_show_as_page"><?php _e("as a page under the dashboard menu", 'simple-history') ?></label>
1463
+
1464
+ <?php
1465
+ }
1466
+
1467
+ /**
1468
+ * Settings section to clear database
1469
+ */
1470
+ function settings_field_clear_log() {
1471
+
1472
+ $clear_link = add_query_arg("", "");
1473
+ $clear_link = wp_nonce_url( $clear_link, "simple_history_clear_log", "simple_history_clear_log_nonce" );
1474
+ $clear_days = $this->get_clear_history_interval();
1475
+
1476
+ echo "<p>";
1477
+ if ( $clear_days > 0 ) {
1478
+ echo sprintf( __('Items in the database are automatically removed after %1$s days.', "simple-history"), $clear_days);
1479
+ } else {
1480
+ _e( 'Items in the database are kept forever.', 'simple-history');
1481
+ }
1482
+ echo "</p>";
1483
+
1484
+ printf('<p><a class="button js-SimpleHistory-Settings-ClearLog" href="%2$s">%1$s</a></p>', __('Clear log now', 'simple-history'), $clear_link);
1485
+ }
1486
+
1487
+ /**
1488
+ * How old log entried are allowed to be.
1489
+ * 0 = don't delete old entries.
1490
+ *
1491
+ * @return int Number of days.
1492
+ */
1493
+ function get_clear_history_interval() {
1494
+
1495
+ $days = 60;
1496
+
1497
+ $days = (int) apply_filters("simple_history_db_purge_days_interval", $days);
1498
+ $days = (int) apply_filters("simple_history/db_purge_days_interval", $days);
1499
+
1500
+ return $days;
1501
+
1502
+ }
1503
+
1504
+ /**
1505
+ * Removes all items from the log
1506
+ */
1507
+ function clear_log() {
1508
+
1509
+ global $wpdb;
1510
+
1511
+ $tableprefix = $wpdb->prefix;
1512
+ $simple_history_table = SimpleHistory::DBTABLE;
1513
+ $simple_history_context_table = SimpleHistory::DBTABLE_CONTEXTS;
1514
+
1515
+ $sql = "DELETE FROM {$tableprefix}{$simple_history_table}";
1516
+ $wpdb->query($sql);
1517
+
1518
+ $sql = "DELETE FROM {$tableprefix}{$simple_history_context_table}";
1519
+ $wpdb->query($sql);
1520
+
1521
+ }
1522
+
1523
+ /**
1524
+ * Removes old entries from the db
1525
+ * @TODO this function does not remove old entries from context table
1526
+ */
1527
+ function purge_db() {
1528
+
1529
+ $do_purge_history = true;
1530
+ $do_purge_history = apply_filters("simple_history_allow_db_purge", $do_purge_history);
1531
+ $do_purge_history = apply_filters("simple_history/allow_db_purge", $do_purge_history);
1532
+ if ( ! $do_purge_history ) {
1533
+ return;
1534
+ }
1535
+
1536
+ $days = $this->get_clear_history_interval();
1537
+
1538
+ // Never clear log if days = 0
1539
+ if (0 == $days) {
1540
+ return;
1541
+ }
1542
+
1543
+ global $wpdb;
1544
+ $tableprefix = $wpdb->prefix;
1545
+ $simple_history_table = SimpleHistory::DBTABLE;
1546
+
1547
+ $sql = "DELETE FROM {$tableprefix}{$simple_history_table} WHERE DATE_ADD(date, INTERVAL $days DAY) < now()";
1548
+
1549
+ $wpdb->query($sql);
1550
+
1551
+ }
1552
+
1553
+ /**
1554
+ * Return plain text output for a log row
1555
+ * Uses the getLogRowPlainTextOutput of the logger that logged the row
1556
+ * with fallback to SimpleLogger if logger is not available
1557
+ *
1558
+ * @param array $row
1559
+ * @return string
1560
+ */
1561
+ public function getLogRowPlainTextOutput($row) {
1562
+
1563
+ $row_logger = $row->logger;
1564
+ $logger = null;
1565
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
1566
+
1567
+ if ( ! isset( $row->context["_message_key"] ) ) {
1568
+ $row->context["_message_key"] = null;
1569
+ }
1570
+
1571
+ // Fallback to SimpleLogger if no logger exists for row
1572
+ if ( ! isset( $this->instantiatedLoggers[$row_logger] ) ) {
1573
+ $row_logger = "SimpleLogger";
1574
+ }
1575
+
1576
+ $logger = $this->instantiatedLoggers[ $row_logger ]["instance"];
1577
+
1578
+ return $logger->getLogRowPlainTextOutput( $row );
1579
+
1580
+ }
1581
+
1582
+ /**
1583
+ * Return header output for a log row
1584
+ * Uses the getLogRowHeaderOutput of the logger that logged the row
1585
+ * with fallback to SimpleLogger if logger is not available
1586
+ *
1587
+ * Loggers are discouraged to override this in the loggers,
1588
+ * because the output should be the same for all items in the gui
1589
+ *
1590
+ * @param array $row
1591
+ * @return string
1592
+ */
1593
+ public function getLogRowHeaderOutput($row) {
1594
+
1595
+ $row_logger = $row->logger;
1596
+ $logger = null;
1597
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
1598
+
1599
+ // Fallback to SimpleLogger if no logger exists for row
1600
+ if ( ! isset( $this->instantiatedLoggers[$row_logger] ) ) {
1601
+ $row_logger = "SimpleLogger";
1602
+ }
1603
+
1604
+ $logger = $this->instantiatedLoggers[$row_logger]["instance"];
1605
+
1606
+ return $logger->getLogRowHeaderOutput( $row );
1607
+
1608
+ }
1609
+
1610
+ /**
1611
+ *
1612
+ *
1613
+ * @param array $row
1614
+ * @return string
1615
+ */
1616
+ private function getLogRowSenderImageOutput($row) {
1617
+
1618
+ $row_logger = $row->logger;
1619
+ $logger = null;
1620
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
1621
+
1622
+ // Fallback to SimpleLogger if no logger exists for row
1623
+ if ( ! isset( $this->instantiatedLoggers[$row_logger] ) ) {
1624
+ $row_logger = "SimpleLogger";
1625
+ }
1626
+
1627
+ $logger = $this->instantiatedLoggers[$row_logger]["instance"];
1628
+
1629
+ return $logger->getLogRowSenderImageOutput( $row );
1630
+
1631
+ }
1632
+
1633
+ public function getLogRowDetailsOutput($row) {
1634
+
1635
+ $row_logger = $row->logger;
1636
+ $logger = null;
1637
+ $row->context = isset( $row->context ) && is_array( $row->context ) ? $row->context : array();
1638
+
1639
+ // Fallback to SimpleLogger if no logger exists for row
1640
+ if ( ! isset( $this->instantiatedLoggers[$row_logger] ) ) {
1641
+ $row_logger = "SimpleLogger";
1642
+ }
1643
+
1644
+ $logger = $this->instantiatedLoggers[$row_logger]["instance"];
1645
+
1646
+ return $logger->getLogRowDetailsOutput( $row );
1647
+
1648
+ }
1649
+
1650
+ /**
1651
+ * Works like json_encode, but adds JSON_PRETTY_PRINT if the current php version supports it
1652
+ * i.e. PHP is 5.4.0 or greated
1653
+ *
1654
+ * @param $value array|object|string|whatever that is json_encode'able
1655
+ */
1656
+ public static function json_encode($value) {
1657
+
1658
+ return version_compare(PHP_VERSION, '5.4.0') >=0 ? json_encode($value, JSON_PRETTY_PRINT) : json_encode($value);
1659
+
1660
+ }
1661
+
1662
+ /**
1663
+ * Returns true if $haystack ends with $needle
1664
+ * @param string $haystack
1665
+ * @param string $needle
1666
+ */
1667
+ public static function ends_with( $haystack, $needle ) {
1668
+ return $needle === substr( $haystack, -strlen( $needle ) );
1669
+ }
1670
+
1671
+ /**
1672
+ * Returns the HTML output for a log row, to be used in the GUI/Activity Feed
1673
+ *
1674
+ * @param array $oneLogRow SimpleHistoryLogQuery array with data from SimpleHistoryLogQuery
1675
+ * @return string
1676
+ */
1677
+ public function getLogRowHTMLOutput($oneLogRow, $args) {
1678
+
1679
+ $defaults = array(
1680
+ "type" => "overview" // or "single" to include more stuff
1681
+ );
1682
+
1683
+ $args = wp_parse_args( $args, $defaults );
1684
+
1685
+ $header_html = $this->getLogRowHeaderOutput($oneLogRow);
1686
+ $plain_text_html = $this->getLogRowPlainTextOutput($oneLogRow);
1687
+ $sender_image_html = $this->getLogRowSenderImageOutput($oneLogRow);
1688
+
1689
+ // Details = for example thumbnail of media
1690
+ $details_html = trim( $this->getLogRowDetailsOutput($oneLogRow) );
1691
+ if ($details_html) {
1692
+
1693
+ $details_html = sprintf(
1694
+ '<div class="SimpleHistoryLogitem__details">%1$s</div>',
1695
+ $details_html
1696
+ );
1697
+
1698
+ }
1699
+
1700
+ // subsequentOccasions = including the current one
1701
+ $occasions_count = $oneLogRow->subsequentOccasions - 1;
1702
+ $occasions_html = "";
1703
+ if ($occasions_count > 0) {
1704
+
1705
+ $occasions_html = '<div class="SimpleHistoryLogitem__occasions">';
1706
+
1707
+ $occasions_html .= '<a href="#" class="SimpleHistoryLogitem__occasionsLink">';
1708
+ $occasions_html .= sprintf(
1709
+ __('+%1$s more', "simple-history"),
1710
+ $occasions_count
1711
+ );
1712
+ $occasions_html .= '</a>';
1713
+
1714
+ $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoading">';
1715
+ $occasions_html .= sprintf(
1716
+ __('Loading…', "simple-history"),
1717
+ $occasions_count
1718
+ );
1719
+ $occasions_html .= '</span>';
1720
+
1721
+ $occasions_html .= '<span class="SimpleHistoryLogitem__occasionsLoaded">';
1722
+ $occasions_html .= sprintf(
1723
+ __('Showing %1$s more', "simple-history"),
1724
+ $occasions_count
1725
+ );
1726
+ $occasions_html .= '</span>';
1727
+
1728
+ $occasions_html .= '</div>';
1729
+
1730
+ }
1731
+
1732
+ $data_attrs = "";
1733
+ $data_attrs .= sprintf(' data-row-id="%1$d" ', $oneLogRow->id );
1734
+ $data_attrs .= sprintf(' data-occasions-count="%1$d" ', $occasions_count );
1735
+ $data_attrs .= sprintf(' data-occasions-id="%1$s" ', $oneLogRow->occasionsID );
1736
+ $data_attrs .= sprintf(' data-ip-address="%1$s" ', esc_attr( $oneLogRow->context["_server_remote_addr"] ) );
1737
+
1738
+ // If type is single then include more details
1739
+ $more_details_html = "";
1740
+ if ( $args["type"] == "single" ) {
1741
+
1742
+ $more_details_html .= sprintf('<h2 class="SimpleHistoryLogitem__moreDetailsHeadline">%1$s</h2>', __("Context data", "simple-history"));
1743
+ $more_details_html .= "<p>" . __("This is potentially useful meta data that a logger have saved.", "simple-history") . "</p>";
1744
+ $more_details_html .= "<table class='SimpleHistoryLogitem__moreDetailsContext'>";
1745
+ $more_details_html .= sprintf(
1746
+ '<tr>
1747
+ <th>%1$s</th>
1748
+ <th>%2$s</th>
1749
+ </tr>',
1750
+ "Key",
1751
+ "Value"
1752
+ );
1753
+
1754
+ foreach ($oneLogRow as $rowKey => $rowVal) {
1755
+
1756
+ // skip arrays and objects and such
1757
+ if ( is_array( $rowVal ) || is_object( $rowVal ) ) {
1758
+ continue;
1759
+ }
1760
+
1761
+ $more_details_html .= sprintf(
1762
+ '<tr>
1763
+ <td>%1$s</td>
1764
+ <td>%2$s</td>
1765
+ </tr>',
1766
+ esc_html( $rowKey ),
1767
+ esc_html( $rowVal )
1768
+ );
1769
+
1770
+ }
1771
+
1772
+ foreach ($oneLogRow->context as $contextKey => $contextVal) {
1773
+
1774
+ $more_details_html .= sprintf(
1775
+ '<tr>
1776
+ <td>%1$s</td>
1777
+ <td>%2$s</td>
1778
+ </tr>',
1779
+ esc_html( $contextKey ),
1780
+ esc_html( $contextVal )
1781
+ );
1782
+
1783
+ }
1784
+
1785
+ $more_details_html .= "</table>";
1786
+
1787
+ $more_details_html = sprintf(
1788
+ '<div class="SimpleHistoryLogitem__moreDetails">%1$s</div>',
1789
+ $more_details_html
1790
+ );
1791
+
1792
+ }
1793
+
1794
+ $class_sender = "";
1795
+ if (isset($oneLogRow->initiator) && !empty($oneLogRow->initiator)) {
1796
+ $class_sender .= "SimpleHistoryLogitem--initiator-" . esc_attr($oneLogRow->initiator);
1797
+ }
1798
+
1799
+ /*$level_html = sprintf(
1800
+ '<span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%1$s</span>',
1801
+ $row->level
1802
+ );*/
1803
+
1804
+ // Always append the log level tag
1805
+ $log_level_tag_html = sprintf(
1806
+ ' <span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%1$s</span>',
1807
+ $oneLogRow->level
1808
+ );
1809
+
1810
+ $plain_text_html .= $log_level_tag_html;
1811
+
1812
+ // Generate the HTML output for a row
1813
+ $output = sprintf(
1814
+ '
1815
+ <li %8$s class="SimpleHistoryLogitem SimpleHistoryLogitem--loglevel-%5$s SimpleHistoryLogitem--logger-%7$s %10$s">
1816
+ <div class="SimpleHistoryLogitem__firstcol">
1817
+ <div class="SimpleHistoryLogitem__senderImage">%3$s</div>
1818
+ </div>
1819
+ <div class="SimpleHistoryLogitem__secondcol">
1820
+ <div class="SimpleHistoryLogitem__header">%1$s</div>
1821
+ <div class="SimpleHistoryLogitem__text">%2$s</div>
1822
+ %6$s <!-- details_html -->
1823
+ %9$s <!-- more details html -->
1824
+ %4$s <!-- occasions -->
1825
+ </div>
1826
+ </li>
1827
+ ',
1828
+ $header_html, // 1
1829
+ $plain_text_html, // 2
1830
+ $sender_image_html, // 3
1831
+ $occasions_html, // 4
1832
+ $oneLogRow->level, // 5
1833
+ $details_html, // 6
1834
+ $oneLogRow->logger, // 7
1835
+ $data_attrs, // 8 data attributes
1836
+ $more_details_html, // 9
1837
+ $class_sender // 10
1838
+ );
1839
+
1840
+ // Get the main message row.
1841
+ // Should be as plain as possible, like plain text
1842
+ // but with links to for example users and posts
1843
+ #SimpleLoggerFormatter::getRowTextOutput($oneLogRow);
1844
+
1845
+ // Get detailed HTML-based output
1846
+ // May include images, lists, any cool stuff needed to view
1847
+ #SimpleLoggerFormatter::getRowHTMLOutput($oneLogRow);
1848
+
1849
+ return trim($output);
1850
+
1851
+ }
1852
+
1853
+ public function getInstantiatedLoggers() {
1854
+
1855
+ return $this->instantiatedLoggers;
1856
+
1857
+ }
1858
+
1859
+ public function getInstantiatedLoggerBySlug($slug = "") {
1860
+
1861
+ if (empty( $slug )) {
1862
+ return false;
1863
+ }
1864
+
1865
+ foreach ($this->getInstantiatedLoggers() as $one_logger) {
1866
+
1867
+ if ( $slug == $one_logger["instance"]->slug ) {
1868
+ return $one_logger["instance"];
1869
+ }
1870
+
1871
+ }
1872
+
1873
+ return false;
1874
+
1875
+ }
1876
+
1877
+ /**
1878
+ * Check which loggers a user has the right to read and return an array
1879
+ * with all loggers they are allowed to read
1880
+ *
1881
+ * @param int $user_id Id of user to get loggers for. Defaults to current user id.
1882
+ * @param string $format format to return loggers in. Default is array.
1883
+ * @return array
1884
+ */
1885
+ public function getLoggersThatUserCanRead($user_id = "", $format = "array") {
1886
+
1887
+ $arr_loggers_user_can_view = array();
1888
+
1889
+ if ( ! is_numeric($user_id) ) {
1890
+ $user_id = get_current_user_id();
1891
+ }
1892
+
1893
+ $loggers = $this->getInstantiatedLoggers();
1894
+ foreach ($loggers as $one_logger) {
1895
+
1896
+ $logger_capability = $one_logger["instance"]->getCapability();
1897
+
1898
+ //$arr_loggers_user_can_view = apply_filters("simple_history/loggers_user_can_read", $user_id, $arr_loggers_user_can_view);
1899
+ $user_can_read_logger = user_can( $user_id, $logger_capability );
1900
+ $user_can_read_logger = apply_filters("simple_history/loggers_user_can_read/can_read_single_logger", $user_can_read_logger, $one_logger["instance"], $user_id);
1901
+
1902
+ if ( $user_can_read_logger ) {
1903
+ $arr_loggers_user_can_view[] = $one_logger;
1904
+ }
1905
+
1906
+ }
1907
+
1908
+ /**
1909
+ * Fires before Simple History does it's init stuff
1910
+ *
1911
+ * @since 2.0
1912
+ *
1913
+ * @param array $arr_loggers_user_can_view Array with loggers that user $user_id can read
1914
+ * @param int user_id ID of user to check read capability for
1915
+ */
1916
+ $arr_loggers_user_can_view = apply_filters("simple_history/loggers_user_can_read", $arr_loggers_user_can_view, $user_id);
1917
+
1918
+ // just return array with slugs in parenthesis suitable for sql-where
1919
+ if ( "sql" == $format ) {
1920
+
1921
+ $str_return = "(";
1922
+
1923
+ foreach ($arr_loggers_user_can_view as $one_logger) {
1924
+
1925
+ $str_return .= sprintf(
1926
+ '"%1$s", ',
1927
+ $one_logger["instance"]->slug
1928
+ );
1929
+
1930
+ }
1931
+
1932
+ $str_return = rtrim($str_return, " ,");
1933
+ $str_return .= ")";
1934
+
1935
+ return $str_return;
1936
+
1937
+ }
1938
+
1939
+
1940
+ return $arr_loggers_user_can_view;
1941
+
1942
+ }
1943
+
1944
+ /**
1945
+ * Retrieve the avatar for a user who provided a user ID or email address.
1946
+ * A modified version of the function that comes with WordPress, but we
1947
+ * want to allow/show gravatars even if they are disabled in discussion settings
1948
+ *
1949
+ * @since 2.0
1950
+ *
1951
+ * @param string $email email address
1952
+ * @param int $size Size of the avatar image
1953
+ * @param string $default URL to a default image to use if no avatar is available
1954
+ * @param string $alt Alternative text to use in image tag. Defaults to blank
1955
+ * @return string <img> tag for the user's avatar
1956
+ */
1957
+ function get_avatar( $email, $size = '96', $default = '', $alt = false ) {
1958
+
1959
+ if ( false === $alt)
1960
+ $safe_alt = '';
1961
+ else
1962
+ $safe_alt = esc_attr( $alt );
1963
+
1964
+ if ( !is_numeric($size) )
1965
+ $size = '96';
1966
+
1967
+ if ( empty($default) ) {
1968
+ $avatar_default = get_option('avatar_default');
1969
+ if ( empty($avatar_default) )
1970
+ $default = 'mystery';
1971
+ else
1972
+ $default = $avatar_default;
1973
+ }
1974
+
1975
+ if ( !empty($email) )
1976
+ $email_hash = md5( strtolower( trim( $email ) ) );
1977
+
1978
+ if ( is_ssl() ) {
1979
+ $host = 'https://secure.gravatar.com';
1980
+ } else {
1981
+ if ( !empty($email) )
1982
+ $host = sprintf( "http://%d.gravatar.com", ( hexdec( $email_hash[0] ) % 2 ) );
1983
+ else
1984
+ $host = 'http://0.gravatar.com';
1985
+ }
1986
+
1987
+ if ( 'mystery' == $default )
1988
+ $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}"; // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com')
1989
+ elseif ( 'blank' == $default )
1990
+ $default = $email ? 'blank' : includes_url( 'images/blank.gif' );
1991
+ elseif ( !empty($email) && 'gravatar_default' == $default )
1992
+ $default = '';
1993
+ elseif ( 'gravatar_default' == $default )
1994
+ $default = "$host/avatar/?s={$size}";
1995
+ elseif ( empty($email) )
1996
+ $default = "$host/avatar/?d=$default&amp;s={$size}";
1997
+ elseif ( strpos($default, 'http://') === 0 )
1998
+ $default = add_query_arg( 's', $size, $default );
1999
+
2000
+ if ( !empty($email) ) {
2001
+ $out = "$host/avatar/";
2002
+ $out .= $email_hash;
2003
+ $out .= '?s='.$size;
2004
+ $out .= '&amp;d=' . urlencode( $default );
2005
+
2006
+ $rating = get_option('avatar_rating');
2007
+ if ( !empty( $rating ) )
2008
+ $out .= "&amp;r={$rating}";
2009
+
2010
+ $out = str_replace( '&#038;', '&amp;', esc_url( $out ) );
2011
+ $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo' height='{$size}' width='{$size}' />";
2012
+ } else {
2013
+ $out = esc_url( $default );
2014
+ $avatar = "<img alt='{$safe_alt}' src='{$out}' class='avatar avatar-{$size} photo avatar-default' height='{$size}' width='{$size}' />";
2015
+ }
2016
+
2017
+ return $avatar;
2018
+ }
2019
+
2020
+ /**
2021
+ * Quick stats above the log
2022
+ * Uses filter "simple_history/history_page/before_gui" to output its contents
2023
+ */
2024
+ public function output_quick_stats() {
2025
+
2026
+ global $wpdb;
2027
+
2028
+ // Get number of events today
2029
+ $logQuery = new SimpleHistoryLogQuery();
2030
+ $logResults = $logQuery->query(array(
2031
+ "posts_per_page" => 1,
2032
+ "date_from" => strtotime("today")
2033
+ ));
2034
+
2035
+ $sql_loggers_in = $this->getLoggersThatUserCanRead(get_current_user_id(), "sql");
2036
+ $sql_users_today = sprintf('
2037
+ SELECT
2038
+ DISTINCT(c.value) AS user_id
2039
+ #h.id, h.logger, h.level, h.initiator, h.date
2040
+ FROM %3$s AS h
2041
+ INNER JOIN %4$s AS c
2042
+ ON c.history_id = h.id AND c.key = "_user_id"
2043
+ WHERE
2044
+ initiator = "wp_user"
2045
+ AND logger IN %1$s
2046
+ AND date > "%2$s"
2047
+ ',
2048
+ $sql_loggers_in,
2049
+ date("Y-m-d H:i", strtotime("today")),
2050
+ $wpdb->prefix . SimpleHistory::DBTABLE,
2051
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS
2052
+ );
2053
+
2054
+ $results_users_today = $wpdb->get_results($sql_users_today);
2055
+
2056
+ ?>
2057
+ <div class="SimpleHistoryQuickStats">
2058
+ <p>
2059
+ <?php
2060
+
2061
+ $msg_tmpl = "";
2062
+
2063
+ if ( $logResults["total_row_count"] == 0 ) {
2064
+
2065
+ $msg_tmpl = __("No events today so far.", "simple-history");
2066
+
2067
+ } elseif ( $logResults["total_row_count"] == 1 ) {
2068
+
2069
+ $msg_tmpl = __('%1$d event today from one user.', "simple-history");
2070
+
2071
+ } elseif ( $logResults["total_row_count"] > 0 && sizeof( $results_users_today ) > 1 ) {
2072
+
2073
+ $msg_tmpl = __('%1$d events today from %2$d users.', "simple-history");
2074
+
2075
+ } elseif ( $logResults["total_row_count"] > 0 && sizeof( $results_users_today ) == 1 ) {
2076
+
2077
+ $msg_tmpl = __('%1$d events today from one user.', "simple-history");
2078
+
2079
+ }
2080
+
2081
+ // only show stats if we have something to output
2082
+ if ( $msg_tmpl ) {
2083
+
2084
+ printf(
2085
+ $msg_tmpl,
2086
+ $logResults["total_row_count"],
2087
+ sizeof( $results_users_today )
2088
+ );
2089
+
2090
+ // Space between texts
2091
+ /*
2092
+ echo " ";
2093
+
2094
+ // http://playground-root.ep/wp-admin/options-general.php?page=simple_history_settings_menu_slug&selected-tab=stats
2095
+ printf(
2096
+ '<a href="%1$s">View more stats</a>.',
2097
+ add_query_arg("selected-tab", "stats", menu_page_url(SimpleHistory::SETTINGS_MENU_SLUG, 0))
2098
+ );
2099
+ */
2100
+
2101
+ }
2102
+
2103
+ ?>
2104
+ </p>
2105
+ </div>
2106
+ <?php
2107
+
2108
+ }
2109
+
2110
+ } // class
2111
+
SimpleHistoryFunctions.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Helper function with same name as the SimpleLogger-class
5
+ *
6
+ * Makes call like this possible:
7
+ * SimpleLogger()->info("This is a message sent to the log");
8
+ */
9
+ function SimpleLogger() {
10
+ return new SimpleLogger( $GLOBALS["simple_history"] );
11
+ }
12
+
13
+ /**
14
+ * Add event to history table
15
+ * This is here for backwards compatibility
16
+ * If you use this please consider using
17
+ * SimpleHistory()->info();
18
+ * instead
19
+ */
20
+ function simple_history_add($args) {
21
+
22
+ $defaults = array(
23
+ "action" => null,
24
+ "object_type" => null,
25
+ "object_subtype" => null,
26
+ "object_id" => null,
27
+ "object_name" => null,
28
+ "user_id" => null,
29
+ "description" => null
30
+ );
31
+
32
+ $context = wp_parse_args( $args, $defaults );
33
+
34
+ $message = "{$context["object_type"]} {$context["object_name"]} {$context["action"]}";
35
+
36
+ SimpleLogger()->info($message, $context);
37
+
38
+ } // simple_history_add
SimpleHistoryLogQuery.php ADDED
@@ -0,0 +1,742 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Queries the Simple History Log
5
+ */
6
+ class SimpleHistoryLogQuery {
7
+
8
+ public function __construct() {
9
+
10
+ /*
11
+ if ( is_array($args) && ! empty($args) ) {
12
+
13
+ return $this->query($args);
14
+
15
+ }
16
+ */
17
+
18
+ }
19
+
20
+ public function query($args) {
21
+
22
+ $defaults = array(
23
+
24
+ // overview | occasions
25
+ "type" => "overview",
26
+
27
+ // Number of posts to show per page. 0 to show all.
28
+ "posts_per_page" => 0,
29
+
30
+ // Page to show. 1 = first page
31
+ "paged" => 1,
32
+
33
+ // Array. Only get posts that are in array.
34
+ "post__in" => null,
35
+
36
+ // array or html
37
+ "format" => "array",
38
+
39
+ // If max_id_first_page is set then only get rows
40
+ // that have id equal or lower than this, to make
41
+ "max_id_first_page" => null,
42
+
43
+ // if since_id is set the rows returned will only be rows with an ID greater than (i.e. more recent than) since_id
44
+ "since_id" => null,
45
+
46
+ // date range
47
+ // in unix datetime or Y-m-d H:i (or format compatible with strtotime())
48
+ "date_from" => null,
49
+ "date_to" => null,
50
+
51
+ // months in format "Y-m"
52
+ // array or comma separated
53
+ "months" => 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
+
70
+ $args = wp_parse_args( $args, $defaults );
71
+ // sf_d($args, "Run log query with args");
72
+
73
+ /*
74
+ Subequent occasions query thanks to this Stack Overflow thread:
75
+ http://stackoverflow.com/questions/13566303/how-to-group-subsequent-rows-based-on-a-criteria-and-then-count-them-mysql/13567320#13567320
76
+ Similar questions that I didn't manage to understand, work, or did try:
77
+ - http://stackoverflow.com/questions/23651176/mysql-query-if-dates-are-subsequent
78
+ - http://stackoverflow.com/questions/17651868/mysql-group-by-subsequent
79
+ - http://stackoverflow.com/questions/4495242/mysql-number-of-subsequent-occurrences
80
+ - http://stackoverflow.com/questions/20446242/postgresql-group-subsequent-rows
81
+ - http://stackoverflow.com/questions/17061156/mysql-group-by-range
82
+ - http://stackoverflow.com/questions/6602006/complicated-query-with-group-by-and-range-of-prices-in-mysql
83
+ */
84
+
85
+ global $wpdb;
86
+
87
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
88
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
89
+
90
+ $where = "1 = 1";
91
+ $limit = "";
92
+ $inner_where = "";
93
+
94
+ if ( "overview" === $args["type"] || "single" === $args["type"] ) {
95
+
96
+ // Set variables used by query
97
+ $sql_set_var = "SET @a:='', @counter:=1, @groupby:=0";
98
+ $wpdb->query( $sql_set_var );
99
+
100
+ // Query template
101
+ // 1 = where
102
+ // 2 = limit
103
+ // 3 = db name
104
+ // 4 = where for inner calc sql query thingie
105
+ // 5 = db name contexts
106
+ $sql_tmpl = '
107
+ SELECT
108
+ SQL_CALC_FOUND_ROWS
109
+ t.id,
110
+ t.logger,
111
+ t.level,
112
+ t.date,
113
+ t.message,
114
+ t.type,
115
+ t.initiator,
116
+ t.occasionsID,
117
+ count(REPEATED) AS subsequentOccasions,
118
+ t.rep,
119
+ t.repeated,
120
+ t.occasionsIDType,
121
+ c1.value AS context_message_key
122
+ FROM
123
+ (
124
+ SELECT
125
+ id,
126
+ logger,
127
+ level,
128
+ message,
129
+ type,
130
+ initiator,
131
+ occasionsID,
132
+ date,
133
+ IF(@a=occasionsID,@counter:=@counter+1,@counter:=1) AS rep,
134
+ IF(@counter=1,@groupby:=@groupby+1,@groupby) AS repeated,
135
+ @a:=occasionsID occasionsIDType
136
+ FROM %3$s
137
+
138
+ # Add where here
139
+ WHERE 1 = 1
140
+ %4$s
141
+
142
+ ORDER BY id DESC
143
+ ) AS t
144
+
145
+ LEFT OUTER JOIN %5$s AS c1 ON c1.history_id = t.id AND c1.key = "_message_key"
146
+
147
+ WHERE %1$s
148
+ GROUP BY repeated
149
+ ORDER BY id DESC
150
+ %2$s
151
+ ';
152
+
153
+ $sh = $GLOBALS["simple_history"];
154
+
155
+ // Only include loggers that the current user can view
156
+ $sql_loggers_user_can_view = $sh->getLoggersThatUserCanRead(get_current_user_id(), "sql");
157
+ $inner_where = " AND logger IN {$sql_loggers_user_can_view}";
158
+
159
+ } else if ( "occasions" === $args["type"] ) {
160
+
161
+ // Query template
162
+ // 1 = where
163
+ // 2 = limit
164
+ // 3 = db name
165
+ $sql_tmpl = '
166
+ SELECT t.*,
167
+ # fake columns that exist in overview query
168
+ 1 as subsequentOccasions
169
+ FROM %3$s AS t
170
+ WHERE %1$s
171
+ ORDER BY id DESC
172
+ %2$s
173
+ ';
174
+
175
+ $where .= " AND t.id <= " . (int) $args["logRowID"];
176
+ $where .= " AND t.occasionsID = '" . esc_sql( $args["occasionsID"] ) . "'";
177
+
178
+ $limit = "LIMIT " . (int) $args["occasionsCount"];
179
+
180
+ // [logRowID] =&gt; 353
181
+ // [occasionsID] =&gt; 73b06d5740d15e35079b6aa024255cb3
182
+ // [occasionsCount] =&gt; 18
183
+
184
+ }
185
+
186
+ // Determine limit
187
+ // Both posts_per_pae and paged must be set
188
+ $is_limit_query = ( is_numeric( $args["posts_per_page"] ) && $args["posts_per_page"] > 0 );
189
+ $is_limit_query = $is_limit_query && ( is_numeric( $args["paged"] ) && $args["paged"] > 0 );
190
+ if ($is_limit_query) {
191
+ $limit_offset = ($args["paged"] - 1) * $args["posts_per_page"];
192
+ $limit .= sprintf('LIMIT %1$d, %2$d', $limit_offset, $args["posts_per_page"] );
193
+ }
194
+
195
+ // Determine where
196
+ if ( $args["post__in"] && is_array( $args["post__in"] ) ) {
197
+
198
+ $where .= sprintf(' AND t.id IN (%1$s)', implode(",", $args["post__in"]));
199
+
200
+ }
201
+
202
+ // If max_id_first_page is then then only include rows
203
+ // with id equal to or earlier
204
+ if ( isset($args["max_id_first_page"]) && is_numeric($args["max_id_first_page"]) ) {
205
+
206
+ $max_id_first_page = (int) $args["max_id_first_page"];
207
+ $where .= sprintf(
208
+ ' AND t.id <= %1$d',
209
+ $max_id_first_page
210
+ );
211
+
212
+ }
213
+
214
+ if ( isset($args["since_id"]) && is_numeric($args["since_id"]) ) {
215
+
216
+ $since_id = (int) $args["since_id"];
217
+ /*
218
+ $where .= sprintf(
219
+ ' AND t.id > %1$d',
220
+ $since_id
221
+ );
222
+ */
223
+ // Add where to inner because that's faster
224
+ $inner_where .= sprintf(
225
+ ' AND id > %1$d',
226
+ $since_id
227
+ );
228
+
229
+ }
230
+
231
+ // Append date where
232
+ if ( ! empty( $args["date_from"] ) ) {
233
+
234
+ // date_to=2014-08-01
235
+ // if date is not numeric assume Y-m-d H:i-format
236
+ $date_from = $args["date_from"];
237
+ if ( ! is_numeric( $date_from ) ) {
238
+ $date_from = strtotime( $date_from );
239
+ }
240
+
241
+ $inner_where .= "\n" . sprintf(' AND UNIX_TIMESTAMP(date) >= %1$d', esc_sql( $date_from ) );
242
+
243
+ }
244
+
245
+ if ( ! empty( $args["date_to"] ) ) {
246
+
247
+ // date_to=2014-08-01
248
+ // if date is not numeric assume Y-m-d H:i-format
249
+ $date_to = $args["date_to"];
250
+ if ( ! is_numeric( $date_to ) ) {
251
+ $date_to = strtotime( $date_to );
252
+ }
253
+
254
+ $inner_where .= "\n" . sprintf(' AND UNIX_TIMESTAMP(date) <= %1$d', esc_sql( $date_to ) );
255
+
256
+ }
257
+
258
+ // months, in format "Y-m"
259
+ if ( ! empty( $args["months"] ) ) {
260
+
261
+ if ( is_array( $args["months"] ) ) {
262
+ $arr_months = $args["months"];
263
+ } else {
264
+ $arr_months = explode(",", $args["months"]);
265
+ }
266
+
267
+ $sql_months = '
268
+ # sql_months
269
+ AND (
270
+ ';
271
+
272
+ foreach ( $arr_months as $one_month ) {
273
+
274
+ // beginning of month
275
+ // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08") ) . "\n";
276
+ // >> 2014-08-01 00:00
277
+ $date_month_beginning = strtotime( $one_month );
278
+
279
+ // end of month
280
+ // $ php -r ' echo date("Y-m-d H:i", strtotime("2014-08 + 1 month") ) . "\n";'
281
+ // >> 2014-09-01 00:00
282
+ $date_month_end = strtotime( "{$one_month} + 1 month" );
283
+
284
+ $sql_months .= sprintf(
285
+ '
286
+ (
287
+ UNIX_TIMESTAMP(date) >=%1$d
288
+ AND UNIX_TIMESTAMP(date) <= %2$d
289
+ )
290
+
291
+ OR
292
+ ',
293
+ $date_month_beginning, // 1
294
+ $date_month_end // 2
295
+
296
+ );
297
+
298
+ }
299
+
300
+ $sql_months = trim($sql_months);
301
+ $sql_months = rtrim($sql_months, " OR ");
302
+
303
+ $sql_months .= '
304
+ # end sql_months and wrap
305
+ )
306
+ ';
307
+
308
+ $inner_where .= $sql_months;
309
+ // echo $inner_where;exit;
310
+
311
+ }
312
+
313
+ // search
314
+ if ( ! empty( $args["search"] ) ) {
315
+
316
+ $search_words = $args["search"];
317
+ $str_search_conditions = "";
318
+ $arr_search_words = preg_split("/[\s,]+/", $search_words);
319
+
320
+ // create array of all searched words
321
+ // split both spaces and commas and such
322
+ $arr_sql_like_cols = array("message", "logger", "level");
323
+
324
+ foreach ($arr_sql_like_cols as $one_col) {
325
+
326
+ $str_sql_search_words = "";
327
+
328
+
329
+ foreach ($arr_search_words as $one_search_word) {
330
+
331
+ if ( method_exists($wpdb, "esc_like") ) {
332
+ $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) );
333
+ } else {
334
+ $str_like = like_escape( esc_sql( $one_search_word ) );
335
+ }
336
+
337
+ $str_sql_search_words .= sprintf(
338
+ ' AND %1$s LIKE "%2$s" ',
339
+ $one_col,
340
+ "%{$str_like}%"
341
+ );
342
+
343
+ }
344
+
345
+ $str_sql_search_words = ltrim($str_sql_search_words, ' AND ');
346
+
347
+ $str_search_conditions .= "\n" . sprintf(
348
+ ' OR ( %1$s ) ',
349
+ $str_sql_search_words
350
+ );
351
+
352
+ }
353
+
354
+ $str_search_conditions = preg_replace('/^OR /', " ", trim($str_search_conditions));
355
+
356
+ // also search contexts
357
+ $str_search_conditions .= "\n OR ( ";
358
+ foreach ($arr_search_words as $one_search_word) {
359
+
360
+ if ( method_exists($wpdb, "esc_like") ) {
361
+ $str_like = esc_sql( $wpdb->esc_like( $one_search_word ) );
362
+ } else {
363
+ $str_like = like_escape( esc_sql( $one_search_word ) );
364
+ }
365
+
366
+ $str_search_conditions .= "\n" . sprintf(
367
+ ' id IN ( SELECT history_id FROM %1$s AS c WHERE c.value LIKE "%2$s" ) AND ',
368
+ $table_name_contexts, // 1
369
+ "%" . $str_like . "%" // 2
370
+ );
371
+
372
+ }
373
+ $str_search_conditions = preg_replace('/ AND $/', "", $str_search_conditions);
374
+
375
+ $str_search_conditions .= "\n ) "; // end or for contexts
376
+
377
+ $inner_where .= "\n AND \n(\n {$str_search_conditions} \n ) ";
378
+
379
+ #echo $inner_where;exit;
380
+
381
+ }
382
+
383
+ // log levels
384
+ // comma separated
385
+ // 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
386
+ if ( ! empty( $args["loglevels"] ) ) {
387
+
388
+ $sql_loglevels = "";
389
+
390
+ if ( is_array( $args["loglevels"] ) ) {
391
+ $arr_loglevels = $args["loglevels"];
392
+ } else {
393
+ $arr_loglevels = explode(",", $args["loglevels"]);
394
+ }
395
+
396
+ foreach ( $arr_loglevels as $one_loglevel ) {
397
+ $sql_loglevels .= sprintf(' "%s", ', esc_sql( $one_loglevel ));
398
+ }
399
+
400
+ if ( $sql_loglevels ) {
401
+ $sql_loglevels = rtrim( $sql_loglevels, " ," );
402
+ $sql_loglevels = "\n AND level IN ({$sql_loglevels}) ";
403
+ }
404
+
405
+ $inner_where .= $sql_loglevels;
406
+
407
+ }
408
+
409
+ // messages
410
+ if ( ! empty( $args["messages"] ) ) {
411
+
412
+ #print_r($args["messages"]);exit;
413
+ /*
414
+ Array
415
+ (
416
+ [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
417
+ [1] => SimpleCommentsLogger:SimpleCommentsLogger:comment_status_spam,SimpleCommentsLogger:trackback_status_spam,SimpleCommentsLogger:pingback_status_spam
418
+ )
419
+ */
420
+
421
+ // Array with loggers and messages
422
+ $arr_loggers_and_messages = array();
423
+
424
+ // Tranform from get'et format to our own internal format
425
+ foreach ( (array) $args["messages"] as $one_arr_messages_row ) {
426
+
427
+ $arr_row_messages = explode(",", $one_arr_messages_row);
428
+ #print_r($arr_row_messages);#exit;
429
+ /*
430
+
431
+ Array
432
+ (
433
+ [0] => SimpleCommentsLogger:anon_comment_added
434
+ [1] => SimpleCommentsLogger:user_comment_added
435
+ [2] => SimpleCommentsLogger:anon_trackback_added
436
+ */
437
+ foreach ($arr_row_messages as $one_row_logger_and_message) {
438
+
439
+ $arr_one_logger_and_message = explode(":", $one_row_logger_and_message);
440
+
441
+ if ( ! isset( $arr_loggers_and_messages[$arr_one_logger_and_message[0]] ) ) {
442
+ $arr_loggers_and_messages[$arr_one_logger_and_message[0]] = array();
443
+ }
444
+
445
+ $arr_loggers_and_messages[ $arr_one_logger_and_message[0] ][] = $arr_one_logger_and_message[1];
446
+
447
+ }
448
+
449
+ }
450
+
451
+ // Now create sql where based on loggers and messages
452
+ $sql_messages_where = " AND (";
453
+ #print_r($arr_loggers_and_messages);exit;
454
+ foreach ($arr_loggers_and_messages as $logger_slug => $logger_messages) {
455
+ $sql_messages_where .= sprintf(
456
+ '
457
+ (
458
+ logger = "%1$s"
459
+ AND c1.value IN (%2$s)
460
+ )
461
+ OR ',
462
+ esc_sql( $logger_slug ),
463
+ "'" . implode("','", $logger_messages) . "'"
464
+ );
465
+ }
466
+ // remove last or
467
+ $sql_messages_where = preg_replace('/OR $/', "", $sql_messages_where);
468
+
469
+ $sql_messages_where .= "\n )";
470
+ #echo $sql_messages_where;exit;
471
+ $where .= $sql_messages_where;
472
+
473
+ /*
474
+ print_r($arr_loggers_and_messages);exit;
475
+ Array
476
+ (
477
+ [SimpleCommentsLogger] => Array
478
+ (
479
+ [0] => anon_comment_added
480
+ [1] => user_comment_added
481
+ [2] => anon_trackback_added
482
+ [3] => user_trackback_added
483
+ [4] => anon_pingback_added
484
+ [5] => user_pingback_added
485
+ [6] => comment_edited
486
+ [7] => trackback_edited
487
+ [8] => pingback_edited
488
+ [9] => comment_status_approve
489
+ [10] => trackback_status_approve
490
+ [11] => pingback_status_approve
491
+ [12] => comment_status_hold
492
+ [13] => trackback_status_hold
493
+ [14] => pingback_status_hold
494
+ [15] => comment_status_spam
495
+ [16] => trackback_status_spam
496
+ [17] => pingback_status_spam
497
+ [18] => comment_status_trash
498
+ [19] => trackback_status_trash
499
+ [20] => pingback_status_trash
500
+ [21] => comment_untrashed
501
+ [22] => trackback_untrashed
502
+ [23] => pingback_untrashed
503
+ [24] => comment_deleted
504
+ [25] => trackback_deleted
505
+ [26] => pingback_deleted
506
+ )
507
+
508
+ [SimpleUserLogger] => Array
509
+ (
510
+ [0] => SimpleUserLogger
511
+ [1] => SimpleUserLogger
512
+ )
513
+
514
+ )
515
+
516
+ */
517
+
518
+ }
519
+
520
+ // loggers
521
+ // comma separated
522
+ // 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
523
+ if ( ! empty( $args["loggers"] ) ) {
524
+
525
+ $sql_loggers = "";
526
+ if ( is_array( $args["loggers"] ) ) {
527
+ $arr_loggers = $args["loggers"];
528
+ } else {
529
+ $arr_loggers = explode(",", $args["loggers"]);
530
+ }
531
+
532
+ #print_r($args["loggers"]);exit;
533
+ #print_r($arr_loggers);exit;
534
+ /*
535
+ Example of version with logger + message keys
536
+ Array
537
+ (
538
+ [0] => SimpleUserLogger:user_created
539
+ [1] => SimpleUserLogger:user_deleted
540
+ )
541
+ */
542
+
543
+ foreach ( $arr_loggers as $one_logger ) {
544
+ $sql_loggers .= sprintf(' "%s", ', esc_sql( $one_logger ));
545
+ }
546
+
547
+ if ( $sql_loggers ) {
548
+ $sql_loggers = rtrim( $sql_loggers, " ," );
549
+ $sql_loggers = "\n AND logger IN ({$sql_loggers}) ";
550
+ }
551
+
552
+ $inner_where .= $sql_loggers;
553
+
554
+ }
555
+
556
+ // user, a single userID
557
+ if ( ! empty( $args["user"] ) && is_numeric( $args["user"] ) ) {
558
+
559
+ $userID = (int) $args["user"];
560
+ $sql_user = sprintf(
561
+ '
562
+ AND id IN ( SELECT history_id FROM %1$s AS c WHERE c.key = "_user_id" AND c.value = %2$s )
563
+ ',
564
+ $table_name_contexts, // 1
565
+ $userID // 2
566
+ );
567
+
568
+ $inner_where .= $sql_user;
569
+
570
+ }
571
+
572
+
573
+ /**
574
+ * Filter the sql template
575
+ *
576
+ * @since 2.0
577
+ *
578
+ * @param string $sql_tmpl
579
+ */
580
+ $sql_tmpl = apply_filters("simple_history/log_query_sql_template", $sql_tmpl);
581
+
582
+ /**
583
+ * Filter the sql template where clause
584
+ *
585
+ * @since 2.0
586
+ *
587
+ * @param string $where
588
+ */
589
+ $where = apply_filters("simple_history/log_query_sql_where", $where);
590
+
591
+ /**
592
+ * Filter the sql template limit
593
+ *
594
+ * @since 2.0
595
+ *
596
+ * @param string $limit
597
+ */
598
+ $limit = apply_filters("simple_history/log_query_limit", $limit);
599
+
600
+ /**
601
+ * Filter the sql template limit
602
+ *
603
+ * @since 2.0
604
+ *
605
+ * @param string $limit
606
+ */
607
+ $inner_where = apply_filters("simple_history/log_query_inner_where", $inner_where);
608
+
609
+ $sql = sprintf(
610
+ $sql_tmpl, // sprintf template
611
+ $where, // 1
612
+ $limit, // 2
613
+ $table_name, // 3
614
+ $inner_where,// 4
615
+ $table_name_contexts // 5
616
+ );
617
+
618
+
619
+ /**
620
+ * Filter the final sql query
621
+ *
622
+ * @since 2.0
623
+ *
624
+ * @param string $sql
625
+ */
626
+ $sql = apply_filters("simple_history/log_query_sql", $sql);
627
+
628
+ // Remove comments below to debug query (includes query in json result)
629
+ // $include_query_in_result = true;
630
+ if (isset($_GET["SimpleHistoryLogQuery-showDebug"]) && $_GET["SimpleHistoryLogQuery-showDebug"]) {
631
+
632
+ echo "<pre>";
633
+ echo $sql_set_var;
634
+ echo $sql;
635
+ exit;
636
+
637
+ }
638
+
639
+ $log_rows = $wpdb->get_results($sql, OBJECT_K);
640
+ $num_rows = sizeof($log_rows);
641
+
642
+ // Find total number of rows that we would have gotten without pagination
643
+ // This is the number of rows with occasions taken into consideration
644
+ $sql_found_rows = 'SELECT FOUND_ROWS()';
645
+ $total_found_rows = (int) $wpdb->get_var( $sql_found_rows );
646
+
647
+ // Add context
648
+ $post_ids = wp_list_pluck( $log_rows, "id" );
649
+
650
+ if ( empty($post_ids) ) {
651
+ $context_results = array();
652
+ } else {
653
+ $sql_context = sprintf('SELECT * FROM %2$s WHERE history_id IN (%1$s)', join(",", $post_ids), $table_name_contexts);
654
+ $context_results = $wpdb->get_results($sql_context);
655
+ }
656
+
657
+ foreach ( $context_results as $context_row ) {
658
+
659
+ if ( ! isset( $log_rows[ $context_row->history_id ]->context ) ) {
660
+ $log_rows[ $context_row->history_id ]->context = array();
661
+ }
662
+
663
+ $log_rows[ $context_row->history_id ]->context[ $context_row->key ] = $context_row->value;
664
+
665
+ }
666
+
667
+ // Remove id from keys, because they are cumbersome when working with JSON
668
+ $log_rows = array_values($log_rows);
669
+ $min_id = null;
670
+ $max_id = null;
671
+
672
+ if ( sizeof($log_rows) ) {
673
+
674
+ // Max id is simply the id of the first row
675
+ $max_id = reset($log_rows)->id;
676
+
677
+ // Min id = to find the lowest id we must take occasions into consideration
678
+ $last_row = end($log_rows);
679
+ $last_row_occasions_count = (int) $last_row->subsequentOccasions - 1;
680
+ if ($last_row_occasions_count === 0) {
681
+
682
+ // Last row did not have any more occasions, so get min_id directly from the row
683
+ $min_id = $last_row->id;
684
+
685
+ } else {
686
+
687
+ // Last row did have occaions, so fetch all occasions, and find id of last one
688
+ $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
689
+ $sql = sprintf(
690
+ '
691
+ SELECT id, date, occasionsID
692
+ FROM %1$s
693
+ WHERE id <= %2$s
694
+ ORDER BY id DESC
695
+ LIMIT %3$s
696
+ ',
697
+ $db_table,
698
+ $last_row->id,
699
+ $last_row_occasions_count + 1
700
+ );
701
+
702
+ $results = $wpdb->get_results( $sql );
703
+
704
+ // the last occasion has the id we consider last in this paged result
705
+ $min_id = end($results)->id;
706
+
707
+ }
708
+
709
+ }
710
+
711
+ // Calc pages
712
+ if ( $args["posts_per_page"] ) {
713
+ $pages_count = Ceil ( $total_found_rows / (int) $args["posts_per_page"] );
714
+ } else {
715
+ $pages_count = 1;
716
+ }
717
+
718
+ // Create array to return
719
+ // Make all rows a sub key because we want to add some meta info too
720
+ $log_rows_count = sizeof( $log_rows );
721
+ $page_rows_from = ( (int) $args["paged"] * (int) $args["posts_per_page"] ) - (int) $args["posts_per_page"] + 1;
722
+ $page_rows_to = $page_rows_from + $log_rows_count - 1;
723
+ $arr_return = array(
724
+ "total_row_count" => $total_found_rows,
725
+ "pages_count" => $pages_count,
726
+ "page_current" => (int) $args["paged"],
727
+ "page_rows_from" => $page_rows_from,
728
+ "page_rows_to" => $page_rows_to,
729
+ "max_id" => (int) $max_id,
730
+ "min_id" => (int) $min_id,
731
+ "log_rows_count" => $log_rows_count,
732
+ "log_rows" => $log_rows,
733
+ );
734
+
735
+ #sf_d($arr_return, '$arr_return');exit;
736
+
737
+ return $arr_return;
738
+
739
+ } // query
740
+
741
+ } // class
742
+
css/styles.css ADDED
@@ -0,0 +1,783 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /*
3
+
4
+ The spinner that wp uses:;
5
+ /wp-includes/images/spinner.gif
6
+
7
+ */
8
+
9
+ /* clearfix */
10
+ .SimpleHistory__cf:before, .SimpleHistory__cf:after { content:""; display:table; }
11
+ .SimpleHistory__cf:after { clear:both; }
12
+ .SimpleHistory__cf { zoom:1; } /* For IE 6/7 (trigger hasLayout) */
13
+
14
+ .SimpleHistoryGui,
15
+ .SimpleHistoryGuiExample {
16
+ position: relative;
17
+ /* must have a height so "loading..." will be visible in dashboard */
18
+ min-height: 6em;
19
+ }
20
+
21
+ .SimpleHistory__waitingForFirstLoad {
22
+ position: absolute;
23
+ top: 20px;
24
+ left: 20px;
25
+ }
26
+
27
+ .SimpleHistory__waitingForFirstLoad img {
28
+ vertical-align: text-bottom;
29
+ }
30
+
31
+ .SimpleHistory__waitingForFirstLoad--isLoaded {
32
+ display: none;
33
+ }
34
+
35
+ /* wrap around the log items and the pagination */
36
+ .SimpleHistoryLogitemsWrap {
37
+ transition: opacity .1s ease-out;
38
+ }
39
+
40
+ /* on it's own page */
41
+ /*.dashboard_page_simple_history_page .SimpleHistoryLogitemsWrap {*/
42
+ .dashboard_page_simple_history_page .SimpleHistoryGuiWrap {
43
+ /*float: left;*/
44
+ position: relative;
45
+ margin-right: 340px;
46
+ min-height: 200px;
47
+ background-color: rgba(255,255,255,.75);
48
+ }
49
+
50
+ .SimpleHistory--isLoaded.dashboard_page_simple_history_page .SimpleHistoryGuiWrap {
51
+ background-color: transparent;
52
+ }
53
+
54
+ .dashboard_page_simple_history_page .SimpleHistoryGui {
55
+ width: 100%;
56
+ float: left;background: tran
57
+ }
58
+
59
+ /*
60
+ .SimpleHistoryGui:after {
61
+ content: "\f206";
62
+ font-family: dashicons;
63
+ font-size: 510px;
64
+ position: relative;
65
+ line-height: 1;
66
+ color: #ddd;
67
+ z-index: 0;
68
+ }
69
+
70
+ .SimpleHistory--isLoaded .SimpleHistoryGui:after {
71
+ display: none;
72
+ }
73
+ */
74
+
75
+ .SimpleHistory__filters {
76
+ float: right;
77
+ width: 300px;
78
+ margin-right: -340px;
79
+ /* hide by default while log is loading */
80
+ opacity: 0;
81
+ transition: all .25s ease-out;
82
+ }
83
+
84
+ .SimpleHistory--isLoaded .SimpleHistory__filters {
85
+ opacity: 1;
86
+ }
87
+
88
+
89
+ .SimpleHistoryLogitems {
90
+ margin: 0;
91
+ background: #fff;
92
+ border: 1px solid rgb(229, 229, 229);
93
+ opacity: 0;
94
+ transition: all .25s ease-out;
95
+ }
96
+
97
+ .SimpleHistory--isLoaded .SimpleHistoryLogitems,
98
+ .SimpleHistoryGuiExample .SimpleHistoryLogitems {
99
+ opacity: 1;
100
+ }
101
+
102
+
103
+
104
+ /**
105
+ * Log items
106
+ * There is always a 4px left border, just with differents colors
107
+ */
108
+ .SimpleHistoryLogitem {
109
+ position: relative;
110
+ list-style-type: none;
111
+ margin: 0;
112
+ padding: 20px 20px 20px 16px;
113
+ /*border-bottom: 1px solid rgb(229, 229, 229);*/
114
+
115
+ /* uncomment to show border with log color */
116
+ /*border-left: 4px solid transparent;*/
117
+
118
+ overflow: hidden;
119
+ }
120
+
121
+ /* add border below items */
122
+ .SimpleHistoryLogitem::before {
123
+ content: "";
124
+ position: absolute;
125
+ bottom: 0;
126
+ left: 66px;
127
+ height: 1px;
128
+ right: 0;
129
+ background: rgb(229, 229, 229);
130
+ }
131
+
132
+ .SimpleHistoryLogitem:last-of-type:before {
133
+ background: none;
134
+ }
135
+
136
+ /*
137
+ Style different log levels.
138
+ */
139
+
140
+ .SimpleHistoryLogitem--logleveltag {
141
+ display: inline-block;
142
+ background-color: rgba(238, 238, 238, 1);
143
+ font-size: 10px;
144
+ padding: 3px 4px;
145
+ border-radius: 3px;
146
+ /*opacity: .75;*/
147
+ vertical-align: 1px;
148
+ line-height: 1;
149
+ }
150
+
151
+
152
+ .SimpleHistoryLogitem--loglevel-debug {
153
+ border-left-color: #CEF6D8;
154
+ }
155
+ .SimpleHistoryLogitem--logleveltag-debug {
156
+ background-color: #CEF6D8;
157
+ color: #111;
158
+ }
159
+
160
+
161
+ .SimpleHistoryLogitem--loglevel-info {
162
+ border-left-color: white;
163
+ }
164
+ .SimpleHistoryLogitem--logleveltag-info {
165
+ display: none;
166
+ }
167
+
168
+ .SimpleHistoryLogitem--loglevel-notice {
169
+ border-left-color: #FFFFE0;
170
+ border-left-color: rgb(219, 219, 183);
171
+ }
172
+ .SimpleHistoryLogitem--logleveltag-notice {
173
+ background-color: rgb(219, 219, 183);
174
+ color: #111
175
+ }
176
+
177
+ .SimpleHistoryLogitem--loglevel-warning {
178
+ border-left-color: #F7D358;
179
+ }
180
+ .SimpleHistoryLogitem--logleveltag-warning {
181
+ background-color: #F7D358;
182
+ color: #111;
183
+ }
184
+
185
+
186
+ .SimpleHistoryLogitem--loglevel-error {
187
+ border-left-color: #F79F81;
188
+ }
189
+ .SimpleHistoryLogitem--logleveltag-error {
190
+ background-color: #F79F81;
191
+ color: #000;
192
+ }
193
+
194
+
195
+ .SimpleHistoryLogitem--loglevel-critical {
196
+ border-left-color: #FA5858;
197
+ }
198
+ .SimpleHistoryLogitem--logleveltag-critical {
199
+ background-color: #FA5858;
200
+ color: #fff;
201
+ }
202
+
203
+ .SimpleHistoryLogitem--loglevel-alert {
204
+ border-left-color: rgb(199, 69, 69);
205
+ }
206
+ .SimpleHistoryLogitem--logleveltag-alert {
207
+ background-color: rgb(199, 69, 69);
208
+ color: #eee;
209
+ }
210
+
211
+ .SimpleHistoryLogitem--loglevel-emergency {
212
+ border-left-color: #610B0B;
213
+ border-left-color: #DF0101;
214
+ }
215
+ .SimpleHistoryLogitem--logleveltag-emergency {
216
+ background-color: #610B0B;
217
+ background-color: #DF0101;
218
+ color: #eee;
219
+ }
220
+
221
+ .SimpleHistoryLogitem:hover {
222
+ /* same bg color as twitter uses on hover */
223
+ /*background: rgb(245, 248, 250);*/
224
+ }
225
+
226
+ .SimpleHistoryLogitem a {
227
+ text-decoration: none;
228
+ }
229
+
230
+ .SimpleHistoryLogitem a:hover {
231
+ text-decoration: underline;
232
+ }
233
+
234
+ .SimpleHistoryLogitem__firstcol {
235
+ float: left;
236
+ }
237
+
238
+ .SimpleHistoryLogitem__senderImage {
239
+ border-radius: 50%;
240
+ overflow: hidden;
241
+ /*opacity: .8;*/
242
+ margin-top: 5px;
243
+ }
244
+
245
+ .SimpleHistoryLogitem:hover .SimpleHistoryLogitem__senderImage {
246
+ /*opacity: 1;*/
247
+ }
248
+
249
+ .SimpleHistoryLogitem__senderImage img {
250
+ display: block;
251
+ }
252
+
253
+ .SimpleHistoryLogitem__secondcol {
254
+ margin-left: 50px;
255
+ }
256
+
257
+ .SimpleHistoryLogitem__header {
258
+ line-height: 1.2;
259
+ margin-top: 2px;
260
+ }
261
+
262
+ .SimpleHistoryLogitem__header,
263
+ /*.SimpleHistoryLogitem__header time,*/
264
+ .SimpleHistoryLogitem__headerEmail {
265
+ color: rgb(137, 143, 156);
266
+ }
267
+
268
+ .SimpleHistoryLogitem__text,
269
+ .SimpleHistoryLogitem__details,
270
+ .SimpleHistoryLogitem__details p {
271
+ xfont-size: 16px;
272
+ line-height: 1.4;
273
+ }
274
+
275
+ .SimpleHistoryLogitem__text {
276
+ margin-top: 0.4em;
277
+ font-size: 15px;
278
+ color: #333;
279
+ }
280
+
281
+ .SimpleHistoryLogitem__details p {
282
+ margin-top: .4em;
283
+ margin-bottom: .4em;
284
+ }
285
+
286
+ .SimpleHistoryLogitem__occasions {
287
+ color: rgb(137, 143, 156);
288
+ margin-top: 0.4em;
289
+ line-height: 1;
290
+ max-height: 1em;
291
+ }
292
+
293
+ .SimpleHistoryLogitem__details {
294
+ margin-top: 0.4em;
295
+ }
296
+
297
+ /* make video embeds responsive */
298
+ .SimpleHistoryLogitem__details .wp-video {
299
+ max-width: 100%;
300
+ }
301
+
302
+ /*.SimpleHistoryLogitem__details .wp-video-shortcode {
303
+
304
+ }*/
305
+
306
+ /* table with keys and values */
307
+ /*.SimpleHistoryLogitem__keyValueTable {
308
+
309
+ }*/
310
+
311
+ .SimpleHistoryLogitem__keyValueTable th,
312
+ .SimpleHistoryLogitem__keyValueTable td {
313
+ vertical-align: top;
314
+ word-break: break-word;
315
+ }
316
+
317
+ .SimpleHistoryLogitem__keyValueTable tr > td:first-child {
318
+ text-align: right;
319
+ /*font-weight: bold;*/
320
+ padding-right: 1em;
321
+ color: #aaa;
322
+ white-space: nowrap;
323
+ }
324
+
325
+ .SimpleHistoryLogitem__keyValueTable p {
326
+ margin: 0;
327
+ }
328
+
329
+
330
+
331
+ /* wrap span.SimpleHistoryLogitem__inlineDivided around things that should have a bullet between them */
332
+ .SimpleHistoryLogitem__inlineDivided {
333
+ white-space: nowrap;
334
+ }
335
+
336
+ .SimpleHistoryLogitem__inlineDivided em {
337
+ color: rgb(119, 119, 119);
338
+ font-style: normal;
339
+ }
340
+
341
+ .SimpleHistoryLogitem__inlineDivided:before {
342
+ /* \b7 = middot, \a0 = space */
343
+ /*content: '\a0\b7\a0\a0';*/
344
+ content: '\b7\a0';
345
+ /* \2022 = bullet */
346
+ /*content: '\a0\a0\2022\a0\a0';*/
347
+ color: rgb(137, 143, 156);
348
+ }
349
+
350
+ .SimpleHistoryLogitem__inlineDivided:first-child:before {
351
+ content: '';
352
+ }
353
+
354
+ /*
355
+ Images/thumbs can be styles nicely
356
+ */
357
+ .SimpleHistoryLogitemThumbnail {
358
+ display: inline-block;
359
+ margin: .5em 0 0 0;
360
+ padding: 5px;
361
+ border: 1px solid #ddd;
362
+ border-radius: 2px;
363
+
364
+ }
365
+
366
+ .SimpleHistoryLogitemThumbnail img {
367
+ /*
368
+ photoshop-like background that represents tranpsarency
369
+ so user can see that an image have transparency
370
+ */
371
+ display: block;
372
+ background-image: url('data:image/gif;base64,R0lGODlhEAAQAIAAAOXl5f///yH5BAAAAAAALAAAAAAQABAAAAIfhG+hq4jM3IFLJhoswNly/XkcBpIiVaInlLJr9FZWAQA7');
373
+ max-width: 100%;
374
+ max-height: 300px;
375
+ max-height: 200px;
376
+ height: auto;
377
+ }
378
+
379
+
380
+ /*
381
+ when occasions are added
382
+ */
383
+ .SimpleHistoryLogitem--occasionsOpened {
384
+ /*box-shadow: 0px 10px 10px -6px rgba(0, 0, 0, 0.3);
385
+ z-index: 1;*/
386
+ }
387
+
388
+ /* remove border below */
389
+ .SimpleHistoryLogitem--occasionsOpened::before {
390
+ opacity: 0;
391
+ }
392
+
393
+ /* when occasions is loaded and have loaded hide the "show occasions"-link*/
394
+ .SimpleHistoryLogitem--occasionsOpening .SimpleHistoryLogitem__occasionsLink,
395
+ .SimpleHistoryLogitem--occasionsOpened .SimpleHistoryLogitem__occasionsLink {
396
+ display: none;
397
+ }
398
+
399
+ /* hide the "loading" and "loaded occasions" text */
400
+ .SimpleHistoryLogitem__occasionsLoading,
401
+ .SimpleHistoryLogitem__occasionsLoaded {
402
+ display: none;
403
+ }
404
+
405
+ /* show texts during load and when load is done */
406
+ .SimpleHistoryLogitem--occasionsOpening .SimpleHistoryLogitem__occasionsLoading,
407
+ .SimpleHistoryLogitem--occasionsOpened .SimpleHistoryLogitem__occasionsLoaded {
408
+ display: block;
409
+ }
410
+
411
+ /* occasions are added to a wrapper element */
412
+ .SimpleHistoryLogitem__occasionsItemsWrap {
413
+ margin: 0;
414
+ padding: 0;
415
+ }
416
+
417
+ .SimpleHistoryLogitem__occasionsItems {
418
+ opacity: 0;
419
+ max-height: 0;
420
+ transition: opacity .25s ease-out .5s, max-height 2s ease-out 0s, margin .25s .0s ease-out;
421
+ background-color: white;
422
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.25);
423
+ }
424
+
425
+ .SimpleHistoryLogitem__occasionsItems.haveOccasionsAdded {
426
+ opacity: 1;
427
+ max-height: 2000px;
428
+ margin-left: -5px;
429
+ margin-right: -5px;;
430
+ }
431
+
432
+ /*
433
+
434
+ .SimpleHistoryLogitem--occasion {
435
+ opacity: 0;
436
+ padding-top: 0;
437
+ padding-bottom: 0;
438
+ max-height: 0;
439
+ border-top-width : 0;
440
+ border-bottom-width : 0;
441
+ }
442
+ */
443
+
444
+ /*
445
+ .SimpleHistoryLogitem--occasionsOpened:after,
446
+ .SimpleHistoryLogitem--occasion:after {
447
+ content: "";
448
+ position: absolute;
449
+ top: 3px;
450
+ bottom: 3px;
451
+ left: 20px;
452
+ width: 2px;
453
+ background: rgba(0,0,0,0.3);
454
+ border-radius: 3px / 7px;
455
+ }
456
+ */
457
+
458
+ /* when the occasions log rows have been added show them in some kinda fancy way */
459
+ .SimpleHistoryLogitem--occasionAdded {
460
+ max-height: 1000px;
461
+ opacity: 1;
462
+ border-top-width : 1px;
463
+ border-bottom-width : 1px;
464
+ padding-top: 15px;
465
+ padding-bottom: 15px
466
+ /*padding-left: 40px;*/
467
+ }
468
+
469
+
470
+ /* customizations for the dashboard */
471
+ .postbox .SimpleHistoryLogitems {
472
+ border: none;
473
+ }
474
+
475
+ .postbox .SimpleHistoryLogitem {
476
+ /*padding-top: 10px;
477
+ padding-bottom: 10px;*/
478
+ }
479
+
480
+ #simple_history_dashboard_widget .inside {
481
+ padding: 0;
482
+ margin-top: 0;
483
+ }
484
+
485
+ .postbox .SimpleHistoryLogitem__text {
486
+ font-size: 1em;
487
+ }
488
+
489
+ /*
490
+ .postbox .SimpleHistoryLogitem__details {
491
+ display: none;
492
+ }
493
+ */
494
+
495
+ /* // end dashboard */
496
+
497
+
498
+
499
+ /*
500
+ Styles for filter
501
+ */
502
+ /*.simple-history-filters {
503
+ float: left;
504
+ width: 400px;
505
+ margin-left: 50px;
506
+ }
507
+ */
508
+
509
+ .SimpleHistoryLogitems__debug {
510
+ font-family: monospace;
511
+ white-space: pre;
512
+ }
513
+
514
+ /*
515
+ Pagination, below logRows
516
+ */
517
+
518
+ .SimpleHistoryLogitems__pagination {}
519
+
520
+ .SimpleHistoryPaginationPages {
521
+ text-align: center;
522
+ padding-top: 20px;
523
+ padding-bottom: 20px;
524
+ background: white;
525
+ }
526
+
527
+ .postbox .SimpleHistoryPaginationPages {
528
+ padding-top: 8px;
529
+ padding-bottom: 8px;
530
+ }
531
+
532
+ .SimpleHistoryPaginationLink.SimpleHistoryPaginationLink {
533
+ /*line-height: 30px;*/
534
+ /*background: rgb(238, 238, 238);*/
535
+ /*background: rgba(0, 0, 0, 0.05);*/
536
+ /*padding: 0 10px 3px;
537
+ text-decoration: none;*/
538
+ font-size: 16px;
539
+ }
540
+
541
+ .SimpleHistoryPaginationLink.SimpleHistoryPaginationLink:hover {
542
+ color: rgb(255, 255, 255);
543
+ background: rgb(46, 162, 204);
544
+ }
545
+
546
+ .SimpleHistoryPaginationLink.SimpleHistoryPaginationLink.disabled {
547
+ color: rgb(170, 170, 170);
548
+ background: rgb(238, 238, 238);
549
+ background: rgba(0, 0, 0, 0.05);
550
+ cursor: default;
551
+ }
552
+
553
+ .SimpleHistoryPaginationCurrentPage {
554
+ /*padding-top: 0;*/
555
+ text-align: center;
556
+ }
557
+
558
+ /*
559
+ animations/effects
560
+ */
561
+ .SimpleHistory-isLoadingPage .SimpleHistoryLogitemsWrap {
562
+ opacity: .5;
563
+ }
564
+
565
+ /*
566
+ Modal window with detailss
567
+ */
568
+ .SimpleHistory-modal {
569
+ }
570
+
571
+
572
+ .SimpleHistory-modal__background {
573
+ position: fixed;
574
+ top: 0;
575
+ left: 0;
576
+ right: 0;
577
+ bottom: 0;
578
+ background: rgba(0, 0, 0, 0.2);
579
+ z-index: 10;
580
+ }
581
+
582
+ .SimpleHistory-modal__content {
583
+ background: white;
584
+ border-radius: 4px;
585
+ border: 1px solid rgba(0, 0, 0, 0.2);
586
+ bottom: 60px;
587
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
588
+ left: 280px; /* admin meny width is 160 px*/
589
+ position: fixed;
590
+ overflow: auto;
591
+ right: 120px;
592
+ top: 60px;
593
+ z-index: 15;
594
+ }
595
+
596
+ .SimpleHistory-modal__contentInner {
597
+ padding: 15px;
598
+ }
599
+
600
+ .SimpleHistory-modal__contentSpinner {
601
+ margin-left: 20px;
602
+ margin-top: 20px;
603
+ }
604
+
605
+ .SimpleHistory-modal__contentClose {
606
+ position: absolute;
607
+ top: 10px;
608
+ right: 10px;
609
+ }
610
+
611
+ .SimpleHistory-modal__contentClose button {
612
+ cursor: pointer;
613
+ }
614
+
615
+ /* if wp left meny is collapsed .folded is added and meny width is 36 px */
616
+ .folded .SimpleHistory-modal__content {
617
+ left: 156px /* admin meny width is 160 px*/
618
+ }
619
+ @media only screen and (max-width: 900px) {
620
+ .SimpleHistory-modal__content {
621
+ left: 156px /* admin meny width is 160 px*/
622
+ }
623
+ }
624
+
625
+
626
+
627
+ .SimpleHistory-modal__content--enter {
628
+ transition: all .15s ease-out;
629
+ -webkit-transform: scale(0.95);
630
+ transform: scale(0.95);
631
+ opacity: 0;
632
+ }
633
+
634
+ .SimpleHistory-modal__content--enter-active {
635
+ opacity: 1;
636
+ -webkit-transform: scale(1);
637
+ transform: scale(1);
638
+ }
639
+
640
+ .SimpleHistory-modal__content--leave-active {
641
+ opacity: 0;
642
+ -webkit-transform: scale(0.95);
643
+ transform: scale(0.95);
644
+ }
645
+
646
+ .SimpleHistory-modal__leave-active {
647
+ transition: all .15s .15s ease-out, visibility 0s .30s;
648
+
649
+ opacity: 0;
650
+ visibility: hidden;
651
+ }
652
+
653
+ .admin-bar .SimpleHistory-modal__content {
654
+ top: 92px /* admin bar height is 32 px*/
655
+ }
656
+
657
+ /* style the logRow a bit more when shown in modal */
658
+ .SimpleHistory-modal .SimpleHistoryLogitem__header {
659
+ font-size: 16px;
660
+ }
661
+
662
+ .SimpleHistory-modal .SimpleHistoryLogitem__text,
663
+ .SimpleHistory-modal .SimpleHistoryLogitem__details,
664
+ .SimpleHistory-modal .SimpleHistoryLogitem__details p,
665
+ .SimpleHistory-modal .SimpleHistoryLogitem__moreDetails,
666
+ .SimpleHistory-modal .SimpleHistoryLogitem__moreDetails p {
667
+ font-size: 18px;
668
+ }
669
+
670
+ .SimpleHistory-modal .SimpleHistoryLogitem:hover {
671
+ background: inherit;
672
+ }
673
+
674
+ .SimpleHistory-modal .SimpleHistoryLogitem__senderImage {
675
+ /*opacity: 1;*/
676
+ }
677
+
678
+ .SimpleHistoryLogitem__moreDetails {
679
+ border-top: 1px solid rgb(229, 229, 229);
680
+ margin-top: 20px;
681
+ }
682
+
683
+ .SimpleHistoryLogitem__moreDetailsHeadline {
684
+ font-size: 14px;
685
+ font-weight: bold;
686
+ padding: 0;
687
+ margin-top: 20px;
688
+ margin-bottom: 0;
689
+ }
690
+
691
+ .SimpleHistory-modal .SimpleHistoryLogitem__moreDetails p {
692
+ font-size: 14px;
693
+ }
694
+
695
+ .SimpleHistoryLogitem__moreDetailsContext {
696
+ font-family: monospace;
697
+ white-space: pre-line;
698
+ font-size: 12px;
699
+ border-collapse: collapse;
700
+ }
701
+
702
+ .SimpleHistoryLogitem__moreDetailsContext th,
703
+ .SimpleHistoryLogitem__moreDetailsContext td {
704
+ padding: 4px;
705
+ padding-right: 20px;
706
+ }
707
+
708
+ .SimpleHistoryLogitem__moreDetailsContext tr:nth-child(odd) {
709
+ background-color: #eee;
710
+ }
711
+
712
+ .SimpleHistory-modal .SimpleHistoryLogitem--initiator-web_user .SimpleHistoryLogitem__secondcol,
713
+ .SimpleHistory-modal .SimpleHistoryLogitem--initiator-other .SimpleHistoryLogitem__secondcol {
714
+ margin-left: 0;
715
+ }
716
+
717
+ /** wordpress as initiator = add wordpress icon */
718
+ /** anonymous user as initiator = plain user image */
719
+ .SimpleHistoryLogitem--initiator-wp .SimpleHistoryLogitem__senderImage:before,
720
+ .SimpleHistoryLogitem--initiator-web_user .SimpleHistoryLogitem__senderImage:before {
721
+ display: inline-block;
722
+ -webkit-font-smoothing: antialiased;
723
+ font: normal 32px/1 'dashicons';
724
+ vertical-align: top;
725
+ color: #999;
726
+ }
727
+
728
+ .SimpleHistoryLogitem--initiator-wp .SimpleHistoryLogitem__senderImage:before {
729
+ content: "\f120";
730
+ }
731
+
732
+ .SimpleHistoryLogitem--initiator-web_user .SimpleHistoryLogitem__senderImage:before {
733
+ content: "\f110";
734
+ }
735
+
736
+ /** quick stats = stats above log gui */
737
+ .SimpleHistoryQuickStats,
738
+ .SimpleHistoryQuickStats p {
739
+ font-size: 16px;
740
+ }
741
+
742
+ .SimpleHistoryQuickStats a {
743
+ text-decoration: none;
744
+ }
745
+
746
+ /* in dashboard add margin and smaller fonts */
747
+ .postbox .SimpleHistoryQuickStats {
748
+ margin-left: 13px;
749
+ margin-right: 13px;
750
+ font-size: 14px;
751
+ }
752
+
753
+ .postbox .SimpleHistoryQuickStats p {
754
+ font-size: 14px;
755
+ }
756
+
757
+ /* icon on log page*/
758
+ .SimpleHistoryPageHeadline__icon {
759
+ font-size: 24px;
760
+ line-height: 30px;
761
+ width: 24px;
762
+ }
763
+
764
+ /* if not hits when using filter function */
765
+ .SimpleHistory--hasNoHits {
766
+ }
767
+
768
+ .SimpleHistory--hasNoHits .SimpleHistoryLogitems__pagination {
769
+ display: none;
770
+ }
771
+
772
+ .SimpleHistory--hasNoHits .SimpleHistoryLogitems {
773
+ display: none;
774
+ }
775
+
776
+ .SimpleHistoryLogitems__noHits {
777
+ display: none;
778
+ }
779
+
780
+ .SimpleHistory--hasNoHits .SimpleHistoryLogitems__noHits {
781
+ display: block;
782
+ }
783
+
dropins/SimpleHistoryDonateDropin.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Dropin Name: Donate things
5
+ Dropin URI: http://simple-history.com/
6
+ Author: Pär Thernström
7
+ */
8
+
9
+ /**
10
+ * Simple History Donate dropin
11
+ * Put some donate messages here and there
12
+ */
13
+ class SimpleHistoryDonateDropin {
14
+
15
+ // Simple History instance
16
+ private $sh;
17
+
18
+ function __construct($sh) {
19
+
20
+ $this->sh = $sh;
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
+ /**
27
+ * Add link to the donate page in the Plugins » Installed plugins screen
28
+ * Called from filter 'plugin_row_meta'
29
+ */
30
+ function action_plugin_row_meta($links, $file) {
31
+
32
+ if ($file == $this->sh->plugin_basename) {
33
+
34
+ $links = array_merge(
35
+ $links,
36
+ array( sprintf( '<a href="http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=pluginpage&utm_campaign=simplehistory">%1$s</a>', __('Donate', "simple-history") ) )
37
+ );
38
+
39
+ }
40
+
41
+ return $links;
42
+
43
+ }
44
+
45
+ public function add_settings() {
46
+
47
+ $settings_section_id = "simple_history_settings_section_donate";
48
+
49
+ add_settings_section(
50
+ $settings_section_id,
51
+ _x("Donate", "donate settings headline", "simple-history"), // No title __("General", "simple-history"),
52
+ array($this, "settings_section_output"),
53
+ SimpleHistory::SETTINGS_MENU_SLUG // same slug as for options menu page
54
+ );
55
+
56
+ // Empty section to make more room below
57
+ /*
58
+ add_settings_field(
59
+ "simple_history_settings_donate",
60
+ "", // __("Donate", "simple-history"),
61
+ array($this, "settings_field_donate"),
62
+ SimpleHistory::SETTINGS_MENU_SLUG,
63
+ $settings_section_id
64
+ );
65
+ */
66
+
67
+ }
68
+
69
+ function settings_section_output() {
70
+
71
+ printf(
72
+ __( 'If you find Simple History useful please <a href="%1$s">donate</a> or <a href="%2$s">buy me something from my Amazon wish list</a>.', "simple-history"),
73
+ "http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=pluginpage&utm_campaign=simplehistory",
74
+ "http://www.amazon.co.uk/registry/wishlist/IAEZWNLQQICG"
75
+ );
76
+
77
+ }
78
+
79
+
80
+ function settings_field_donate() {
81
+ }
82
+
83
+ } // end rss class
dropins/SimpleHistoryFilterDropin.css ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ /*.SimpleHistory__filters {
3
+
4
+ }*/
5
+
6
+ .SimpleHistory__filters__form select {
7
+ width: 100%;
8
+ }
9
+
10
+ .SimpleHistory__filters__form input[type=text],
11
+ .SimpleHistory__filters__form input[type=search] {
12
+ width: 100%;
13
+ }
14
+
15
+ /**
16
+ * Search results in filter
17
+ */
18
+ .SimpleHistory__filters__userfilter__gravatar,
19
+ .SimpleHistory__filters__userfilter__primary,
20
+ .SimpleHistory__filters__userfilter__secondary {
21
+ display: inline-block;
22
+ vertical-align: middle;
23
+ line-height: 1;
24
+ }
25
+
26
+ .SimpleHistory__filters__userfilter__primary {
27
+ margin-right: 5px;
28
+ }
29
+
30
+ .SimpleHistory__filters__userfilter__secondary {
31
+ color: #999;
32
+ }
33
+
34
+ .SimpleHistory__filters__userfilter__gravatar {
35
+ margin-right: 10px;
36
+ }
37
+
dropins/SimpleHistoryFilterDropin.js ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ *
3
+ */
4
+
5
+ var SimpleHistoryFilterDropin = (function($) {
6
+
7
+ var $elms = {};
8
+ var isFilteringActive = false;
9
+ var activeFilters = {};
10
+
11
+ function init() {
12
+
13
+ addFetchListener();
14
+
15
+ }
16
+
17
+ function onDomReadyInit() {
18
+
19
+ $elms.filter_container = $(".SimpleHistory__filters");
20
+ $elms.filter_user = $elms.filter_container.find(".SimpleHistory__filters__filter--user");
21
+ $elms.filter_button = $elms.filter_container.find(".js-SimpleHistoryFilterDropin-doFilter");
22
+ $elms.filter_form = $elms.filter_container.find(".js-SimpleHistory__filters__form");
23
+
24
+ enhanceSelects();
25
+ addListeners();
26
+
27
+ }
28
+
29
+ function addListeners() {
30
+
31
+ $elms.filter_form.on("submit", onSubmitForm);
32
+
33
+ }
34
+
35
+ function onSubmitForm(e) {
36
+
37
+ e.preventDefault();
38
+
39
+ // form serialize
40
+ // search=apa&loglevels=critical&loglevels=alert&loggers=SimpleMediaLogger&loggers=SimpleMenuLogger&user=1&months=2014-09 SimpleHistoryFilterDropin.js?ver=2.0:40
41
+ var $search = $elms.filter_form.find("[name='search']");
42
+ var $loglevels = $elms.filter_form.find("[name='loglevels']");
43
+ var $messages = $elms.filter_form.find("[name='messages']");
44
+ var $user = $elms.filter_form.find("[name='user']");
45
+ var $months = $elms.filter_form.find("[name='months']");
46
+
47
+ // If any of our search boxes are filled in we consider ourself to be in search mode
48
+ isFilteringActive = false;
49
+ activeFilters = {};
50
+
51
+ if ( $.trim( $search.val() )) {
52
+ isFilteringActive = true;
53
+ activeFilters.search = $search.val();
54
+ }
55
+
56
+ if ( $loglevels.val() && $loglevels.val().length ) {
57
+ isFilteringActive = true;
58
+ activeFilters.loglevels = $loglevels.val();
59
+ }
60
+
61
+ if ( $messages.val() && $messages.val().length ) {
62
+ isFilteringActive = true;
63
+ activeFilters.messages = $messages.val();
64
+ }
65
+
66
+ if ( $.trim( $user.val() )) {
67
+ isFilteringActive = true;
68
+ activeFilters.user = $user.val();
69
+ }
70
+
71
+ if ( $months.val() && $months.val().length ) {
72
+ isFilteringActive = true;
73
+ activeFilters.months = $months.val();
74
+ }
75
+
76
+ //console.log( "filtering is active:", isFilteringActive );
77
+ // console.log($search.val(), $loglevels.val(), $loggers.val(), $user.val(), $months.val());
78
+
79
+ // Reload the log rows collection
80
+ simple_history.logRowsCollection.reload();
81
+
82
+ }
83
+
84
+ function addFetchListener() {
85
+
86
+ $(document).on("SimpleHistory:mainViewInitBeforeLoadRows", function() {
87
+
88
+ // Modify query string parameters before the log rows collection fetches/syncs
89
+ simple_history.logRowsCollection.on("before_fetch", modifyFetchData);
90
+
91
+ });
92
+
93
+ // Alter api args used by new log rows notifier
94
+ $(document).on("SimpleHistory:NewRowsNotifier:apiArgs", modifyNewRowsNotifierApiArgs);
95
+
96
+
97
+ }
98
+
99
+ function modifyNewRowsNotifierApiArgs(e, apiArgs) {
100
+
101
+ if (isFilteringActive) {
102
+
103
+ apiArgs = _.extend(apiArgs, activeFilters);
104
+
105
+ }
106
+
107
+ }
108
+
109
+ function modifyFetchData(collection, url_data) {
110
+
111
+ if (isFilteringActive) {
112
+
113
+ url_data = _.extend(url_data, activeFilters);
114
+
115
+ }
116
+
117
+ }
118
+
119
+ function enhanceSelects() {
120
+
121
+ $elms.filter_user.select2({
122
+ minimumInputLength: 2,
123
+ allowClear: true,
124
+ placeholder: "All users",
125
+ ajax: {
126
+ url: ajaxurl,
127
+ dataType: "json",
128
+ cache: true,
129
+ data: function (term, page) {
130
+ return {
131
+ q: term, // search term
132
+ page_limit: 10,
133
+ action: "simple_history_filters_search_user"
134
+ };
135
+ },
136
+ results: function (data, page) { // parse the results into the format expected by Select2.
137
+ // since we are using custom formatting functions we do not need to alter remote JSON data
138
+ return data.data;
139
+ }
140
+ },
141
+ formatResult: formatUsers,
142
+ formatSelection: formatUsers,
143
+ escapeMarkup: function(m) { return m; }
144
+ });
145
+
146
+ $(".SimpleHistory__filters__filter--logger").select2({
147
+ });
148
+
149
+ $(".SimpleHistory__filters__filter--date").select2({
150
+ width: "element"
151
+ });
152
+
153
+ $(".SimpleHistory__filters__filter--loglevel").select2({
154
+ formatResult: formatLoglevel,
155
+ formatSelection: formatLoglevel,
156
+ escapeMarkup: function(m) { return m; }
157
+ });
158
+
159
+ }
160
+
161
+ function formatUsers(userdata) {
162
+
163
+ var html = "";
164
+ html += "<div class='SimpleHistory__filters__userfilter__gravatar'>";
165
+ html += userdata.gravatar;
166
+ html += "</div>";
167
+ html += "<div class='SimpleHistory__filters__userfilter__primary'>";
168
+ html += userdata.user_email;
169
+ html += "</div>";
170
+ html += "<div class='SimpleHistory__filters__userfilter__secondary'>";
171
+ html += userdata.user_login;
172
+ html += "</div>";
173
+ return html;
174
+
175
+ }
176
+
177
+ function formatLoglevel(loglevel) {
178
+
179
+ var originalOption = loglevel.element;
180
+ var $originalOption = $(originalOption);
181
+ var color = $originalOption.data("color");
182
+
183
+ var html = "<span style=\"border-radius: 50%; border: 1px solid rgba(0,0,0,.1); margin-right: 5px; width: .75em; height: .75em; line-height: 1; display: inline-block; background-color: " + $originalOption.data('color') + "; '\"></span>" + loglevel.text;
184
+ return html;
185
+
186
+ }
187
+
188
+ return {
189
+ init: init,
190
+ onDomReadyInit: onDomReadyInit
191
+ };
192
+
193
+ })(jQuery);
194
+
195
+ SimpleHistoryFilterDropin.init();
196
+
197
+ jQuery(document).ready(function() {
198
+ SimpleHistoryFilterDropin.onDomReadyInit();
199
+ });
200
+
dropins/SimpleHistoryFilterDropin.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Dropin Name: Filter GUI
5
+ Dropin URI: http://simple-history.com/
6
+ Author: Pär Thernström
7
+ */
8
+
9
+ class SimpleHistoryFilterDropin {
10
+
11
+ // Simple History instance
12
+ private $sh;
13
+
14
+ 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/history_page/after_gui", array( $this, "gui_page_filters") );
20
+ add_action("wp_ajax_simple_history_filters_search_user", array( $this, "ajax_simple_history_filters_search_user") );
21
+
22
+ }
23
+
24
+ public function enqueue_admin_scripts() {
25
+
26
+ $file_url = plugin_dir_url(__FILE__);
27
+
28
+ wp_enqueue_script("simple_history_FilterDropin", $file_url . "SimpleHistoryFilterDropin.js", array("jquery"), SimpleHistory::VERSION, true);
29
+
30
+ wp_enqueue_style("simple_history_FilterDropin", $file_url . "SimpleHistoryFilterDropin.css", null, SimpleHistory::VERSION);
31
+
32
+ }
33
+
34
+ public function gui_page_filters() {
35
+
36
+ $loggers_user_can_read = $this->sh->getLoggersThatUserCanRead();
37
+
38
+ ?>
39
+ <div class="SimpleHistory__filters">
40
+
41
+ <form class="SimpleHistory__filters__form js-SimpleHistory__filters__form">
42
+
43
+ <h3><?php _e("Filter history", "simple-history") ?></h3>
44
+
45
+ <p>
46
+ <input type="search" placeholder="Search" name="search">
47
+ </p>
48
+
49
+ <p>
50
+ <select name="loglevels" class="SimpleHistory__filters__filter SimpleHistory__filters__filter--loglevel" style="width: 300px" placeholder="<?php _e("All log levels", "simple-history") ?>" multiple>
51
+ <option value="debug" data-color="#CEF6D8">Debug</option>
52
+ <option value="info" data-color="white">Info</option>
53
+ <option value="notice" data-color="rgb(219, 219, 183)">Notice</option>
54
+ <option value="warning" data-color="#F7D358">Warning</option>
55
+ <option value="error" data-color="#F79F81">Error</option>
56
+ <option value="critical" data-color="#FA5858">Critical</option>
57
+ <option value="alert" data-color="rgb(199, 69, 69)">Alert</option>
58
+ <option value="emergency" data-color="#DF0101">Emergency</option>
59
+ </select>
60
+ </p>
61
+
62
+ <p>
63
+ <select name="messages" class="SimpleHistory__filters__filter SimpleHistory__filters__filter--logger" style="width: 300px"
64
+ placeholder="<?php _e("All messages", "simple-history") ?>" multiple>
65
+ <?php
66
+ foreach ($loggers_user_can_read as $logger) {
67
+
68
+ $logger_info = $logger["instance"]->getInfo();
69
+ $logger_slug = $logger["instance"]->slug;
70
+
71
+ // Get labels for logger
72
+ if ( isset( $logger_info["labels"]["search"] ) ) {
73
+
74
+ printf('<optgroup label="%1$s">', esc_attr( $logger_info["labels"]["search"]["label"] ) );
75
+
76
+ // If all activity
77
+ if ( ! empty( $logger_info["labels"]["search"]["label_all"] ) ) {
78
+
79
+ $arr_all_search_messages = array();
80
+ foreach ( $logger_info["labels"]["search"]["options"] as $option_key => $option_messages ) {
81
+ $arr_all_search_messages = array_merge($arr_all_search_messages, $option_messages);
82
+ }
83
+
84
+ foreach ($arr_all_search_messages as $key => $val) {
85
+ $arr_all_search_messages[ $key ] = $logger_slug . ":" . $val;
86
+ }
87
+
88
+ printf('<option value="%2$s">%1$s</option>', esc_attr( $logger_info["labels"]["search"]["label_all"] ), esc_attr( implode(",", $arr_all_search_messages) ));
89
+
90
+ }
91
+
92
+ // For each specific search option
93
+ foreach ( $logger_info["labels"]["search"]["options"] as $option_key => $option_messages ) {
94
+
95
+ foreach ($option_messages as $key => $val) {
96
+ $option_messages[ $key ] = $logger_slug . ":" . $val;
97
+ }
98
+
99
+ $str_option_messages = implode(",", $option_messages);
100
+ printf('<option value="%2$s">%1$s</option>', esc_attr( $option_key ), esc_attr( $str_option_messages ));
101
+
102
+ }
103
+
104
+ printf('</optgroup>');
105
+
106
+ }
107
+
108
+ }
109
+ ?>
110
+ </select>
111
+ </p>
112
+
113
+ <p>
114
+ <input type="text"
115
+ name = "user"
116
+ class="SimpleHistory__filters__filter SimpleHistory__filters__filter--user"
117
+ style="width: 300px"
118
+ placeholder="<?php _e("All users", "simple-history") ?>" />
119
+ </p>
120
+
121
+ <?php
122
+ global $wpdb;
123
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
124
+ $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead(null, "sql");
125
+ $sql_dates = sprintf('
126
+ SELECT DISTINCT ( date_format(DATE, "%%Y-%%m") ) AS yearMonth
127
+ FROM %s
128
+ WHERE logger IN %s
129
+ ORDER BY yearMonth DESC
130
+ ', $table_name, // 1
131
+ $loggers_user_can_read_sql_in // 2
132
+ );
133
+
134
+ $result_months = $wpdb->get_results($sql_dates);
135
+ ?>
136
+ <p>
137
+ <select class="SimpleHistory__filters__filter SimpleHistory__filters__filter--date"
138
+ name="months"
139
+ placeholder="<?php echo _e("All dates", "simple-history") ?>" multiple>
140
+ <?php
141
+ foreach ($result_months as $row) {
142
+ printf(
143
+ '<option value="%1$s">%2$s</option>',
144
+ $row->yearMonth,
145
+ date_i18n( "F Y", strtotime($row->yearMonth) )
146
+ );
147
+ }
148
+ ?>
149
+ </select>
150
+ </p>
151
+
152
+ <p>
153
+ <button class="button js-SimpleHistoryFilterDropin-doFilter"><?php _e("Filter", "simple-history") ?></button>
154
+ </p>
155
+
156
+ </form>
157
+
158
+ </div>
159
+ <?php
160
+
161
+ } // function
162
+
163
+ /**
164
+ * Return users
165
+ */
166
+ public function ajax_simple_history_filters_search_user() {
167
+
168
+ $q = isset( $_GET["q"] ) ? $_GET["q"] : "";
169
+ $page_limit = isset( $_GET["page_limit"] ) ? (int) $_GET["page_limit"] : "";
170
+
171
+ if ( ! $q || ! $page_limit ) {
172
+ return;
173
+ }
174
+
175
+ // Search both current users and all logged rows,
176
+ // because a user can change email
177
+ // search in context: user_id, user_email, user_login
178
+ // search in wp_users: login, nicename, user_email
179
+
180
+ // Can't get this simple query to work, so using my own query instead
181
+ /*
182
+ $wp_users = get_users( array(
183
+ "search" => "*{$q}*"
184
+ ));
185
+ */
186
+ global $wpdb;
187
+
188
+ if ( method_exists($wpdb, "esc_like") ) {
189
+ $str_like = $wpdb->esc_like( $q );
190
+ } else {
191
+ $str_like = like_escape( esc_sql( $q ) );
192
+ }
193
+
194
+ $sql_users = $wpdb->prepare(
195
+ 'SELECT ID as id, user_login, user_nicename, user_email, display_name FROM %1$s
196
+ WHERE
197
+ user_login LIKE "%%%2$s%%"
198
+ OR user_nicename LIKE "%%%2$s%%"
199
+ OR user_email LIKE "%%%2$s%%"
200
+ OR display_name LIKE "%%%2$s%%"
201
+ ',
202
+ $wpdb->users,
203
+ $str_like
204
+ );
205
+
206
+ $results_user = $wpdb->get_results( $sql_users );
207
+
208
+ // add gravatars to user array
209
+ array_walk( $results_user, array($this, "add_gravatar_to_user_array") );
210
+
211
+ $data = array(
212
+ "results" => array(
213
+ ),
214
+ "more" => false,
215
+ "context" => array()
216
+ );
217
+
218
+ $data["results"] = array_merge( $data["results"], $results_user );
219
+
220
+ wp_send_json_success( $data );
221
+
222
+ } // function
223
+
224
+ function add_gravatar_to_user_array(& $val, $index) {
225
+
226
+ $val->text = sprintf(
227
+ '%1$s - %2$s',
228
+ $val->user_login,
229
+ $val->user_email
230
+ );
231
+
232
+ $val->gravatar = $this->sh->get_avatar( $val->user_email, "18", "mm");
233
+
234
+ }
235
+
236
+ } // end class
237
+
dropins/SimpleHistoryIpInfoDropin.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ .SimpleHistoryLogitem__anonUserWithIp {
2
+ }
3
+
4
+ .SimpleHistoryLogitem__anonUserWithIp__theIp:hover {
5
+ /* cursor: pointer;
6
+ text-decoration: underline;*/
7
+ }
dropins/SimpleHistoryIpInfoDropin.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ (function($) {
3
+
4
+ var $logItems = $(".SimpleHistoryLogitems");
5
+
6
+ $logItems.on("click", ".SimpleHistoryLogitem__anonUserWithIp__theIp", function(e) {
7
+
8
+ var $elm = $(this);
9
+ var ipAddress = $elm.closest(".SimpleHistoryLogitem").data("ipAddress");
10
+
11
+ if (! ipAddress) {
12
+ return;
13
+ }
14
+
15
+ return lookupIpAddress(ipAddress);
16
+
17
+ });
18
+
19
+ function lookupIpAddress(ipAddress) {
20
+
21
+ $.get("http://ipinfo.io/" + ipAddress, onIpAddressLookupkResponse, "jsonp");
22
+
23
+ return false;
24
+
25
+ }
26
+
27
+ function onIpAddressLookupkResponse(d) {
28
+
29
+ console.log("got data", d);
30
+
31
+ }
32
+
33
+ })(jQuery);
dropins/SimpleHistoryIpInfoDropin.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Dropin Name: IP Info
5
+ Dropin URI: http://simple-history.com/
6
+ Author: Pär Thernström
7
+ */
8
+
9
+ class SimpleHistoryIpInfoDropin {
10
+
11
+ private $sh;
12
+
13
+ function __construct($sh) {
14
+
15
+ $this->sh = $sh;
16
+
17
+ // Since it's not quite done yet, it's for da devs only for now
18
+ if ( ! defined("SIMPLE_HISTORY_DEV") || ! SIMPLE_HISTORY_DEV ) {
19
+ return;
20
+ }
21
+
22
+ add_action("simple_history/enqueue_admin_scripts", array($this, "enqueue_admin_scripts"));
23
+
24
+ }
25
+
26
+ public function enqueue_admin_scripts() {
27
+
28
+ $file_url = plugin_dir_url(__FILE__);
29
+
30
+ wp_enqueue_script("simple_history_IpInfoDropin", $file_url . "SimpleHistoryIpInfoDropin.js", array("jquery"), SimpleHistory::VERSION, true);
31
+
32
+ wp_enqueue_style("simple_history_IpInfoDropin", $file_url . "SimpleHistoryIpInfoDropin.css", null, SimpleHistory::VERSION);
33
+
34
+ }
35
+
36
+ } // end class
37
+
dropins/SimpleHistoryNewRowsNotifier.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Dropin Name: New Items Notifier
5
+ * Dropin Description: Checks for new rows and displays a info bar when new items are available
6
+ * Dropin URI: http://simple-history.com/
7
+ * Author: Pär Thernström
8
+ */
9
+
10
+ class SimpleHistoryNewRowsNotifier {
11
+
12
+ // Simple History instance
13
+ private $sh;
14
+
15
+ // How often we should check for new rows, in ms
16
+ private $interval = 10000;
17
+
18
+ function __construct($sh) {
19
+
20
+ $this->sh = $sh;
21
+
22
+ // How often the script checks for new rows
23
+ $this->interval = (int) apply_filters("SimpleHistoryNewRowsNotifier/interval", $this->interval);
24
+
25
+ add_action( 'wp_ajax_SimpleHistoryNewRowsNotifier', array($this, 'ajax') );
26
+ add_action( "simple_history/enqueue_admin_scripts", array($this, "enqueue_admin_scripts") );
27
+
28
+ }
29
+
30
+ public function enqueue_admin_scripts() {
31
+
32
+ $file_url = plugin_dir_url(__FILE__);
33
+
34
+ wp_enqueue_script("simple_history_NewRowsNotifierDropin", $file_url . "SimpleHistoryNewRowsNotifierDropin.js", array("jquery"), SimpleHistory::VERSION, true);
35
+
36
+ $arr_localize_data = array(
37
+ "interval" => $this->interval,
38
+ "errorCheck" =>_x('An error occured while checking for new log rows', 'New rows notifier: error while checking for new rows', 'simple-history')
39
+ );
40
+
41
+ wp_localize_script( "simple_history_NewRowsNotifierDropin", "simple_history_NewRowsNotifierDropin", $arr_localize_data );
42
+
43
+ wp_enqueue_style( "simple_history_NewRowsNotifierDropin", $file_url . "SimpleHistoryNewRowsNotifierDropin.css", null, SimpleHistory::VERSION);
44
+
45
+ }
46
+
47
+ public function ajax() {
48
+
49
+ $apiArgs = isset( $_GET["apiArgs"] ) ? $_GET["apiArgs"] : array();
50
+
51
+ if ( ! $apiArgs ) {
52
+ wp_send_json_error( array("error" => "MISSING_APIARGS") );
53
+ exit;
54
+ }
55
+
56
+ if ( empty( $apiArgs["since_id"] ) || ! is_numeric( $apiArgs["since_id"] ) ) {
57
+ wp_send_json_error( array("error" => "MISSING_SINCE_ID") );
58
+ exit;
59
+ }
60
+
61
+ // $since_id = isset( $_GET["since_id"] ) ? absint($_GET["since_id"]) : null;
62
+
63
+ $logQueryArgs = $apiArgs;
64
+
65
+ $logQuery = new SimpleHistoryLogQuery();
66
+
67
+ $answer = $logQuery->query( $logQueryArgs );
68
+
69
+ // Use our own repsonse array instead of $answer to keep size down
70
+ $json_data = array();
71
+
72
+ $numNewRows = isset( $answer["total_row_count"] ) ? $answer["total_row_count"] : 0;
73
+ $json_data["num_new_rows"] = $numNewRows;
74
+
75
+ if ($numNewRows) {
76
+
77
+ // We have new rows
78
+
79
+ // Append strings
80
+ $textRowsFound = sprintf( _n( '1 new row', '%d new rows', $numNewRows, 'simple-history' ), $numNewRows );
81
+ $json_data["strings"] = array(
82
+ "newRowsFound" => $textRowsFound
83
+ );
84
+
85
+ }
86
+
87
+ wp_send_json_success( $json_data );
88
+
89
+ }
90
+
91
+ } // class
92
+
dropins/SimpleHistoryNewRowsNotifierDropin.css ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .SimpleHistoryDropin__NewRowsNotifier {
2
+ max-height: 0;
3
+ overflow: hidden;
4
+ text-align: center;
5
+ background: white;
6
+ line-height: 40px;
7
+ background: rgba(0, 255, 30, 0.15);
8
+ -webkit-transition: max-height .5s ease-out, background 0s;
9
+ transition: max-height .5s ease-out, background 0s;
10
+ }
11
+
12
+ .SimpleHistoryDropin__NewRowsNotifier--haveNewRows {
13
+ max-height: 50px;
14
+ cursor: pointer;
15
+ }
16
+
17
+ .SimpleHistoryDropin__NewRowsNotifier--haveNewRows:before {
18
+ content: '\f463';
19
+ font: 400 20px/1 dashicons;
20
+ -webkit-font-smoothing: antialiased;
21
+ display: inline-block;
22
+ vertical-align: middle;
23
+ margin-right: .5em;
24
+ }
25
+
26
+ .SimpleHistoryDropin__NewRowsNotifier--haveNewRows:hover {
27
+ /*text-decoration: underline;*/
28
+ background: rgba(0, 255, 30, 0.5);
29
+ }
30
+
31
+ /* when there is a remote error or server down etc */
32
+ .SimpleHistoryDropin__NewRowsNotifier--haveErrorCheck {
33
+ max-height: 50px;
34
+ background: rgb(254, 247, 241);
35
+ }
36
+
37
+ .SimpleHistoryLogitem--newRowSinceReload {
38
+ background: yellow;
39
+ background: rgba(254, 255, 0, 0.1);
40
+ }
dropins/SimpleHistoryNewRowsNotifierDropin.js ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+
3
+ var elmWrapperClass = ".SimpleHistoryLogitems__above";
4
+ var $elmWrapper;
5
+ var $elm;
6
+ var ajaxurl = window.ajaxurl;
7
+ var intervalID;
8
+ var ajax_jqXHR;
9
+
10
+ var checkForUpdates = function() {
11
+
12
+ var firstPageMaxID = simple_history.logRowsCollection.max_id_first_page;
13
+ var apiArgs = {
14
+ since_id: firstPageMaxID
15
+ };
16
+
17
+ // Let plugins filter the API args
18
+ $(document).trigger("SimpleHistory:NewRowsNotifier:apiArgs", apiArgs);
19
+
20
+ ajax_jqXHR = $.get(ajaxurl, {
21
+ action: "SimpleHistoryNewRowsNotifier",
22
+ apiArgs: apiArgs
23
+ }, function() {}, "json").done(function(response) {
24
+
25
+ // Always remove possible error class
26
+ $elm.removeClass("SimpleHistoryDropin__NewRowsNotifier--haveErrorCheck");
27
+
28
+ // If new rows have been added then max_id is not 0 and larger than previous max id
29
+ // Also total_row_count shows the number of added rows
30
+ if (response && response.data && response.data.num_new_rows) {
31
+
32
+ $elm.html( response.data.strings.newRowsFound );
33
+ $elm.addClass("SimpleHistoryDropin__NewRowsNotifier--haveNewRows");
34
+
35
+ }
36
+
37
+ }).fail(function(jqXHR, textStatus, errorThrown) {
38
+
39
+ $elm.html( simple_history_NewRowsNotifierDropin.errorCheck );
40
+ $elm.addClass("SimpleHistoryDropin__NewRowsNotifier--haveErrorCheck");
41
+
42
+ });
43
+
44
+ };
45
+
46
+ // When the log is loaded the first time
47
+ // Actually it's also called when log is reloaded
48
+ $(document).on("SimpleHistory:logRowsCollectionFirstLoad", function() {
49
+
50
+ if (!$elmWrapper) {
51
+
52
+ $elmWrapper = $(elmWrapperClass);
53
+ $elm = $("<div />",{
54
+ class: "SimpleHistoryDropin__NewRowsNotifier"
55
+ });
56
+ $elm.appendTo($elmWrapper);
57
+
58
+ }
59
+
60
+ intervalID = setInterval(checkForUpdates, simple_history_NewRowsNotifierDropin.interval);
61
+
62
+ });
63
+
64
+ // Reload the log When we click on the div with info about new rows
65
+ $(document).on("click", ".SimpleHistoryDropin__NewRowsNotifier", function(e) {
66
+
67
+ // Stop polling and stop any outgoing ajax request
68
+ clearInterval(intervalID);
69
+ ajax_jqXHR.abort();
70
+
71
+ var prev_max_id = simple_history.rowsView.collection.max_id;
72
+
73
+ simple_history.rowsView.once("renderDone", function() {
74
+
75
+ var new_max_id = this.collection.max_id;
76
+
77
+ var $logItems = jQuery(".SimpleHistoryLogitems li");
78
+ var $newLogItems = $logItems.filter(function(i, elm) {
79
+ var $elm = $(elm);
80
+ var rowID = parseInt( $elm.data("row-id"), 10 );
81
+ return (rowID > prev_max_id);
82
+ });
83
+
84
+ $newLogItems.addClass("SimpleHistoryLogitem--newRowSinceReload");
85
+
86
+ });
87
+
88
+ simple_history.logRowsCollection.reload();
89
+
90
+ });
91
+
92
+ $(document).on("SimpleHistory:logRowsCollectionReload", function() {
93
+
94
+ if (!$elm) {
95
+ return;
96
+ }
97
+
98
+ $elm.removeClass("SimpleHistoryDropin__NewRowsNotifier--haveNewRows");
99
+ });
100
+
101
+ }(jQuery));
dropins/SimpleHistoryRSSDropin.php ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Dropin Name: Global RSS Feed
5
+ Dropin URI: http://simple-history.com/
6
+ Author: Pär Thernström
7
+ */
8
+
9
+ /**
10
+ * Simple History RSS Feed drop-in
11
+ */
12
+ class SimpleHistoryRSSDropin {
13
+
14
+ function __construct($sh) {
15
+
16
+ $this->sh = $sh;
17
+
18
+ if ( ! function_exists('get_editable_roles') ) {
19
+ require_once( ABSPATH . '/wp-admin/includes/user.php' );
20
+ }
21
+
22
+ // Generate a rss secret, if it does not exist
23
+ if ( ! get_option("simple_history_rss_secret") ) {
24
+ $this->update_rss_secret();
25
+ }
26
+
27
+ add_action( 'init', array($this, 'check_for_rss_feed_request') );
28
+
29
+ // Add settings with prio 11 so it' added after the main Simple History settings
30
+ add_action( 'admin_menu', array($this, 'add_settings'), 11 );
31
+
32
+ }
33
+
34
+ /**
35
+ * Add settings for the RSS feed
36
+ * + also regenerates the secret if requested
37
+ */
38
+ public function add_settings() {
39
+
40
+ /**
41
+ * Start new section for RSS feed
42
+ */
43
+ $settings_section_rss_id = "simple_history_settings_section_rss";
44
+
45
+ add_settings_section(
46
+ $settings_section_rss_id,
47
+ _x("RSS feed", "rss 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
+ // RSS address
53
+ add_settings_field(
54
+ "simple_history_rss_feed",
55
+ __("Address", "simple-history"),
56
+ array($this, "settings_field_rss"),
57
+ SimpleHistory::SETTINGS_MENU_SLUG,
58
+ $settings_section_rss_id
59
+ );
60
+
61
+ // Regnerate address
62
+ add_settings_field(
63
+ "simple_history_rss_feed_regenerate_secret",
64
+ __("Regenerate", "simple-history"),
65
+ array($this, "settings_field_rss_regenerate"),
66
+ SimpleHistory::SETTINGS_MENU_SLUG,
67
+ $settings_section_rss_id
68
+ );
69
+
70
+ // Create new RSS secret
71
+ $create_new_secret = false;
72
+ $create_secret_nonce_name = "simple_history_rss_secret_regenerate_nonce";
73
+
74
+ if ( isset( $_GET[$create_secret_nonce_name] ) && wp_verify_nonce( $_GET[$create_secret_nonce_name], 'simple_history_rss_update_secret')) {
75
+
76
+ $create_new_secret = true;
77
+ $this->update_rss_secret();
78
+
79
+ // Add updated-message and store in transient and then redirect
80
+ // This is the way options.php does it.
81
+ $msg = __("Created new secret RSS address", 'simple-history');
82
+ add_settings_error( "simple_history_rss_feed_regenerate_secret", "simple_history_rss_feed_regenerate_secret", $msg, "updated" );
83
+ set_transient('settings_errors', get_settings_errors(), 30);
84
+
85
+ $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
86
+ wp_redirect( $goback );
87
+ exit;
88
+
89
+ }
90
+
91
+ } // settings
92
+
93
+
94
+ /**
95
+ * Check if current request is a request for the RSS feed
96
+ */
97
+ function check_for_rss_feed_request() {
98
+
99
+ // check for RSS
100
+ // don't know if this is the right way to do this, but it seems to work!
101
+ if ( isset( $_GET["simple_history_get_rss"] ) ) {
102
+
103
+ $this->output_rss();
104
+ exit;
105
+
106
+ }
107
+
108
+ }
109
+
110
+ /**
111
+ * Modify capability check so all users reading rss feed (logged in or not) can read all loggers
112
+ */
113
+ public function on_can_read_single_logger( $user_can_read_logger, $logger_instance, $user_id ) {
114
+
115
+ $user_can_read_logger = true;
116
+
117
+ return $user_can_read_logger;
118
+
119
+ }
120
+
121
+ /**
122
+ * Output RSS
123
+ */
124
+ function output_rss() {
125
+
126
+ $rss_secret_option = get_option("simple_history_rss_secret");
127
+ $rss_secret_get = isset( $_GET["rss_secret"] ) ? $_GET["rss_secret"] : "";
128
+
129
+ if ( empty( $rss_secret_option ) || empty( $rss_secret_get ) ) {
130
+ die();
131
+ }
132
+
133
+ $rss_show = true;
134
+ $rss_show = apply_filters("simple_history/rss_feed_show", $rss_show);
135
+ if( ! $rss_show ) {
136
+ wp_die( 'Nothing here.' );
137
+ }
138
+
139
+ header ("Content-Type:text/xml");
140
+ echo '<?xml version="1.0" encoding="UTF-8"?>';
141
+ $self_link = $this->get_rss_address();
142
+
143
+ if ( $rss_secret_option === $rss_secret_get ) {
144
+
145
+ ?>
146
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
147
+ <channel>
148
+ <title><?php printf(__("History for %s", 'simple-history'), get_bloginfo("name")) ?></title>
149
+ <description><?php printf(__("WordPress History for %s", 'simple-history'), get_bloginfo("name")) ?></description>
150
+ <link><?php echo get_bloginfo("url") ?></link>
151
+ <atom:link href="<?php echo $self_link; ?>" rel="self" type="application/atom+xml" />
152
+ <?php
153
+
154
+ // Override capability check: if you have a valid rss_secret_key you can read it all
155
+ $action_tag = "simple_history/loggers_user_can_read/can_read_single_logger";
156
+ add_action( $action_tag, array($this, "on_can_read_single_logger") );
157
+
158
+ // Modify header time output so it does not show relative date or time ago-format
159
+ // Because we don't know when a user reads the RSS feed, time ago format may be very inaccurate
160
+ add_action("simple_history/header_just_now_max_time", "__return_zero");
161
+ add_action("simple_history/header_time_ago_max_time", "__return_zero");
162
+
163
+ // Get log rows
164
+ $args = array(
165
+ "posts_per_page" => 10
166
+ );
167
+
168
+ $args = apply_filters("simple_history/rss_feed_args", $args);
169
+
170
+ $logQuery = new SimpleHistoryLogQuery();
171
+ $queryResults = $logQuery->query($args);
172
+
173
+ // Remove capability override after query is done
174
+ // remove_action( $action_tag, array($this, "on_can_read_single_logger") );
175
+
176
+ foreach ($queryResults["log_rows"] as $row) {
177
+
178
+ $header_output = $this->sh->getLogRowHeaderOutput( $row );
179
+ $text_output = $this->sh->getLogRowPlainTextOutput( $row );
180
+ $details_output = $this->sh->getLogRowDetailsOutput( $row );
181
+ $item_guid = home_url() . "?SimpleHistoryGuid=" . $row->id;
182
+
183
+ #$item_title = wp_kses( $header_output . ": " . $text_output, array() );
184
+ $item_title = wp_kses( $text_output, array() );
185
+
186
+ ?>
187
+ <item>
188
+ <title><?php echo $item_title; ?></title>
189
+ <description><![CDATA[
190
+ <p><?php echo $header_output ?></p>
191
+ <p><?php echo $text_output ?></p>
192
+ <div><?php echo $details_output ?></div>
193
+ <?php
194
+ $occasions = $row->subsequentOccasions - 1;
195
+ if ( $occasions ) {
196
+ printf( _n('+%1$s occasion', '+%1$s occasions', "simple-history"), $occasions );
197
+ }
198
+ ?>
199
+ ]]></description>
200
+ <author><?php echo $row->initiator ?></author>
201
+ <pubDate><?php echo date("D, d M Y H:i:s", strtotime($row->date)) ?> GMT</pubDate>
202
+ <guid isPermaLink="false"><?php echo $item_guid ?></guid>
203
+ <link><?php echo $item_guid ?></link>
204
+ </item>
205
+ <?php
206
+ /*
207
+ [0] =&gt; stdClass Object
208
+ (
209
+ [id] =&gt; 27324
210
+ [logger] =&gt; SimplePluginLogger
211
+ [level] =&gt; info
212
+ [date] =&gt; 2014-10-15 06:50:01
213
+ [message] =&gt; Updated plugin &quot;{plugin_name}&quot; from {plugin_prev_version} to {plugin_version}
214
+ [type] =&gt;
215
+ [initiator] =&gt; wp_user
216
+ [occasionsID] =&gt; 75e8aeab3e43b37f8a458f3744c4995f
217
+ [subsequentOccasions] =&gt; 1
218
+ [rep] =&gt; 1
219
+ [repeated] =&gt; 1
220
+ [occasionsIDType] =&gt; 75e8aeab3e43b37f8a458f3744c4995f
221
+ [context] =&gt; Array
222
+ (
223
+ [plugin_slug] =&gt; google-analytics-for-wordpress
224
+ [plugin_name] =&gt; Google Analytics by Yoast
225
+ [plugin_title] =&gt; &lt;a href=&quot;https://yoast.com/wordpress/plugins/google-analytics/#utm_source=wordpress&amp;#038;utm_medium=plugin&amp;#038;utm_campaign=wpgaplugin&amp;#038;utm_content=v504&quot;&gt;Google Analytics by Yoast&lt;/a&gt;
226
+ [plugin_description] =&gt; This plugin makes it simple to add Google Analytics to your WordPress blog, adding lots of features, eg. error page, search result and automatic clickout and download tracking. &lt;cite&gt;By &lt;a href=&quot;https://yoast.com/&quot;&gt;Team Yoast&lt;/a&gt;.&lt;/cite&gt;
227
+ [plugin_author] =&gt; &lt;a href=&quot;https://yoast.com/&quot;&gt;Team Yoast&lt;/a&gt;
228
+ [plugin_version] =&gt; 5.0.7
229
+ [plugin_url] =&gt; https://yoast.com/wordpress/plugins/google-analytics/#utm_source=wordpress&amp;#038;utm_medium=plugin&amp;#038;utm_campaign=wpgaplugin&amp;#038;utm_content=v504
230
+ [plugin_update_info_plugin] =&gt; google-analytics-for-wordpress/googleanalytics.php
231
+ [plugin_update_info_package] =&gt; https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.5.0.7.zip
232
+ [plugin_prev_version] =&gt; 5.0.6
233
+ [_message_key] =&gt; plugin_bulk_updated
234
+ [_user_id] =&gt; 1
235
+ [_user_login] =&gt; admin
236
+ [_user_email] =&gt; par.thernstrom@gmail.com
237
+ [_server_remote_addr] =&gt; ::1
238
+ [_server_http_referer] =&gt; http://playground-root.ep/wp-admin/update-core.php?action=do-plugin-upgrade
239
+ )
240
+
241
+ )
242
+ */
243
+
244
+ }
245
+
246
+ ?>
247
+ </channel>
248
+ </rss>
249
+ <?php
250
+ } else {
251
+
252
+ // RSS secret was not ok
253
+ ?>
254
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
255
+ <channel>
256
+ <title><?php printf(__("History for %s", 'simple-history'), get_bloginfo("name")) ?></title>
257
+ <description><?php printf(__("WordPress History for %s", 'simple-history'), get_bloginfo("name")) ?></description>
258
+ <link><?php echo home_url() ?></link>
259
+ <item>
260
+ <title><?php _e("Wrong RSS secret", 'simple-history')?></title>
261
+ <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>
262
+ <pubDate><?php echo date("D, d M Y H:i:s", time()) ?> GMT</pubDate>
263
+ <guid><?php echo home_url() . "?SimpleHistoryGuid=wrong-secret" ?></guid>
264
+ </item>
265
+ </channel>
266
+ </rss>
267
+ <?php
268
+
269
+ }
270
+
271
+ } // rss
272
+
273
+
274
+ /**
275
+ * Create a new RSS secret
276
+ *
277
+ * @return string new secret
278
+ */
279
+ function update_rss_secret() {
280
+
281
+ $rss_secret = "";
282
+
283
+ for ($i=0; $i<20; $i++) {
284
+ $rss_secret .= chr(rand(97,122));
285
+ }
286
+
287
+ update_option("simple_history_rss_secret", $rss_secret);
288
+
289
+ return $rss_secret;
290
+ }
291
+
292
+ /**
293
+ * Output for settings field that show current RSS address
294
+ */
295
+ function settings_field_rss() {
296
+
297
+ $rss_address = $this->get_rss_address();
298
+
299
+ echo "<p><code><a href='$rss_address'>$rss_address</a></code></p>";
300
+
301
+ }
302
+
303
+ /**
304
+ * Output for settings field that regenerates the RSS adress/secret
305
+ */
306
+ function settings_field_rss_regenerate() {
307
+
308
+ $update_link = add_query_arg("", "");
309
+ $update_link = wp_nonce_url( $update_link, "simple_history_rss_update_secret", "simple_history_rss_secret_regenerate_nonce" );
310
+
311
+ echo "<p>";
312
+ _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');
313
+ echo "</p>";
314
+ echo "<p>";
315
+ printf( '<a class="button" href="%1$s">%2$s</a>', $update_link, __('Generate new address', "simple-history") );
316
+
317
+ echo "</p>";
318
+
319
+ }
320
+
321
+
322
+ /**
323
+ * Get the URL to the RSS feed
324
+ * @return string URL
325
+ */
326
+ function get_rss_address() {
327
+
328
+ $rss_secret = get_option("simple_history_rss_secret");
329
+ $rss_address = add_query_arg(array("simple_history_get_rss" => "1", "rss_secret" => $rss_secret), get_bloginfo("url") . "/");
330
+ $rss_address = htmlspecialchars($rss_address, ENT_COMPAT, "UTF-8");
331
+
332
+ return $rss_address;
333
+
334
+ }
335
+
336
+ /**
337
+ * Content for section intro. Leave it be, even if empty.
338
+ * Called from add_sections_setting.
339
+ */
340
+ function settings_section_output() {
341
+
342
+ echo "<p>";
343
+ _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');
344
+ echo "</p>";
345
+
346
+ }
347
+
348
+ } // end rss class
dropins/SimpleHistorySettingsLogtestDropin.php ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class SimpleHistorySettingsLogtestDropin {
4
+
5
+ // Simple History instance
6
+ private $sh;
7
+
8
+ public function __construct($sh) {
9
+
10
+ // Since it's not quite done yet, it's for da devs only for now
11
+ if ( ! defined("SIMPLE_HISTORY_DEV") || ! SIMPLE_HISTORY_DEV ) {
12
+ return;
13
+ }
14
+
15
+ $this->sh = $sh;
16
+
17
+ // How do we register this to the settings array?
18
+ $sh->registerSettingsTab(array(
19
+ "slug" => "testLog",
20
+ "name" => __("Test data (debug)", "simple-history"),
21
+ "function" => array($this, "output")
22
+ ));
23
+
24
+ #add_action( 'admin_enqueue_scripts', array( $this, 'on_admin_enqueue_scripts') );
25
+ add_action( 'admin_head', array( $this, "on_admin_head" ) );
26
+ add_action( 'wp_ajax_SimpleHistoryAddLogTest', array( $this, "on_ajax_add_logtests" ) );
27
+
28
+ }
29
+
30
+ public function on_ajax_add_logtests() {
31
+
32
+ $this->doLogTestThings();
33
+
34
+ $arr = array(
35
+ "message" => "did it!"
36
+ );
37
+
38
+ wp_send_json_success( $arr );
39
+
40
+ }
41
+
42
+ public function on_admin_head() {
43
+
44
+ ?>
45
+ <script>
46
+
47
+ jQuery(function($) {
48
+
49
+ var button = $(".js-SimpleHistorySettingsLogtestDropin-addStuff");
50
+ var messageDone = $(".js-SimpleHistorySettingsLogtestDropin-addStuffDone");
51
+ var messageWorking = $(".js-SimpleHistorySettingsLogtestDropin-addStuffWorking");
52
+
53
+ button.on("click", function(e) {
54
+
55
+ messageWorking.show();
56
+ messageDone.hide();
57
+
58
+ $.post(ajaxurl, {
59
+ action: "SimpleHistoryAddLogTest"
60
+ }).done(function(r) {
61
+
62
+ messageWorking.hide();
63
+ messageDone.show();
64
+
65
+ });
66
+
67
+ });
68
+
69
+ });
70
+
71
+
72
+ </script>
73
+ <?php
74
+
75
+ }
76
+
77
+ public function output() {
78
+
79
+ ?>
80
+ <h2>Test data</h2>
81
+
82
+ <p>Add lots of test data to the log database.</p>
83
+
84
+ <p>
85
+ <button class="button js-SimpleHistorySettingsLogtestDropin-addStuff">Ok, add lots of stuff to the log!</button>
86
+ </p>
87
+
88
+ <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffDone">
89
+ <p>Done! Added lots of test rows</p>
90
+ </div>
91
+
92
+ <div class="updated hidden js-SimpleHistorySettingsLogtestDropin-addStuffWorking">
93
+ <p>Adding...</p>
94
+ </div>
95
+
96
+ <?php
97
+
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 = date('Y-m-d H:i:s', strtotime("now -{$i}days"));
107
+ SimpleLogger()->info(
108
+ 'Entry with date in the past', array(
109
+ "_date" => $str_date,
110
+ "_occasionsID" => "past_date:{$str_date}"
111
+ ));
112
+ }
113
+ }
114
+
115
+ SimpleLogger()->info("This is a message sent to the log");
116
+
117
+ // Second log entry with same info will make these two become an occasionGroup,
118
+ // collapsing their entries into one expandable log item
119
+ SimpleLogger()->info("This is a message sent to the log");
120
+
121
+ // Log entries can be of different severity
122
+ SimpleLogger()->info("User admin edited page 'About our company'");
123
+ SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
124
+ SimpleLogger()->debug("Ok, cron job is running!");
125
+
126
+ // Log entries can have placeholders and context
127
+ // This makes log entried translatable and filterable
128
+ for ($i = 0; $i < rand(1, 50); $i++) {
129
+ SimpleLogger()->notice(
130
+ "User {username} edited page {pagename}",
131
+ array(
132
+ "username" => "bonnyerden",
133
+ "pagename" => "My test page",
134
+ "_initiator" => SimpleLoggerLogInitiators::WP_USER,
135
+ "_user_id" => rand(1,20),
136
+ "_user_login" => "loginname" . rand(1,20),
137
+ "_user_email" => "user" . rand(1,20) . "@example.com"
138
+ )
139
+ );
140
+ }
141
+ #return;
142
+
143
+ // Log entried can have custom occasionsID
144
+ // This will group items together and a log entry will only be shown once
145
+ // in the log overview
146
+ for ($i = 0; $i < rand(1, 50); $i++) {
147
+ SimpleLogger()->notice("User {username} edited page {pagename}", array(
148
+ "username" => "admin",
149
+ "pagename" => "My test page",
150
+ "_occasionsID" => "username:1,postID:24884,action:edited"
151
+ ));
152
+ }
153
+
154
+ SimpleLogger()->info(
155
+ "WordPress updated itself from version {from_version} to {to_version}",
156
+ array(
157
+ "from_version" => "3.8",
158
+ "to_version" => "3.8.1",
159
+ "_initiator" => SimpleLoggerLogInitiators::WORDPRESS
160
+ )
161
+ );
162
+
163
+ SimpleLogger()->info(
164
+ "Plugin {plugin_name} was updated from version {plugin_from_version} to version {plugin_to_version}",
165
+ array(
166
+ "plugin_name" => "CMS Tree Page View",
167
+ "plugin_from_version" => "4.0",
168
+ "plugin_to_version" => "4.2",
169
+ "_initiator" => SimpleLoggerLogInitiators::WORDPRESS
170
+ )
171
+ );
172
+
173
+ SimpleLogger()->info(
174
+ "Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}",
175
+ array(
176
+ "plugin_name" => "Ninja Forms",
177
+ "plugin_from_version" => "1.1",
178
+ "plugin_to_version" => "1.1.2",
179
+ "_initiator" => SimpleLoggerLogInitiators::WP_USER
180
+ )
181
+ );
182
+
183
+ SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
184
+ "_initiator" => SimpleLoggerLogInitiators::WEB_USER
185
+ ));
186
+
187
+ SimpleLogger()->info(
188
+ "Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}",
189
+ array(
190
+ "plugin_name" => "Simple Fields",
191
+ "plugin_from_version" => "1.3.7",
192
+ "plugin_to_version" => "1.3.8",
193
+ "_initiator" => SimpleLoggerLogInitiators::WP_USER
194
+ )
195
+ );
196
+
197
+ SimpleLogger()->error("A JavaScript error was detected on page 'About us'", array(
198
+ "_initiator" => SimpleLoggerLogInitiators::WEB_USER
199
+ ));
200
+
201
+ SimpleLogger()->debug("WP Cron 'my_test_cron_job' finished in 0.012 seconds", array(
202
+ "_initiator" => SimpleLoggerLogInitiators::WORDPRESS
203
+ ));
204
+
205
+ for ($i = 0; $i < rand(50,1000); $i++) {
206
+ SimpleLogger()->warning(
207
+ 'An attempt to login as user "{user_login}" failed to login because the wrong password was entered', array(
208
+ "user_login" => "admin",
209
+ "_userID" => null,
210
+ "_initiator" => SimpleLoggerLogInitiators::WEB_USER
211
+ ));
212
+ }
213
+
214
+ // Add more data to context array. Data can be used later on to show detailed info about a log entry.
215
+ SimpleLogger()->info("Edited product '{pagename}'", array(
216
+ "pagename" => "We are hiring!",
217
+ "_postType" => "product",
218
+ "_userID" => 1,
219
+ "_userLogin" => "jessie",
220
+ "_userEmail" => "jessie@example.com",
221
+ "_occasionsID" => "username:1,postID:24885,action:edited"
222
+ ));
223
+
224
+ }
225
+
226
+ }
227
+
228
+
229
+ /*
230
+ add_action("init", function() {
231
+
232
+ register_post_type("texts", array(
233
+ "show_ui" => true
234
+ ));
235
+
236
+ register_post_type("products", array(
237
+ "labels" => array(
238
+ "name" => "Products",
239
+ "singular_name" => "Product"
240
+ ),
241
+ "public" => true
242
+ ));
243
+
244
+ // Example from the codex
245
+ $labels = array(
246
+ 'name' => _x( 'Books', 'post type general name', 'your-plugin-textdomain' ),
247
+ 'singular_name' => _x( 'Book', 'post type singular name', 'your-plugin-textdomain' ),
248
+ 'menu_name' => _x( 'Books', 'admin menu', 'your-plugin-textdomain' ),
249
+ 'name_admin_bar' => _x( 'Book', 'add new on admin bar', 'your-plugin-textdomain' ),
250
+ 'add_new' => _x( 'Add New', 'book', 'your-plugin-textdomain' ),
251
+ 'add_new_item' => __( 'Add New Book', 'your-plugin-textdomain' ),
252
+ 'new_item' => __( 'New Book', 'your-plugin-textdomain' ),
253
+ 'edit_item' => __( 'Edit Book', 'your-plugin-textdomain' ),
254
+ 'view_item' => __( 'View Book', 'your-plugin-textdomain' ),
255
+ 'all_items' => __( 'All Books', 'your-plugin-textdomain' ),
256
+ 'search_items' => __( 'Search Books', 'your-plugin-textdomain' ),
257
+ 'parent_item_colon' => __( 'Parent Books:', 'your-plugin-textdomain' ),
258
+ 'not_found' => __( 'No books found.', 'your-plugin-textdomain' ),
259
+ 'not_found_in_trash' => __( 'No books found in Trash.', 'your-plugin-textdomain' ),
260
+ );
261
+
262
+ $args = array(
263
+ 'labels' => $labels,
264
+ 'public' => true,
265
+ 'publicly_queryable' => true,
266
+ 'show_ui' => true,
267
+ 'show_in_menu' => true,
268
+ 'query_var' => true,
269
+ 'rewrite' => array( 'slug' => 'book' ),
270
+ 'capability_type' => 'post',
271
+ 'has_archive' => true,
272
+ 'hierarchical' => false,
273
+ 'menu_position' => null,
274
+ 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
275
+ );
276
+
277
+ register_post_type( 'book', $args );
278
+
279
+ });
280
+ */
281
+
282
+
283
+
284
+ // Log testing beloe
285
+ #return;
286
+ //*
287
+
288
+
289
+
290
+
291
+
292
+ //*/
dropins/SimpleHistorySettingsStatsDropin.css ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .SimpleHistoryStats__intro {
2
+ font-size: 1.4em;
3
+ }
4
+
5
+ .SimpleHistoryStats__graphs {
6
+
7
+ }
8
+ .SimpleHistoryStats__graph {
9
+ float: left;
10
+ width: 50%;
11
+ }
dropins/SimpleHistorySettingsStatsDropin.php ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Dropin Name: Settings stats
5
+ Dropin Description: Adds a tab with stats
6
+ Dropin URI: http://simple-history.com/
7
+ Author: Pär Thernström
8
+ */
9
+
10
+ class SimpleHistorySettingsStatsDropin {
11
+
12
+ // Simple History instance
13
+ private $sh;
14
+
15
+ public function __construct($sh) {
16
+
17
+ // Since it's not quite done yet, it's for da devs only for now
18
+ if ( ! defined("SIMPLE_HISTORY_DEV") || ! SIMPLE_HISTORY_DEV ) {
19
+ return;
20
+ }
21
+
22
+ $this->sh = $sh;
23
+
24
+ // How do we register this to the settings array?
25
+ $sh->registerSettingsTab(array(
26
+ "slug" => "stats",
27
+ "name" => __("Stats", "simple-history"),
28
+ "function" => array($this, "output")
29
+ ));
30
+
31
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts') );
32
+
33
+ }
34
+
35
+ public function on_admin_enqueue_scripts() {
36
+
37
+ $file_url = plugin_dir_url(__FILE__);
38
+
39
+ wp_enqueue_script( "google-ajax-api", "https://www.google.com/jsapi");
40
+ wp_enqueue_style( "simple_history_SettingsStatsDropin", $file_url . "SimpleHistorySettingsStatsDropin.css", null, SimpleHistory::VERSION);
41
+
42
+ }
43
+
44
+ public function output() {
45
+
46
+ global $wpdb;
47
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
48
+ $table_name_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
49
+
50
+ #$period_days = (int) 28;
51
+ $period_days = (int) 14;
52
+ $period_start_date = DateTime::createFromFormat('U', strtotime("-$period_days days"));
53
+ $period_end_date = DateTime::createFromFormat('U', time());
54
+
55
+ // Colors taken from the gogole chart example that was found in this Stack Overflow thread:
56
+ // http://stackoverflow.com/questions/236936/how-pick-colors-for-a-pie-chart
57
+ $arr_colors = explode(",", "8a56e2,cf56e2,e256ae,e25668,e28956,e2cf56,aee256,68e256,56e289,56e2cf,56aee2,5668e2");
58
+
59
+ // Load google charts libraries
60
+ ?>
61
+ <script>
62
+ google.load('visualization', '1', {'packages':['corechart']});
63
+ </script>
64
+ <?php
65
+
66
+ ?>
67
+ <!-- Overview, larger text -->
68
+ <div class='SimpleHistoryStats__intro'>
69
+ <?php
70
+ include(__DIR__ . "/../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(__DIR__ . "/../templates/settings-statsRowsPerDay.php") ?>
80
+ </div><!-- // end bar chart rows per day -->
81
+
82
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--loggersPie'>
83
+ <?php include(__DIR__ . "/../templates/settings-statsLoggers.php") ?>
84
+ </div>
85
+
86
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--logLevels'>
87
+ <?php include(__DIR__ . "/../templates/settings-statsLogLevels.php") ?>
88
+ </div>
89
+
90
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--users'>
91
+ <?php include(__DIR__ . "/../templates/settings-statsUsers.php") ?>
92
+ </div>
93
+
94
+ <!--
95
+ <div class='SimpleHistoryStats__graph SimpleHistoryStats__graph--initiators'>
96
+ <?php include(__DIR__ . "/../templates/settings-statsInitiators.php") ?>
97
+ </div>
98
+ -->
99
+
100
+
101
+ </div><!-- // end charts wrapper -->
102
+
103
+ <?php
104
+
105
+ include(__DIR__ . "/../templates/settings-statsForGeeks.php");
106
+
107
+ }
108
+
109
+
110
+
111
+ }
112
+
113
+
examples.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // No external calls allowed
4
+ exit;
5
+
6
+ /**
7
+ * Some examples of filter usage and so on
8
+ */
9
+
10
+ // Never clear the log (default is 60 days)
11
+ add_filter("simple_history/db_purge_days_interval", "__return_zero");
12
+
13
+
14
+ /**
15
+ * Example of logging
16
+ */
17
+
18
+ // Add a message to the history log
19
+ SimpleLogger()->info("This is a message sent to the log");
20
+
21
+ // Add log entries with different severities
22
+ SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
23
+ SimpleLogger()->debug("Ok, cron job is running!");
24
+
25
+ // Add a message to the history log
26
+ // and then add a second log entry with same info and Simple History
27
+ // will make these two become an "occasionGroup",
28
+ // i.e. collapsing their entries into one expandable log item
29
+ SimpleLogger()->info("This is a message sent to the log");
30
+ SimpleLogger()->info("This is a message sent to the log");
31
+
32
+ // Log entries can have placeholders and context
33
+ // This makes log entried translatable and filterable
34
+ SimpleLogger()->notice(
35
+ "User {username} edited page {pagename}",
36
+ array(
37
+ "username" => "jessie",
38
+ "pagename" => "My test page",
39
+ "_initiator" => SimpleLoggerLogInitiators::WP_USER,
40
+ "_user_id" => 5,
41
+ "_user_login" => "jess",
42
+ "_user_email" => "jessie@example.com"
43
+ )
44
+ );
45
+
46
+ // Log entried can have custom occasionsID
47
+ // This will group items together and a log entry will only be shown once
48
+ // in the log overview, even if the logged messages are different
49
+ for ($i = 0; $i < rand(1, 50); $i++) {
50
+ SimpleLogger()->notice("User {username} edited page {pagename}", array(
51
+ "username" => "example_user_{$i}",
52
+ "pagename" => "My test page",
53
+ "_occasionsID" => "postID:24884,action:edited"
54
+ ));
55
+ }
56
+
57
+ // Events can have different "initiators",
58
+ // i.e. who was responsible for the logged event
59
+ // Initiator "WORDPRESS" means that WordPress did something on it's own
60
+ SimpleLogger()->info(
61
+ "WordPress updated itself from version {from_version} to {to_version}",
62
+ array(
63
+ "from_version" => "3.8",
64
+ "to_version" => "3.8.1",
65
+ "_initiator" => SimpleLoggerLogInitiators::WORDPRESS
66
+ )
67
+ );
68
+
69
+ // Initiator "WP_USER" means that a logged in user did someting
70
+ SimpleLogger()->info(
71
+ "Updated plugin {plugin_name} from version {plugin_from_version} to version {plugin_to_version}",
72
+ array(
73
+ "plugin_name" => "Ninja Forms",
74
+ "plugin_from_version" => "1.1",
75
+ "plugin_to_version" => "1.1.2",
76
+ "_initiator" => SimpleLoggerLogInitiators::WP_USER
77
+ )
78
+ );
79
+
80
+ // // Initiator "WEB_USER" means that an unknown internet user did something
81
+ SimpleLogger()->warning("An attempt to login as user 'administrator' failed to login because the wrong password was entered", array(
82
+ "_initiator" => SimpleLoggerLogInitiators::WEB_USER
83
+ ));
84
+
85
+
86
+ // Use the "context array" to add more data to your logged event
87
+ // Data can be used later on to show detailed info about a log entry
88
+ // and does not need to be shown on the overview screen
89
+ SimpleLogger()->info("Edited product '{pagename}'", array(
90
+ "pagename" => "We are hiring!",
91
+ "_postType" => "product",
92
+ "_userID" => 1,
93
+ "_userLogin" => "jessie",
94
+ "_userEmail" => "jessie@example.com",
95
+ "_occasionsID" => "username:1,postID:24885,action:edited"
96
+ ));
gruntfile.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function (grunt) {
2
+
3
+ require('time-grunt')(grunt);
4
+
5
+ // Require all grunt-tasks instead of manually initialize them.
6
+ require('load-grunt-tasks')(grunt);
7
+
8
+ grunt.initConfig({
9
+
10
+ makepot: {
11
+ target: {
12
+ options: {
13
+ cwd: '', // Directory of files to internationalize.
14
+ domainPath: 'languages/', // Where to save the POT file.
15
+ exclude: [], // List of files or directories to ignore.
16
+ include: [], // List of files or directories to include.
17
+ i18nToolsPath: 'node_modules/grunt-wp-i18n/vendor/wp-i18n-tools', // Path to the i18n tools directory.
18
+ mainFile: '', // Main project file.
19
+ potComments: '', // The copyright at the beginning of the POT file.
20
+ potFilename: '', // Name of the POT file.
21
+ potHeaders: {
22
+ poedit: true, // Includes common Poedit headers.
23
+ 'x-poedit-keywordslist': true // Include a list of all possible gettext functions.
24
+ }, // Headers to add to the generated POT file.
25
+ processPot: null, // A callback function for manipulating the POT file.
26
+ type: 'wp-plugin', // Type of project (wp-plugin or wp-theme).
27
+ updateTimestamp: true // Whether the POT-Creation-Date should be updated without other changes.
28
+ }
29
+ }
30
+ }
31
+ });
32
+
33
+ // Task(s) to run. Default is default.
34
+ grunt.registerTask('default', ["makepot"]);
35
+
36
+ };
index.php CHANGED
@@ -1,2140 +1,105 @@
1
  <?php
2
  /*
3
  Plugin Name: Simple History
4
- Plugin URI: http://eskapism.se/code-playground/simple-history/
5
- Description: Get a log/history/audit log/version history of the changes made by users in WordPress.
6
- Version: 1.3.11
7
  Author: Pär Thernström
8
- Author URI: http://eskapism.se/
9
  License: GPL2
10
  */
11
 
12
- /* Copyright 2010 Pär Thernström (email: par.thernstrom@gmail.com)
13
 
14
- This program is free software; you can redistribute it and/or modify
15
- it under the terms of the GNU General Public License, version 2, as
16
- published by the Free Software Foundation.
17
 
18
- This program is distributed in the hope that it will be useful,
19
- but WITHOUT ANY WARRANTY; without even the implied warranty of
20
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
- GNU General Public License for more details.
22
 
23
- You should have received a copy of the GNU General Public License
24
- along with this program; if not, write to the Free Software
25
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
  */
27
 
28
- load_plugin_textdomain('simple-history', false, "/simple-history/languages");
29
 
30
- define( "SIMPLE_HISTORY_VERSION", "1.3.11");
31
- define( "SIMPLE_HISTORY_NAME", "Simple History");
32
-
33
- // Find the plugin directory URL
34
- $aa = __FILE__;
35
- if ( isset( $mu_plugin ) ) {
36
- $aa = $mu_plugin;
37
- }
38
- if ( isset( $network_plugin ) ) {
39
- $aa = $network_plugin;
40
- }
41
- if ( isset( $plugin ) ) {
42
- $aa = $plugin;
43
- }
44
-
45
- $plugin_dir_url = plugin_dir_url(basename($aa)) . basename(dirname(__FILE__)) . '/';
46
- define("SIMPLE_HISTORY_URL", $plugin_dir_url);
47
-
48
- /**
49
- * Let's begin on a class, since they rule so much more than functions.
50
- */
51
- class simple_history {
52
-
53
- var
54
- $plugin_foldername_and_filename,
55
- $view_history_capability,
56
- $view_settings_capability
57
- ;
58
-
59
- function __construct() {
60
-
61
- add_action( 'admin_init', array($this, 'admin_init') );
62
- add_action( 'init', array($this, 'init') );
63
- add_action( 'admin_menu', array($this, 'admin_menu') );
64
- add_action( 'wp_dashboard_setup', array($this, 'wp_dashboard_setup') );
65
- add_action( 'wp_ajax_simple_history_ajax', array($this, 'ajax') );
66
- add_filter( 'plugin_action_links_simple-history/index.php', array($this, "plugin_action_links"), 10, 4);
67
-
68
- $this->plugin_foldername_and_filename = basename(dirname(__FILE__)) . "/" . basename(__FILE__);
69
-
70
- $this->view_history_capability = "edit_pages";
71
- $this->view_history_capability = apply_filters("simple_history_view_history_capability", $this->view_history_capability);
72
-
73
- $this->view_settings_capability = "manage_options";
74
- $this->view_settings_capability = apply_filters("simple_history_view_settings_capability", $this->view_settings_capability);
75
-
76
- $this->add_types_for_translation();
77
-
78
- // Load Extender
79
- require_once ( dirname(__FILE__) . "/simple-history-extender/simple-history-extender.php" );
80
-
81
- }
82
-
83
- function get_pager_size() {
84
- $pager_size = get_option("simple_history_pager_size", 5);
85
- return $pager_size;
86
- }
87
-
88
- /**
89
- * Some post types etc are added as variables from the log, so to catch these for translation I just add them as dummy stuff here.
90
- * There is probably a better way to do this, but this should work anyway
91
- */
92
- function add_types_for_translation() {
93
- $dummy = __("added", "simple-history");
94
- $dummy = __("approved", "simple-history");
95
- $dummy = __("unapproved", "simple-history");
96
- $dummy = __("marked as spam", "simple-history");
97
- $dummy = __("trashed", "simple-history");
98
- $dummy = __("untrashed", "simple-history");
99
- $dummy = __("created", "simple-history");
100
- $dummy = __("deleted", "simple-history");
101
- $dummy = __("updated", "simple-history");
102
- $dummy = __("nav_menu_item", "simple-history");
103
- $dummy = __("attachment", "simple-history");
104
- $dummy = __("user", "simple-history");
105
- $dummy = __("settings page", "simple-history");
106
- $dummy = __("edited", "simple-history");
107
- $dummy = __("comment", "simple-history");
108
- $dummy = __("logged in", "simple-history");
109
- $dummy = __("logged out", "simple-history");
110
- $dummy = __("added", "simple-history");
111
- $dummy = __("modified", "simple-history");
112
- $dummy = __("upgraded its database", "simple-history");
113
- $dummy = __("plugin", "simple-history");
114
- }
115
-
116
- function plugin_action_links($actions, $b, $c, $d) {
117
- $settings_page_url = menu_page_url("simple_history_settings_menu_slug", 0);
118
- $actions[] = "<a href='$settings_page_url'>Settings</a>";
119
- return $actions;
120
-
121
- }
122
-
123
- function wp_dashboard_setup() {
124
- if (simple_history_setting_show_on_dashboard()) {
125
- if (current_user_can($this->view_history_capability)) {
126
- wp_add_dashboard_widget("simple_history_dashboard_widget", __("History", 'simple-history'), "simple_history_dashboard");
127
- }
128
- }
129
- }
130
-
131
- // stuff that happens in the admin
132
- // "admin_init is triggered before any other hook when a user access the admin area"
133
- function admin_init() {
134
-
135
- // posts
136
- add_action("save_post", "simple_history_save_post");
137
- add_action("transition_post_status", "simple_history_transition_post_status", 10, 3);
138
- add_action("delete_post", "simple_history_delete_post");
139
-
140
- // attachments/media
141
- add_action("add_attachment", "simple_history_add_attachment");
142
- add_action("edit_attachment", "simple_history_edit_attachment");
143
- add_action("delete_attachment", "simple_history_delete_attachment");
144
-
145
- // comments
146
- add_action("edit_comment", "simple_history_edit_comment");
147
- add_action("delete_comment", "simple_history_delete_comment");
148
- add_action("wp_set_comment_status", "simple_history_set_comment_status", 10, 2);
149
-
150
- // settings (all built in except permalinks)
151
- $arr_option_pages = array("general", "writing", "reading", "discussion", "media", "privacy");
152
- foreach ($arr_option_pages as $one_option_page_name) {
153
- $new_func = create_function('$capability', '
154
- return simple_history_add_update_option_page($capability, "'.$one_option_page_name.'");
155
- ');
156
- add_filter("option_page_capability_{$one_option_page_name}", $new_func);
157
- }
158
-
159
- // settings page for permalinks
160
- add_action('check_admin_referer', "simple_history_add_update_option_page_permalinks", 10, 2);
161
-
162
- // core update = wordpress updates
163
- add_action( '_core_updated_successfully', array($this, "action_core_updated") );
164
-
165
- // add donate link to plugin list page
166
- add_action("plugin_row_meta", array($this, "action_plugin_row_meta"), 10, 2);
167
-
168
- // check if database needs upgrade
169
- $this->check_upgrade_stuff();
170
-
171
- // add scripts and styles
172
- add_action("admin_enqueue_scripts", array($this, "admin_enqueue"));
173
-
174
- }
175
-
176
- // enqueue styles and scripts, but only to our own pages
177
- function admin_enqueue($hook) {
178
- if ( ($hook == "settings_page_simple_history_settings_menu_slug") || (simple_history_setting_show_on_dashboard() && $hook == "index.php") || (simple_history_setting_show_as_page() && $hook == "dashboard_page_simple_history_page")) {
179
- wp_enqueue_style( "simple_history_styles", SIMPLE_HISTORY_URL . "styles.css", false, SIMPLE_HISTORY_VERSION );
180
- wp_enqueue_script("simple_history", SIMPLE_HISTORY_URL . "scripts.js", array("jquery"), SIMPLE_HISTORY_VERSION);
181
- }
182
- }
183
-
184
- // WordPress Core updated
185
- function action_core_updated($wp_version) {
186
- simple_history_add("action=updated&object_type=wordpress_core&object_id=wordpress_core&object_name=".sprintf(__('WordPress %1$s', 'simple-history'), $wp_version));
187
- }
188
-
189
- function filter_option_page_capability($capability) {
190
- return $capability;
191
- }
192
-
193
- // Add link to donate page. Note to self: does not work on dev install because of dir being trunk and not "simple-history"
194
- function action_plugin_row_meta($links, $file) {
195
-
196
- if ($file == $this->plugin_foldername_and_filename) {
197
- return array_merge(
198
- $links,
199
- array( sprintf( '<a href="http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=pluginpage&utm_campaign=simplehistory">%1$s</a>', __('Donate', "simple-history") ) )
200
- );
201
- }
202
- return $links;
203
-
204
- }
205
-
206
-
207
- // check some things regarding update
208
- function check_upgrade_stuff() {
209
-
210
- global $wpdb;
211
-
212
- $db_version = get_option("simple_history_db_version");
213
- $table_name = $wpdb->prefix . "simple_history";
214
- // $db_version = FALSE;
215
-
216
- if ( false === $db_version ) {
217
-
218
- // db fix has never been run
219
- // user is on version 0.4 or earlier
220
- // = database is not using utf-8
221
- // so fix that
222
-
223
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
224
- #echo "begin upgrading database";
225
-
226
- // We change the varchar size to add one num just to force update of encoding. dbdelta didn't see it otherwise.
227
- $sql = "CREATE TABLE " . $table_name . " (
228
- id int(10) NOT NULL AUTO_INCREMENT,
229
- date datetime NOT NULL,
230
- action VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
231
- object_type VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
232
- object_subtype VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
233
- user_id int(10) NOT NULL,
234
- object_id int(10) NOT NULL,
235
- object_name VARCHAR(256) NOT NULL COLLATE utf8_general_ci,
236
- action_description longtext,
237
- PRIMARY KEY (id)
238
- ) CHARACTER SET=utf8;";
239
-
240
- // Upgrade db / fix utf for varchars
241
- dbDelta($sql);
242
-
243
- // Fix UTF-8 for table
244
- $sql = sprintf('alter table %1$s charset=utf8;', $table_name);
245
- $wpdb->query($sql);
246
-
247
- // Store this upgrade in ourself :)
248
- simple_history_add("action=" . 'upgraded its database' . "&object_type=plugin&object_name=" . SIMPLE_HISTORY_NAME);
249
-
250
- #echo "done upgrading database";
251
-
252
- update_option("simple_history_db_version", 1);
253
-
254
- } // done pre db ver 1 things
255
-
256
- // DB version is 1, upgrade to 2
257
- if ( 1 == intval($db_version) ) {
258
-
259
- // Add column for action description in non-translateable free text
260
- $sql = "ALTER TABLE {$table_name} ADD COLUMN action_description longtext";
261
- $wpdb->query($sql);
262
-
263
- simple_history_add("action=" . 'upgraded its database' . "&object_type=plugin&object_name=" . SIMPLE_HISTORY_NAME . "&description=Database version is now version 2");
264
- update_option("simple_history_db_version", 2);
265
-
266
- }
267
-
268
- // Check that all options we use are set to their defaults, if they miss value
269
- // Each option that is missing a value will make a sql cal otherwise = unnecessary
270
- $arr_options = array(
271
- array(
272
- "name" => "sh_extender_modules",
273
- "default_value" => ""
274
- ),
275
- array(
276
- "name" => "simple_history_show_as_page",
277
- "default_value" => 1
278
- ),
279
- array(
280
- "name" => "simple_history_show_on_dashboard",
281
- "default_value" => 0
282
- )
283
- );
284
-
285
- foreach ($arr_options as $one_option) {
286
-
287
- if ( false === ($option_value = get_option( $one_option["name"] ) ) ) {
288
-
289
- // Value is not set in db, so set it to a default
290
- update_option( $one_option["name"], $one_option["default_value"] );
291
-
292
- }
293
- }
294
-
295
- }
296
-
297
- function settings_page() {
298
-
299
- ?>
300
- <div class="wrap">
301
- <form method="post" action="options.php">
302
- <h2><?php _e("Simple History Settings", "simple-history") ?></h2>
303
- <?php do_settings_sections("simple_history_settings_menu_slug"); ?>
304
- <?php settings_fields("simple_history_settings_group"); ?>
305
- <?php submit_button(); ?>
306
- </form>
307
- </div>
308
- <?php
309
-
310
- }
311
-
312
- function admin_menu() {
313
-
314
- // show as page?
315
- if (simple_history_setting_show_as_page()) {
316
- add_dashboard_page(SIMPLE_HISTORY_NAME, __("History", 'simple-history'), $this->view_history_capability, "simple_history_page", "simple_history_management_page");
317
- }
318
-
319
- // add page for settings
320
- $show_settings_page = TRUE;
321
- $show_settings_page = apply_filters("simple_history_show_settings_page", $show_settings_page);
322
- if ($show_settings_page) {
323
- add_options_page(__('Simple History Settings', "simple-history"), SIMPLE_HISTORY_NAME, $this->view_settings_capability, "simple_history_settings_menu_slug", array($this, 'settings_page'));
324
- }
325
-
326
- add_settings_section("simple_history_settings_section", __("", "simple-history"), "simple_history_settings_page", "simple_history_settings_menu_slug");
327
-
328
- add_settings_field("simple_history_settings_field_1", __("Show Simple History", "simple-history"), "simple_history_settings_field", "simple_history_settings_menu_slug", "simple_history_settings_section");
329
- add_settings_field("simple_history_settings_field_5", __("Number of items per page", "simple-history"), "simple_history_settings_field_number_of_items", "simple_history_settings_menu_slug", "simple_history_settings_section");
330
- add_settings_field("simple_history_settings_field_2", __("RSS feed", "simple-history"), "simple_history_settings_field_rss", "simple_history_settings_menu_slug", "simple_history_settings_section");
331
- add_settings_field("simple_history_settings_field_4", __("Clear log", "simple-history"), "simple_history_settings_field_clear_log", "simple_history_settings_menu_slug", "simple_history_settings_section");
332
- add_settings_field("simple_history_settings_field_3", __("Donate", "simple-history"), "simple_history_settings_field_donate", "simple_history_settings_menu_slug", "simple_history_settings_section");
333
-
334
- register_setting("simple_history_settings_group", "simple_history_show_on_dashboard");
335
- register_setting("simple_history_settings_group", "simple_history_show_as_page");
336
- register_setting("simple_history_settings_group", "simple_history_pager_size");
337
-
338
- }
339
-
340
- /**
341
- * Log failed login attempt to username that exists
342
- */
343
- function log_wp_authenticate_user($user, $password) {
344
-
345
- if ( ! wp_check_password($password, $user->user_pass, $user->ID) ) {
346
-
347
- // call __() to make translation exist
348
- __("failed to log in because they entered the wrong password", "simple-history");
349
-
350
- $description = "";
351
- $description .= "HTTP_USER_AGENT: " . $_SERVER["HTTP_USER_AGENT"];
352
- $description .= "\nHTTP_REFERER: " . $_SERVER["HTTP_REFERER"];
353
- $description .= "\nREMOTE_ADDR: " . $_SERVER["REMOTE_ADDR"];
354
-
355
- $args = array(
356
- "object_type" => "user",
357
- "object_name" => $user->user_login,
358
- "action" => "failed to log in because they entered the wrong password",
359
- "object_id" => $user->ID,
360
- "description" => $description
361
- );
362
-
363
- simple_history_add($args);
364
-
365
- }
366
-
367
- return $user;
368
-
369
- }
370
-
371
- /**
372
- * Init for both public and admin
373
- */
374
- function init() {
375
-
376
- // user login and logout
377
- add_action("wp_login", "simple_history_wp_login");
378
- add_action("wp_logout", "simple_history_wp_logout");
379
-
380
- // user failed login attempt to username that exists
381
- #$user = apply_filters('wp_authenticate_user', $user, $password);
382
- add_action("wp_authenticate_user", array($this, "log_wp_authenticate_user"), 10, 2);
383
-
384
- // user profile page modifications
385
- add_action("delete_user", "simple_history_delete_user");
386
- add_action("user_register", "simple_history_user_register");
387
- add_action("profile_update", "simple_history_profile_update");
388
 
389
- // options
390
- #add_action("updated_option", "simple_history_updated_option", 10, 3);
391
- #add_action("updated_option", "simple_history_updated_option2", 10, 2);
392
- #add_action("updated_option", "simple_history_updated_option3", 10, 1);
393
- #add_action("update_option", "simple_history_update_option", 10, 3);
394
-
395
- // plugin
396
- add_action("activated_plugin", "simple_history_activated_plugin");
397
- add_action("deactivated_plugin", "simple_history_deactivated_plugin");
398
-
399
- // check for RSS
400
- // don't know if this is the right way to do this, but it seems to work!
401
- if ( isset($_GET["simple_history_get_rss"]) ) {
402
-
403
- $this->output_rss();
404
-
405
- }
406
-
407
- }
408
 
409
  /**
410
- * Output RSS
 
 
411
  */
412
- function output_rss() {
413
-
414
- $rss_secret_option = get_option("simple_history_rss_secret");
415
- $rss_secret_get = isset( $_GET["rss_secret"] ) ? $_GET["rss_secret"] : "";
416
-
417
- if ( empty($rss_secret_option) || empty($rss_secret_get) ) {
418
- die();
419
- }
420
 
421
- $rss_show = true;
422
- $rss_show = apply_filters("simple_history/rss_feed_show", $rss_show);
423
- if( ! $rss_show ) {
424
- wp_die( 'Nothing here.' );
425
- }
426
 
427
- header ("Content-Type:text/xml");
428
- echo '<?xml version="1.0" encoding="UTF-8"?>';
429
- $self_link = simple_history_get_rss_address();
430
 
431
- if ($rss_secret_option === $rss_secret_get) {
432
- ?>
433
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
434
- <channel>
435
- <title><?php printf(__("History for %s", 'simple-history'), get_bloginfo("name")) ?></title>
436
- <description><?php printf(__("WordPress History for %s", 'simple-history'), get_bloginfo("name")) ?></description>
437
- <link><?php echo get_bloginfo("url") ?></link>
438
- <atom:link href="<?php echo $self_link; ?>" rel="self" type="application/atom+xml" />
439
- <?php
440
-
441
- // Add filters here
442
- /*
443
- "page" => 0,
444
- "items" => $simple_history->get_pager_size(),
445
- "filter_type" => "",
446
- "filter_user" => "",
447
- "search" => "",
448
- "num_added" => 0
449
- */
450
-
451
-
452
- $args = array(
453
- "items" => "10"
454
- );
455
 
456
- $args = apply_filters("simple_history/rss_feed_args", $args);
457
-
458
- $arr_items = simple_history_get_items_array($args);
459
- foreach ($arr_items as $one_item) {
460
- $object_type = ucwords($one_item->object_type);
461
- $object_name = esc_html($one_item->object_name);
462
- $user = get_user_by("id", $one_item->user_id);
463
- $user_nicename = esc_html(@$user->user_nicename);
464
- $user_email = esc_html(@$user->user_email);
465
- $description = "";
466
- if ($user_nicename) {
467
- $description .= sprintf(__("By %s", 'simple-history'), $user_nicename);
468
- $description .= "<br />";
469
- }
470
- if ($one_item->occasions) {
471
- $description .= sprintf(__("%d occasions", 'simple-history'), sizeof($one_item->occasions));
472
- $description .= "<br />";
473
- }
474
- $description = apply_filters("simple_history_rss_item_description", $description, $one_item);
475
-
476
- $item_title = esc_html($object_type) . " \"" . esc_html($object_name) . "\" {$one_item->action}";
477
- $item_title = html_entity_decode($item_title, ENT_COMPAT, "UTF-8");
478
- $item_title = apply_filters("simple_history_rss_item_title", $item_title, $one_item);
479
-
480
- $item_guid = home_url() . "?simple-history-guid=" . $one_item->id;
481
-
482
- ?>
483
- <item>
484
- <title><![CDATA[<?php echo $item_title; ?>]]></title>
485
- <description><![CDATA[<?php echo $description ?>]]></description>
486
- <author><?php echo $user_email . ' (' . $user_nicename . ')' ?></author>
487
- <pubDate><?php echo date("D, d M Y H:i:s", strtotime($one_item->date)) ?> GMT</pubDate>
488
- <guid isPermaLink="false"><?php echo $item_guid ?></guid>
489
- <link><?php echo $item_guid ?></link>
490
- </item>
491
- <?php
492
- }
493
- ?>
494
- </channel>
495
- </rss>
496
- <?php
497
- } else {
498
- // not ok rss secret
499
- ?>
500
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
501
- <channel>
502
- <title><?php printf(__("History for %s", 'simple-history'), get_bloginfo("name")) ?></title>
503
- <description><?php printf(__("WordPress History for %s", 'simple-history'), get_bloginfo("name")) ?></description>
504
- <link><?php echo home_url() ?></link>
505
- <item>
506
- <title><?php _e("Wrong RSS secret", 'simple-history')?></title>
507
- <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>
508
- <pubDate><?php echo date("D, d M Y H:i:s", time()) ?> GMT</pubDate>
509
- <guid><?php echo home_url() . "?simple-history-guid=wrong-secret" ?></guid>
510
- </item>
511
- </channel>
512
- </rss>
513
- <?php
514
-
515
- }
516
- exit;
517
- } // rss
518
-
519
- /**
520
- * Get history from ajax
521
- */
522
- function ajax() {
523
-
524
- global $simple_history;
525
-
526
- $type = isset($_POST["type"]) ? $_POST["type"] : "";
527
- $subtype = isset($_POST["subtype"]) ? $_POST["subtype"] : "";
528
-
529
- // We get users by username, so get username from id
530
- $user_id = (int) $_POST["user_id"];
531
- if (empty($user_id)) {
532
- $user = "";
533
- } else {
534
- $user_obj = new WP_User($user_id);
535
- if ( ! $user_obj->exists() ) exit;
536
- $user = $user_obj->user_login;
537
- };
538
-
539
- // page to show. 1 = first page.
540
- $page = 0;
541
- if (isset($_POST["page"])) {
542
- $page = (int) $_POST["page"];
543
- }
544
-
545
- // number of items to get
546
- $items = (int) (isset($_POST["items"])) ? $_POST["items"] : $simple_history->get_pager_size();
547
-
548
- // number of prev added items = number of items to skip before starting to add $items num of new items
549
- $num_added = (int) (isset($_POST["num_added"])) ? $_POST["num_added"] : $simple_history->get_pager_size();
550
-
551
- $search = (isset($_POST["search"])) ? $_POST["search"] : "";
552
-
553
- $filter_type = $type . "/" . $subtype;
554
-
555
- $args = array(
556
- "is_ajax" => true,
557
- "filter_type" => $filter_type,
558
- "filter_user" => $user,
559
- "page" => $page,
560
- "items" => $items,
561
- "num_added" => $num_added,
562
- "search" => $search
563
- );
564
-
565
- $arr_json = array(
566
- "status" => "ok",
567
- "error" => "",
568
- "items_li" => "",
569
- "filtered_items_total_count" => 0,
570
- "filtered_items_total_count_string" => "",
571
- "filtered_items_total_pages" => 0
572
- );
573
-
574
- // ob_start();
575
- $return = simple_history_print_history($args);
576
- // $return = ob_get_clean();
577
- if ("noMoreItems" == $return) {
578
- $arr_json["status"] = "error";
579
- $arr_json["error"] = "noMoreItems";
580
- } else {
581
- $arr_json["items_li"] = $return;
582
- // total number of event. really bad way since we get them all again. need to fix this :/
583
- $args["items"] = "all";
584
- $all_items = simple_history_get_items_array($args);
585
- $arr_json["filtered_items_total_count"] = sizeof($all_items);
586
- $arr_json["filtered_items_total_count_string"] = sprintf(_n('One item', '%1$d items', sizeof($all_items), "simple-history"), sizeof($all_items));
587
- $arr_json["filtered_items_total_pages"] = ceil($arr_json["filtered_items_total_count"] / $simple_history->get_pager_size());
588
- }
589
-
590
- header("Content-type: application/json");
591
- echo json_encode($arr_json);
592
-
593
- exit;
594
-
595
- }
596
-
597
- } // class
598
-
599
- // Boot up
600
- $simple_history = new simple_history;
601
-
602
-
603
- function simple_history_settings_page() {
604
- // never remove this function, it must exist.
605
- // echo "Please choose options for simple history ...";
606
- }
607
-
608
- // get settings if plugin should be visible on dasboard. default in no since 0.7
609
- function simple_history_setting_show_on_dashboard() {
610
- $show_on_dashboard = get_option("simple_history_show_on_dashboard", 0);
611
- $show_on_dashboard = apply_filters("simple_history_show_on_dashboard", $show_on_dashboard);
612
- return (bool) $show_on_dashboard;
613
- }
614
- function simple_history_setting_show_as_page() {
615
- $setting = get_option("simple_history_show_as_page", 1);
616
- $setting = apply_filters("simple_history_show_as_page", $setting);
617
- return (bool) $setting;
618
-
619
- }
620
-
621
- function simple_history_settings_field_number_of_items() {
622
-
623
- global $simple_history;
624
- $current_pager_size = $simple_history->get_pager_size();
625
-
626
- ?>
627
- <select name="simple_history_pager_size">
628
- <option <?php echo $current_pager_size == 5 ? "selected" : "" ?> value="5">5</option>
629
- <option <?php echo $current_pager_size == 10 ? "selected" : "" ?> value="10">10</option>
630
- <option <?php echo $current_pager_size == 15 ? "selected" : "" ?> value="15">15</option>
631
- <option <?php echo $current_pager_size == 20 ? "selected" : "" ?> value="20">20</option>
632
- <option <?php echo $current_pager_size == 25 ? "selected" : "" ?> value="25">25</option>
633
- <option <?php echo $current_pager_size == 30 ? "selected" : "" ?> value="30">30</option>
634
- <option <?php echo $current_pager_size == 40 ? "selected" : "" ?> value="40">40</option>
635
- <option <?php echo $current_pager_size == 50 ? "selected" : "" ?> value="50">50</option>
636
- <option <?php echo $current_pager_size == 75 ? "selected" : "" ?> value="75">75</option>
637
- <option <?php echo $current_pager_size == 100 ? "selected" : "" ?> value="100">100</option>
638
- </select>
639
- <?php
640
-
641
- }
642
-
643
- function simple_history_settings_field() {
644
- $show_on_dashboard = simple_history_setting_show_on_dashboard();
645
- $show_as_page = simple_history_setting_show_as_page();
646
- ?>
647
-
648
- <input <?php echo $show_on_dashboard ? "checked='checked'" : "" ?> type="checkbox" value="1" name="simple_history_show_on_dashboard" id="simple_history_show_on_dashboard" class="simple_history_show_on_dashboard" />
649
- <label for="simple_history_show_on_dashboard"><?php _e("on the dashboard", 'simple-history') ?></label>
650
-
651
- <br />
652
-
653
- <input <?php echo $show_as_page ? "checked='checked'" : "" ?> type="checkbox" value="1" name="simple_history_show_as_page" id="simple_history_show_as_page" class="simple_history_show_as_page" />
654
- <label for="simple_history_show_as_page"><?php _e("as a page under the dashboard menu", 'simple-history') ?></label>
655
-
656
- <?php
657
- }
658
-
659
- /**
660
- * Settings section to clear database
661
- */
662
- function simple_history_settings_field_clear_log() {
663
-
664
- $clear_log = false;
665
-
666
- if (isset($_GET["simple_history_clear_log"]) && $_GET["simple_history_clear_log"]) {
667
- $clear_log = true;
668
- echo "<div class='simple-history-settings-page-updated'><p>";
669
- _e("Cleared database", 'simple-history');
670
- echo "</p></div>";
671
- }
672
-
673
- if ($clear_log) {
674
- simple_history_clear_log();
675
- }
676
-
677
- _e("Items in the database are automatically removed after 60 days.", 'simple-history');
678
- $update_link = add_query_arg("simple_history_clear_log", "1");
679
- printf(' <a href="%2$s">%1$s</a>', __('Clear it now.', 'simple-history'), $update_link);
680
- }
681
-
682
- function simple_history_clear_log() {
683
- global $wpdb;
684
- $tableprefix = $wpdb->prefix;
685
- $sql = "DELETE FROM {$tableprefix}simple_history";
686
- $wpdb->query($sql);
687
- }
688
-
689
- function simple_history_settings_field_donate() {
690
- ?>
691
- <p>
692
- <?php
693
- _e('
694
- Please
695
- <a href="http://eskapism.se/sida/donate/?utm_source=wordpress&utm_medium=settingpage&utm_campaign=simplehistory">
696
- donate
697
- </a> to support the development of this plugin and to keep it free.
698
- Thanks!
699
- ', "simple-history")
700
  ?>
701
- </p>
702
- <?php
703
- }
704
-
705
-
706
- function simple_history_get_rss_address() {
707
- $rss_secret = get_option("simple_history_rss_secret");
708
- $rss_address = add_query_arg(array("simple_history_get_rss" => "1", "rss_secret" => $rss_secret), get_bloginfo("url") . "/");
709
- $rss_address = htmlspecialchars($rss_address, ENT_COMPAT, "UTF-8");
710
- return $rss_address;
711
- }
712
-
713
- function simple_history_update_rss_secret() {
714
- $rss_secret = "";
715
- for ($i=0; $i<20; $i++) {
716
- $rss_secret .= chr(rand(97,122));
717
- }
718
- update_option("simple_history_rss_secret", $rss_secret);
719
- return $rss_secret;
720
- }
721
-
722
- function simple_history_settings_field_rss() {
723
- $create_new_secret = false;
724
- if (isset($_GET["simple_history_rss_update_secret"]) && $_GET["simple_history_rss_update_secret"]) {
725
- $create_new_secret = true;
726
- echo "<div class='simple-history-settings-page-updated'><p>";
727
- _e("Created new secret RSS address", 'simple-history');
728
- echo "</p></div>";
729
- }
730
-
731
- if ($create_new_secret) {
732
- simple_history_update_rss_secret();
733
- }
734
-
735
- $rss_address = simple_history_get_rss_address();
736
- echo "<code><a href='$rss_address'>$rss_address</a></code>";
737
- echo "<br />";
738
- _e("This is a secret RSS feed for Simple History. Only share the link with people you trust", 'simple-history');
739
- echo "<br />";
740
- $update_link = add_query_arg("simple_history_rss_update_secret", "1");
741
- printf(__("You can <a href='%s'>generate a new address</a> for the RSS feed. This is useful if you think that the address has fallen into the wrong hands.", 'simple-history'), $update_link);
742
- }
743
-
744
- // @todo: move all add-related stuff to own file? there are so many of them.. kinda confusing, ey.
745
-
746
- /**
747
- * Plugin is activated
748
- * plugin_name is like admin-menu-tree-page-view/index.php
749
- */
750
- function simple_history_activated_plugin($plugin_name) {
751
-
752
- // Fetch info about the plugin
753
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name );
754
-
755
- if ( is_array( $plugin_data ) && ! empty( $plugin_data["Name"] ) ) {
756
- $plugin_name = urlencode( $plugin_data["Name"] );
757
- } else {
758
- $plugin_name = urlencode($plugin_name);
759
- }
760
-
761
- simple_history_add("action=activated&object_type=plugin&object_name=$plugin_name");
762
- }
763
-
764
- /**
765
- * Plugin is deactivated
766
- * plugin_name is like admin-menu-tree-page-view/index.php
767
- */
768
- function simple_history_deactivated_plugin($plugin_name) {
769
-
770
- // Fetch info about the plugin
771
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name );
772
-
773
- if ( is_array( $plugin_data ) && ! empty( $plugin_data["Name"] ) ) {
774
- $plugin_name = urlencode( $plugin_data["Name"] );
775
- } else {
776
- $plugin_name = urlencode($plugin_name);
777
  }
778
-
779
- simple_history_add("action=deactivated&object_type=plugin&object_name=$plugin_name");
780
-
781
- }
782
-
783
- function simple_history_edit_comment($comment_id) {
784
-
785
- $comment_data = get_comment($comment_id, ARRAY_A);
786
- $comment_post_ID = $comment_data["comment_post_ID"];
787
- $post = get_post($comment_post_ID);
788
- $post_title = get_the_title($comment_post_ID);
789
- $excerpt = get_comment_excerpt($comment_id);
790
- $author = get_comment_author($comment_id);
791
-
792
- $str = sprintf( "$excerpt [" . __('From %1$s on %2$s') . "]", $author, $post_title );
793
- $str = urlencode($str);
794
-
795
- simple_history_add("action=edited&object_type=comment&object_name=$str&object_id=$comment_id");
796
 
797
  }
798
 
799
- function simple_history_delete_comment($comment_id) {
800
-
801
- $comment_data = get_comment($comment_id, ARRAY_A);
802
- $comment_post_ID = $comment_data["comment_post_ID"];
803
- $post = get_post($comment_post_ID);
804
- $post_title = get_the_title($comment_post_ID);
805
- $excerpt = get_comment_excerpt($comment_id);
806
- $author = get_comment_author($comment_id);
807
-
808
- $str = sprintf( "$excerpt [" . __('From %1$s on %2$s') . "]", $author, $post_title );
809
- $str = urlencode($str);
810
-
811
- simple_history_add("action=deleted&object_type=comment&object_name=$str&object_id=$comment_id");
812
-
813
- }
814
-
815
- function simple_history_set_comment_status($comment_id, $new_status) {
816
- #echo "<br>new status: $new_status<br>"; // 0
817
- // $new_status hold (unapproved), approve, spam, trash
818
- $comment_data = get_comment($comment_id, ARRAY_A);
819
- $comment_post_ID = $comment_data["comment_post_ID"];
820
- $post = get_post($comment_post_ID);
821
- $post_title = get_the_title($comment_post_ID);
822
- $excerpt = get_comment_excerpt($comment_id);
823
- $author = get_comment_author($comment_id);
824
-
825
- $action = "";
826
- if ("approve" == $new_status) {
827
- $action = 'approved';
828
- } elseif ("hold" == $new_status) {
829
- $action = 'unapproved';
830
- } elseif ("spam" == $new_status) {
831
- $action = 'marked as spam';
832
- } elseif ("trash" == $new_status) {
833
- $action = 'trashed';
834
- } elseif ("0" == $new_status) {
835
- $action = 'untrashed';
836
- }
837
-
838
- $action = urlencode($action);
839
-
840
- $str = sprintf( "$excerpt [" . __('From %1$s on %2$s') . "]", $author, $post_title );
841
- $str = urlencode($str);
842
-
843
- simple_history_add("action=$action&object_type=comment&object_name=$str&object_id=$comment_id");
844
-
845
- }
846
-
847
- function simple_history_update_option($option, $oldval, $newval) {
848
-
849
- if ($option == "active_plugins") {
850
-
851
- $debug = "\n";
852
- $debug .= "\nsimple_history_update_option()";
853
- $debug .= "\noption: $option";
854
- $debug .= "\noldval: " . print_r($oldval, true);
855
- $debug .= "\nnewval: " . print_r($newval, true);
856
-
857
- // Returns an array containing all the entries from array1 that are not present in any of the other arrays.
858
- // alltså:
859
- // om newval är array1 och innehåller en rad så är den tillagd
860
- // om oldval är array1 och innhåller en rad så är den bortagen
861
- $diff_added = array_diff((array) $newval, (array) $oldval);
862
- $diff_removed = array_diff((array) $oldval, (array) $newval);
863
- $debug .= "\ndiff_added: " . print_r($diff_added, true);
864
- $debug .= "\ndiff_removed: " . print_r($diff_removed, true);
865
- }
866
- }
867
-
868
- function simple_history_updated_option($option, $oldval, $newval) {
869
  /*
870
- echo "<br><br>simple_history_updated_option()";
871
- echo "<br>Updated option $option";
872
- echo "<br>oldval: ";
873
- bonny_d($oldval);
874
- echo "<br>newval:";
875
- bonny_d($newval);
876
  */
877
-
878
- }
879
-
880
-
881
  /*
882
- function simple_history_updated_option2($option, $oldval) {
883
- echo "<br><br>xxx_simple_history_updated_option2";
884
- bonny_d($option);
885
- bonny_d($oldval);
886
- }
887
- function simple_history_updated_option3($option) {
888
- echo "<br><br>xxx_simple_history_updated_option3";
889
- echo "<br>option: $option";
890
  }
891
  */
892
 
893
- function simple_history_add_attachment($attachment_id) {
894
- $post = get_post($attachment_id);
895
- $post_title = urlencode(get_the_title($post->ID));
896
- simple_history_add("action=added&object_type=attachment&object_id=$attachment_id&object_name=$post_title");
897
-
898
- }
899
- function simple_history_edit_attachment($attachment_id) {
900
- // is this only being called if the title of the attachment is changed?!
901
- $post = get_post($attachment_id);
902
- $post_title = urlencode(get_the_title($post->ID));
903
- simple_history_add("action=updated&object_type=attachment&object_id=$attachment_id&object_name=$post_title");
904
- }
905
- function simple_history_delete_attachment($attachment_id) {
906
- $post = get_post($attachment_id);
907
- $post_title = urlencode(get_the_title($post->ID));
908
- simple_history_add("action=deleted&object_type=attachment&object_id=$attachment_id&object_name=$post_title");
909
- }
910
-
911
- // user is updated
912
- function simple_history_profile_update($user_id) {
913
- $user = get_user_by("id", $user_id);
914
- $user_nicename = urlencode($user->user_nicename);
915
- simple_history_add("action=updated&object_type=user&object_id=$user_id&object_name=$user_nicename");
916
- }
917
-
918
- // user is created
919
- function simple_history_user_register($user_id) {
920
- $user = get_user_by("id", $user_id);
921
- $user_nicename = urlencode($user->user_nicename);
922
- simple_history_add("action=created&object_type=user&object_id=$user_id&object_name=$user_nicename");
923
- }
924
-
925
- // user is deleted
926
- function simple_history_delete_user($user_id) {
927
- $user = get_user_by("id", $user_id);
928
- $user_nicename = urlencode($user->user_nicename);
929
- simple_history_add("action=deleted&object_type=user&object_id=$user_id&object_name=$user_nicename");
930
- }
931
-
932
- // user logs in
933
- function simple_history_wp_login($user) {
934
- $current_user = wp_get_current_user();
935
- $user = get_user_by("login", $user);
936
- $user_nicename = urlencode($user->user_nicename);
937
- // if user id = null then it's because we are logged out and then no one is acutally loggin in.. like a.. ghost-user!
938
- if ($current_user->ID == 0) {
939
- $user_id = $user->ID;
940
- } else {
941
- $user_id = $current_user->ID;
942
- }
943
- simple_history_add("action=logged in&object_type=user&object_id=".$user->ID."&user_id=$user_id&object_name=$user_nicename");
944
- }
945
- // user logs out
946
- function simple_history_wp_logout() {
947
- $current_user = wp_get_current_user();
948
- $current_user_id = $current_user->ID;
949
- $user_nicename = urlencode($current_user->user_nicename);
950
- simple_history_add("action=logged out&object_type=user&object_id=$current_user_id&object_name=$user_nicename");
951
- }
952
-
953
- function simple_history_delete_post($post_id) {
954
- if (wp_is_post_revision($post_id) == false) {
955
- $post = get_post($post_id);
956
- if ($post->post_status != "auto-draft" && $post->post_status != "inherit") {
957
- $post_title = urlencode(get_the_title($post->ID));
958
- simple_history_add("action=deleted&object_type=post&object_subtype=" . $post->post_type . "&object_id=$post_id&object_name=$post_title");
959
- }
960
- }
961
- }
962
-
963
- function simple_history_save_post($post_id) {
964
-
965
- if (wp_is_post_revision($post_id) == false) {
966
- // not a revision
967
- // it should also not be of type auto draft
968
- $post = get_post($post_id);
969
- if ($post->post_status != "auto-draft") {
970
- // bonny_d($post);
971
- #echo "save";
972
- // [post_title] => neu
973
- // [post_type] => page
974
- }
975
-
976
- }
977
- }
978
-
979
- // post has changed status
980
- function simple_history_transition_post_status($new_status, $old_status, $post) {
981
-
982
- #echo "<br>From $old_status to $new_status";
983
-
984
- // From new to auto-draft <- ignore
985
- // From new to inherit <- ignore
986
- // From auto-draft to draft <- page/post created
987
- // From draft to draft
988
- // From draft to pending
989
- // From pending to publish
990
- # From pending to trash
991
- // if not from & to = same, then user has changed something
992
- //bonny_d($post); // regular post object
993
- if ($old_status == "auto-draft" && ($new_status != "auto-draft" && $new_status != "inherit")) {
994
- // page created
995
- $action = "created";
996
- } elseif ($new_status == "auto-draft" || ($old_status == "new" && $new_status == "inherit")) {
997
- // page...eh.. just leave it.
998
- return;
999
- } elseif ($new_status == "trash") {
1000
- $action = "deleted";
1001
- } else {
1002
- // page updated. i guess.
1003
- $action = "updated";
1004
- }
1005
- $object_type = "post";
1006
- $object_subtype = $post->post_type;
1007
-
1008
- // Attempt to auto-translate post types*/
1009
- // no, no longer, do it at presentation instead
1010
- #$object_type = __( ucfirst ( $object_type ) );
1011
- #$object_subtype = __( ucfirst ( $object_subtype ) );
1012
-
1013
- if ($object_subtype == "revision") {
1014
- // don't log revisions
1015
- return;
1016
- }
1017
-
1018
- if (wp_is_post_revision($post->ID) === false) {
1019
- // ok, no revision
1020
- $object_id = $post->ID;
1021
- } else {
1022
- return;
1023
- }
1024
-
1025
- $post_title = get_the_title($post->ID);
1026
- $post_title = urlencode($post_title);
1027
-
1028
- simple_history_add("action=$action&object_type=$object_type&object_subtype=$object_subtype&object_id=$object_id&object_name=$post_title");
1029
- }
1030
-
1031
-
1032
- /**
1033
- * add event to history table
1034
- */
1035
- function simple_history_add($args) {
1036
-
1037
- $defaults = array(
1038
- "action" => null,
1039
- "object_type" => null,
1040
- "object_subtype" => null,
1041
- "object_id" => null,
1042
- "object_name" => null,
1043
- "user_id" => null,
1044
- "description" => null
1045
- );
1046
-
1047
- $args = wp_parse_args( $args, $defaults );
1048
-
1049
- $action = esc_sql($args["action"]);
1050
- $object_type = esc_sql($args["object_type"]);
1051
- $object_subtype = esc_sql($args["object_subtype"]);
1052
- $object_id = esc_sql($args["object_id"]);
1053
- $object_name = esc_sql($args["object_name"]);
1054
- $user_id = $args["user_id"];
1055
- $description = esc_sql($args["description"]);
1056
-
1057
- global $wpdb;
1058
- $tableprefix = $wpdb->prefix;
1059
- if ($user_id) {
1060
- $current_user_id = $user_id;
1061
- } else {
1062
- $current_user = wp_get_current_user();
1063
- $current_user_id = (int) $current_user->ID;
1064
- }
1065
-
1066
- // date, store at utc or local time
1067
- // anything is better than now() anyway!
1068
- // WP seems to use the local time, so I will go with that too I think
1069
- // GMT/UTC-time is: date_i18n($timezone_format, false, 'gmt'));
1070
- // local time is: date_i18n($timezone_format));
1071
- $localtime = current_time("mysql");
1072
- $sql = "
1073
- INSERT INTO {$tableprefix}simple_history
1074
- SET
1075
- date = '$localtime',
1076
- action = '$action',
1077
- object_type = '$object_type',
1078
- object_subtype = '$object_subtype',
1079
- user_id = '$current_user_id',
1080
- object_id = '$object_id',
1081
- object_name = '$object_name',
1082
- action_description = '$description'
1083
- ";
1084
- $wpdb->query($sql);
1085
- }
1086
-
1087
- /**
1088
- * Removes old entries from the db
1089
- * @todo: let user set value, if any
1090
- */
1091
- function simple_history_purge_db() {
1092
-
1093
- $do_purge_history = TRUE;
1094
- $do_purge_history = apply_filters("simple_history_allow_db_purge", $do_purge_history);
1095
-
1096
- global $wpdb;
1097
- $tableprefix = $wpdb->prefix;
1098
-
1099
- $days = 60;
1100
- $days = (int) apply_filters("simple_history_db_purge_days_interval", $days);
1101
-
1102
- $sql = "DELETE FROM {$tableprefix}simple_history WHERE DATE_ADD(date, INTERVAL $days DAY) < now()";
1103
-
1104
- if ($do_purge_history) {
1105
- $wpdb->query($sql);
1106
- }
1107
-
1108
- }
1109
-
1110
- // widget on dashboard
1111
- function simple_history_dashboard() {
1112
- simple_history_purge_db();
1113
- echo '<div class="wrap simple-history-wrap">';
1114
- simple_history_print_nav();
1115
- echo simple_history_print_history();
1116
- echo simple_history_get_pagination();
1117
- echo '</div>';
1118
- }
1119
-
1120
- // own page under dashboard
1121
- function simple_history_management_page() {
1122
-
1123
- global $simple_history;
1124
-
1125
- simple_history_purge_db();
1126
-
1127
- ?>
1128
-
1129
- <div class="wrap simple-history-wrap">
1130
- <h2><?php echo __("History", 'simple-history') ?></h2>
1131
- <?php
1132
- simple_history_print_nav(array("from_page=1"));
1133
- echo simple_history_print_history(array("items" => $simple_history->get_pager_size(), "from_page" => "1"));
1134
- echo simple_history_get_pagination();
1135
- ?>
1136
- </div>
1137
-
1138
- <?php
1139
-
1140
- }
1141
-
1142
- if (!function_exists("bonny_d")) {
1143
- function bonny_d($var) {
1144
- echo "<pre>";
1145
- print_r($var);
1146
- echo "</pre>";
1147
- }
1148
- }
1149
-
1150
- // when activating plugin: create tables
1151
- // __FILE__ doesnt work for me because of soft linkes directories
1152
- register_activation_hook( WP_PLUGIN_DIR . "/simple-history/index.php" , 'simple_history_install' );
1153
-
1154
  /*
1155
- The theory behind the right way to do this. The proper way to handle an upgrade path is to only
1156
- run an upgrade procedure when you need to. Ideally, you would store a “version” in your
1157
- plugin’s database option, and then a version in the code. If they do not match, you
1158
- would fire your upgrade procedure, and then set the database option to equal the version in
1159
- the code. This is how many plugins handle upgrades, and this is how core works as well.
1160
- */
1161
 
1162
- // when installing plugin: create table
1163
- function simple_history_install() {
1164
 
1165
- global $wpdb;
 
1166
 
1167
- $table_name = $wpdb->prefix . "simple_history";
1168
- #if($wpdb->get_var("show tables like '$table_name'") != $table_name) {
1169
 
1170
- $sql = "CREATE TABLE " . $table_name . " (
1171
- id int(10) NOT NULL AUTO_INCREMENT,
1172
- date datetime NOT NULL,
1173
- action varchar(255) NOT NULL COLLATE utf8_general_ci,
1174
- object_type varchar(255) NOT NULL COLLATE utf8_general_ci,
1175
- object_subtype VARCHAR(255) NOT NULL COLLATE utf8_general_ci,
1176
- user_id int(10) NOT NULL,
1177
- object_id int(10) NOT NULL,
1178
- object_name varchar(255) NOT NULL COLLATE utf8_general_ci,
1179
- action_description longtext,
1180
- PRIMARY KEY (id)
1181
- ) CHARACTER SET=utf8;";
1182
 
1183
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
1184
- dbDelta($sql);
1185
-
1186
- // add ourself as a history item.
1187
- $plugin_name = urlencode(SIMPLE_HISTORY_NAME);
1188
-
1189
- #}
1190
-
1191
- simple_history_add("action=activated&object_type=plugin&object_name=$plugin_name");
1192
-
1193
- // also generate a rss secret, if it does not exist
1194
- if (!get_option("simple_history_rss_secret")) {
1195
- simple_history_update_rss_secret();
1196
- }
1197
-
1198
- update_option("simple_history_version", SIMPLE_HISTORY_VERSION);
1199
-
1200
- }
1201
-
1202
- /**
1203
- * Output navigation at top with filters for type, users, and free text search input
1204
- */
1205
- function simple_history_print_nav() {
1206
-
1207
- global $wpdb;
1208
- $tableprefix = $wpdb->prefix;
1209
-
1210
- // fetch all types that are in the log
1211
- if (isset($_GET["simple_history_type_to_show"])) {
1212
- $simple_history_type_to_show = $_GET["simple_history_type_to_show"];
1213
- } else {
1214
- $simple_history_type_to_show = "";
1215
- }
1216
-
1217
- // Get all object types and object subtypes
1218
- // order by the number of times they occur
1219
- $sql = "SELECT
1220
- count(object_type) AS object_type_count,
1221
- object_type, object_subtype
1222
- FROM {$tableprefix}simple_history
1223
- GROUP BY object_type, object_subtype
1224
- ORDER BY object_type_count DESC, object_type, object_subtype
1225
- ";
1226
- $arr_types = $wpdb->get_results($sql);
1227
-
1228
- $css = "";
1229
- if (empty($simple_history_type_to_show)) {
1230
- $css = "class='selected'";
1231
- }
1232
-
1233
- // Reload-button
1234
- $str_reload_button = sprintf('<a class="simple-fields-reload" title="%1$s" href="#"><span>Reload</span></a>', esc_attr__("Reload history", "simple-history"));
1235
- echo $str_reload_button;
1236
-
1237
- // Begin select
1238
- $str_types_select = "";
1239
- $str_types_select .= "<select name='' class='simple-history-filter simple-history-filter-type'>";
1240
-
1241
- $total_object_num_count = 0;
1242
- foreach ( $arr_types as $one_type ) {
1243
- $total_object_num_count += $one_type->object_type_count;
1244
- }
1245
-
1246
- // First filter is "all types"
1247
- $link = esc_html(add_query_arg("simple_history_type_to_show", ""));
1248
- $str_types_desc = __("All types", 'simple-history');
1249
-
1250
- $str_types_select .= sprintf('<option data-simple-history-filter-type="" data-simple-history-filter-subtype="" value="%1$s">%2$s (%3$d)</option>', $link, esc_html($str_types_desc), $total_object_num_count );
1251
-
1252
- // Loop through all types
1253
- // $one_type->object_type = user | post | attachment | comment | plugin | attachment | post | Reply | Topic | Widget | Wordpress_core
1254
- // $one_type->object_subtype = page | nav_menu_item | ...
1255
- #sf_d($arr_types);
1256
- foreach ($arr_types as $one_type) {
1257
-
1258
- $css = "";
1259
- $option_selected = "";
1260
- if ($one_type->object_subtype && $simple_history_type_to_show == ($one_type->object_type."/".$one_type->object_subtype)) {
1261
- $css = "class='selected'";
1262
- $option_selected = " selected ";
1263
- } elseif (!$one_type->object_subtype && $simple_history_type_to_show == $one_type->object_type) {
1264
- $css = "class='selected'";
1265
- $option_selected = " selected ";
1266
- }
1267
-
1268
- // Create link to filter this type + subtype
1269
- $arg = "";
1270
- if ($one_type->object_subtype) {
1271
- $arg = $one_type->object_type."/".$one_type->object_subtype;
1272
- } else {
1273
- $arg = $one_type->object_type;
1274
- }
1275
- $link = esc_html(add_query_arg("simple_history_type_to_show", $arg));
1276
-
1277
- // Begin option
1278
- $str_types_select .= sprintf(
1279
- '<option %1$s data-simple-history-filter-type="%2$s" data-simple-history-filter-subtype="%3$s" value="%4$s">',
1280
- $option_selected, // 1
1281
- $one_type->object_type, // 2
1282
- $one_type->object_subtype, // 3
1283
- $link // 4
1284
- );
1285
-
1286
- // Some built in types we translate with built in translation, the others we use simple history for
1287
- // TODO: use WP-function to get all built in types?
1288
- $object_type_translated = "";
1289
- $object_subtype_translated = "";
1290
-
1291
- // Get built in post types
1292
- $arr_built_in_post_types = get_post_types( array("_builtin" => true) );
1293
-
1294
- $object_type_translated = "";
1295
- $object_subtype_translated = "";
1296
-
1297
- // For built in types
1298
- if ( in_array( $one_type->object_type, $arr_built_in_post_types ) ) {
1299
-
1300
- // Get name of main type
1301
- $object_post_type_object = get_post_type_object( $one_type->object_type );
1302
- $object_type_translated = $object_post_type_object->labels->name;
1303
-
1304
- // Get name of subtype
1305
- $object_subtype_post_type_object = get_post_type_object( $one_type->object_subtype );
1306
- if ( ! is_null( $object_subtype_post_type_object ) ) {
1307
- $object_subtype_translated = $object_subtype_post_type_object->labels->name;;
1308
- }
1309
-
1310
- }
1311
-
1312
- if ( empty( $object_type_translated ) ) {
1313
- $object_type_translated = ucfirst( esc_html__( $one_type->object_type, "simple-history") );
1314
- }
1315
-
1316
- if ( empty( $object_subtype_translated ) ) {
1317
- $object_subtype_translated = ucfirst( esc_html__( $one_type->object_subtype, "simple-history") );
1318
- }
1319
-
1320
- // Add name of type (post / attachment / user / etc.)
1321
-
1322
- // built in types use only subtype
1323
- if ( in_array( $one_type->object_type, $arr_built_in_post_types ) && ! empty($object_subtype_translated) ) {
1324
-
1325
- $str_types_select .= $object_subtype_translated;
1326
-
1327
- } else {
1328
-
1329
- $str_types_select .= $object_type_translated;
1330
-
1331
- // And subtype, if different from main type
1332
- if ($object_subtype_translated && $object_subtype_translated != $object_type_translated) {
1333
- $str_types_select .= "/" . $object_subtype_translated;
1334
- }
1335
-
1336
- }
1337
- // Add object count
1338
- $str_types_select .= sprintf(' (%d)', $one_type->object_type_count);
1339
-
1340
- // Close option
1341
- $str_types_select .= "\n</option>";
1342
-
1343
- // debug
1344
- #$str_types .= " type: " . $one_type->object_type;
1345
- #$str_types .= " type: " . ucfirst($one_type->object_type);
1346
- #$str_types .= " subtype: " . $one_type->object_subtype. " ";
1347
-
1348
- } // foreach arr types
1349
-
1350
- $str_types_select .= "\n</select>";
1351
-
1352
- // Output filters
1353
- if ( ! empty( $arr_types ) ) {
1354
- // echo $str_types;
1355
- echo $str_types_select;
1356
- }
1357
-
1358
- // fetch all users that are in the log
1359
- $sql = "SELECT DISTINCT user_id FROM {$tableprefix}simple_history WHERE user_id <> 0";
1360
- $arr_users_regular = $wpdb->get_results($sql);
1361
- foreach ($arr_users_regular as $one_user) {
1362
- $arr_users[$one_user->user_id] = array("user_id" => $one_user->user_id);
1363
- }
1364
-
1365
- if ( ! empty( $arr_users ) ) {
1366
-
1367
- foreach ($arr_users as $user_id => $one_user) {
1368
- $user = get_user_by("id", $user_id);
1369
- if ($user) {
1370
- $arr_users[$user_id]["user_login"] = $user->user_login;
1371
- $arr_users[$user_id]["user_nicename"] = $user->user_nicename;
1372
- if (isset($user->first_name)) {
1373
- $arr_users[$user_id]["first_name"] = $user->first_name;
1374
- }
1375
- if (isset($user->last_name)) {
1376
- $arr_users[$user_id]["last_name"] = $user->last_name;
1377
- }
1378
- }
1379
- }
1380
 
1381
  }
1382
 
1383
- if (isset($arr_users) && $arr_users) {
1384
-
1385
- if (isset($_GET["simple_history_user_to_show"])) {
1386
- $simple_history_user_to_show = $_GET["simple_history_user_to_show"];
1387
- } else {
1388
- $simple_history_user_to_show = "";
1389
- }
1390
-
1391
- $str_users_select = "";
1392
- $str_users_select .= "<select name='' class='simple-history-filter simple-history-filter-user'>";
1393
-
1394
- $css = "";
1395
- $option_selected = "";
1396
- if (empty($simple_history_user_to_show)) {
1397
- $css = " class='selected' ";
1398
- $option_selected = " selected ";
1399
- }
1400
-
1401
- // All users
1402
- $link = esc_html(add_query_arg("simple_history_user_to_show", ""));
1403
-
1404
- $str_users_select .= sprintf(
1405
- '<option data-simple-history-filter-user-id="%4$s" value="%3$s" %2$s>%1s</option>',
1406
- __("By all users", 'simple-history'), // 1
1407
- $option_selected, // 2
1408
- $link, // 3
1409
- "" // 4
1410
- );
1411
-
1412
- foreach ($arr_users as $user_id => $user_info) {
1413
-
1414
- $user = new WP_User($user_id);
1415
- if ( ! $user->exists() ) continue;
1416
-
1417
- $link = esc_html(add_query_arg("simple_history_user_to_show", $user_id));
1418
-
1419
- $css = "";
1420
- $option_selected = "";
1421
-
1422
- if ($user_id == $simple_history_user_to_show) {
1423
- $css = " class='selected' ";
1424
- $option_selected = " selected ";
1425
- }
1426
-
1427
- // all users must have username and email
1428
- $str_user_name = sprintf('%1$s (%2$s)', esc_attr($user->user_login), esc_attr($user->user_email));
1429
- // if ( ! empty( $user_info["first_name"] ) $user_info["last_name"] );
1430
-
1431
- $str_users_select .= sprintf(
1432
- '<option data-simple-history-filter-user-id="%4$s" %2$s value="%1$s">%1$s</option>',
1433
- $str_user_name, // 1
1434
- $option_selected, // 2
1435
- $link, // 3
1436
- $user_id
1437
- );
1438
-
1439
- }
1440
-
1441
- $str_users_select .= "</select>";
1442
-
1443
- if ( ! empty($str_users_select) ) {
1444
- echo $str_users_select;
1445
- }
1446
-
1447
- }
1448
-
1449
- // search
1450
- $str_search = __("Search", 'simple-history');
1451
- $search = "<p class='simple-history-filter simple-history-filter-search'>
1452
- <input type='text' />
1453
- <input type='button' value='$str_search' class='button' />
1454
- </p>";
1455
- echo $search;
1456
-
1457
- }
1458
-
1459
- function simple_history_get_pagination() {
1460
-
1461
- // pagination
1462
- global $simple_history;
1463
- $all_items = simple_history_get_items_array("items=all");
1464
- $items_count = sizeof($all_items);
1465
- $pages_count = ceil($items_count/$simple_history->get_pager_size());
1466
- $page_current = 1;
1467
-
1468
- $out = sprintf('
1469
- <div class="tablenav simple-history-tablenav">
1470
- <div class="tablenav-pages">
1471
- <span class="displaying-num">%1$s</span>
1472
- <span class="pagination-links">
1473
- <a class="first-page disabled" title="%5$s" href="#"><span>«</span></a>
1474
- <a class="prev-page disabled" title="%6$s" href="#"><span>‹</span></a>
1475
- <span class="paging-input"><input class="current-page" title="%7$s" type="text" name="paged" value="%2$d" size="2"> %8$s <span class="total-pages">%3$d</span></span>
1476
- <a class="next-page %4$s" title="%9$s" href="#"><span>›</span></a>
1477
- <a class="last-page %4$s" title="%10$s" href="#"><span>»</span></a>
1478
- </span>
1479
- </div>
1480
- </div>
1481
- ',
1482
- sprintf(_n('One item', '%1$d items', sizeof($all_items), "simple-history"), sizeof($all_items)),
1483
- $page_current,
1484
- $pages_count,
1485
- ($pages_count == 1) ? "disabled" : "",
1486
- __("Go to the first page"), // 5
1487
- __("Go to the previous page"), // 6
1488
- __("Current page"), // 7
1489
- __("of"), // 8
1490
- __("Go to the next page"), // 9
1491
- __("Go to the last page") // 10
1492
- );
1493
-
1494
- return $out;
1495
-
1496
- }
1497
-
1498
-
1499
- // return an array with all events and occasions
1500
- function simple_history_get_items_array($args = "") {
1501
-
1502
- global $wpdb, $simple_history;
1503
-
1504
- $defaults = array(
1505
- "page" => 0,
1506
- "items" => $simple_history->get_pager_size(),
1507
- "filter_type" => "",
1508
- "filter_user" => "",
1509
- "is_ajax" => false,
1510
- "search" => "",
1511
- "num_added" => 0
1512
- );
1513
- $args = wp_parse_args( $args, $defaults );
1514
-
1515
- $simple_history_type_to_show = $args["filter_type"];
1516
- $simple_history_user_to_show = $args["filter_user"];
1517
-
1518
- $where = " WHERE 1=1 ";
1519
- if ($simple_history_type_to_show) {
1520
- $filter_type = "";
1521
- $filter_subtype = "";
1522
- if (strpos($simple_history_type_to_show, "/") !== false) {
1523
- // split it up
1524
- $arr_args = explode("/", $simple_history_type_to_show);
1525
- $filter_type = $arr_args[0];
1526
- $filter_subtype = $arr_args[1];
1527
- } else {
1528
- $filter_type = $simple_history_type_to_show;
1529
- }
1530
- if ($filter_type) {
1531
- $where .= " AND lower(object_type) = '" . esc_sql(strtolower($filter_type)) . "' ";
1532
- }
1533
- if ($filter_subtype) {
1534
- $where .= " AND lower(object_subtype) = '" . esc_sql(strtolower($filter_subtype)) . "' ";
1535
- }
1536
- }
1537
- if ($simple_history_user_to_show) {
1538
-
1539
- $userinfo = get_user_by("slug", $simple_history_user_to_show);
1540
-
1541
- if (isset($userinfo->ID)) {
1542
- $where .= " AND user_id = '" . $userinfo->ID . "'";
1543
- }
1544
-
1545
- }
1546
-
1547
- $tableprefix = $wpdb->prefix;
1548
-
1549
- $sql = "SELECT * FROM {$tableprefix}simple_history $where ORDER BY date DESC, id DESC ";
1550
- #sf_d($args);
1551
- #echo "\n$sql\n";
1552
- $rows = $wpdb->get_results($sql);
1553
-
1554
- $loopNum = 0;
1555
- $real_loop_num = -1;
1556
-
1557
- $search = strtolower($args["search"]);
1558
-
1559
- $arr_events = array();
1560
- if ($rows) {
1561
- $prev_row = null;
1562
- foreach ($rows as $one_row) {
1563
-
1564
- // check if this event is same as prev event
1565
- // todo: how to do with object_name vs object id?
1566
- // if object_id is same as prev, but object_name differ, then it's the same object but with a new name
1567
- // we store it as same and use occations to output the name etc of
1568
- if (
1569
- $prev_row
1570
- && $one_row->action == $prev_row->action
1571
- && $one_row->object_type == $prev_row->object_type
1572
- && $one_row->object_type == $prev_row->object_type
1573
- && $one_row->object_subtype == $prev_row->object_subtype
1574
- && $one_row->user_id == $prev_row->user_id
1575
- && (
1576
- (!empty($one_row->object_id) && !empty($prev_row->object_id))
1577
- && ($one_row->object_id == $prev_row->object_id)
1578
- || ($one_row->object_name == $prev_row->object_name)
1579
- )
1580
- ) {
1581
-
1582
- // this event is like the previous event, but only with a different date
1583
- // so add it to the last element in arr_events
1584
- $arr_events[$prev_row->id]->occasions[] = $one_row;
1585
-
1586
- } else {
1587
-
1588
- #echo "<br>real_loop_num: $real_loop_num";
1589
- #echo "<br>loop_num: $loopNum";
1590
-
1591
- // check if we have a search. of so, only add if there is a match
1592
- $do_add = FALSE;
1593
- if ($search) {
1594
- /* echo "<br>search: $search";
1595
- echo "<br>object_name_lower: $object_name_lower";
1596
- echo "<br>objecttype: " . $one_row->object_type;
1597
- echo "<br>object_subtype: " . $one_row->object_subtype;
1598
- // */
1599
- if (strpos(strtolower($one_row->object_name), $search) !== FALSE) {
1600
- $do_add = TRUE;
1601
- } else if (strpos(strtolower($one_row->object_type), $search) !== FALSE) {
1602
- $do_add = TRUE;
1603
- } else if (strpos(strtolower($one_row->object_subtype), $search) !== FALSE) {
1604
- $do_add = TRUE;
1605
- } else if (strpos(strtolower($one_row->action), $search) !== FALSE) {
1606
- $do_add = TRUE;
1607
- } else if (strpos(strtolower($one_row->action_description), $search) !== FALSE) {
1608
- $do_add = TRUE;
1609
- }
1610
- } else {
1611
- $do_add = TRUE;
1612
- }
1613
-
1614
- if ($do_add) {
1615
- $real_loop_num++;
1616
- }
1617
-
1618
- // new event, not as previous one
1619
- if ($do_add) {
1620
- $arr_events[$one_row->id] = $one_row;
1621
- $arr_events[$one_row->id]->occasions = array();
1622
- $loopNum++;
1623
- $prev_row = $one_row;
1624
- }
1625
-
1626
- }
1627
- }
1628
-
1629
- }
1630
-
1631
- // arr_events is now all events
1632
- // but we only want some of them
1633
- // limit by using
1634
- // num_added = number of prev added items
1635
- // items = number of items to get
1636
- /*sf_d($args["num_added"]);
1637
- sf_d($args["items"]);
1638
- sf_d($arr_events);
1639
- // */
1640
- //
1641
- //$offset = $args["num_added"]; // old way when we appended
1642
- /*
1643
- <pre class='sf_box_debug'>Array
1644
- (
1645
- [page] =&gt; 1
1646
- [items] =&gt; 5
1647
- [filter_type] =&gt; /
1648
- [filter_user] =&gt;
1649
- [is_ajax] =&gt; 1
1650
- [search] =&gt;
1651
- [num_added] =&gt; 5
1652
- )
1653
  */
1654
 
1655
- if (is_numeric($args["items"]) && $args["items"] > 0) {
1656
- #sf_d($args);
1657
- $offset = ($args["page"] * $args["items"]);
1658
- #echo "offset: $offset";
1659
- $arr_events = array_splice($arr_events, $offset, $args["items"]);
1660
- }
1661
-
1662
- return $arr_events;
1663
-
1664
- }
1665
-
1666
- // return the log
1667
- // taking filtrering into consideration
1668
- function simple_history_print_history($args = null) {
1669
-
1670
- global $simple_history;
1671
-
1672
- $arr_events = simple_history_get_items_array($args);
1673
- #sf_d($arr_events);
1674
- #sf_d($args);sf_d($arr_events);
1675
- $defaults = array(
1676
- "page" => 0,
1677
- "items" => $simple_history->get_pager_size(),
1678
- "filter_type" => "",
1679
- "filter_user" => "",
1680
- "is_ajax" => false
1681
- );
1682
-
1683
- $args = wp_parse_args( $args, $defaults );
1684
- $output = "";
1685
- if ($arr_events) {
1686
- if (!$args["is_ajax"]) {
1687
- // if not ajax, print the div
1688
- $output .= "<div class='simple-history-ol-wrapper'><ol class='simple-history'>";
1689
- }
1690
-
1691
- $loopNum = 0;
1692
- $real_loop_num = -1;
1693
- foreach ($arr_events as $one_row) {
1694
-
1695
- $real_loop_num++;
1696
-
1697
- $object_type = $one_row->object_type;
1698
- $object_type_lcase = strtolower($object_type);
1699
- $object_subtype = $one_row->object_subtype;
1700
- $object_id = $one_row->object_id;
1701
- $object_name = $one_row->object_name;
1702
- $user_id = $one_row->user_id;
1703
- $action = $one_row->action;
1704
- $action_description = $one_row->action_description;
1705
- $occasions = $one_row->occasions;
1706
- $num_occasions = sizeof($occasions);
1707
- $object_image_out = "";
1708
-
1709
- $css = "";
1710
- if ("attachment" == $object_type_lcase) {
1711
- if (wp_get_attachment_image_src($object_id, array(50,50), true)) {
1712
- // yep, it's an attachment and it has an icon/thumbnail
1713
- $css .= ' simple-history-has-attachment-thumnbail ';
1714
- }
1715
- }
1716
- if ("user" == $object_type_lcase) {
1717
- $css .= ' simple-history-has-attachment-thumnbail ';
1718
- }
1719
-
1720
- if ($num_occasions > 0) {
1721
- $css .= ' simple-history-has-occasions ';
1722
- }
1723
-
1724
- $output .= "<li class='$css'>";
1725
-
1726
- $output .= "<div class='first'>";
1727
-
1728
- // who performed the action
1729
- $who = "";
1730
- $user = get_user_by("id", $user_id); // false if user does not exist
1731
-
1732
- if ($user) {
1733
- $user_avatar = get_avatar($user->user_email, "32");
1734
- $user_link = "user-edit.php?user_id={$user->ID}";
1735
- $who_avatar = sprintf('<a class="simple-history-who-avatar" href="%2$s">%1$s</a>', $user_avatar, $user_link);
1736
- } else {
1737
- $user_avatar = get_avatar("", "32");
1738
- $who_avatar = sprintf('<span class="simple-history-who-avatar">%1$s</span>', $user_avatar);
1739
- }
1740
- $output .= $who_avatar;
1741
-
1742
- // section with info about the user who did something
1743
- $who .= "<span class='who'>";
1744
- if ($user) {
1745
- $who .= sprintf('<a href="%2$s">%1$s</a>', $user->user_nicename, $user_link);
1746
- if (isset($user->first_name) || isset($user->last_name)) {
1747
- if ($user->first_name || $user->last_name) {
1748
- $who .= " (";
1749
- if ($user->first_name && $user->last_name) {
1750
- $who .= esc_html($user->first_name) . " " . esc_html($user->last_name);
1751
- } else {
1752
- $who .= esc_html($user->first_name) . esc_html($user->last_name); // just one of them, no space necessary
1753
- }
1754
- $who .= ")";
1755
- }
1756
- }
1757
- } else {
1758
- $who .= "&lt;" . __("Unknown or deleted user", 'simple-history') ."&gt;";
1759
- }
1760
- $who .= "</span>";
1761
-
1762
- // what and object
1763
- if ("post" == $object_type_lcase) {
1764
-
1765
- $post_out = "";
1766
-
1767
- // Get real name for post type (not just the slug for custom post types)
1768
- $post_type_object = get_post_type_object( $object_subtype );
1769
- if ( is_null($post_type_object) ) {
1770
- $post_out .= esc_html__( ucfirst( $object_subtype ) );
1771
- } else {
1772
- $post_out .= esc_html__( ucfirst( $post_type_object->labels->singular_name ) );
1773
- }
1774
-
1775
- $post = get_post($object_id);
1776
-
1777
- if (null == $post) {
1778
- // post does not exist, probably deleted
1779
- // check if object_name exists
1780
- if ($object_name) {
1781
- $post_out .= " <span class='simple-history-title'>\"" . esc_html($object_name) . "\"</span>";
1782
- } else {
1783
- $post_out .= " <span class='simple-history-title'>&lt;unknown name&gt;</span>";
1784
- }
1785
- } else {
1786
- #$title = esc_html($post->post_title);
1787
- $title = get_the_title($post->ID);
1788
- $title = esc_html($title);
1789
- $edit_link = get_edit_post_link($object_id, 'display');
1790
- $post_out .= " <a href='$edit_link'>";
1791
- $post_out .= "<span class='simple-history-title'>{$title}</span>";
1792
- $post_out .= "</a>";
1793
- }
1794
-
1795
- $post_out .= " " . esc_html__($action, "simple-history");
1796
-
1797
- $post_out = ucfirst($post_out);
1798
- $output .= $post_out;
1799
-
1800
-
1801
- } elseif ("attachment" == $object_type_lcase) {
1802
-
1803
- $attachment_out = "";
1804
- $attachment_out .= __("attachment", 'simple-history') . " ";
1805
-
1806
- $post = get_post($object_id);
1807
-
1808
- if ($post) {
1809
-
1810
- // Post for attachment was found
1811
 
1812
- $title = esc_html(get_the_title($post->ID));
1813
- $edit_link = get_edit_post_link($object_id, 'display');
1814
- $attachment_metadata = wp_get_attachment_metadata( $object_id );
1815
- $attachment_file = get_attached_file( $object_id );
1816
- $attachment_mime = get_post_mime_type( $object_id );
1817
- $attachment_url = wp_get_attachment_url( $object_id );
1818
 
1819
- // Check that file exists. It may not due to local dev vs remove dev etc.
1820
- $file_exists = file_exists($attachment_file);
1821
-
1822
- // Get attachment thumbnail. 60 x 60 is the same size as the media overview uses
1823
- // Is thumbnail of object if image, is wp icon if not
1824
- $attachment_image_src = wp_get_attachment_image_src($object_id, array(60, 60), true);
1825
- if ($attachment_image_src && $file_exists) {
1826
- $object_image_out .= "<a class='simple-history-attachment-thumbnail' href='$edit_link'><img src='{$attachment_image_src[0]}' alt='Attachment icon' width='{$attachment_image_src[1]}' height='{$attachment_image_src[2]}' /></a>";
1827
- } else {
1828
- $object_image_out .= "<a class='simple-history-attachment-thumbnail' href='$edit_link'></a>";
1829
- }
1830
-
1831
- // Begin adding nice to have meta info about to attachment (name, size, mime, etc.)
1832
- $object_image_out .= "<div class='simple-history-attachment-meta'>";
1833
-
1834
- // File name
1835
-
1836
- // Get size in human readable format. Code snippet from media.php
1837
- $sizes = array( 'KB', 'MB', 'GB' );
1838
-
1839
- $attachment_filesize = "";
1840
- if ( $file_exists ) {
1841
- $attachment_filesize = filesize( $attachment_file );
1842
- for ( $u = -1; $attachment_filesize > 1024 && $u < count( $sizes ) - 1; $u++ ) {
1843
- $attachment_filesize /= 1024;
1844
- }
1845
- }
1846
-
1847
- if (empty($attachment_filesize)) {
1848
- $str_attachment_size = "<p>" . __("File size: Unknown ", "simple-history") . "</p>";
1849
- } else {
1850
- $size_unit = ($u == -1) ? __("bytes", "simple-history") : $sizes[$u];
1851
- $str_attachment_size = sprintf('<p>%1$s %2$s %3$s</p>', __("File size:", "simple-history"), round( $attachment_filesize, 0 ), $size_unit );
1852
- }
1853
-
1854
- // File type
1855
- $file_type_out = "";
1856
- if ( preg_match( '/^.*?\.(\w+)$/', $attachment_file, $matches ) )
1857
- $file_type_out .= esc_html( strtoupper( $matches[1] ) );
1858
- else
1859
- $file_type_out .= strtoupper( str_replace( 'image/', '', $post->post_mime_type ) );
1860
-
1861
- // Media size, width x height
1862
- $media_dims = "";
1863
- if ( ! empty( $attachment_metadata['width'] ) && ! empty( $attachment_metadata['height'] ) ) {
1864
- $media_dims .= "<span>{$attachment_metadata['width']}&nbsp;&times;&nbsp;{$attachment_metadata['height']}</span>";
1865
- }
1866
-
1867
- // Generate string with metainfo
1868
- $object_image_out .= $str_attachment_size;
1869
- $object_image_out .= sprintf('<p>%1$s %2$s</p>', __("File type:"), $file_type_out );
1870
- if ( ! empty( $media_dims ) ) $object_image_out .= sprintf('<p>%1$s %2$s</p>', __("Dimensions:"), $media_dims );
1871
- if ( ! empty( $attachment_metadata["length_formatted"] ) ) $object_image_out .= sprintf('<p>%1$s %2$s</p>', __("Length:"), $attachment_metadata["length_formatted"] );
1872
-
1873
- // end attachment meta info box output
1874
- $object_image_out .= "</div>"; // close simple-history-attachment-meta
1875
-
1876
- $attachment_out .= " <a href='$edit_link'>";
1877
- $attachment_out .= "<span class='simple-history-title'>{$title}</span>";
1878
- $attachment_out .= "</a>";
1879
-
1880
- } else {
1881
-
1882
- // Post for attachment was not found
1883
- if ($object_name) {
1884
- $attachment_out .= "<span class='simple-history-title'>\"" . esc_html($object_name) . "\"</span>";
1885
- } else {
1886
- $attachment_out .= " <span class='simple-history-title'>&lt;deleted&gt;</span>";
1887
- }
1888
-
1889
- }
1890
-
1891
- $attachment_out .= " " . esc_html__($action, "simple-history") . " ";
1892
-
1893
- $attachment_out = ucfirst($attachment_out);
1894
- $output .= $attachment_out;
1895
-
1896
- } elseif ("user" == $object_type_lcase) {
1897
-
1898
- $user_out = "";
1899
- $user_out .= __("user", 'simple-history');
1900
- $user = get_user_by("id", $object_id);
1901
- if ($user) {
1902
- $user_link = "user-edit.php?user_id={$user->ID}";
1903
- $user_out .= "<span class='simple-history-title'>";
1904
- $user_out .= " <a href='$user_link'>";
1905
- $user_out .= $user->user_nicename;
1906
- $user_out .= "</a>";
1907
- if (isset($user->first_name) && isset($user->last_name)) {
1908
- if ($user->first_name || $user->last_name) {
1909
- $user_out .= " (";
1910
- if ($user->first_name && $user->last_name) {
1911
- $user_out .= esc_html($user->first_name) . " " . esc_html($user->last_name);
1912
- } else {
1913
- $user_out .= esc_html($user->first_name) . esc_html($user->last_name); // just one of them, no space necessary
1914
- }
1915
- $user_out .= ")";
1916
- }
1917
- }
1918
- $user_out .= "</span>";
1919
- } else {
1920
- // most likely deleted user
1921
- $user_link = "";
1922
- $user_out .= " \"" . esc_html($object_name) . "\"";
1923
- }
1924
-
1925
- $user_out .= " " . esc_html__($action, "simple-history");
1926
-
1927
- $user_out = ucfirst($user_out);
1928
- $output .= $user_out;
1929
-
1930
- } elseif ("comment" == $object_type_lcase) {
1931
-
1932
- $comment_link = get_edit_comment_link($object_id);
1933
- $output .= ucwords(esc_html__(ucfirst($object_type))) . " " . esc_html($object_subtype) . " <a href='$comment_link'><span class='simple-history-title'>" . esc_html($object_name) . "\"</span></a> " . esc_html__($action, "simple-history");
1934
-
1935
- } else {
1936
-
1937
- // unknown/general type
1938
- // translate the common types
1939
- $unknown_action = $action;
1940
- switch ($unknown_action) {
1941
- case "activated":
1942
- $unknown_action = __("activated", 'simple-history');
1943
- break;
1944
- case "deactivated":
1945
- $unknown_action = __("deactivated", 'simple-history');
1946
- break;
1947
- case "enabled":
1948
- $unknown_action = __("enabled", 'simple-history');
1949
- break;
1950
- case "disabled":
1951
- $unknown_action = __("disabled", 'simple-history');
1952
- break;
1953
- default:
1954
- $unknown_action = $unknown_action; // dah!
1955
- }
1956
- $output .= ucwords(esc_html__($object_type, "simple-history")) . " " . ucwords(esc_html__($object_subtype, "simple-history")) . " <span class='simple-history-title'>\"" . esc_html($object_name) . "\"</span> " . esc_html($unknown_action);
1957
-
1958
- }
1959
- $output .= "</div>";
1960
-
1961
- // second div = when and who
1962
- $output .= "<div class='second'>";
1963
-
1964
- $date_i18n_date = date_i18n(get_option('date_format'), strtotime($one_row->date), $gmt=false);
1965
- $date_i18n_time = date_i18n(get_option('time_format'), strtotime($one_row->date), $gmt=false);
1966
- $now = strtotime(current_time("mysql"));
1967
- $diff_str = sprintf( __('<span class="when">%1$s ago</span> by %2$s', "simple-history"), human_time_diff(strtotime($one_row->date), $now), $who );
1968
- $output .= $diff_str;
1969
- $output .= "<span class='when_detail'>".sprintf(__('%s at %s', 'simple-history'), $date_i18n_date, $date_i18n_time)."</span>";
1970
-
1971
- // action description
1972
- if ( trim( $action_description ) ) {
1973
- $output .= sprintf(
1974
- '
1975
- <a href="#" class="simple-history-item-description-toggler">%2$s</a>
1976
- <div class="simple-history-item-description-wrap">
1977
- <div class="simple-history-action-description">%1$s</div>
1978
- </div>
1979
- ',
1980
- nl2br( esc_attr( $action_description ) ), // 2
1981
- __("Details", "simple-history") // 2
1982
- );
1983
- }
1984
-
1985
- $output .= "</div>";
1986
-
1987
- // Object image
1988
- if ( $object_image_out ) {
1989
-
1990
- $output .= sprintf(
1991
- '
1992
- <div class="simple-history-object-image">
1993
- %1$s
1994
- </div>
1995
- ',
1996
- $object_image_out
1997
- );
1998
-
1999
- }
2000
-
2001
- // occasions
2002
- if ($num_occasions > 0) {
2003
- $output .= "<div class='third'>";
2004
- if ($num_occasions == 1) {
2005
- $one_occasion = __("+ 1 occasion", 'simple-history');
2006
- $output .= "<a class='simple-history-occasion-show' href='#'>$one_occasion</a>";
2007
- } else {
2008
- $many_occasion = sprintf(__("+ %d occasions", 'simple-history'), $num_occasions);
2009
- $output .= "<a class='simple-history-occasion-show' href='#'>$many_occasion</a>";
2010
- }
2011
-
2012
- $output .= "<ul class='simple-history-occasions hidden'>";
2013
- foreach ($occasions as $one_occasion) {
2014
-
2015
- $output .= "<li>";
2016
-
2017
- $date_i18n_date = date_i18n(get_option('date_format'), strtotime($one_occasion->date), $gmt=false);
2018
- $date_i18n_time = date_i18n(get_option('time_format'), strtotime($one_occasion->date), $gmt=false);
2019
-
2020
- $output .= "<div class='simple-history-occasions-one-when'>";
2021
- $output .= sprintf(
2022
- __('%s ago (%s at %s)', "simple-history"),
2023
- human_time_diff(strtotime($one_occasion->date), $now),
2024
- $date_i18n_date,
2025
- $date_i18n_time
2026
- );
2027
-
2028
- if ( trim( $one_occasion->action_description ) ) {
2029
- $output .= "<a href='#' class='simple-history-occasions-details-toggle'>" . __("Details", "simple-history") . "</a>";
2030
- }
2031
-
2032
- $output .= "</div>";
2033
-
2034
- if ( trim( $one_occasion->action_description ) ) {
2035
- $output .= sprintf(
2036
- '<div class="simple-history-occasions-one-action-description">%1$s</div>',
2037
- nl2br( esc_attr( $one_occasion->action_description ) )
2038
- );
2039
- }
2040
-
2041
-
2042
- $output .= "</li>";
2043
- }
2044
-
2045
- $output .= "</ul>";
2046
-
2047
- $output .= "</div>";
2048
- }
2049
-
2050
- $output .= "</li>";
2051
-
2052
- $loopNum++;
2053
-
2054
- }
2055
-
2056
- // if $loopNum == 0 no items where found for this page
2057
- if ($loopNum == 0) {
2058
- $output .= "noMoreItems";
2059
- }
2060
-
2061
- if ( ! $args["is_ajax"] ) {
2062
-
2063
- // if not ajax, print the divs and stuff we need
2064
- $show_more = "<select>";
2065
- $show_more .= sprintf('<option value=5 %2$s>%1$s</option>', __("Show 5 more", 'simple-history'), ($args["items"] == 5 ? " selected " : "") );
2066
- $show_more .= sprintf('<option value=15 %2$s>%1$s</option>', __("Show 15 more", 'simple-history'), ($args["items"] == 15 ? " selected " : "") );
2067
- $show_more .= sprintf('<option value=50 %2$s>%1$s</option>', __("Show 50 more", 'simple-history'), ($args["items"] == 50 ? " selected " : "") );
2068
- $show_more .= sprintf('<option value=100 %2$s>%1$s</option>', __("Show 100 more", 'simple-history'), ($args["items"] == 100 ? " selected " : "") );
2069
- $show_more .= "</select>";
2070
-
2071
- $no_found = __("No matching items found.", 'simple-history');
2072
- $view_rss = __("RSS feed", 'simple-history');
2073
- $view_rss_link = simple_history_get_rss_address();
2074
- $str_show = __("Show", 'simple-history');
2075
- $output .= "</ol>";
2076
-
2077
- $output .= sprintf( '
2078
- <div class="simple-history-loading">%2$s %1$s</div>
2079
- ',
2080
- __("Loading...", 'simple-history'), // 1
2081
- "<img src='".site_url("wp-admin/images/loading.gif")."' width=16 height=16>"
2082
- );
2083
-
2084
- $output .= "</div>";
2085
-
2086
- $output .= "
2087
- <p class='simple-history-no-more-items'>$no_found</p>
2088
- <p class='simple-history-rss-feed-dashboard'><a title='$view_rss' href='$view_rss_link'>$view_rss</a></p>
2089
- <p class='simple-history-rss-feed-page'><a title='$view_rss' href='$view_rss_link'><span></span>$view_rss</a></p>
2090
- ";
2091
-
2092
- }
2093
-
2094
- } else {
2095
-
2096
- if ($args["is_ajax"]) {
2097
- $output .= "noMoreItems";
2098
- } else {
2099
- $no_found = __("No history items found.", 'simple-history');
2100
- $please_note = __("Please note that Simple History only records things that happen after this plugin have been installed.", 'simple-history');
2101
- $output .= "<p>$no_found</p>";
2102
- $output .= "<p>$please_note</p>";
2103
- }
2104
-
2105
- }
2106
- return $output;
2107
- }
2108
-
2109
- // called when saving an options page
2110
- function simple_history_add_update_option_page($capability = NULL, $option_page = NULL) {
2111
-
2112
- $arr_options_names = array(
2113
- "general" => __("General Settings"),
2114
- "writing" => __("Writing Settings"),
2115
- "reading" => __("Reading Settings"),
2116
- "discussion" => __("Discussion Settings"),
2117
- "media" => __("Media Settings"),
2118
- "privacy" => __("Privacy Settings")
2119
- );
2120
-
2121
- $option_page_name = "";
2122
- if (isset($arr_options_names[$option_page])) {
2123
- $option_page_name = $arr_options_names[$option_page];
2124
- simple_history_add("action=modified&object_type=settings page&object_id=$option_page&object_name=$option_page_name");
2125
- }
2126
-
2127
- return $capability;
2128
- }
2129
-
2130
- // called when updating permalinks
2131
- function simple_history_add_update_option_page_permalinks($action, $result) {
2132
-
2133
- if ("update-permalink" == $action) {
2134
- $option_page_name = __("Permalink Settings");
2135
- $option_page = "permalink";
2136
- simple_history_add("action=modified&object_type=settings page&object_id=$option_page&object_name=$option_page_name");
2137
- }
2138
-
2139
- }
2140
 
 
 
1
  <?php
2
  /*
3
  Plugin Name: Simple History
4
+ Plugin URI: http://simple-history.com
5
+ Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
6
+ Version: 2
7
  Author: Pär Thernström
8
+ Author URI: http://simple-history.com/
9
  License: GPL2
10
  */
11
 
12
+ /* Copyright 2014 Pär Thernström (email: par.thernstrom@gmail.com)
13
 
14
+ This program is free software; you can redistribute it and/or modify
15
+ it under the terms of the GNU General Public License, version 2, as
16
+ published by the Free Software Foundation.
17
 
18
+ This program is distributed in the hope that it will be useful,
19
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ GNU General Public License for more details.
22
 
23
+ You should have received a copy of the GNU General Public License
24
+ along with this program; if not, write to the Free Software
25
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
  */
27
 
 
28
 
29
+ if ( version_compare( phpversion(), "5.3", ">=") ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ /** Load required files */
32
+ require_once(__DIR__ . "/SimpleHistory.php");
33
+ require_once(__DIR__ . "/SimpleHistoryLogQuery.php");
34
+ require_once(__DIR__ . "/SimpleHistoryFunctions.php");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  /**
37
+ * Register function that is called when plugin is installed
38
+ *
39
+ * @TODO: make activatigon multi site aware, as in https://github.com/scribu/wp-proper-network-activation
40
  */
41
+ // register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
 
 
 
 
 
 
 
42
 
43
+ /** Boot up */
44
+ $GLOBALS["simple_history"] = new SimpleHistory();
 
 
 
45
 
46
+ } else {
 
 
47
 
48
+ // user is running to old version of php, add admin notice about that
49
+ add_action( 'admin_notices', 'simple_history_old_version_admin_notice' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ function simple_history_old_version_admin_notice() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  ?>
53
+ <div class="updated error">
54
+ <p><?php _e( 'Simple History is a great plugin, but to use it your server must have at least PHP 5.3 installed.', 'simple-history' ); ?></p>
55
+ </div>
56
+ <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  }
60
 
61
+ // Test log cron things
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  /*
63
+ wp_schedule_event( time(), "hourly", "simple_history_cron_testhook");
 
 
 
 
 
64
  */
 
 
 
 
65
  /*
66
+ wp_clear_scheduled_hook("simple_history_cron_testhook");
67
+ add_action( 'simple_history_cron_testhook', 'simple_history_cron_testhook_function' );
68
+ function simple_history_cron_testhook_function() {
69
+ SimpleLogger()->info("This is a message inside a cron function");
 
 
 
 
70
  }
71
  */
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  /*
74
+ add_action("init", function() {
 
 
 
 
 
75
 
76
+ global $wp_current_filter;
 
77
 
78
+ $doing_cron = get_transient( 'doing_cron' );
79
+ $const_doing_cron = defined('DOING_CRON') && DOING_CRON;
80
 
81
+ if ($const_doing_cron) {
 
82
 
83
+ $current_filter = current_filter();
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ SimpleLogger()->info("This is a message inside init, trying to log crons", array(
86
+ "doing_cron" => simpleHistory::json_encode($doing_cron),
87
+ "current_filter" => $current_filter,
88
+ "wp_current_filter" => $wp_current_filter,
89
+ "wp_current_filter" => simpleHistory::json_encode( $wp_current_filter ),
90
+ "const_doing_cron" => simpleHistory::json_encode($const_doing_cron)
91
+ ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  }
94
 
95
+ }, 100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  */
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
+ //*
100
+ add_action("init", function() {
 
 
 
 
101
 
102
+ #SimpleLogger()->info("This is a regular info message" . time());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ }, 100);
105
+ // */
js/scripts.js ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*jshint multistr: true */
2
+
3
+ var simple_history = (function($) {
4
+
5
+ var api_base_url = window.ajaxurl + "?action=simple_history_api";
6
+
7
+ var debug = function(what) {
8
+
9
+ if (typeof what == "object") {
10
+
11
+ var newWhat = "";
12
+
13
+ _.each(what, function(val, key) {
14
+ newWhat += key + ": " + val + "\n";
15
+ });
16
+
17
+ what = newWhat;
18
+
19
+ }
20
+
21
+ $(".SimpleHistoryLogitems__debug").append("<br>" + what);
22
+
23
+ };
24
+
25
+ var LogRowsCollection = Backbone.Collection.extend({
26
+
27
+ initialize: function(models, options) {
28
+
29
+ this.mainView = options.mainView;
30
+
31
+ $(document).trigger("SimpleHistory:logRowsCollectionInitialize");
32
+
33
+ },
34
+
35
+ reload: function() {
36
+
37
+ this.trigger("reload");
38
+ $(document).trigger("SimpleHistory:logRowsCollectionReload");
39
+
40
+ var pager_size = this.mainView.$el.data("pagerSize");
41
+ this.url = api_base_url + "&type=overview&format=html";
42
+ this.url += "&posts_per_page=" + pager_size;
43
+
44
+ // Reset some vars
45
+ this.api_args = null;
46
+ this.max_id = null;
47
+ this.min_id = null;
48
+ this.pages_count = null;
49
+ this.total_row_count = null;
50
+ this.page_rows_from = null;
51
+ this.page_rows_to = null;
52
+ this.max_id_first_page = null;
53
+
54
+ // Get first page
55
+ // We don't have max_id yet
56
+ var that = this;
57
+ var url_data = {
58
+ paged: 1,
59
+ // here we want to append custom args/data for filters
60
+ };
61
+
62
+ // trigger so plugins can modify url parameters
63
+ this.trigger("before_fetch", this, url_data);
64
+
65
+ this.fetch({
66
+ reset: true,
67
+ data: url_data,
68
+ // called on 404 and similar
69
+ error: function(collection, response, options) {
70
+ collection.trigger("reloadError");
71
+ $(document).trigger("SimpleHistory:logRowsCollectionReloadError");
72
+ },
73
+ success: function(collection, response, options) {
74
+ collection.trigger("reloadDone");
75
+ $(document).trigger("SimpleHistory:logRowsCollectionReloadDone");
76
+ }
77
+ });
78
+
79
+ },
80
+
81
+ /*
82
+ * Parse ajax response to make it fit to format used by backbone
83
+ */
84
+ parse: function(resp, xhr) {
85
+
86
+ if (!resp || !resp.data) {
87
+ alert("Error in response, could not parse");
88
+ return;
89
+ }
90
+
91
+ this.api_args = resp.data.api_args;
92
+ this.max_id = resp.data.max_id;
93
+ this.min_id = resp.data.min_id;
94
+ this.pages_count = resp.data.pages_count;
95
+ this.total_row_count = resp.data.total_row_count;
96
+ this.page_rows_from = resp.data.page_rows_from;
97
+ this.page_rows_to = resp.data.page_rows_to;
98
+
99
+ // Store first max_id found, since that's the max id we use for
100
+ // all subsequent paginations
101
+ if ( ! this.max_id_first_page ) {
102
+
103
+ this.max_id_first_page = this.max_id;
104
+ $(document).trigger("SimpleHistory:logRowsCollectionFirstLoad");
105
+
106
+ $(".SimpleHistory__waitingForFirstLoad").addClass("SimpleHistory__waitingForFirstLoad--isLoaded");
107
+
108
+ // Add class to body to so we can catch loaded log everywhere in CSS
109
+ $("body").addClass("SimpleHistory--isLoaded");
110
+
111
+ }
112
+
113
+ var arrRows = [];
114
+ _.each(resp.data.log_rows, function(row) {
115
+ arrRows.push({
116
+ html: row
117
+ });
118
+ });
119
+
120
+ return arrRows;
121
+ }
122
+
123
+ });
124
+
125
+ var OccasionsLogRowsCollection = Backbone.Collection.extend({
126
+
127
+ initialize: function(models, options) {
128
+
129
+ this.url = api_base_url + "&type=occasions&format=html";
130
+
131
+ this.fetch({
132
+ reset: true,
133
+ data: {
134
+ logRowID: options.logRowID,
135
+ occasionsID: options.occasionsID,
136
+ occasionsCount: options.occasionsCount
137
+ }
138
+ });
139
+
140
+ },
141
+
142
+ parse: function(resp, xhr) {
143
+
144
+ this.api_args = resp.data.api_args;
145
+ this.max_id = resp.data.max_id;
146
+ this.min_id = resp.data.min_id;
147
+ this.pages_count = resp.data.pages_count;
148
+ this.total_row_count = resp.data.total_row_count;
149
+ this.page_rows_from = resp.data.page_rows_from;
150
+ this.page_rows_to = resp.data.page_rows_to;
151
+
152
+ var arrRows = [];
153
+ _.each(resp.data.log_rows, function(row) {
154
+ arrRows.push({
155
+ html: row
156
+ });
157
+ });
158
+
159
+ return arrRows;
160
+ }
161
+
162
+ });
163
+
164
+ var OccasionsView = Backbone.View.extend({
165
+
166
+ initialize: function() {
167
+
168
+ var logRowID = this.attributes.logRow.data("rowId");
169
+ var occasionsCount = this.attributes.logRow.data("occasionsCount");
170
+ var occasionsID = this.attributes.logRow.data("occasionsId");
171
+
172
+ this.attributes.logRow.addClass("SimpleHistoryLogitem--occasionsOpening");
173
+
174
+ this.logRows = new OccasionsLogRowsCollection([], {
175
+ logRow: this.attributes.logRow,
176
+ logRowID: logRowID,
177
+ occasionsID: occasionsID,
178
+ occasionsCount: occasionsCount
179
+ });
180
+
181
+ this.logRows.on("reset", this.render, this);
182
+
183
+ // Trigger event for plugins
184
+ this.logRows.on("reset", function() {
185
+ $(document).trigger("SimpleHistory:logRowsCollectionOccasionsLoaded");
186
+ }, this);
187
+
188
+ },
189
+
190
+ render: function() {
191
+
192
+ var $html = $([]);
193
+
194
+ this.logRows.each(function(model) {
195
+ var $li = $(model.get("html"));
196
+ $li.addClass("SimpleHistoryLogitem--occasion");
197
+ $html = $html.add($li);
198
+ });
199
+
200
+ this.$el.html($html);
201
+
202
+ this.attributes.logRow.removeClass("SimpleHistoryLogitem--occasionsOpening").addClass("SimpleHistoryLogitem--occasionsOpened");
203
+
204
+ this.$el.addClass("haveOccasionsAdded");
205
+
206
+ }
207
+
208
+ });
209
+
210
+ var DetailsModel = Backbone.Model.extend({
211
+ url: api_base_url + "&type=single&format=html"
212
+ });
213
+
214
+ /**
215
+ * DetailsView is a modal popup thingie with all info about a LogRow
216
+ */
217
+ var DetailsView = Backbone.View.extend({
218
+
219
+ initialize: function(attributes) {
220
+
221
+ this.model.fetch({
222
+ data: {
223
+ id: this.model.get("id")
224
+ }
225
+ });
226
+
227
+ this.template = $("#tmpl-simple-history-logitems-modal").html();
228
+ this.show();
229
+
230
+ this.listenTo(this.model, "change", this.render);
231
+
232
+ // also close on esc
233
+ var view = this;
234
+ $(document).on("keydown.simplehistory.modal", function(e) {
235
+ if (e.keyCode == 27) {
236
+ view.close();
237
+ }
238
+ });
239
+
240
+ },
241
+
242
+ events: {
243
+ "click .SimpleHistory-modal__background": "close",
244
+ "click .SimpleHistory-modal__contentClose": "close"
245
+ },
246
+
247
+ show: function() {
248
+
249
+ var $modalEl = $(".SimpleHistory-modal");
250
+
251
+ if (!$modalEl.length) {
252
+ $modalEl = $(this.template);
253
+ $modalEl.appendTo("body");
254
+ }
255
+
256
+ this.setElement($modalEl);
257
+
258
+ var $modalContentEl = $modalEl.find(".SimpleHistory-modal__content");
259
+ $modalContentEl.addClass("SimpleHistory-modal__content--enter");
260
+
261
+ // Force repaint before adding active class
262
+ var offsetHeight = $modalContentEl.get(0).offsetHeight;
263
+ $modalContentEl.addClass("SimpleHistory-modal__content--enter-active");
264
+
265
+ },
266
+
267
+ close: function() {
268
+
269
+ var $modalContentEl = this.$el.find(".SimpleHistory-modal__content");
270
+ $modalContentEl.addClass("SimpleHistory-modal__content--leave");
271
+
272
+ // Force repaint before adding active class
273
+ var offsetHeight = $modalContentEl.get(0).offsetHeight;
274
+
275
+ $modalContentEl.addClass("SimpleHistory-modal__content--leave-active");
276
+ this.$el.addClass("SimpleHistory-modal__leave-active");
277
+
278
+ // Cleanup
279
+ var view = this;
280
+ setTimeout(function() {
281
+ view.$el.remove();
282
+ $(document).off("keyup.simplehistory.modal");
283
+ view.remove();
284
+ Backbone.history.navigate("overview");
285
+ }, 400);
286
+
287
+ },
288
+
289
+ render: function() {
290
+
291
+ var $modalContentInnerEl = this.$el.find(".SimpleHistory-modal__contentInner");
292
+ var logRowLI = this.model.get("data").log_rows[0];
293
+ $modalContentInnerEl.html(logRowLI);
294
+
295
+ }
296
+
297
+ });
298
+
299
+ var RowsView = Backbone.View.extend({
300
+
301
+ initialize: function() {
302
+
303
+ this.collection.on("reset", this.render, this);
304
+ this.collection.on("reload", this.onReload, this);
305
+ this.collection.on("reloadDone", this.onReloadDone, this);
306
+
307
+ // Trigger event for plugins
308
+ this.collection.on("reset", function() {
309
+ $(document).trigger("SimpleHistory:logLoaded");
310
+ }, this);
311
+
312
+ },
313
+
314
+ onReload: function() {
315
+
316
+ $("html").addClass("SimpleHistory-isLoadingPage");
317
+
318
+ },
319
+
320
+ onReloadDone: function() {
321
+
322
+ $("html").removeClass("SimpleHistory-isLoadingPage");
323
+
324
+ var $mainViewElm = this.collection.mainView.$el;
325
+
326
+ // Add message if no hits
327
+ $mainViewElm.removeClass("SimpleHistory--hasNoHits");
328
+ if (! this.collection.length ) {
329
+
330
+ $mainViewElm.addClass("SimpleHistory--hasNoHits");
331
+
332
+ var noHitsClass = "SimpleHistoryLogitems__noHits";
333
+
334
+ // Remove maybe previos div with message
335
+ $mainViewElm.find("." + noHitsClass).remove();
336
+
337
+ // Add div with message
338
+ var $noHitsElm = $("<div />")
339
+ .html( simple_history_script_vars.logNoHits )
340
+ .addClass(noHitsClass)
341
+ .appendTo( $mainViewElm.find(".SimpleHistoryLogitems__above") )
342
+ ;
343
+
344
+ } // add msg
345
+
346
+ },
347
+
348
+ events: {
349
+ "click .SimpleHistoryLogitem__occasions a": "showOccasions",
350
+ "click .SimpleHistoryLogitem__permalink": "permalink"
351
+ },
352
+
353
+ permalink: function(e) {
354
+
355
+ // If cmd is pressed then don't show modal because then user wants
356
+ // to open modal in new window/tab
357
+ if (e.metaKey) {
358
+ return true;
359
+ }
360
+
361
+ e.preventDefault();
362
+
363
+ var $target = $(e.target);
364
+ var $logRow = $target.closest(".SimpleHistoryLogitem");
365
+ var logRowID = $logRow.data("rowId");
366
+
367
+ Backbone.history.navigate("item/" + logRowID, { trigger: true });
368
+
369
+ },
370
+
371
+ showOccasions: function(e) {
372
+
373
+ e.preventDefault();
374
+
375
+ var $target = $(e.target);
376
+ var $logRow = $target.closest(".SimpleHistoryLogitem");
377
+ var $occasionsElm = $("<li class='SimpleHistoryLogitem__occasionsItemsWrap'><ul class='SimpleHistoryLogitem__occasionsItems'/></li>");
378
+
379
+ $logRow.after($occasionsElm);
380
+
381
+ this.occasionsView = new OccasionsView({
382
+ el: $occasionsElm.find(".SimpleHistoryLogitem__occasionsItems"),
383
+ attributes: {
384
+ logRow: $logRow
385
+ }
386
+ });
387
+
388
+ },
389
+
390
+ render: function() {
391
+
392
+ var html = "";
393
+ this.collection.each(function(model) {
394
+ html += model.get("html");
395
+ });
396
+
397
+ this.$el.html( html );
398
+
399
+ // Rendering of log rows items is done
400
+ this.trigger("renderDone");
401
+
402
+ }
403
+
404
+ });
405
+
406
+ var PaginationView = Backbone.View.extend({
407
+
408
+ initialize: function() {
409
+
410
+ this.template = $("#tmpl-simple-history-logitems-pagination").html();
411
+
412
+ $(document).keydown({ view: this }, this.keyboardNav);
413
+
414
+ this.collection.on("reset", this.render, this);
415
+
416
+ },
417
+
418
+ events: {
419
+ "click .SimpleHistoryPaginationLink": "navigateArrow",
420
+ "keyup .SimpleHistoryPaginationCurrentPage": "navigateToPage",
421
+ "keydown": "keydown"
422
+ },
423
+
424
+ keyboardNav: function(e) {
425
+
426
+ // if modal with details is open then don't nav away
427
+ if ($(".SimpleHistory-modal").length) {
428
+ return;
429
+ }
430
+
431
+ // Only go on if on own page
432
+ if (!$(".dashboard_page_simple_history_page").length) {
433
+ return;
434
+ }
435
+
436
+ var paged;
437
+
438
+ if (e.keyCode == 37) {
439
+ // prev page
440
+ paged = +e.data.view.collection.api_args.paged - 1;
441
+ } else if (e.keyCode == 39) {
442
+ // next page
443
+ paged = +e.data.view.collection.api_args.paged + 1;
444
+ }
445
+
446
+ if (paged) {
447
+ e.data.view.fetchPage(paged);
448
+ }
449
+
450
+ },
451
+
452
+ navigateToPage: function(e) {
453
+
454
+ // keycode 13 = enter
455
+ if (e.keyCode == 13) {
456
+
457
+ var $target = $(e.target);
458
+ var paged = parseInt( $target.val() );
459
+
460
+ // We must go to a page more than zero and max total_row_count
461
+ if (paged < 1) {
462
+ paged = 1;
463
+ }
464
+
465
+ if ( paged > this.collection.pages_count ) {
466
+ paged = this.collection.pages_count;
467
+ }
468
+
469
+ this.fetchPage(paged);
470
+
471
+ }
472
+
473
+ },
474
+
475
+ navigateArrow: function(e) {
476
+
477
+ e.preventDefault();
478
+ var $target = $(e.target);
479
+
480
+ // if link has class disabled then don't nav away
481
+ if ($target.is(".disabled")) {
482
+ return;
483
+ }
484
+
485
+ // direction = first|prev|next|last
486
+ var direction = $target.data("direction");
487
+
488
+ var paged;
489
+ switch (direction) {
490
+
491
+ case "first":
492
+ paged = 1;
493
+ break;
494
+
495
+ case "last":
496
+ paged = this.collection.pages_count;
497
+ break;
498
+
499
+ case "prev":
500
+ paged = +this.collection.api_args.paged - 1;
501
+ break;
502
+
503
+ case "next":
504
+ paged = +this.collection.api_args.paged + 1;
505
+ break;
506
+
507
+ }
508
+
509
+ this.fetchPage(paged);
510
+
511
+ },
512
+
513
+ /**
514
+ * Fetch a page from the server
515
+ * Calls collection.fetch with the page we want to view as argument
516
+ */
517
+ fetchPage: function(paged) {
518
+
519
+ $("html").addClass("SimpleHistory-isLoadingPage");
520
+
521
+ var url_data = {
522
+ paged: paged,
523
+ max_id_first_page: this.collection.max_id_first_page
524
+ };
525
+
526
+ this.collection.trigger("before_fetch", this.collection, url_data);
527
+
528
+ // nav = fetch collection items again
529
+ this.collection.fetch({
530
+ reset: true,
531
+ data: url_data,
532
+ success: function() {
533
+ $("html").removeClass("SimpleHistory-isLoadingPage");
534
+ }
535
+ });
536
+
537
+ // Scroll to top of el
538
+ $("html, body").animate({
539
+ scrollTop: this.attributes.mainView.$el.offset().top - 85
540
+ }, 350);
541
+
542
+ },
543
+
544
+ render: function() {
545
+
546
+ var compiled = _.template(this.template);
547
+
548
+ this.$el.html( compiled({
549
+ min_id: this.collection.min_id,
550
+ max_id: this.collection.max_id,
551
+ pages_count: this.collection.pages_count,
552
+ total_row_count: this.collection.total_row_count,
553
+ page_rows_from: this.collection.page_rows_from,
554
+ page_rows_to: this.collection.page_rows_to,
555
+ api_args: this.collection.api_args,
556
+ strings: simple_history_script_vars.pagination
557
+ }) );
558
+
559
+ }
560
+
561
+ });
562
+
563
+ var MainView = Backbone.View.extend({
564
+
565
+ el: ".SimpleHistoryGui",
566
+
567
+ initialize: function() {
568
+
569
+ this.addNeededElements();
570
+
571
+ },
572
+
573
+ manualInitialize: function() {
574
+
575
+ // Don't try to init if our element does not exist
576
+ if (!this.$el.length) {
577
+ return;
578
+ }
579
+
580
+ this.logRouter = new LogRouter();
581
+ Backbone.history.start();
582
+
583
+ this.logRowsCollection = new LogRowsCollection([], {
584
+ mainView: this,
585
+ });
586
+
587
+ this.rowsView = new RowsView({
588
+ el: this.$el.find(".SimpleHistoryLogitems"),
589
+ collection: this.logRowsCollection
590
+ });
591
+
592
+ $(document).trigger("SimpleHistory:mainViewInitBeforeLoadRows");
593
+
594
+ // Load log first time
595
+ this.logRowsCollection.reload();
596
+
597
+ $(document).trigger("SimpleHistory:mainViewInitAfterLoadRows");
598
+
599
+ this.paginationView = new PaginationView({
600
+ el: this.$el.find(".SimpleHistoryLogitems__pagination"),
601
+ collection: this.logRowsCollection,
602
+ attributes: {
603
+ mainView: this
604
+ }
605
+ });
606
+
607
+ $(document).trigger("SimpleHistory:init");
608
+
609
+ },
610
+
611
+ /**
612
+ * Add the elements needed for the GUI
613
+ */
614
+ addNeededElements: function() {
615
+
616
+ var template = $("#tmpl-simple-history-base").html();
617
+ this.$el.html( template );
618
+
619
+ },
620
+
621
+
622
+ });
623
+
624
+ var LogRouter = Backbone.Router.extend({
625
+
626
+ routes: {
627
+ "item/:number": "item",
628
+ '*default': 'default'
629
+ },
630
+
631
+ item: function(logRowID) {
632
+
633
+ var detailsModel = new DetailsModel({
634
+ id: logRowID
635
+ });
636
+
637
+ var detailsView = new DetailsView({
638
+ model: detailsModel
639
+ });
640
+
641
+ },
642
+
643
+ default: function() {
644
+
645
+ return false;
646
+
647
+ }
648
+
649
+ });
650
+
651
+ var mainView = new MainView();
652
+
653
+ // Init MainView on domReady
654
+ // This is to make sure dropins and plugins have been loaded
655
+ $(document).ready(function() {
656
+
657
+ mainView.manualInitialize();
658
+
659
+ });
660
+
661
+ return mainView;
662
+
663
+ })(jQuery);
664
+
665
+ jQuery(".js-SimpleHistory-Settings-ClearLog").on("click", function(e) {
666
+
667
+ if (confirm(simple_history_script_vars.settingsConfirmClearLog)) {
668
+ return;
669
+ } else {
670
+ e.preventDefault();
671
+ }
672
+
673
+ });
674
+
js/select2/LICENSE ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2014 Igor Vaynberg
2
+
3
+ Version: @@ver@@ Timestamp: @@timestamp@@
4
+
5
+ This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
6
+ General Public License version 2 (the "GPL License"). You may choose either license to govern your
7
+ use of this software only upon the condition that you accept all of the terms of either the Apache
8
+ License or the GPL License.
9
+
10
+ You may obtain a copy of the Apache License and the GPL License at:
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+ http://www.gnu.org/licenses/gpl-2.0.html
14
+
15
+ Unless required by applicable law or agreed to in writing, software distributed under the Apache License
16
+ or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17
+ either express or implied. See the Apache License and the GPL License for the specific language governing
18
+ permissions and limitations under the Apache License and the GPL License.
js/select2/README.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Select2
2
+ =======
3
+
4
+ Select2 is a jQuery-based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.
5
+
6
+ To get started, checkout examples and documentation at http://ivaynberg.github.com/select2
7
+
8
+ Use cases
9
+ ---------
10
+
11
+ * Enhancing native selects with search.
12
+ * Enhancing native selects with a better multi-select interface.
13
+ * Loading data from JavaScript: easily load items via ajax and have them searchable.
14
+ * Nesting optgroups: native selects only support one level of nested. Select2 does not have this restriction.
15
+ * Tagging: ability to add new items on the fly.
16
+ * Working with large, remote datasets: ability to partially load a dataset based on the search term.
17
+ * Paging of large datasets: easy support for loading more pages when the results are scrolled to the end.
18
+ * Templating: support for custom rendering of results and selections.
19
+
20
+ Browser compatibility
21
+ ---------------------
22
+ * IE 8+
23
+ * Chrome 8+
24
+ * Firefox 10+
25
+ * Safari 3+
26
+ * Opera 10.6+
27
+
28
+ Usage
29
+ -----
30
+ You can source Select2 directly from a [CDN like JSDliver](http://www.jsdelivr.com/#!select2), [download it from this GitHub repo](https://github.com/ivaynberg/select2/tags), or use one of the integrations below.
31
+
32
+ Integrations
33
+ ------------
34
+
35
+ * [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org))
36
+ * [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails)
37
+ * [AngularUI](http://angular-ui.github.com/#directives-select2) ([AngularJS](angularjs.org))
38
+ * [Django](https://github.com/applegrew/django-select2)
39
+ * [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin)
40
+ * [Symfony2](https://github.com/avocode/FormExtensions)
41
+ * [Bootstrap 2](https://github.com/t0m/select2-bootstrap-css) and [Bootstrap 3](https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3) (CSS skins)
42
+ * [Meteor](https://github.com/nate-strauser/meteor-select2) (modern reactive JavaScript framework; + [Bootstrap 3 skin](https://github.com/esperadomedia/meteor-select2-bootstrap3-css/))
43
+ * [Meteor](https://jquery-select2.meteor.com)
44
+ * [Yii 2.x](http://demos.krajee.com/widgets#select2)
45
+ * [Yii 1.x](https://github.com/tonybolzan/yii-select2)
46
+ * [AtmosphereJS](https://atmospherejs.com/package/jquery-select2)
47
+
48
+ ### Example Integrations
49
+
50
+ * [Knockout.js](https://github.com/ivaynberg/select2/wiki/Knockout.js-Integration)
51
+ * [Socket.IO](https://github.com/ivaynberg/select2/wiki/Socket.IO-Integration)
52
+ * [PHP](https://github.com/ivaynberg/select2/wiki/PHP-Example)
53
+ * [.Net MVC] (https://github.com/ivaynberg/select2/wiki/.Net-MVC-Example)
54
+
55
+ Internationalization (i18n)
56
+ ---------------------------
57
+
58
+ Select2 supports multiple languages by simply including the right language JS
59
+ file (`select2_locale_it.js`, `select2_locale_nl.js`, etc.) after `select2.js`.
60
+
61
+ Missing a language? Just copy `select2_locale_en.js.template`, translate
62
+ it, and make a pull request back to Select2 here on GitHub.
63
+
64
+ Bug tracker
65
+ -----------
66
+
67
+ Have a bug? Please create an issue here on GitHub!
68
+
69
+ https://github.com/ivaynberg/select2/issues
70
+
71
+ Mailing list
72
+ ------------
73
+
74
+ Have a question? Ask on our mailing list!
75
+
76
+ select2@googlegroups.com
77
+
78
+ https://groups.google.com/d/forum/select2
79
+
80
+
81
+ Copyright and license
82
+ ---------------------
83
+
84
+ Copyright 2012 Igor Vaynberg
85
+
86
+ This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
87
+ General Public License version 2 (the "GPL License"). You may choose either license to govern your
88
+ use of this software only upon the condition that you accept all of the terms of either the Apache
89
+ License or the GPL License.
90
+
91
+ You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at:
92
+
93
+ http://www.apache.org/licenses/LICENSE-2.0
94
+ http://www.gnu.org/licenses/gpl-2.0.html
95
+
96
+ Unless required by applicable law or agreed to in writing, software distributed under the Apache License
97
+ or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
98
+ either express or implied. See the Apache License and the GPL License for the specific language governing
99
+ permissions and limitations under the Apache License and the GPL License.
js/select2/bower.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "select2",
3
+ "version": "3.5.1",
4
+ "main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"],
5
+ "dependencies": {
6
+ "jquery": ">= 1.7.1"
7
+ }
8
+ }
js/select2/component.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "select2",
3
+ "repo": "ivaynberg/select2",
4
+ "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
5
+ "version": "3.5.1",
6
+ "demo": "http://ivaynberg.github.io/select2/",
7
+ "keywords": [
8
+ "jquery"
9
+ ],
10
+ "main": "select2.js",
11
+ "styles": [
12
+ "select2.css",
13
+ "select2-bootstrap.css"
14
+ ],
15
+ "scripts": [
16
+ "select2.js",
17
+ "select2_locale_ar.js",
18
+ "select2_locale_bg.js",
19
+ "select2_locale_ca.js",
20
+ "select2_locale_cs.js",
21
+ "select2_locale_da.js",
22
+ "select2_locale_de.js",
23
+ "select2_locale_el.js",
24
+ "select2_locale_es.js",
25
+ "select2_locale_et.js",
26
+ "select2_locale_eu.js",
27
+ "select2_locale_fa.js",
28
+ "select2_locale_fi.js",
29
+ "select2_locale_fr.js",
30
+ "select2_locale_gl.js",
31
+ "select2_locale_he.js",
32
+ "select2_locale_hr.js",
33
+ "select2_locale_hu.js",
34
+ "select2_locale_id.js",
35
+ "select2_locale_is.js",
36
+ "select2_locale_it.js",
37
+ "select2_locale_ja.js",
38
+ "select2_locale_ka.js",
39
+ "select2_locale_ko.js",
40
+ "select2_locale_lt.js",
41
+ "select2_locale_lv.js",
42
+ "select2_locale_mk.js",
43
+ "select2_locale_ms.js",
44
+ "select2_locale_nl.js",
45
+ "select2_locale_no.js",
46
+ "select2_locale_pl.js",
47
+ "select2_locale_pt-BR.js",
48
+ "select2_locale_pt-PT.js",
49
+ "select2_locale_ro.js",
50
+ "select2_locale_ru.js",
51
+ "select2_locale_sk.js",
52
+ "select2_locale_sv.js",
53
+ "select2_locale_th.js",
54
+ "select2_locale_tr.js",
55
+ "select2_locale_uk.js",
56
+ "select2_locale_vi.js",
57
+ "select2_locale_zh-CN.js",
58
+ "select2_locale_zh-TW.js"
59
+ ],
60
+ "images": [
61
+ "select2-spinner.gif",
62
+ "select2.png",
63
+ "select2x2.png"
64
+ ],
65
+ "license": "MIT"
66
+ }
js/select2/composer.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name":
3
+ "ivaynberg/select2",
4
+ "description": "Select2 is a jQuery based replacement for select boxes.",
5
+ "version": "3.5.1",
6
+ "type": "component",
7
+ "homepage": "http://ivaynberg.github.io/select2/",
8
+ "license": "Apache-2.0",
9
+ "require": {
10
+ "robloach/component-installer": "*",
11
+ "components/jquery": ">=1.7.1"
12
+ },
13
+ "extra": {
14
+ "component": {
15
+ "scripts": [
16
+ "select2.js"
17
+ ],
18
+ "files": [
19
+ "select2.js",
20
+ "select2_locale_*.js",
21
+ "select2.css",
22
+ "select2-bootstrap.css",
23
+ "select2-spinner.gif",
24
+ "select2.png",
25
+ "select2x2.png"
26
+ ]
27
+ }
28
+ }
29
+ }
js/select2/package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name" : "Select2",
3
+ "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
4
+ "homepage": "http://ivaynberg.github.io/select2",
5
+ "author": "Igor Vaynberg",
6
+ "repository": {"type": "git", "url": "git://github.com/ivaynberg/select2.git"},
7
+ "main": "select2.js",
8
+ "version": "3.5.1",
9
+ "jspm": {
10
+ "main": "select2",
11
+ "files": ["select2.js", "select2.png", "select2.css", "select2-spinner.gif"],
12
+ "shim": {
13
+ "select2": {
14
+ "imports": ["jquery", "./select2.css!"],
15
+ "exports": "$"
16
+ }
17
+ },
18
+ "buildConfig": { "uglify": true }
19
+ }
20
+ }
js/select2/release.sh ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo -n "Enter the version for this release: "
5
+
6
+ read ver
7
+
8
+ if [ ! $ver ]; then
9
+ echo "Invalid version."
10
+ exit
11
+ fi
12
+
13
+ name="select2"
14
+ js="$name.js"
15
+ mini="$name.min.js"
16
+ css="$name.css"
17
+ release="$name-$ver"
18
+ tag="$ver"
19
+ branch="build-$ver"
20
+ curbranch=`git branch | grep "*" | sed "s/* //"`
21
+ timestamp=$(date)
22
+ tokens="s/@@ver@@/$ver/g;s/\@@timestamp@@/$timestamp/g"
23
+ remote="github"
24
+
25
+ echo "Pulling from origin"
26
+
27
+ git pull
28
+
29
+ echo "Updating Version Identifiers"
30
+
31
+ sed -E -e "s/\"version\": \"([0-9\.]+)\",/\"version\": \"$ver\",/g" -i -- bower.json select2.jquery.json component.json composer.json package.json
32
+
33
+ git add bower.json
34
+ git add select2.jquery.json
35
+ git add component.json
36
+ git add composer.json
37
+ git add package.json
38
+
39
+ git commit -m "modified version identifiers in descriptors for release $ver"
40
+ git push
41
+
42
+ git branch "$branch"
43
+ git checkout "$branch"
44
+
45
+ echo "Tokenizing..."
46
+
47
+ find . -name "$js" | xargs -I{} sed -e "$tokens" -i -- {}
48
+ find . -name "$css" | xargs -I{} sed -e "$tokens" -i -- {}
49
+
50
+ sed -e "s/latest/$ver/g" -i -- bower.json
51
+
52
+ git add "$js"
53
+ git add "$css"
54
+
55
+ echo "Minifying..."
56
+
57
+ echo "/*" > "$mini"
58
+ cat LICENSE | sed "$tokens" >> "$mini"
59
+ echo "*/" >> "$mini"
60
+
61
+ curl -s \
62
+ --data-urlencode "js_code@$js" \
63
+ http://marijnhaverbeke.nl/uglifyjs \
64
+ >> "$mini"
65
+
66
+ git add "$mini"
67
+
68
+ git commit -m "release $ver"
69
+
70
+ echo "Tagging..."
71
+ git tag -a "$tag" -m "tagged version $ver"
72
+ git push "$remote" --tags
73
+
74
+ echo "Cleaning Up..."
75
+
76
+ git checkout "$curbranch"
77
+ git branch -D "$branch"
78
+
79
+ echo "Done"
js/select2/select2-bootstrap.css ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .form-control .select2-choice {
2
+ border: 0;
3
+ border-radius: 2px;
4
+ }
5
+
6
+ .form-control .select2-choice .select2-arrow {
7
+ border-radius: 0 2px 2px 0;
8
+ }
9
+
10
+ .form-control.select2-container {
11
+ height: auto !important;
12
+ padding: 0;
13
+ }
14
+
15
+ .form-control.select2-container.select2-dropdown-open {
16
+ border-color: #5897FB;
17
+ border-radius: 3px 3px 0 0;
18
+ }
19
+
20
+ .form-control .select2-container.select2-dropdown-open .select2-choices {
21
+ border-radius: 3px 3px 0 0;
22
+ }
23
+
24
+ .form-control.select2-container .select2-choices {
25
+ border: 0 !important;
26
+ border-radius: 3px;
27
+ }
28
+
29
+ .control-group.warning .select2-container .select2-choice,
30
+ .control-group.warning .select2-container .select2-choices,
31
+ .control-group.warning .select2-container-active .select2-choice,
32
+ .control-group.warning .select2-container-active .select2-choices,
33
+ .control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice,
34
+ .control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices,
35
+ .control-group.warning .select2-container-multi.select2-container-active .select2-choices {
36
+ border: 1px solid #C09853 !important;
37
+ }
38
+
39
+ .control-group.warning .select2-container .select2-choice div {
40
+ border-left: 1px solid #C09853 !important;
41
+ background: #FCF8E3 !important;
42
+ }
43
+
44
+ .control-group.error .select2-container .select2-choice,
45
+ .control-group.error .select2-container .select2-choices,
46
+ .control-group.error .select2-container-active .select2-choice,
47
+ .control-group.error .select2-container-active .select2-choices,
48
+ .control-group.error .select2-dropdown-open.select2-drop-above .select2-choice,
49
+ .control-group.error .select2-dropdown-open.select2-drop-above .select2-choices,
50
+ .control-group.error .select2-container-multi.select2-container-active .select2-choices {
51
+ border: 1px solid #B94A48 !important;
52
+ }
53
+
54
+ .control-group.error .select2-container .select2-choice div {
55
+ border-left: 1px solid #B94A48 !important;
56
+ background: #F2DEDE !important;
57
+ }
58
+
59
+ .control-group.info .select2-container .select2-choice,
60
+ .control-group.info .select2-container .select2-choices,
61
+ .control-group.info .select2-container-active .select2-choice,
62
+ .control-group.info .select2-container-active .select2-choices,
63
+ .control-group.info .select2-dropdown-open.select2-drop-above .select2-choice,
64
+ .control-group.info .select2-dropdown-open.select2-drop-above .select2-choices,
65
+ .control-group.info .select2-container-multi.select2-container-active .select2-choices {
66
+ border: 1px solid #3A87AD !important;
67
+ }
68
+
69
+ .control-group.info .select2-container .select2-choice div {
70
+ border-left: 1px solid #3A87AD !important;
71
+ background: #D9EDF7 !important;
72
+ }
73
+
74
+ .control-group.success .select2-container .select2-choice,
75
+ .control-group.success .select2-container .select2-choices,
76
+ .control-group.success .select2-container-active .select2-choice,
77
+ .control-group.success .select2-container-active .select2-choices,
78
+ .control-group.success .select2-dropdown-open.select2-drop-above .select2-choice,
79
+ .control-group.success .select2-dropdown-open.select2-drop-above .select2-choices,
80
+ .control-group.success .select2-container-multi.select2-container-active .select2-choices {
81
+ border: 1px solid #468847 !important;
82
+ }
83
+
84
+ .control-group.success .select2-container .select2-choice div {
85
+ border-left: 1px solid #468847 !important;
86
+ background: #DFF0D8 !important;
87
+ }
js/select2/select2-spinner.gif ADDED
Binary file
js/select2/select2.css ADDED
@@ -0,0 +1,704 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Version: 3.5.1 Timestamp: Tue Jul 22 18:58:56 EDT 2014
3
+ */
4
+ .select2-container {
5
+ margin: 0;
6
+ position: relative;
7
+ display: inline-block;
8
+ /* inline-block for ie7 */
9
+ zoom: 1;
10
+ *display: inline;
11
+ vertical-align: middle;
12
+ }
13
+
14
+ .select2-container,
15
+ .select2-drop,
16
+ .select2-search,
17
+ .select2-search input {
18
+ /*
19
+ Force border-box so that % widths fit the parent
20
+ container without overlap because of margin/padding.
21
+ More Info : http://www.quirksmode.org/css/box.html
22
+ */
23
+ -webkit-box-sizing: border-box; /* webkit */
24
+ -moz-box-sizing: border-box; /* firefox */
25
+ box-sizing: border-box; /* css3 */
26
+ }
27
+
28
+ .select2-container .select2-choice {
29
+ display: block;
30
+ height: 26px;
31
+ padding: 0 0 0 8px;
32
+ overflow: hidden;
33
+ position: relative;
34
+
35
+ border: 1px solid #aaa;
36
+ white-space: nowrap;
37
+ line-height: 26px;
38
+ color: #444;
39
+ text-decoration: none;
40
+
41
+ border-radius: 4px;
42
+
43
+ background-clip: padding-box;
44
+
45
+ -webkit-touch-callout: none;
46
+ -webkit-user-select: none;
47
+ -moz-user-select: none;
48
+ -ms-user-select: none;
49
+ user-select: none;
50
+
51
+ background-color: #fff;
52
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
53
+ background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
54
+ background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
55
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
56
+ background-image: linear-gradient(to top, #eee 0%, #fff 50%);
57
+ }
58
+
59
+ html[dir="rtl"] .select2-container .select2-choice {
60
+ padding: 0 8px 0 0;
61
+ }
62
+
63
+ .select2-container.select2-drop-above .select2-choice {
64
+ border-bottom-color: #aaa;
65
+
66
+ border-radius: 0 0 4px 4px;
67
+
68
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
69
+ background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
70
+ background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
71
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
72
+ background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
73
+ }
74
+
75
+ .select2-container.select2-allowclear .select2-choice .select2-chosen {
76
+ margin-right: 42px;
77
+ }
78
+
79
+ .select2-container .select2-choice > .select2-chosen {
80
+ margin-right: 26px;
81
+ display: block;
82
+ overflow: hidden;
83
+
84
+ white-space: nowrap;
85
+
86
+ text-overflow: ellipsis;
87
+ float: none;
88
+ width: auto;
89
+ }
90
+
91
+ html[dir="rtl"] .select2-container .select2-choice > .select2-chosen {
92
+ margin-left: 26px;
93
+ margin-right: 0;
94
+ }
95
+
96
+ .select2-container .select2-choice abbr {
97
+ display: none;
98
+ width: 12px;
99
+ height: 12px;
100
+ position: absolute;
101
+ right: 24px;
102
+ top: 8px;
103
+
104
+ font-size: 1px;
105
+ text-decoration: none;
106
+
107
+ border: 0;
108
+ background: url('select2.png') right top no-repeat;
109
+ cursor: pointer;
110
+ outline: 0;
111
+ }
112
+
113
+ .select2-container.select2-allowclear .select2-choice abbr {
114
+ display: inline-block;
115
+ }
116
+
117
+ .select2-container .select2-choice abbr:hover {
118
+ background-position: right -11px;
119
+ cursor: pointer;
120
+ }
121
+
122
+ .select2-drop-mask {
123
+ border: 0;
124
+ margin: 0;
125
+ padding: 0;
126
+ position: fixed;
127
+ left: 0;
128
+ top: 0;
129
+ min-height: 100%;
130
+ min-width: 100%;
131
+ height: auto;
132
+ width: auto;
133
+ opacity: 0;
134
+ z-index: 9998;
135
+ /* styles required for IE to work */
136
+ background-color: #fff;
137
+ filter: alpha(opacity=0);
138
+ }
139
+
140
+ .select2-drop {
141
+ width: 100%;
142
+ margin-top: -1px;
143
+ position: absolute;
144
+ z-index: 9999;
145
+ top: 100%;
146
+
147
+ background: #fff;
148
+ color: #000;
149
+ border: 1px solid #aaa;
150
+ border-top: 0;
151
+
152
+ border-radius: 0 0 4px 4px;
153
+
154
+ -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
155
+ box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
156
+ }
157
+
158
+ .select2-drop.select2-drop-above {
159
+ margin-top: 1px;
160
+ border-top: 1px solid #aaa;
161
+ border-bottom: 0;
162
+
163
+ border-radius: 4px 4px 0 0;
164
+
165
+ -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
166
+ box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
167
+ }
168
+
169
+ .select2-drop-active {
170
+ border: 1px solid #5897fb;
171
+ border-top: none;
172
+ }
173
+
174
+ .select2-drop.select2-drop-above.select2-drop-active {
175
+ border-top: 1px solid #5897fb;
176
+ }
177
+
178
+ .select2-drop-auto-width {
179
+ border-top: 1px solid #aaa;
180
+ width: auto;
181
+ }
182
+
183
+ .select2-drop-auto-width .select2-search {
184
+ padding-top: 4px;
185
+ }
186
+
187
+ .select2-container .select2-choice .select2-arrow {
188
+ display: inline-block;
189
+ width: 18px;
190
+ height: 100%;
191
+ position: absolute;
192
+ right: 0;
193
+ top: 0;
194
+
195
+ border-left: 1px solid #aaa;
196
+ border-radius: 0 4px 4px 0;
197
+
198
+ background-clip: padding-box;
199
+
200
+ background: #ccc;
201
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
202
+ background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
203
+ background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
204
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
205
+ background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
206
+ }
207
+
208
+ html[dir="rtl"] .select2-container .select2-choice .select2-arrow {
209
+ left: 0;
210
+ right: auto;
211
+
212
+ border-left: none;
213
+ border-right: 1px solid #aaa;
214
+ border-radius: 4px 0 0 4px;
215
+ }
216
+
217
+ .select2-container .select2-choice .select2-arrow b {
218
+ display: block;
219
+ width: 100%;
220
+ height: 100%;
221
+ background: url('select2.png') no-repeat 0 1px;
222
+ }
223
+
224
+ html[dir="rtl"] .select2-container .select2-choice .select2-arrow b {
225
+ background-position: 2px 1px;
226
+ }
227
+
228
+ .select2-search {
229
+ display: inline-block;
230
+ width: 100%;
231
+ min-height: 26px;
232
+ margin: 0;
233
+ padding-left: 4px;
234
+ padding-right: 4px;
235
+
236
+ position: relative;
237
+ z-index: 10000;
238
+
239
+ white-space: nowrap;
240
+ }
241
+
242
+ .select2-search input {
243
+ width: 100%;
244
+ height: auto !important;
245
+ min-height: 26px;
246
+ padding: 4px 20px 4px 5px;
247
+ margin: 0;
248
+
249
+ outline: 0;
250
+ font-family: sans-serif;
251
+ font-size: 1em;
252
+
253
+ border: 1px solid #aaa;
254
+ border-radius: 0;
255
+
256
+ -webkit-box-shadow: none;
257
+ box-shadow: none;
258
+
259
+ background: #fff url('select2.png') no-repeat 100% -22px;
260
+ background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
261
+ background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
262
+ background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
263
+ background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
264
+ }
265
+
266
+ html[dir="rtl"] .select2-search input {
267
+ padding: 4px 5px 4px 20px;
268
+
269
+ background: #fff url('select2.png') no-repeat -37px -22px;
270
+ background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
271
+ background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
272
+ background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
273
+ background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
274
+ }
275
+
276
+ .select2-drop.select2-drop-above .select2-search input {
277
+ margin-top: 4px;
278
+ }
279
+
280
+ .select2-search input.select2-active {
281
+ background: #fff url('select2-spinner.gif') no-repeat 100%;
282
+ background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
283
+ background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
284
+ background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
285
+ background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
286
+ }
287
+
288
+ .select2-container-active .select2-choice,
289
+ .select2-container-active .select2-choices {
290
+ border: 1px solid #5897fb;
291
+ outline: none;
292
+
293
+ -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
294
+ box-shadow: 0 0 5px rgba(0, 0, 0, .3);
295
+ }
296
+
297
+ .select2-dropdown-open .select2-choice {
298
+ border-bottom-color: transparent;
299
+ -webkit-box-shadow: 0 1px 0 #fff inset;
300
+ box-shadow: 0 1px 0 #fff inset;
301
+
302
+ border-bottom-left-radius: 0;
303
+ border-bottom-right-radius: 0;
304
+
305
+ background-color: #eee;
306
+ background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
307
+ background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
308
+ background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
309
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
310
+ background-image: linear-gradient(to top, #fff 0%, #eee 50%);
311
+ }
312
+
313
+ .select2-dropdown-open.select2-drop-above .select2-choice,
314
+ .select2-dropdown-open.select2-drop-above .select2-choices {
315
+ border: 1px solid #5897fb;
316
+ border-top-color: transparent;
317
+
318
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
319
+ background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
320
+ background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
321
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
322
+ background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
323
+ }
324
+
325
+ .select2-dropdown-open .select2-choice .select2-arrow {
326
+ background: transparent;
327
+ border-left: none;
328
+ filter: none;
329
+ }
330
+ html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
331
+ border-right: none;
332
+ }
333
+
334
+ .select2-dropdown-open .select2-choice .select2-arrow b {
335
+ background-position: -18px 1px;
336
+ }
337
+
338
+ html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b {
339
+ background-position: -16px 1px;
340
+ }
341
+
342
+ .select2-hidden-accessible {
343
+ border: 0;
344
+ clip: rect(0 0 0 0);
345
+ height: 1px;
346
+ margin: -1px;
347
+ overflow: hidden;
348
+ padding: 0;
349
+ position: absolute;
350
+ width: 1px;
351
+ }
352
+
353
+ /* results */
354
+ .select2-results {
355
+ max-height: 200px;
356
+ padding: 0 0 0 4px;
357
+ margin: 4px 4px 4px 0;
358
+ position: relative;
359
+ overflow-x: hidden;
360
+ overflow-y: auto;
361
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
362
+ }
363
+
364
+ html[dir="rtl"] .select2-results {
365
+ padding: 0 4px 0 0;
366
+ margin: 4px 0 4px 4px;
367
+ }
368
+
369
+ .select2-results ul.select2-result-sub {
370
+ margin: 0;
371
+ padding-left: 0;
372
+ }
373
+
374
+ .select2-results li {
375
+ list-style: none;
376
+ display: list-item;
377
+ background-image: none;
378
+ }
379
+
380
+ .select2-results li.select2-result-with-children > .select2-result-label {
381
+ font-weight: bold;
382
+ }
383
+
384
+ .select2-results .select2-result-label {
385
+ padding: 3px 7px 4px;
386
+ margin: 0;
387
+ cursor: pointer;
388
+
389
+ min-height: 1em;
390
+
391
+ -webkit-touch-callout: none;
392
+ -webkit-user-select: none;
393
+ -moz-user-select: none;
394
+ -ms-user-select: none;
395
+ user-select: none;
396
+ }
397
+
398
+ .select2-results-dept-1 .select2-result-label { padding-left: 20px }
399
+ .select2-results-dept-2 .select2-result-label { padding-left: 40px }
400
+ .select2-results-dept-3 .select2-result-label { padding-left: 60px }
401
+ .select2-results-dept-4 .select2-result-label { padding-left: 80px }
402
+ .select2-results-dept-5 .select2-result-label { padding-left: 100px }
403
+ .select2-results-dept-6 .select2-result-label { padding-left: 110px }
404
+ .select2-results-dept-7 .select2-result-label { padding-left: 120px }
405
+
406
+ .select2-results .select2-highlighted {
407
+ background: #3875d7;
408
+ color: #fff;
409
+ }
410
+
411
+ .select2-results li em {
412
+ background: #feffde;
413
+ font-style: normal;
414
+ }
415
+
416
+ .select2-results .select2-highlighted em {
417
+ background: transparent;
418
+ }
419
+
420
+ .select2-results .select2-highlighted ul {
421
+ background: #fff;
422
+ color: #000;
423
+ }
424
+
425
+ .select2-results .select2-no-results,
426
+ .select2-results .select2-searching,
427
+ .select2-results .select2-ajax-error,
428
+ .select2-results .select2-selection-limit {
429
+ background: #f4f4f4;
430
+ display: list-item;
431
+ padding-left: 5px;
432
+ }
433
+
434
+ /*
435
+ disabled look for disabled choices in the results dropdown
436
+ */
437
+ .select2-results .select2-disabled.select2-highlighted {
438
+ color: #666;
439
+ background: #f4f4f4;
440
+ display: list-item;
441
+ cursor: default;
442
+ }
443
+ .select2-results .select2-disabled {
444
+ background: #f4f4f4;
445
+ display: list-item;
446
+ cursor: default;
447
+ }
448
+
449
+ .select2-results .select2-selected {
450
+ display: none;
451
+ }
452
+
453
+ .select2-more-results.select2-active {
454
+ background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
455
+ }
456
+
457
+ .select2-results .select2-ajax-error {
458
+ background: rgba(255, 50, 50, .2);
459
+ }
460
+
461
+ .select2-more-results {
462
+ background: #f4f4f4;
463
+ display: list-item;
464
+ }
465
+
466
+ /* disabled styles */
467
+
468
+ .select2-container.select2-container-disabled .select2-choice {
469
+ background-color: #f4f4f4;
470
+ background-image: none;
471
+ border: 1px solid #ddd;
472
+ cursor: default;
473
+ }
474
+
475
+ .select2-container.select2-container-disabled .select2-choice .select2-arrow {
476
+ background-color: #f4f4f4;
477
+ background-image: none;
478
+ border-left: 0;
479
+ }
480
+
481
+ .select2-container.select2-container-disabled .select2-choice abbr {
482
+ display: none;
483
+ }
484
+
485
+
486
+ /* multiselect */
487
+
488
+ .select2-container-multi .select2-choices {
489
+ height: auto !important;
490
+ height: 1%;
491
+ margin: 0;
492
+ padding: 0 5px 0 0;
493
+ position: relative;
494
+
495
+ border: 1px solid #aaa;
496
+ cursor: text;
497
+ overflow: hidden;
498
+
499
+ background-color: #fff;
500
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
501
+ background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
502
+ background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
503
+ background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
504
+ }
505
+
506
+ html[dir="rtl"] .select2-container-multi .select2-choices {
507
+ padding: 0 0 0 5px;
508
+ }
509
+
510
+ .select2-locked {
511
+ padding: 3px 5px 3px 5px !important;
512
+ }
513
+
514
+ .select2-container-multi .select2-choices {
515
+ min-height: 26px;
516
+ }
517
+
518
+ .select2-container-multi.select2-container-active .select2-choices {
519
+ border: 1px solid #5897fb;
520
+ outline: none;
521
+
522
+ -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
523
+ box-shadow: 0 0 5px rgba(0, 0, 0, .3);
524
+ }
525
+ .select2-container-multi .select2-choices li {
526
+ float: left;
527
+ list-style: none;
528
+ }
529
+ html[dir="rtl"] .select2-container-multi .select2-choices li
530
+ {
531
+ float: right;
532
+ }
533
+ .select2-container-multi .select2-choices .select2-search-field {
534
+ margin: 0;
535
+ padding: 0;
536
+ white-space: nowrap;
537
+ }
538
+
539
+ .select2-container-multi .select2-choices .select2-search-field input {
540
+ padding: 5px;
541
+ margin: 1px 0;
542
+
543
+ font-family: sans-serif;
544
+ font-size: 100%;
545
+ color: #666;
546
+ outline: 0;
547
+ border: 0;
548
+ -webkit-box-shadow: none;
549
+ box-shadow: none;
550
+ background: transparent !important;
551
+ }
552
+
553
+ .select2-container-multi .select2-choices .select2-search-field input.select2-active {
554
+ background: #fff url('select2-spinner.gif') no-repeat 100% !important;
555
+ }
556
+
557
+ .select2-default {
558
+ color: #999 !important;
559
+ }
560
+
561
+ .select2-container-multi .select2-choices .select2-search-choice {
562
+ padding: 3px 5px 3px 18px;
563
+ margin: 3px 0 3px 5px;
564
+ position: relative;
565
+
566
+ line-height: 13px;
567
+ color: #333;
568
+ cursor: default;
569
+ border: 1px solid #aaaaaa;
570
+
571
+ border-radius: 3px;
572
+
573
+ -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
574
+ box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
575
+
576
+ background-clip: padding-box;
577
+
578
+ -webkit-touch-callout: none;
579
+ -webkit-user-select: none;
580
+ -moz-user-select: none;
581
+ -ms-user-select: none;
582
+ user-select: none;
583
+
584
+ background-color: #e4e4e4;
585
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
586
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
587
+ background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
588
+ background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
589
+ background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
590
+ }
591
+ html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
592
+ {
593
+ margin: 3px 5px 3px 0;
594
+ padding: 3px 18px 3px 5px;
595
+ }
596
+ .select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
597
+ cursor: default;
598
+ }
599
+ .select2-container-multi .select2-choices .select2-search-choice-focus {
600
+ background: #d4d4d4;
601
+ }
602
+
603
+ .select2-search-choice-close {
604
+ display: block;
605
+ width: 12px;
606
+ height: 13px;
607
+ position: absolute;
608
+ right: 3px;
609
+ top: 4px;
610
+
611
+ font-size: 1px;
612
+ outline: none;
613
+ background: url('select2.png') right top no-repeat;
614
+ }
615
+ html[dir="rtl"] .select2-search-choice-close {
616
+ right: auto;
617
+ left: 3px;
618
+ }
619
+
620
+ .select2-container-multi .select2-search-choice-close {
621
+ left: 3px;
622
+ }
623
+
624
+ html[dir="rtl"] .select2-container-multi .select2-search-choice-close {
625
+ left: auto;
626
+ right: 2px;
627
+ }
628
+
629
+ .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
630
+ background-position: right -11px;
631
+ }
632
+ .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
633
+ background-position: right -11px;
634
+ }
635
+
636
+ /* disabled styles */
637
+ .select2-container-multi.select2-container-disabled .select2-choices {
638
+ background-color: #f4f4f4;
639
+ background-image: none;
640
+ border: 1px solid #ddd;
641
+ cursor: default;
642
+ }
643
+
644
+ .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
645
+ padding: 3px 5px 3px 5px;
646
+ border: 1px solid #ddd;
647
+ background-image: none;
648
+ background-color: #f4f4f4;
649
+ }
650
+
651
+ .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
652
+ background: none;
653
+ }
654
+ /* end multiselect */
655
+
656
+
657
+ .select2-result-selectable .select2-match,
658
+ .select2-result-unselectable .select2-match {
659
+ text-decoration: underline;
660
+ }
661
+
662
+ .select2-offscreen, .select2-offscreen:focus {
663
+ clip: rect(0 0 0 0) !important;
664
+ width: 1px !important;
665
+ height: 1px !important;
666
+ border: 0 !important;
667
+ margin: 0 !important;
668
+ padding: 0 !important;
669
+ overflow: hidden !important;
670
+ position: absolute !important;
671
+ outline: 0 !important;
672
+ left: 0px !important;
673
+ top: 0px !important;
674
+ }
675
+
676
+ .select2-display-none {
677
+ display: none;
678
+ }
679
+
680
+ .select2-measure-scrollbar {
681
+ position: absolute;
682
+ top: -10000px;
683
+ left: -10000px;
684
+ width: 100px;
685
+ height: 100px;
686
+ overflow: scroll;
687
+ }
688
+
689
+ /* Retina-ize icons */
690
+
691
+ @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
692
+ .select2-search input,
693
+ .select2-search-choice-close,
694
+ .select2-container .select2-choice abbr,
695
+ .select2-container .select2-choice .select2-arrow b {
696
+ background-image: url('select2x2.png') !important;
697
+ background-repeat: no-repeat !important;
698
+ background-size: 60px 40px !important;
699
+ }
700
+
701
+ .select2-search input {
702
+ background-position: 100% -21px !important;
703
+ }
704
+ }
js/select2/select2.jquery.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "select2",
3
+ "title": "Select2",
4
+ "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
5
+ "keywords": [
6
+ "select",
7
+ "autocomplete",
8
+ "typeahead",
9
+ "dropdown",
10
+ "multiselect",
11
+ "tag",
12
+ "tagging"
13
+ ],
14
+ "version": "3.5.1",
15
+ "author": {
16
+ "name": "Igor Vaynberg",
17
+ "url": "https://github.com/ivaynberg"
18
+ },
19
+ "licenses": [
20
+ {
21
+ "type": "Apache",
22
+ "url": "http://www.apache.org/licenses/LICENSE-2.0"
23
+ },
24
+ {
25
+ "type": "GPL v2",
26
+ "url": "http://www.gnu.org/licenses/gpl-2.0.html"
27
+ }
28
+ ],
29
+ "bugs": "https://github.com/ivaynberg/select2/issues",
30
+ "homepage": "http://ivaynberg.github.com/select2",
31
+ "docs": "http://ivaynberg.github.com/select2/",
32
+ "download": "https://github.com/ivaynberg/select2/tags",
33
+ "dependencies": {
34
+ "jquery": ">=1.7.1"
35
+ }
36
+ }
js/select2/select2.js ADDED
@@ -0,0 +1,3508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright 2012 Igor Vaynberg
3
+
4
+ Version: 3.5.1 Timestamp: Tue Jul 22 18:58:56 EDT 2014
5
+
6
+ This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
7
+ General Public License version 2 (the "GPL License"). You may choose either license to govern your
8
+ use of this software only upon the condition that you accept all of the terms of either the Apache
9
+ License or the GPL License.
10
+
11
+ You may obtain a copy of the Apache License and the GPL License at:
12
+
13
+ http://www.apache.org/licenses/LICENSE-2.0
14
+ http://www.gnu.org/licenses/gpl-2.0.html
15
+
16
+ Unless required by applicable law or agreed to in writing, software distributed under the
17
+ Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
18
+ CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
19
+ the specific language governing permissions and limitations under the Apache License and the GPL License.
20
+ */
21
+ (function ($) {
22
+ if(typeof $.fn.each2 == "undefined") {
23
+ $.extend($.fn, {
24
+ /*
25
+ * 4-10 times faster .each replacement
26
+ * use it carefully, as it overrides jQuery context of element on each iteration
27
+ */
28
+ each2 : function (c) {
29
+ var j = $([0]), i = -1, l = this.length;
30
+ while (
31
+ ++i < l
32
+ && (j.context = j[0] = this[i])
33
+ && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
34
+ );
35
+ return this;
36
+ }
37
+ });
38
+ }
39
+ })(jQuery);
40
+
41
+ (function ($, undefined) {
42
+ "use strict";
43
+ /*global document, window, jQuery, console */
44
+
45
+ if (window.Select2 !== undefined) {
46
+ return;
47
+ }
48
+
49
+ var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
50
+ lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
51
+
52
+ KEY = {
53
+ TAB: 9,
54
+ ENTER: 13,
55
+ ESC: 27,
56
+ SPACE: 32,
57
+ LEFT: 37,
58
+ UP: 38,
59
+ RIGHT: 39,
60
+ DOWN: 40,
61
+ SHIFT: 16,
62
+ CTRL: 17,
63
+ ALT: 18,
64
+ PAGE_UP: 33,
65
+ PAGE_DOWN: 34,
66
+ HOME: 36,
67
+ END: 35,
68
+ BACKSPACE: 8,
69
+ DELETE: 46,
70
+ isArrow: function (k) {
71
+ k = k.which ? k.which : k;
72
+ switch (k) {
73
+ case KEY.LEFT:
74
+ case KEY.RIGHT:
75
+ case KEY.UP:
76
+ case KEY.DOWN:
77
+ return true;
78
+ }
79
+ return false;
80
+ },
81
+ isControl: function (e) {
82
+ var k = e.which;
83
+ switch (k) {
84
+ case KEY.SHIFT:
85
+ case KEY.CTRL:
86
+ case KEY.ALT:
87
+ return true;
88
+ }
89
+
90
+ if (e.metaKey) return true;
91
+
92
+ return false;
93
+ },
94
+ isFunctionKey: function (k) {
95
+ k = k.which ? k.which : k;
96
+ return k >= 112 && k <= 123;
97
+ }
98
+ },
99
+ MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>",
100
+
101
+ DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"};
102
+
103
+ $document = $(document);
104
+
105
+ nextUid=(function() { var counter=1; return function() { return counter++; }; }());
106
+
107
+
108
+ function reinsertElement(element) {
109
+ var placeholder = $(document.createTextNode(''));
110
+
111
+ element.before(placeholder);
112
+ placeholder.before(element);
113
+ placeholder.remove();
114
+ }
115
+
116
+ function stripDiacritics(str) {
117
+ // Used 'uni range + named function' from http://jsperf.com/diacritics/18
118
+ function match(a) {
119
+ return DIACRITICS[a] || a;
120
+ }
121
+
122
+ return str.replace(/[^\u0000-\u007E]/g, match);
123
+ }
124
+
125
+ function indexOf(value, array) {
126
+ var i = 0, l = array.length;
127
+ for (; i < l; i = i + 1) {
128
+ if (equal(value, array[i])) return i;
129
+ }
130
+ return -1;
131
+ }
132
+
133
+ function measureScrollbar () {
134
+ var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
135
+ $template.appendTo('body');
136
+
137
+ var dim = {
138
+ width: $template.width() - $template[0].clientWidth,
139
+ height: $template.height() - $template[0].clientHeight
140
+ };
141
+ $template.remove();
142
+
143
+ return dim;
144
+ }
145
+
146
+ /**
147
+ * Compares equality of a and b
148
+ * @param a
149
+ * @param b
150
+ */
151
+ function equal(a, b) {
152
+ if (a === b) return true;
153
+ if (a === undefined || b === undefined) return false;
154
+ if (a === null || b === null) return false;
155
+ // Check whether 'a' or 'b' is a string (primitive or object).
156
+ // The concatenation of an empty string (+'') converts its argument to a string's primitive.
157
+ if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
158
+ if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
164
+ * strings
165
+ * @param string
166
+ * @param separator
167
+ */
168
+ function splitVal(string, separator) {
169
+ var val, i, l;
170
+ if (string === null || string.length < 1) return [];
171
+ val = string.split(separator);
172
+ for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
173
+ return val;
174
+ }
175
+
176
+ function getSideBorderPadding(element) {
177
+ return element.outerWidth(false) - element.width();
178
+ }
179
+
180
+ function installKeyUpChangeEvent(element) {
181
+ var key="keyup-change-value";
182
+ element.on("keydown", function () {
183
+ if ($.data(element, key) === undefined) {
184
+ $.data(element, key, element.val());
185
+ }
186
+ });
187
+ element.on("keyup", function () {
188
+ var val= $.data(element, key);
189
+ if (val !== undefined && element.val() !== val) {
190
+ $.removeData(element, key);
191
+ element.trigger("keyup-change");
192
+ }
193
+ });
194
+ }
195
+
196
+
197
+ /**
198
+ * filters mouse events so an event is fired only if the mouse moved.
199
+ *
200
+ * filters out mouse events that occur when mouse is stationary but
201
+ * the elements under the pointer are scrolled.
202
+ */
203
+ function installFilteredMouseMove(element) {
204
+ element.on("mousemove", function (e) {
205
+ var lastpos = lastMousePosition;
206
+ if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
207
+ $(e.target).trigger("mousemove-filtered", e);
208
+ }
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
214
+ * within the last quietMillis milliseconds.
215
+ *
216
+ * @param quietMillis number of milliseconds to wait before invoking fn
217
+ * @param fn function to be debounced
218
+ * @param ctx object to be used as this reference within fn
219
+ * @return debounced version of fn
220
+ */
221
+ function debounce(quietMillis, fn, ctx) {
222
+ ctx = ctx || undefined;
223
+ var timeout;
224
+ return function () {
225
+ var args = arguments;
226
+ window.clearTimeout(timeout);
227
+ timeout = window.setTimeout(function() {
228
+ fn.apply(ctx, args);
229
+ }, quietMillis);
230
+ };
231
+ }
232
+
233
+ function installDebouncedScroll(threshold, element) {
234
+ var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
235
+ element.on("scroll", function (e) {
236
+ if (indexOf(e.target, element.get()) >= 0) notify(e);
237
+ });
238
+ }
239
+
240
+ function focus($el) {
241
+ if ($el[0] === document.activeElement) return;
242
+
243
+ /* set the focus in a 0 timeout - that way the focus is set after the processing
244
+ of the current event has finished - which seems like the only reliable way
245
+ to set focus */
246
+ window.setTimeout(function() {
247
+ var el=$el[0], pos=$el.val().length, range;
248
+
249
+ $el.focus();
250
+
251
+ /* make sure el received focus so we do not error out when trying to manipulate the caret.
252
+ sometimes modals or others listeners may steal it after its set */
253
+ var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0);
254
+ if (isVisible && el === document.activeElement) {
255
+
256
+ /* after the focus is set move the caret to the end, necessary when we val()
257
+ just before setting focus */
258
+ if(el.setSelectionRange)
259
+ {
260
+ el.setSelectionRange(pos, pos);
261
+ }
262
+ else if (el.createTextRange) {
263
+ range = el.createTextRange();
264
+ range.collapse(false);
265
+ range.select();
266
+ }
267
+ }
268
+ }, 0);
269
+ }
270
+
271
+ function getCursorInfo(el) {
272
+ el = $(el)[0];
273
+ var offset = 0;
274
+ var length = 0;
275
+ if ('selectionStart' in el) {
276
+ offset = el.selectionStart;
277
+ length = el.selectionEnd - offset;
278
+ } else if ('selection' in document) {
279
+ el.focus();
280
+ var sel = document.selection.createRange();
281
+ length = document.selection.createRange().text.length;
282
+ sel.moveStart('character', -el.value.length);
283
+ offset = sel.text.length - length;
284
+ }
285
+ return { offset: offset, length: length };
286
+ }
287
+
288
+ function killEvent(event) {
289
+ event.preventDefault();
290
+ event.stopPropagation();
291
+ }
292
+ function killEventImmediately(event) {
293
+ event.preventDefault();
294
+ event.stopImmediatePropagation();
295
+ }
296
+
297
+ function measureTextWidth(e) {
298
+ if (!sizer){
299
+ var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
300
+ sizer = $(document.createElement("div")).css({
301
+ position: "absolute",
302
+ left: "-10000px",
303
+ top: "-10000px",
304
+ display: "none",
305
+ fontSize: style.fontSize,
306
+ fontFamily: style.fontFamily,
307
+ fontStyle: style.fontStyle,
308
+ fontWeight: style.fontWeight,
309
+ letterSpacing: style.letterSpacing,
310
+ textTransform: style.textTransform,
311
+ whiteSpace: "nowrap"
312
+ });
313
+ sizer.attr("class","select2-sizer");
314
+ $("body").append(sizer);
315
+ }
316
+ sizer.text(e.val());
317
+ return sizer.width();
318
+ }
319
+
320
+ function syncCssClasses(dest, src, adapter) {
321
+ var classes, replacements = [], adapted;
322
+
323
+ classes = $.trim(dest.attr("class"));
324
+
325
+ if (classes) {
326
+ classes = '' + classes; // for IE which returns object
327
+
328
+ $(classes.split(/\s+/)).each2(function() {
329
+ if (this.indexOf("select2-") === 0) {
330
+ replacements.push(this);
331
+ }
332
+ });
333
+ }
334
+
335
+ classes = $.trim(src.attr("class"));
336
+
337
+ if (classes) {
338
+ classes = '' + classes; // for IE which returns object
339
+
340
+ $(classes.split(/\s+/)).each2(function() {
341
+ if (this.indexOf("select2-") !== 0) {
342
+ adapted = adapter(this);
343
+
344
+ if (adapted) {
345
+ replacements.push(adapted);
346
+ }
347
+ }
348
+ });
349
+ }
350
+
351
+ dest.attr("class", replacements.join(" "));
352
+ }
353
+
354
+
355
+ function markMatch(text, term, markup, escapeMarkup) {
356
+ var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
357
+ tl=term.length;
358
+
359
+ if (match<0) {
360
+ markup.push(escapeMarkup(text));
361
+ return;
362
+ }
363
+
364
+ markup.push(escapeMarkup(text.substring(0, match)));
365
+ markup.push("<span class='select2-match'>");
366
+ markup.push(escapeMarkup(text.substring(match, match + tl)));
367
+ markup.push("</span>");
368
+ markup.push(escapeMarkup(text.substring(match + tl, text.length)));
369
+ }
370
+
371
+ function defaultEscapeMarkup(markup) {
372
+ var replace_map = {
373
+ '\\': '&#92;',
374
+ '&': '&amp;',
375
+ '<': '&lt;',
376
+ '>': '&gt;',
377
+ '"': '&quot;',
378
+ "'": '&#39;',
379
+ "/": '&#47;'
380
+ };
381
+
382
+ return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
383
+ return replace_map[match];
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Produces an ajax-based query function
389
+ *
390
+ * @param options object containing configuration parameters
391
+ * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
392
+ * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
393
+ * @param options.url url for the data
394
+ * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
395
+ * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified
396
+ * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
397
+ * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2.
398
+ * The expected format is an object containing the following keys:
399
+ * results array of objects that will be used as choices
400
+ * more (optional) boolean indicating whether there are more results available
401
+ * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
402
+ */
403
+ function ajax(options) {
404
+ var timeout, // current scheduled but not yet executed request
405
+ handler = null,
406
+ quietMillis = options.quietMillis || 100,
407
+ ajaxUrl = options.url,
408
+ self = this;
409
+
410
+ return function (query) {
411
+ window.clearTimeout(timeout);
412
+ timeout = window.setTimeout(function () {
413
+ var data = options.data, // ajax data function
414
+ url = ajaxUrl, // ajax url string or function
415
+ transport = options.transport || $.fn.select2.ajaxDefaults.transport,
416
+ // deprecated - to be removed in 4.0 - use params instead
417
+ deprecated = {
418
+ type: options.type || 'GET', // set type of request (GET or POST)
419
+ cache: options.cache || false,
420
+ jsonpCallback: options.jsonpCallback||undefined,
421
+ dataType: options.dataType||"json"
422
+ },
423
+ params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
424
+
425
+ data = data ? data.call(self, query.term, query.page, query.context) : null;
426
+ url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
427
+
428
+ if (handler && typeof handler.abort === "function") { handler.abort(); }
429
+
430
+ if (options.params) {
431
+ if ($.isFunction(options.params)) {
432
+ $.extend(params, options.params.call(self));
433
+ } else {
434
+ $.extend(params, options.params);
435
+ }
436
+ }
437
+
438
+ $.extend(params, {
439
+ url: url,
440
+ dataType: options.dataType,
441
+ data: data,
442
+ success: function (data) {
443
+ // TODO - replace query.page with query so users have access to term, page, etc.
444
+ // added query as third paramter to keep backwards compatibility
445
+ var results = options.results(data, query.page, query);
446
+ query.callback(results);
447
+ },
448
+ error: function(jqXHR, textStatus, errorThrown){
449
+ var results = {
450
+ hasError: true,
451
+ jqXHR: jqXHR,
452
+ textStatus: textStatus,
453
+ errorThrown: errorThrown,
454
+ };
455
+
456
+ query.callback(results);
457
+ }
458
+ });
459
+ handler = transport.call(self, params);
460
+ }, quietMillis);
461
+ };
462
+ }
463
+
464
+ /**
465
+ * Produces a query function that works with a local array
466
+ *
467
+ * @param options object containing configuration parameters. The options parameter can either be an array or an
468
+ * object.
469
+ *
470
+ * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
471
+ *
472
+ * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
473
+ * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
474
+ * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
475
+ * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
476
+ * the text.
477
+ */
478
+ function local(options) {
479
+ var data = options, // data elements
480
+ dataText,
481
+ tmp,
482
+ text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
483
+
484
+ if ($.isArray(data)) {
485
+ tmp = data;
486
+ data = { results: tmp };
487
+ }
488
+
489
+ if ($.isFunction(data) === false) {
490
+ tmp = data;
491
+ data = function() { return tmp; };
492
+ }
493
+
494
+ var dataItem = data();
495
+ if (dataItem.text) {
496
+ text = dataItem.text;
497
+ // if text is not a function we assume it to be a key name
498
+ if (!$.isFunction(text)) {
499
+ dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
500
+ text = function (item) { return item[dataText]; };
501
+ }
502
+ }
503
+
504
+ return function (query) {
505
+ var t = query.term, filtered = { results: [] }, process;
506
+ if (t === "") {
507
+ query.callback(data());
508
+ return;
509
+ }
510
+
511
+ process = function(datum, collection) {
512
+ var group, attr;
513
+ datum = datum[0];
514
+ if (datum.children) {
515
+ group = {};
516
+ for (attr in datum) {
517
+ if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
518
+ }
519
+ group.children=[];
520
+ $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
521
+ if (group.children.length || query.matcher(t, text(group), datum)) {
522
+ collection.push(group);
523
+ }
524
+ } else {
525
+ if (query.matcher(t, text(datum), datum)) {
526
+ collection.push(datum);
527
+ }
528
+ }
529
+ };
530
+
531
+ $(data().results).each2(function(i, datum) { process(datum, filtered.results); });
532
+ query.callback(filtered);
533
+ };
534
+ }
535
+
536
+ // TODO javadoc
537
+ function tags(data) {
538
+ var isFunc = $.isFunction(data);
539
+ return function (query) {
540
+ var t = query.term, filtered = {results: []};
541
+ var result = isFunc ? data(query) : data;
542
+ if ($.isArray(result)) {
543
+ $(result).each(function () {
544
+ var isObject = this.text !== undefined,
545
+ text = isObject ? this.text : this;
546
+ if (t === "" || query.matcher(t, text)) {
547
+ filtered.results.push(isObject ? this : {id: this, text: this});
548
+ }
549
+ });
550
+ query.callback(filtered);
551
+ }
552
+ };
553
+ }
554
+
555
+ /**
556
+ * Checks if the formatter function should be used.
557
+ *
558
+ * Throws an error if it is not a function. Returns true if it should be used,
559
+ * false if no formatting should be performed.
560
+ *
561
+ * @param formatter
562
+ */
563
+ function checkFormatter(formatter, formatterName) {
564
+ if ($.isFunction(formatter)) return true;
565
+ if (!formatter) return false;
566
+ if (typeof(formatter) === 'string') return true;
567
+ throw new Error(formatterName +" must be a string, function, or falsy value");
568
+ }
569
+
570
+ /**
571
+ * Returns a given value
572
+ * If given a function, returns its output
573
+ *
574
+ * @param val string|function
575
+ * @param context value of "this" to be passed to function
576
+ * @returns {*}
577
+ */
578
+ function evaluate(val, context) {
579
+ if ($.isFunction(val)) {
580
+ var args = Array.prototype.slice.call(arguments, 2);
581
+ return val.apply(context, args);
582
+ }
583
+ return val;
584
+ }
585
+
586
+ function countResults(results) {
587
+ var count = 0;
588
+ $.each(results, function(i, item) {
589
+ if (item.children) {
590
+ count += countResults(item.children);
591
+ } else {
592
+ count++;
593
+ }
594
+ });
595
+ return count;
596
+ }
597
+
598
+ /**
599
+ * Default tokenizer. This function uses breaks the input on substring match of any string from the
600
+ * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
601
+ * two options have to be defined in order for the tokenizer to work.
602
+ *
603
+ * @param input text user has typed so far or pasted into the search field
604
+ * @param selection currently selected choices
605
+ * @param selectCallback function(choice) callback tho add the choice to selection
606
+ * @param opts select2's opts
607
+ * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
608
+ */
609
+ function defaultTokenizer(input, selection, selectCallback, opts) {
610
+ var original = input, // store the original so we can compare and know if we need to tell the search to update its text
611
+ dupe = false, // check for whether a token we extracted represents a duplicate selected choice
612
+ token, // token
613
+ index, // position at which the separator was found
614
+ i, l, // looping variables
615
+ separator; // the matched separator
616
+
617
+ if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
618
+
619
+ while (true) {
620
+ index = -1;
621
+
622
+ for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
623
+ separator = opts.tokenSeparators[i];
624
+ index = input.indexOf(separator);
625
+ if (index >= 0) break;
626
+ }
627
+
628
+ if (index < 0) break; // did not find any token separator in the input string, bail
629
+
630
+ token = input.substring(0, index);
631
+ input = input.substring(index + separator.length);
632
+
633
+ if (token.length > 0) {
634
+ token = opts.createSearchChoice.call(this, token, selection);
635
+ if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
636
+ dupe = false;
637
+ for (i = 0, l = selection.length; i < l; i++) {
638
+ if (equal(opts.id(token), opts.id(selection[i]))) {
639
+ dupe = true; break;
640
+ }
641
+ }
642
+
643
+ if (!dupe) selectCallback(token);
644
+ }
645
+ }
646
+ }
647
+
648
+ if (original!==input) return input;
649
+ }
650
+
651
+ function cleanupJQueryElements() {
652
+ var self = this;
653
+
654
+ $.each(arguments, function (i, element) {
655
+ self[element].remove();
656
+ self[element] = null;
657
+ });
658
+ }
659
+
660
+ /**
661
+ * Creates a new class
662
+ *
663
+ * @param superClass
664
+ * @param methods
665
+ */
666
+ function clazz(SuperClass, methods) {
667
+ var constructor = function () {};
668
+ constructor.prototype = new SuperClass;
669
+ constructor.prototype.constructor = constructor;
670
+ constructor.prototype.parent = SuperClass.prototype;
671
+ constructor.prototype = $.extend(constructor.prototype, methods);
672
+ return constructor;
673
+ }
674
+
675
+ AbstractSelect2 = clazz(Object, {
676
+
677
+ // abstract
678
+ bind: function (func) {
679
+ var self = this;
680
+ return function () {
681
+ func.apply(self, arguments);
682
+ };
683
+ },
684
+
685
+ // abstract
686
+ init: function (opts) {
687
+ var results, search, resultsSelector = ".select2-results";
688
+
689
+ // prepare options
690
+ this.opts = opts = this.prepareOpts(opts);
691
+
692
+ this.id=opts.id;
693
+
694
+ // destroy if called on an existing component
695
+ if (opts.element.data("select2") !== undefined &&
696
+ opts.element.data("select2") !== null) {
697
+ opts.element.data("select2").destroy();
698
+ }
699
+
700
+ this.container = this.createContainer();
701
+
702
+ this.liveRegion = $("<span>", {
703
+ role: "status",
704
+ "aria-live": "polite"
705
+ })
706
+ .addClass("select2-hidden-accessible")
707
+ .appendTo(document.body);
708
+
709
+ this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
710
+ this.containerEventName= this.containerId
711
+ .replace(/([.])/g, '_')
712
+ .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
713
+ this.container.attr("id", this.containerId);
714
+
715
+ this.container.attr("title", opts.element.attr("title"));
716
+
717
+ this.body = $("body");
718
+
719
+ syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
720
+
721
+ this.container.attr("style", opts.element.attr("style"));
722
+ this.container.css(evaluate(opts.containerCss, this.opts.element));
723
+ this.container.addClass(evaluate(opts.containerCssClass, this.opts.element));
724
+
725
+ this.elementTabIndex = this.opts.element.attr("tabindex");
726
+
727
+ // swap container for the element
728
+ this.opts.element
729
+ .data("select2", this)
730
+ .attr("tabindex", "-1")
731
+ .before(this.container)
732
+ .on("click.select2", killEvent); // do not leak click events
733
+
734
+ this.container.data("select2", this);
735
+
736
+ this.dropdown = this.container.find(".select2-drop");
737
+
738
+ syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
739
+
740
+ this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element));
741
+ this.dropdown.data("select2", this);
742
+ this.dropdown.on("click", killEvent);
743
+
744
+ this.results = results = this.container.find(resultsSelector);
745
+ this.search = search = this.container.find("input.select2-input");
746
+
747
+ this.queryCount = 0;
748
+ this.resultsPage = 0;
749
+ this.context = null;
750
+
751
+ // initialize the container
752
+ this.initContainer();
753
+
754
+ this.container.on("click", killEvent);
755
+
756
+ installFilteredMouseMove(this.results);
757
+
758
+ this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent));
759
+ this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) {
760
+ this._touchEvent = true;
761
+ this.highlightUnderEvent(event);
762
+ }));
763
+ this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved));
764
+ this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved));
765
+
766
+ // Waiting for a click event on touch devices to select option and hide dropdown
767
+ // otherwise click will be triggered on an underlying element
768
+ this.dropdown.on('click', this.bind(function (event) {
769
+ if (this._touchEvent) {
770
+ this._touchEvent = false;
771
+ this.selectHighlighted();
772
+ }
773
+ }));
774
+
775
+ installDebouncedScroll(80, this.results);
776
+ this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
777
+
778
+ // do not propagate change event from the search field out of the component
779
+ $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
780
+ $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
781
+
782
+ // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
783
+ if ($.fn.mousewheel) {
784
+ results.mousewheel(function (e, delta, deltaX, deltaY) {
785
+ var top = results.scrollTop();
786
+ if (deltaY > 0 && top - deltaY <= 0) {
787
+ results.scrollTop(0);
788
+ killEvent(e);
789
+ } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
790
+ results.scrollTop(results.get(0).scrollHeight - results.height());
791
+ killEvent(e);
792
+ }
793
+ });
794
+ }
795
+
796
+ installKeyUpChangeEvent(search);
797
+ search.on("keyup-change input paste", this.bind(this.updateResults));
798
+ search.on("focus", function () { search.addClass("select2-focused"); });
799
+ search.on("blur", function () { search.removeClass("select2-focused");});
800
+
801
+ this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
802
+ if ($(e.target).closest(".select2-result-selectable").length > 0) {
803
+ this.highlightUnderEvent(e);
804
+ this.selectHighlighted(e);
805
+ }
806
+ }));
807
+
808
+ // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
809
+ // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
810
+ // dom it will trigger the popup close, which is not what we want
811
+ // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
812
+ this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); });
813
+
814
+ this.nextSearchTerm = undefined;
815
+
816
+ if ($.isFunction(this.opts.initSelection)) {
817
+ // initialize selection based on the current value of the source element
818
+ this.initSelection();
819
+
820
+ // if the user has provided a function that can set selection based on the value of the source element
821
+ // we monitor the change event on the element and trigger it, allowing for two way synchronization
822
+ this.monitorSource();
823
+ }
824
+
825
+ if (opts.maximumInputLength !== null) {
826
+ this.search.attr("maxlength", opts.maximumInputLength);
827
+ }
828
+
829
+ var disabled = opts.element.prop("disabled");
830
+ if (disabled === undefined) disabled = false;
831
+ this.enable(!disabled);
832
+
833
+ var readonly = opts.element.prop("readonly");
834
+ if (readonly === undefined) readonly = false;
835
+ this.readonly(readonly);
836
+
837
+ // Calculate size of scrollbar
838
+ scrollBarDimensions = scrollBarDimensions || measureScrollbar();
839
+
840
+ this.autofocus = opts.element.prop("autofocus");
841
+ opts.element.prop("autofocus", false);
842
+ if (this.autofocus) this.focus();
843
+
844
+ this.search.attr("placeholder", opts.searchInputPlaceholder);
845
+ },
846
+
847
+ // abstract
848
+ destroy: function () {
849
+ var element=this.opts.element, select2 = element.data("select2"), self = this;
850
+
851
+ this.close();
852
+
853
+ if (element.length && element[0].detachEvent) {
854
+ element.each(function () {
855
+ this.detachEvent("onpropertychange", self._sync);
856
+ });
857
+ }
858
+ if (this.propertyObserver) {
859
+ this.propertyObserver.disconnect();
860
+ this.propertyObserver = null;
861
+ }
862
+ this._sync = null;
863
+
864
+ if (select2 !== undefined) {
865
+ select2.container.remove();
866
+ select2.liveRegion.remove();
867
+ select2.dropdown.remove();
868
+ element
869
+ .removeClass("select2-offscreen")
870
+ .removeData("select2")
871
+ .off(".select2")
872
+ .prop("autofocus", this.autofocus || false);
873
+ if (this.elementTabIndex) {
874
+ element.attr({tabindex: this.elementTabIndex});
875
+ } else {
876
+ element.removeAttr("tabindex");
877
+ }
878
+ element.show();
879
+ }
880
+
881
+ cleanupJQueryElements.call(this,
882
+ "container",
883
+ "liveRegion",
884
+ "dropdown",
885
+ "results",
886
+ "search"
887
+ );
888
+ },
889
+
890
+ // abstract
891
+ optionToData: function(element) {
892
+ if (element.is("option")) {
893
+ return {
894
+ id:element.prop("value"),
895
+ text:element.text(),
896
+ element: element.get(),
897
+ css: element.attr("class"),
898
+ disabled: element.prop("disabled"),
899
+ locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
900
+ };
901
+ } else if (element.is("optgroup")) {
902
+ return {
903
+ text:element.attr("label"),
904
+ children:[],
905
+ element: element.get(),
906
+ css: element.attr("class")
907
+ };
908
+ }
909
+ },
910
+
911
+ // abstract
912
+ prepareOpts: function (opts) {
913
+ var element, select, idKey, ajaxUrl, self = this;
914
+
915
+ element = opts.element;
916
+
917
+ if (element.get(0).tagName.toLowerCase() === "select") {
918
+ this.select = select = opts.element;
919
+ }
920
+
921
+ if (select) {
922
+ // these options are not allowed when attached to a select because they are picked up off the element itself
923
+ $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
924
+ if (this in opts) {
925
+ throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
926
+ }
927
+ });
928
+ }
929
+
930
+ opts = $.extend({}, {
931
+ populateResults: function(container, results, query) {
932
+ var populate, id=this.opts.id, liveRegion=this.liveRegion;
933
+
934
+ populate=function(results, container, depth) {
935
+
936
+ var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
937
+
938
+ results = opts.sortResults(results, container, query);
939
+
940
+ // collect the created nodes for bulk append
941
+ var nodes = [];
942
+ for (i = 0, l = results.length; i < l; i = i + 1) {
943
+
944
+ result=results[i];
945
+
946
+ disabled = (result.disabled === true);
947
+ selectable = (!disabled) && (id(result) !== undefined);
948
+
949
+ compound=result.children && result.children.length > 0;
950
+
951
+ node=$("<li></li>");
952
+ node.addClass("select2-results-dept-"+depth);
953
+ node.addClass("select2-result");
954
+ node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
955
+ if (disabled) { node.addClass("select2-disabled"); }
956
+ if (compound) { node.addClass("select2-result-with-children"); }
957
+ node.addClass(self.opts.formatResultCssClass(result));
958
+ node.attr("role", "presentation");
959
+
960
+ label=$(document.createElement("div"));
961
+ label.addClass("select2-result-label");
962
+ label.attr("id", "select2-result-label-" + nextUid());
963
+ label.attr("role", "option");
964
+
965
+ formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
966
+ if (formatted!==undefined) {
967
+ label.html(formatted);
968
+ node.append(label);
969
+ }
970
+
971
+
972
+ if (compound) {
973
+
974
+ innerContainer=$("<ul></ul>");
975
+ innerContainer.addClass("select2-result-sub");
976
+ populate(result.children, innerContainer, depth+1);
977
+ node.append(innerContainer);
978
+ }
979
+
980
+ node.data("select2-data", result);
981
+ nodes.push(node[0]);
982
+ }
983
+
984
+ // bulk append the created nodes
985
+ container.append(nodes);
986
+ liveRegion.text(opts.formatMatches(results.length));
987
+ };
988
+
989
+ populate(results, container, 0);
990
+ }
991
+ }, $.fn.select2.defaults, opts);
992
+
993
+ if (typeof(opts.id) !== "function") {
994
+ idKey = opts.id;
995
+ opts.id = function (e) { return e[idKey]; };
996
+ }
997
+
998
+ if ($.isArray(opts.element.data("select2Tags"))) {
999
+ if ("tags" in opts) {
1000
+ throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
1001
+ }
1002
+ opts.tags=opts.element.data("select2Tags");
1003
+ }
1004
+
1005
+ if (select) {
1006
+ opts.query = this.bind(function (query) {
1007
+ var data = { results: [], more: false },
1008
+ term = query.term,
1009
+ children, placeholderOption, process;
1010
+
1011
+ process=function(element, collection) {
1012
+ var group;
1013
+ if (element.is("option")) {
1014
+ if (query.matcher(term, element.text(), element)) {
1015
+ collection.push(self.optionToData(element));
1016
+ }
1017
+ } else if (element.is("optgroup")) {
1018
+ group=self.optionToData(element);
1019
+ element.children().each2(function(i, elm) { process(elm, group.children); });
1020
+ if (group.children.length>0) {
1021
+ collection.push(group);
1022
+ }
1023
+ }
1024
+ };
1025
+
1026
+ children=element.children();
1027
+
1028
+ // ignore the placeholder option if there is one
1029
+ if (this.getPlaceholder() !== undefined && children.length > 0) {
1030
+ placeholderOption = this.getPlaceholderOption();
1031
+ if (placeholderOption) {
1032
+ children=children.not(placeholderOption);
1033
+ }
1034
+ }
1035
+
1036
+ children.each2(function(i, elm) { process(elm, data.results); });
1037
+
1038
+ query.callback(data);
1039
+ });
1040
+ // this is needed because inside val() we construct choices from options and their id is hardcoded
1041
+ opts.id=function(e) { return e.id; };
1042
+ } else {
1043
+ if (!("query" in opts)) {
1044
+
1045
+ if ("ajax" in opts) {
1046
+ ajaxUrl = opts.element.data("ajax-url");
1047
+ if (ajaxUrl && ajaxUrl.length > 0) {
1048
+ opts.ajax.url = ajaxUrl;
1049
+ }
1050
+ opts.query = ajax.call(opts.element, opts.ajax);
1051
+ } else if ("data" in opts) {
1052
+ opts.query = local(opts.data);
1053
+ } else if ("tags" in opts) {
1054
+ opts.query = tags(opts.tags);
1055
+ if (opts.createSearchChoice === undefined) {
1056
+ opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
1057
+ }
1058
+ if (opts.initSelection === undefined) {
1059
+ opts.initSelection = function (element, callback) {
1060
+ var data = [];
1061
+ $(splitVal(element.val(), opts.separator)).each(function () {
1062
+ var obj = { id: this, text: this },
1063
+ tags = opts.tags;
1064
+ if ($.isFunction(tags)) tags=tags();
1065
+ $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
1066
+ data.push(obj);
1067
+ });
1068
+
1069
+ callback(data);
1070
+ };
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ if (typeof(opts.query) !== "function") {
1076
+ throw "query function not defined for Select2 " + opts.element.attr("id");
1077
+ }
1078
+
1079
+ if (opts.createSearchChoicePosition === 'top') {
1080
+ opts.createSearchChoicePosition = function(list, item) { list.unshift(item); };
1081
+ }
1082
+ else if (opts.createSearchChoicePosition === 'bottom') {
1083
+ opts.createSearchChoicePosition = function(list, item) { list.push(item); };
1084
+ }
1085
+ else if (typeof(opts.createSearchChoicePosition) !== "function") {
1086
+ throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
1087
+ }
1088
+
1089
+ return opts;
1090
+ },
1091
+
1092
+ /**
1093
+ * Monitor the original element for changes and update select2 accordingly
1094
+ */
1095
+ // abstract
1096
+ monitorSource: function () {
1097
+ var el = this.opts.element, observer, self = this;
1098
+
1099
+ el.on("change.select2", this.bind(function (e) {
1100
+ if (this.opts.element.data("select2-change-triggered") !== true) {
1101
+ this.initSelection();
1102
+ }
1103
+ }));
1104
+
1105
+ this._sync = this.bind(function () {
1106
+
1107
+ // sync enabled state
1108
+ var disabled = el.prop("disabled");
1109
+ if (disabled === undefined) disabled = false;
1110
+ this.enable(!disabled);
1111
+
1112
+ var readonly = el.prop("readonly");
1113
+ if (readonly === undefined) readonly = false;
1114
+ this.readonly(readonly);
1115
+
1116
+ syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
1117
+ this.container.addClass(evaluate(this.opts.containerCssClass, this.opts.element));
1118
+
1119
+ syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
1120
+ this.dropdown.addClass(evaluate(this.opts.dropdownCssClass, this.opts.element));
1121
+
1122
+ });
1123
+
1124
+ // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener)
1125
+ if (el.length && el[0].attachEvent) {
1126
+ el.each(function() {
1127
+ this.attachEvent("onpropertychange", self._sync);
1128
+ });
1129
+ }
1130
+
1131
+ // safari, chrome, firefox, IE11
1132
+ observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
1133
+ if (observer !== undefined) {
1134
+ if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
1135
+ this.propertyObserver = new observer(function (mutations) {
1136
+ $.each(mutations, self._sync);
1137
+ });
1138
+ this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
1139
+ }
1140
+ },
1141
+
1142
+ // abstract
1143
+ triggerSelect: function(data) {
1144
+ var evt = $.Event("select2-selecting", { val: this.id(data), object: data, choice: data });
1145
+ this.opts.element.trigger(evt);
1146
+ return !evt.isDefaultPrevented();
1147
+ },
1148
+
1149
+ /**
1150
+ * Triggers the change event on the source element
1151
+ */
1152
+ // abstract
1153
+ triggerChange: function (details) {
1154
+
1155
+ details = details || {};
1156
+ details= $.extend({}, details, { type: "change", val: this.val() });
1157
+ // prevents recursive triggering
1158
+ this.opts.element.data("select2-change-triggered", true);
1159
+ this.opts.element.trigger(details);
1160
+ this.opts.element.data("select2-change-triggered", false);
1161
+
1162
+ // some validation frameworks ignore the change event and listen instead to keyup, click for selects
1163
+ // so here we trigger the click event manually
1164
+ this.opts.element.click();
1165
+
1166
+ // ValidationEngine ignores the change event and listens instead to blur
1167
+ // so here we trigger the blur event manually if so desired
1168
+ if (this.opts.blurOnChange)
1169
+ this.opts.element.blur();
1170
+ },
1171
+
1172
+ //abstract
1173
+ isInterfaceEnabled: function()
1174
+ {
1175
+ return this.enabledInterface === true;
1176
+ },
1177
+
1178
+ // abstract
1179
+ enableInterface: function() {
1180
+ var enabled = this._enabled && !this._readonly,
1181
+ disabled = !enabled;
1182
+
1183
+ if (enabled === this.enabledInterface) return false;
1184
+
1185
+ this.container.toggleClass("select2-container-disabled", disabled);
1186
+ this.close();
1187
+ this.enabledInterface = enabled;
1188
+
1189
+ return true;
1190
+ },
1191
+
1192
+ // abstract
1193
+ enable: function(enabled) {
1194
+ if (enabled === undefined) enabled = true;
1195
+ if (this._enabled === enabled) return;
1196
+ this._enabled = enabled;
1197
+
1198
+ this.opts.element.prop("disabled", !enabled);
1199
+ this.enableInterface();
1200
+ },
1201
+
1202
+ // abstract
1203
+ disable: function() {
1204
+ this.enable(false);
1205
+ },
1206
+
1207
+ // abstract
1208
+ readonly: function(enabled) {
1209
+ if (enabled === undefined) enabled = false;
1210
+ if (this._readonly === enabled) return;
1211
+ this._readonly = enabled;
1212
+
1213
+ this.opts.element.prop("readonly", enabled);
1214
+ this.enableInterface();
1215
+ },
1216
+
1217
+ // abstract
1218
+ opened: function () {
1219
+ return (this.container) ? this.container.hasClass("select2-dropdown-open") : false;
1220
+ },
1221
+
1222
+ // abstract
1223
+ positionDropdown: function() {
1224
+ var $dropdown = this.dropdown,
1225
+ offset = this.container.offset(),
1226
+ height = this.container.outerHeight(false),
1227
+ width = this.container.outerWidth(false),
1228
+ dropHeight = $dropdown.outerHeight(false),
1229
+ $window = $(window),
1230
+ windowWidth = $window.width(),
1231
+ windowHeight = $window.height(),
1232
+ viewPortRight = $window.scrollLeft() + windowWidth,
1233
+ viewportBottom = $window.scrollTop() + windowHeight,
1234
+ dropTop = offset.top + height,
1235
+ dropLeft = offset.left,
1236
+ enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
1237
+ enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(),
1238
+ dropWidth = $dropdown.outerWidth(false),
1239
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
1240
+ aboveNow = $dropdown.hasClass("select2-drop-above"),
1241
+ bodyOffset,
1242
+ above,
1243
+ changeDirection,
1244
+ css,
1245
+ resultsListNode;
1246
+
1247
+ // always prefer the current above/below alignment, unless there is not enough room
1248
+ if (aboveNow) {
1249
+ above = true;
1250
+ if (!enoughRoomAbove && enoughRoomBelow) {
1251
+ changeDirection = true;
1252
+ above = false;
1253
+ }
1254
+ } else {
1255
+ above = false;
1256
+ if (!enoughRoomBelow && enoughRoomAbove) {
1257
+ changeDirection = true;
1258
+ above = true;
1259
+ }
1260
+ }
1261
+
1262
+ //if we are changing direction we need to get positions when dropdown is hidden;
1263
+ if (changeDirection) {
1264
+ $dropdown.hide();
1265
+ offset = this.container.offset();
1266
+ height = this.container.outerHeight(false);
1267
+ width = this.container.outerWidth(false);
1268
+ dropHeight = $dropdown.outerHeight(false);
1269
+ viewPortRight = $window.scrollLeft() + windowWidth;
1270
+ viewportBottom = $window.scrollTop() + windowHeight;
1271
+ dropTop = offset.top + height;
1272
+ dropLeft = offset.left;
1273
+ dropWidth = $dropdown.outerWidth(false);
1274
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1275
+ $dropdown.show();
1276
+
1277
+ // fix so the cursor does not move to the left within the search-textbox in IE
1278
+ this.focusSearch();
1279
+ }
1280
+
1281
+ if (this.opts.dropdownAutoWidth) {
1282
+ resultsListNode = $('.select2-results', $dropdown)[0];
1283
+ $dropdown.addClass('select2-drop-auto-width');
1284
+ $dropdown.css('width', '');
1285
+ // Add scrollbar width to dropdown if vertical scrollbar is present
1286
+ dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
1287
+ dropWidth > width ? width = dropWidth : dropWidth = width;
1288
+ dropHeight = $dropdown.outerHeight(false);
1289
+ enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
1290
+ }
1291
+ else {
1292
+ this.container.removeClass('select2-drop-auto-width');
1293
+ }
1294
+
1295
+ //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
1296
+ //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove);
1297
+
1298
+ // fix positioning when body has an offset and is not position: static
1299
+ if (this.body.css('position') !== 'static') {
1300
+ bodyOffset = this.body.offset();
1301
+ dropTop -= bodyOffset.top;
1302
+ dropLeft -= bodyOffset.left;
1303
+ }
1304
+
1305
+ if (!enoughRoomOnRight) {
1306
+ dropLeft = offset.left + this.container.outerWidth(false) - dropWidth;
1307
+ }
1308
+
1309
+ css = {
1310
+ left: dropLeft,
1311
+ width: width
1312
+ };
1313
+
1314
+ if (above) {
1315
+ css.top = offset.top - dropHeight;
1316
+ css.bottom = 'auto';
1317
+ this.container.addClass("select2-drop-above");
1318
+ $dropdown.addClass("select2-drop-above");
1319
+ }
1320
+ else {
1321
+ css.top = dropTop;
1322
+ css.bottom = 'auto';
1323
+ this.container.removeClass("select2-drop-above");
1324
+ $dropdown.removeClass("select2-drop-above");
1325
+ }
1326
+ css = $.extend(css, evaluate(this.opts.dropdownCss, this.opts.element));
1327
+
1328
+ $dropdown.css(css);
1329
+ },
1330
+
1331
+ // abstract
1332
+ shouldOpen: function() {
1333
+ var event;
1334
+
1335
+ if (this.opened()) return false;
1336
+
1337
+ if (this._enabled === false || this._readonly === true) return false;
1338
+
1339
+ event = $.Event("select2-opening");
1340
+ this.opts.element.trigger(event);
1341
+ return !event.isDefaultPrevented();
1342
+ },
1343
+
1344
+ // abstract
1345
+ clearDropdownAlignmentPreference: function() {
1346
+ // clear the classes used to figure out the preference of where the dropdown should be opened
1347
+ this.container.removeClass("select2-drop-above");
1348
+ this.dropdown.removeClass("select2-drop-above");
1349
+ },
1350
+
1351
+ /**
1352
+ * Opens the dropdown
1353
+ *
1354
+ * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
1355
+ * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
1356
+ */
1357
+ // abstract
1358
+ open: function () {
1359
+
1360
+ if (!this.shouldOpen()) return false;
1361
+
1362
+ this.opening();
1363
+
1364
+ // Only bind the document mousemove when the dropdown is visible
1365
+ $document.on("mousemove.select2Event", function (e) {
1366
+ lastMousePosition.x = e.pageX;
1367
+ lastMousePosition.y = e.pageY;
1368
+ });
1369
+
1370
+ return true;
1371
+ },
1372
+
1373
+ /**
1374
+ * Performs the opening of the dropdown
1375
+ */
1376
+ // abstract
1377
+ opening: function() {
1378
+ var cid = this.containerEventName,
1379
+ scroll = "scroll." + cid,
1380
+ resize = "resize."+cid,
1381
+ orient = "orientationchange."+cid,
1382
+ mask;
1383
+
1384
+ this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
1385
+
1386
+ this.clearDropdownAlignmentPreference();
1387
+
1388
+ if(this.dropdown[0] !== this.body.children().last()[0]) {
1389
+ this.dropdown.detach().appendTo(this.body);
1390
+ }
1391
+
1392
+ // create the dropdown mask if doesn't already exist
1393
+ mask = $("#select2-drop-mask");
1394
+ if (mask.length == 0) {
1395
+ mask = $(document.createElement("div"));
1396
+ mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
1397
+ mask.hide();
1398
+ mask.appendTo(this.body);
1399
+ mask.on("mousedown touchstart click", function (e) {
1400
+ // Prevent IE from generating a click event on the body
1401
+ reinsertElement(mask);
1402
+
1403
+ var dropdown = $("#select2-drop"), self;
1404
+ if (dropdown.length > 0) {
1405
+ self=dropdown.data("select2");
1406
+ if (self.opts.selectOnBlur) {
1407
+ self.selectHighlighted({noFocus: true});
1408
+ }
1409
+ self.close();
1410
+ e.preventDefault();
1411
+ e.stopPropagation();
1412
+ }
1413
+ });
1414
+ }
1415
+
1416
+ // ensure the mask is always right before the dropdown
1417
+ if (this.dropdown.prev()[0] !== mask[0]) {
1418
+ this.dropdown.before(mask);
1419
+ }
1420
+
1421
+ // move the global id to the correct dropdown
1422
+ $("#select2-drop").removeAttr("id");
1423
+ this.dropdown.attr("id", "select2-drop");
1424
+
1425
+ // show the elements
1426
+ mask.show();
1427
+
1428
+ this.positionDropdown();
1429
+ this.dropdown.show();
1430
+ this.positionDropdown();
1431
+
1432
+ this.dropdown.addClass("select2-drop-active");
1433
+
1434
+ // attach listeners to events that can change the position of the container and thus require
1435
+ // the position of the dropdown to be updated as well so it does not come unglued from the container
1436
+ var that = this;
1437
+ this.container.parents().add(window).each(function () {
1438
+ $(this).on(resize+" "+scroll+" "+orient, function (e) {
1439
+ if (that.opened()) that.positionDropdown();
1440
+ });
1441
+ });
1442
+
1443
+
1444
+ },
1445
+
1446
+ // abstract
1447
+ close: function () {
1448
+ if (!this.opened()) return;
1449
+
1450
+ var cid = this.containerEventName,
1451
+ scroll = "scroll." + cid,
1452
+ resize = "resize."+cid,
1453
+ orient = "orientationchange."+cid;
1454
+
1455
+ // unbind event listeners
1456
+ this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
1457
+
1458
+ this.clearDropdownAlignmentPreference();
1459
+
1460
+ $("#select2-drop-mask").hide();
1461
+ this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
1462
+ this.dropdown.hide();
1463
+ this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
1464
+ this.results.empty();
1465
+
1466
+ // Now that the dropdown is closed, unbind the global document mousemove event
1467
+ $document.off("mousemove.select2Event");
1468
+
1469
+ this.clearSearch();
1470
+ this.search.removeClass("select2-active");
1471
+ this.opts.element.trigger($.Event("select2-close"));
1472
+ },
1473
+
1474
+ /**
1475
+ * Opens control, sets input value, and updates results.
1476
+ */
1477
+ // abstract
1478
+ externalSearch: function (term) {
1479
+ this.open();
1480
+ this.search.val(term);
1481
+ this.updateResults(false);
1482
+ },
1483
+
1484
+ // abstract
1485
+ clearSearch: function () {
1486
+
1487
+ },
1488
+
1489
+ //abstract
1490
+ getMaximumSelectionSize: function() {
1491
+ return evaluate(this.opts.maximumSelectionSize, this.opts.element);
1492
+ },
1493
+
1494
+ // abstract
1495
+ ensureHighlightVisible: function () {
1496
+ var results = this.results, children, index, child, hb, rb, y, more, topOffset;
1497
+
1498
+ index = this.highlight();
1499
+
1500
+ if (index < 0) return;
1501
+
1502
+ if (index == 0) {
1503
+
1504
+ // if the first element is highlighted scroll all the way to the top,
1505
+ // that way any unselectable headers above it will also be scrolled
1506
+ // into view
1507
+
1508
+ results.scrollTop(0);
1509
+ return;
1510
+ }
1511
+
1512
+ children = this.findHighlightableChoices().find('.select2-result-label');
1513
+
1514
+ child = $(children[index]);
1515
+
1516
+ topOffset = (child.offset() || {}).top || 0;
1517
+
1518
+ hb = topOffset + child.outerHeight(true);
1519
+
1520
+ // if this is the last child lets also make sure select2-more-results is visible
1521
+ if (index === children.length - 1) {
1522
+ more = results.find("li.select2-more-results");
1523
+ if (more.length > 0) {
1524
+ hb = more.offset().top + more.outerHeight(true);
1525
+ }
1526
+ }
1527
+
1528
+ rb = results.offset().top + results.outerHeight(true);
1529
+ if (hb > rb) {
1530
+ results.scrollTop(results.scrollTop() + (hb - rb));
1531
+ }
1532
+ y = topOffset - results.offset().top;
1533
+
1534
+ // make sure the top of the element is visible
1535
+ if (y < 0 && child.css('display') != 'none' ) {
1536
+ results.scrollTop(results.scrollTop() + y); // y is negative
1537
+ }
1538
+ },
1539
+
1540
+ // abstract
1541
+ findHighlightableChoices: function() {
1542
+ return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)");
1543
+ },
1544
+
1545
+ // abstract
1546
+ moveHighlight: function (delta) {
1547
+ var choices = this.findHighlightableChoices(),
1548
+ index = this.highlight();
1549
+
1550
+ while (index > -1 && index < choices.length) {
1551
+ index += delta;
1552
+ var choice = $(choices[index]);
1553
+ if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
1554
+ this.highlight(index);
1555
+ break;
1556
+ }
1557
+ }
1558
+ },
1559
+
1560
+ // abstract
1561
+ highlight: function (index) {
1562
+ var choices = this.findHighlightableChoices(),
1563
+ choice,
1564
+ data;
1565
+
1566
+ if (arguments.length === 0) {
1567
+ return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
1568
+ }
1569
+
1570
+ if (index >= choices.length) index = choices.length - 1;
1571
+ if (index < 0) index = 0;
1572
+
1573
+ this.removeHighlight();
1574
+
1575
+ choice = $(choices[index]);
1576
+ choice.addClass("select2-highlighted");
1577
+
1578
+ // ensure assistive technology can determine the active choice
1579
+ this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id"));
1580
+
1581
+ this.ensureHighlightVisible();
1582
+
1583
+ this.liveRegion.text(choice.text());
1584
+
1585
+ data = choice.data("select2-data");
1586
+ if (data) {
1587
+ this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
1588
+ }
1589
+ },
1590
+
1591
+ removeHighlight: function() {
1592
+ this.results.find(".select2-highlighted").removeClass("select2-highlighted");
1593
+ },
1594
+
1595
+ touchMoved: function() {
1596
+ this._touchMoved = true;
1597
+ },
1598
+
1599
+ clearTouchMoved: function() {
1600
+ this._touchMoved = false;
1601
+ },
1602
+
1603
+ // abstract
1604
+ countSelectableResults: function() {
1605
+ return this.findHighlightableChoices().length;
1606
+ },
1607
+
1608
+ // abstract
1609
+ highlightUnderEvent: function (event) {
1610
+ var el = $(event.target).closest(".select2-result-selectable");
1611
+ if (el.length > 0 && !el.is(".select2-highlighted")) {
1612
+ var choices = this.findHighlightableChoices();
1613
+ this.highlight(choices.index(el));
1614
+ } else if (el.length == 0) {
1615
+ // if we are over an unselectable item remove all highlights
1616
+ this.removeHighlight();
1617
+ }
1618
+ },
1619
+
1620
+ // abstract
1621
+ loadMoreIfNeeded: function () {
1622
+ var results = this.results,
1623
+ more = results.find("li.select2-more-results"),
1624
+ below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
1625
+ page = this.resultsPage + 1,
1626
+ self=this,
1627
+ term=this.search.val(),
1628
+ context=this.context;
1629
+
1630
+ if (more.length === 0) return;
1631
+ below = more.offset().top - results.offset().top - results.height();
1632
+
1633
+ if (below <= this.opts.loadMorePadding) {
1634
+ more.addClass("select2-active");
1635
+ this.opts.query({
1636
+ element: this.opts.element,
1637
+ term: term,
1638
+ page: page,
1639
+ context: context,
1640
+ matcher: this.opts.matcher,
1641
+ callback: this.bind(function (data) {
1642
+
1643
+ // ignore a response if the select2 has been closed before it was received
1644
+ if (!self.opened()) return;
1645
+
1646
+
1647
+ self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
1648
+ self.postprocessResults(data, false, false);
1649
+
1650
+ if (data.more===true) {
1651
+ more.detach().appendTo(results).text(evaluate(self.opts.formatLoadMore, self.opts.element, page+1));
1652
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1653
+ } else {
1654
+ more.remove();
1655
+ }
1656
+ self.positionDropdown();
1657
+ self.resultsPage = page;
1658
+ self.context = data.context;
1659
+ this.opts.element.trigger({ type: "select2-loaded", items: data });
1660
+ })});
1661
+ }
1662
+ },
1663
+
1664
+ /**
1665
+ * Default tokenizer function which does nothing
1666
+ */
1667
+ tokenize: function() {
1668
+
1669
+ },
1670
+
1671
+ /**
1672
+ * @param initial whether or not this is the call to this method right after the dropdown has been opened
1673
+ */
1674
+ // abstract
1675
+ updateResults: function (initial) {
1676
+ var search = this.search,
1677
+ results = this.results,
1678
+ opts = this.opts,
1679
+ data,
1680
+ self = this,
1681
+ input,
1682
+ term = search.val(),
1683
+ lastTerm = $.data(this.container, "select2-last-term"),
1684
+ // sequence number used to drop out-of-order responses
1685
+ queryNumber;
1686
+
1687
+ // prevent duplicate queries against the same term
1688
+ if (initial !== true && lastTerm && equal(term, lastTerm)) return;
1689
+
1690
+ $.data(this.container, "select2-last-term", term);
1691
+
1692
+ // if the search is currently hidden we do not alter the results
1693
+ if (initial !== true && (this.showSearchInput === false || !this.opened())) {
1694
+ return;
1695
+ }
1696
+
1697
+ function postRender() {
1698
+ search.removeClass("select2-active");
1699
+ self.positionDropdown();
1700
+ if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) {
1701
+ self.liveRegion.text(results.text());
1702
+ }
1703
+ else {
1704
+ self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length));
1705
+ }
1706
+ }
1707
+
1708
+ function render(html) {
1709
+ results.html(html);
1710
+ postRender();
1711
+ }
1712
+
1713
+ queryNumber = ++this.queryCount;
1714
+
1715
+ var maxSelSize = this.getMaximumSelectionSize();
1716
+ if (maxSelSize >=1) {
1717
+ data = this.data();
1718
+ if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
1719
+ render("<li class='select2-selection-limit'>" + evaluate(opts.formatSelectionTooBig, opts.element, maxSelSize) + "</li>");
1720
+ return;
1721
+ }
1722
+ }
1723
+
1724
+ if (search.val().length < opts.minimumInputLength) {
1725
+ if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
1726
+ render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooShort, opts.element, search.val(), opts.minimumInputLength) + "</li>");
1727
+ } else {
1728
+ render("");
1729
+ }
1730
+ if (initial && this.showSearch) this.showSearch(true);
1731
+ return;
1732
+ }
1733
+
1734
+ if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
1735
+ if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
1736
+ render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooLong, opts.element, search.val(), opts.maximumInputLength) + "</li>");
1737
+ } else {
1738
+ render("");
1739
+ }
1740
+ return;
1741
+ }
1742
+
1743
+ if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
1744
+ render("<li class='select2-searching'>" + evaluate(opts.formatSearching, opts.element) + "</li>");
1745
+ }
1746
+
1747
+ search.addClass("select2-active");
1748
+
1749
+ this.removeHighlight();
1750
+
1751
+ // give the tokenizer a chance to pre-process the input
1752
+ input = this.tokenize();
1753
+ if (input != undefined && input != null) {
1754
+ search.val(input);
1755
+ }
1756
+
1757
+ this.resultsPage = 1;
1758
+
1759
+ opts.query({
1760
+ element: opts.element,
1761
+ term: search.val(),
1762
+ page: this.resultsPage,
1763
+ context: null,
1764
+ matcher: opts.matcher,
1765
+ callback: this.bind(function (data) {
1766
+ var def; // default choice
1767
+
1768
+ // ignore old responses
1769
+ if (queryNumber != this.queryCount) {
1770
+ return;
1771
+ }
1772
+
1773
+ // ignore a response if the select2 has been closed before it was received
1774
+ if (!this.opened()) {
1775
+ this.search.removeClass("select2-active");
1776
+ return;
1777
+ }
1778
+
1779
+ // handle ajax error
1780
+ if(data.hasError !== undefined && checkFormatter(opts.formatAjaxError, "formatAjaxError")) {
1781
+ render("<li class='select2-ajax-error'>" + evaluate(opts.formatAjaxError, opts.element, data.jqXHR, data.textStatus, data.errorThrown) + "</li>");
1782
+ return;
1783
+ }
1784
+
1785
+ // save context, if any
1786
+ this.context = (data.context===undefined) ? null : data.context;
1787
+ // create a default choice and prepend it to the list
1788
+ if (this.opts.createSearchChoice && search.val() !== "") {
1789
+ def = this.opts.createSearchChoice.call(self, search.val(), data.results);
1790
+ if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
1791
+ if ($(data.results).filter(
1792
+ function () {
1793
+ return equal(self.id(this), self.id(def));
1794
+ }).length === 0) {
1795
+ this.opts.createSearchChoicePosition(data.results, def);
1796
+ }
1797
+ }
1798
+ }
1799
+
1800
+ if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
1801
+ render("<li class='select2-no-results'>" + evaluate(opts.formatNoMatches, opts.element, search.val()) + "</li>");
1802
+ return;
1803
+ }
1804
+
1805
+ results.empty();
1806
+ self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
1807
+
1808
+ if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
1809
+ results.append("<li class='select2-more-results'>" + opts.escapeMarkup(evaluate(opts.formatLoadMore, opts.element, this.resultsPage)) + "</li>");
1810
+ window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
1811
+ }
1812
+
1813
+ this.postprocessResults(data, initial);
1814
+
1815
+ postRender();
1816
+
1817
+ this.opts.element.trigger({ type: "select2-loaded", items: data });
1818
+ })});
1819
+ },
1820
+
1821
+ // abstract
1822
+ cancel: function () {
1823
+ this.close();
1824
+ },
1825
+
1826
+ // abstract
1827
+ blur: function () {
1828
+ // if selectOnBlur == true, select the currently highlighted option
1829
+ if (this.opts.selectOnBlur)
1830
+ this.selectHighlighted({noFocus: true});
1831
+
1832
+ this.close();
1833
+ this.container.removeClass("select2-container-active");
1834
+ // synonymous to .is(':focus'), which is available in jquery >= 1.6
1835
+ if (this.search[0] === document.activeElement) { this.search.blur(); }
1836
+ this.clearSearch();
1837
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
1838
+ },
1839
+
1840
+ // abstract
1841
+ focusSearch: function () {
1842
+ focus(this.search);
1843
+ },
1844
+
1845
+ // abstract
1846
+ selectHighlighted: function (options) {
1847
+ if (this._touchMoved) {
1848
+ this.clearTouchMoved();
1849
+ return;
1850
+ }
1851
+ var index=this.highlight(),
1852
+ highlighted=this.results.find(".select2-highlighted"),
1853
+ data = highlighted.closest('.select2-result').data("select2-data");
1854
+
1855
+ if (data) {
1856
+ this.highlight(index);
1857
+ this.onSelect(data, options);
1858
+ } else if (options && options.noFocus) {
1859
+ this.close();
1860
+ }
1861
+ },
1862
+
1863
+ // abstract
1864
+ getPlaceholder: function () {
1865
+ var placeholderOption;
1866
+ return this.opts.element.attr("placeholder") ||
1867
+ this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
1868
+ this.opts.element.data("placeholder") ||
1869
+ this.opts.placeholder ||
1870
+ ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
1871
+ },
1872
+
1873
+ // abstract
1874
+ getPlaceholderOption: function() {
1875
+ if (this.select) {
1876
+ var firstOption = this.select.children('option').first();
1877
+ if (this.opts.placeholderOption !== undefined ) {
1878
+ //Determine the placeholder option based on the specified placeholderOption setting
1879
+ return (this.opts.placeholderOption === "first" && firstOption) ||
1880
+ (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
1881
+ } else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") {
1882
+ //No explicit placeholder option specified, use the first if it's blank
1883
+ return firstOption;
1884
+ }
1885
+ }
1886
+ },
1887
+
1888
+ /**
1889
+ * Get the desired width for the container element. This is
1890
+ * derived first from option `width` passed to select2, then
1891
+ * the inline 'style' on the original element, and finally
1892
+ * falls back to the jQuery calculated element width.
1893
+ */
1894
+ // abstract
1895
+ initContainerWidth: function () {
1896
+ function resolveContainerWidth() {
1897
+ var style, attrs, matches, i, l, attr;
1898
+
1899
+ if (this.opts.width === "off") {
1900
+ return null;
1901
+ } else if (this.opts.width === "element"){
1902
+ return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
1903
+ } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
1904
+ // check if there is inline style on the element that contains width
1905
+ style = this.opts.element.attr('style');
1906
+ if (style !== undefined) {
1907
+ attrs = style.split(';');
1908
+ for (i = 0, l = attrs.length; i < l; i = i + 1) {
1909
+ attr = attrs[i].replace(/\s/g, '');
1910
+ matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
1911
+ if (matches !== null && matches.length >= 1)
1912
+ return matches[1];
1913
+ }
1914
+ }
1915
+
1916
+ if (this.opts.width === "resolve") {
1917
+ // next check if css('width') can resolve a width that is percent based, this is sometimes possible
1918
+ // when attached to input type=hidden or elements hidden via css
1919
+ style = this.opts.element.css('width');
1920
+ if (style.indexOf("%") > 0) return style;
1921
+
1922
+ // finally, fallback on the calculated width of the element
1923
+ return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
1924
+ }
1925
+
1926
+ return null;
1927
+ } else if ($.isFunction(this.opts.width)) {
1928
+ return this.opts.width();
1929
+ } else {
1930
+ return this.opts.width;
1931
+ }
1932
+ };
1933
+
1934
+ var width = resolveContainerWidth.call(this);
1935
+ if (width !== null) {
1936
+ this.container.css("width", width);
1937
+ }
1938
+ }
1939
+ });
1940
+
1941
+ SingleSelect2 = clazz(AbstractSelect2, {
1942
+
1943
+ // single
1944
+
1945
+ createContainer: function () {
1946
+ var container = $(document.createElement("div")).attr({
1947
+ "class": "select2-container"
1948
+ }).html([
1949
+ "<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
1950
+ " <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
1951
+ " <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
1952
+ "</a>",
1953
+ "<label for='' class='select2-offscreen'></label>",
1954
+ "<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
1955
+ "<div class='select2-drop select2-display-none'>",
1956
+ " <div class='select2-search'>",
1957
+ " <label for='' class='select2-offscreen'></label>",
1958
+ " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
1959
+ " aria-autocomplete='list' />",
1960
+ " </div>",
1961
+ " <ul class='select2-results' role='listbox'>",
1962
+ " </ul>",
1963
+ "</div>"].join(""));
1964
+ return container;
1965
+ },
1966
+
1967
+ // single
1968
+ enableInterface: function() {
1969
+ if (this.parent.enableInterface.apply(this, arguments)) {
1970
+ this.focusser.prop("disabled", !this.isInterfaceEnabled());
1971
+ }
1972
+ },
1973
+
1974
+ // single
1975
+ opening: function () {
1976
+ var el, range, len;
1977
+
1978
+ if (this.opts.minimumResultsForSearch >= 0) {
1979
+ this.showSearch(true);
1980
+ }
1981
+
1982
+ this.parent.opening.apply(this, arguments);
1983
+
1984
+ if (this.showSearchInput !== false) {
1985
+ // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
1986
+ // all other browsers handle this just fine
1987
+
1988
+ this.search.val(this.focusser.val());
1989
+ }
1990
+ if (this.opts.shouldFocusInput(this)) {
1991
+ this.search.focus();
1992
+ // move the cursor to the end after focussing, otherwise it will be at the beginning and
1993
+ // new text will appear *before* focusser.val()
1994
+ el = this.search.get(0);
1995
+ if (el.createTextRange) {
1996
+ range = el.createTextRange();
1997
+ range.collapse(false);
1998
+ range.select();
1999
+ } else if (el.setSelectionRange) {
2000
+ len = this.search.val().length;
2001
+ el.setSelectionRange(len, len);
2002
+ }
2003
+ }
2004
+
2005
+ // initializes search's value with nextSearchTerm (if defined by user)
2006
+ // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
2007
+ if(this.search.val() === "") {
2008
+ if(this.nextSearchTerm != undefined){
2009
+ this.search.val(this.nextSearchTerm);
2010
+ this.search.select();
2011
+ }
2012
+ }
2013
+
2014
+ this.focusser.prop("disabled", true).val("");
2015
+ this.updateResults(true);
2016
+ this.opts.element.trigger($.Event("select2-open"));
2017
+ },
2018
+
2019
+ // single
2020
+ close: function () {
2021
+ if (!this.opened()) return;
2022
+ this.parent.close.apply(this, arguments);
2023
+
2024
+ this.focusser.prop("disabled", false);
2025
+
2026
+ if (this.opts.shouldFocusInput(this)) {
2027
+ this.focusser.focus();
2028
+ }
2029
+ },
2030
+
2031
+ // single
2032
+ focus: function () {
2033
+ if (this.opened()) {
2034
+ this.close();
2035
+ } else {
2036
+ this.focusser.prop("disabled", false);
2037
+ if (this.opts.shouldFocusInput(this)) {
2038
+ this.focusser.focus();
2039
+ }
2040
+ }
2041
+ },
2042
+
2043
+ // single
2044
+ isFocused: function () {
2045
+ return this.container.hasClass("select2-container-active");
2046
+ },
2047
+
2048
+ // single
2049
+ cancel: function () {
2050
+ this.parent.cancel.apply(this, arguments);
2051
+ this.focusser.prop("disabled", false);
2052
+
2053
+ if (this.opts.shouldFocusInput(this)) {
2054
+ this.focusser.focus();
2055
+ }
2056
+ },
2057
+
2058
+ // single
2059
+ destroy: function() {
2060
+ $("label[for='" + this.focusser.attr('id') + "']")
2061
+ .attr('for', this.opts.element.attr("id"));
2062
+ this.parent.destroy.apply(this, arguments);
2063
+
2064
+ cleanupJQueryElements.call(this,
2065
+ "selection",
2066
+ "focusser"
2067
+ );
2068
+ },
2069
+
2070
+ // single
2071
+ initContainer: function () {
2072
+
2073
+ var selection,
2074
+ container = this.container,
2075
+ dropdown = this.dropdown,
2076
+ idSuffix = nextUid(),
2077
+ elementLabel;
2078
+
2079
+ if (this.opts.minimumResultsForSearch < 0) {
2080
+ this.showSearch(false);
2081
+ } else {
2082
+ this.showSearch(true);
2083
+ }
2084
+
2085
+ this.selection = selection = container.find(".select2-choice");
2086
+
2087
+ this.focusser = container.find(".select2-focusser");
2088
+
2089
+ // add aria associations
2090
+ selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix);
2091
+ this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix);
2092
+ this.results.attr("id", "select2-results-"+idSuffix);
2093
+ this.search.attr("aria-owns", "select2-results-"+idSuffix);
2094
+
2095
+ // rewrite labels from original element to focusser
2096
+ this.focusser.attr("id", "s2id_autogen"+idSuffix);
2097
+
2098
+ elementLabel = $("label[for='" + this.opts.element.attr("id") + "']");
2099
+
2100
+ this.focusser.prev()
2101
+ .text(elementLabel.text())
2102
+ .attr('for', this.focusser.attr('id'));
2103
+
2104
+ // Ensure the original element retains an accessible name
2105
+ var originalTitle = this.opts.element.attr("title");
2106
+ this.opts.element.attr("title", (originalTitle || elementLabel.text()));
2107
+
2108
+ this.focusser.attr("tabindex", this.elementTabIndex);
2109
+
2110
+ // write label for search field using the label from the focusser element
2111
+ this.search.attr("id", this.focusser.attr('id') + '_search');
2112
+
2113
+ this.search.prev()
2114
+ .text($("label[for='" + this.focusser.attr('id') + "']").text())
2115
+ .attr('for', this.search.attr('id'));
2116
+
2117
+ this.search.on("keydown", this.bind(function (e) {
2118
+ if (!this.isInterfaceEnabled()) return;
2119
+
2120
+ // filter 229 keyCodes (input method editor is processing key input)
2121
+ if (229 == e.keyCode) return;
2122
+
2123
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2124
+ // prevent the page from scrolling
2125
+ killEvent(e);
2126
+ return;
2127
+ }
2128
+
2129
+ switch (e.which) {
2130
+ case KEY.UP:
2131
+ case KEY.DOWN:
2132
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2133
+ killEvent(e);
2134
+ return;
2135
+ case KEY.ENTER:
2136
+ this.selectHighlighted();
2137
+ killEvent(e);
2138
+ return;
2139
+ case KEY.TAB:
2140
+ this.selectHighlighted({noFocus: true});
2141
+ return;
2142
+ case KEY.ESC:
2143
+ this.cancel(e);
2144
+ killEvent(e);
2145
+ return;
2146
+ }
2147
+ }));
2148
+
2149
+ this.search.on("blur", this.bind(function(e) {
2150
+ // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
2151
+ // without this the search field loses focus which is annoying
2152
+ if (document.activeElement === this.body.get(0)) {
2153
+ window.setTimeout(this.bind(function() {
2154
+ if (this.opened()) {
2155
+ this.search.focus();
2156
+ }
2157
+ }), 0);
2158
+ }
2159
+ }));
2160
+
2161
+ this.focusser.on("keydown", this.bind(function (e) {
2162
+ if (!this.isInterfaceEnabled()) return;
2163
+
2164
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
2165
+ return;
2166
+ }
2167
+
2168
+ if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
2169
+ killEvent(e);
2170
+ return;
2171
+ }
2172
+
2173
+ if (e.which == KEY.DOWN || e.which == KEY.UP
2174
+ || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
2175
+
2176
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
2177
+
2178
+ this.open();
2179
+ killEvent(e);
2180
+ return;
2181
+ }
2182
+
2183
+ if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
2184
+ if (this.opts.allowClear) {
2185
+ this.clear();
2186
+ }
2187
+ killEvent(e);
2188
+ return;
2189
+ }
2190
+ }));
2191
+
2192
+
2193
+ installKeyUpChangeEvent(this.focusser);
2194
+ this.focusser.on("keyup-change input", this.bind(function(e) {
2195
+ if (this.opts.minimumResultsForSearch >= 0) {
2196
+ e.stopPropagation();
2197
+ if (this.opened()) return;
2198
+ this.open();
2199
+ }
2200
+ }));
2201
+
2202
+ selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
2203
+ if (!this.isInterfaceEnabled()) return;
2204
+ this.clear();
2205
+ killEventImmediately(e);
2206
+ this.close();
2207
+ this.selection.focus();
2208
+ }));
2209
+
2210
+ selection.on("mousedown touchstart", this.bind(function (e) {
2211
+ // Prevent IE from generating a click event on the body
2212
+ reinsertElement(selection);
2213
+
2214
+ if (!this.container.hasClass("select2-container-active")) {
2215
+ this.opts.element.trigger($.Event("select2-focus"));
2216
+ }
2217
+
2218
+ if (this.opened()) {
2219
+ this.close();
2220
+ } else if (this.isInterfaceEnabled()) {
2221
+ this.open();
2222
+ }
2223
+
2224
+ killEvent(e);
2225
+ }));
2226
+
2227
+ dropdown.on("mousedown touchstart", this.bind(function() {
2228
+ if (this.opts.shouldFocusInput(this)) {
2229
+ this.search.focus();
2230
+ }
2231
+ }));
2232
+
2233
+ selection.on("focus", this.bind(function(e) {
2234
+ killEvent(e);
2235
+ }));
2236
+
2237
+ this.focusser.on("focus", this.bind(function(){
2238
+ if (!this.container.hasClass("select2-container-active")) {
2239
+ this.opts.element.trigger($.Event("select2-focus"));
2240
+ }
2241
+ this.container.addClass("select2-container-active");
2242
+ })).on("blur", this.bind(function() {
2243
+ if (!this.opened()) {
2244
+ this.container.removeClass("select2-container-active");
2245
+ this.opts.element.trigger($.Event("select2-blur"));
2246
+ }
2247
+ }));
2248
+ this.search.on("focus", this.bind(function(){
2249
+ if (!this.container.hasClass("select2-container-active")) {
2250
+ this.opts.element.trigger($.Event("select2-focus"));
2251
+ }
2252
+ this.container.addClass("select2-container-active");
2253
+ }));
2254
+
2255
+ this.initContainerWidth();
2256
+ this.opts.element.addClass("select2-offscreen");
2257
+ this.setPlaceholder();
2258
+
2259
+ },
2260
+
2261
+ // single
2262
+ clear: function(triggerChange) {
2263
+ var data=this.selection.data("select2-data");
2264
+ if (data) { // guard against queued quick consecutive clicks
2265
+ var evt = $.Event("select2-clearing");
2266
+ this.opts.element.trigger(evt);
2267
+ if (evt.isDefaultPrevented()) {
2268
+ return;
2269
+ }
2270
+ var placeholderOption = this.getPlaceholderOption();
2271
+ this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
2272
+ this.selection.find(".select2-chosen").empty();
2273
+ this.selection.removeData("select2-data");
2274
+ this.setPlaceholder();
2275
+
2276
+ if (triggerChange !== false){
2277
+ this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
2278
+ this.triggerChange({removed:data});
2279
+ }
2280
+ }
2281
+ },
2282
+
2283
+ /**
2284
+ * Sets selection based on source element's value
2285
+ */
2286
+ // single
2287
+ initSelection: function () {
2288
+ var selected;
2289
+ if (this.isPlaceholderOptionSelected()) {
2290
+ this.updateSelection(null);
2291
+ this.close();
2292
+ this.setPlaceholder();
2293
+ } else {
2294
+ var self = this;
2295
+ this.opts.initSelection.call(null, this.opts.element, function(selected){
2296
+ if (selected !== undefined && selected !== null) {
2297
+ self.updateSelection(selected);
2298
+ self.close();
2299
+ self.setPlaceholder();
2300
+ self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val());
2301
+ }
2302
+ });
2303
+ }
2304
+ },
2305
+
2306
+ isPlaceholderOptionSelected: function() {
2307
+ var placeholderOption;
2308
+ if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered
2309
+ return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
2310
+ || (this.opts.element.val() === "")
2311
+ || (this.opts.element.val() === undefined)
2312
+ || (this.opts.element.val() === null);
2313
+ },
2314
+
2315
+ // single
2316
+ prepareOpts: function () {
2317
+ var opts = this.parent.prepareOpts.apply(this, arguments),
2318
+ self=this;
2319
+
2320
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
2321
+ // install the selection initializer
2322
+ opts.initSelection = function (element, callback) {
2323
+ var selected = element.find("option").filter(function() { return this.selected && !this.disabled });
2324
+ // a single select box always has a value, no need to null check 'selected'
2325
+ callback(self.optionToData(selected));
2326
+ };
2327
+ } else if ("data" in opts) {
2328
+ // install default initSelection when applied to hidden input and data is local
2329
+ opts.initSelection = opts.initSelection || function (element, callback) {
2330
+ var id = element.val();
2331
+ //search in data by id, storing the actual matching item
2332
+ var match = null;
2333
+ opts.query({
2334
+ matcher: function(term, text, el){
2335
+ var is_match = equal(id, opts.id(el));
2336
+ if (is_match) {
2337
+ match = el;
2338
+ }
2339
+ return is_match;
2340
+ },
2341
+ callback: !$.isFunction(callback) ? $.noop : function() {
2342
+ callback(match);
2343
+ }
2344
+ });
2345
+ };
2346
+ }
2347
+
2348
+ return opts;
2349
+ },
2350
+
2351
+ // single
2352
+ getPlaceholder: function() {
2353
+ // if a placeholder is specified on a single select without a valid placeholder option ignore it
2354
+ if (this.select) {
2355
+ if (this.getPlaceholderOption() === undefined) {
2356
+ return undefined;
2357
+ }
2358
+ }
2359
+
2360
+ return this.parent.getPlaceholder.apply(this, arguments);
2361
+ },
2362
+
2363
+ // single
2364
+ setPlaceholder: function () {
2365
+ var placeholder = this.getPlaceholder();
2366
+
2367
+ if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
2368
+
2369
+ // check for a placeholder option if attached to a select
2370
+ if (this.select && this.getPlaceholderOption() === undefined) return;
2371
+
2372
+ this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
2373
+
2374
+ this.selection.addClass("select2-default");
2375
+
2376
+ this.container.removeClass("select2-allowclear");
2377
+ }
2378
+ },
2379
+
2380
+ // single
2381
+ postprocessResults: function (data, initial, noHighlightUpdate) {
2382
+ var selected = 0, self = this, showSearchInput = true;
2383
+
2384
+ // find the selected element in the result list
2385
+
2386
+ this.findHighlightableChoices().each2(function (i, elm) {
2387
+ if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
2388
+ selected = i;
2389
+ return false;
2390
+ }
2391
+ });
2392
+
2393
+ // and highlight it
2394
+ if (noHighlightUpdate !== false) {
2395
+ if (initial === true && selected >= 0) {
2396
+ this.highlight(selected);
2397
+ } else {
2398
+ this.highlight(0);
2399
+ }
2400
+ }
2401
+
2402
+ // hide the search box if this is the first we got the results and there are enough of them for search
2403
+
2404
+ if (initial === true) {
2405
+ var min = this.opts.minimumResultsForSearch;
2406
+ if (min >= 0) {
2407
+ this.showSearch(countResults(data.results) >= min);
2408
+ }
2409
+ }
2410
+ },
2411
+
2412
+ // single
2413
+ showSearch: function(showSearchInput) {
2414
+ if (this.showSearchInput === showSearchInput) return;
2415
+
2416
+ this.showSearchInput = showSearchInput;
2417
+
2418
+ this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
2419
+ this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
2420
+ //add "select2-with-searchbox" to the container if search box is shown
2421
+ $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
2422
+ },
2423
+
2424
+ // single
2425
+ onSelect: function (data, options) {
2426
+
2427
+ if (!this.triggerSelect(data)) { return; }
2428
+
2429
+ var old = this.opts.element.val(),
2430
+ oldData = this.data();
2431
+
2432
+ this.opts.element.val(this.id(data));
2433
+ this.updateSelection(data);
2434
+
2435
+ this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
2436
+
2437
+ this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2438
+ this.close();
2439
+
2440
+ if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) {
2441
+ this.focusser.focus();
2442
+ }
2443
+
2444
+ if (!equal(old, this.id(data))) {
2445
+ this.triggerChange({ added: data, removed: oldData });
2446
+ }
2447
+ },
2448
+
2449
+ // single
2450
+ updateSelection: function (data) {
2451
+
2452
+ var container=this.selection.find(".select2-chosen"), formatted, cssClass;
2453
+
2454
+ this.selection.data("select2-data", data);
2455
+
2456
+ container.empty();
2457
+ if (data !== null) {
2458
+ formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
2459
+ }
2460
+ if (formatted !== undefined) {
2461
+ container.append(formatted);
2462
+ }
2463
+ cssClass=this.opts.formatSelectionCssClass(data, container);
2464
+ if (cssClass !== undefined) {
2465
+ container.addClass(cssClass);
2466
+ }
2467
+
2468
+ this.selection.removeClass("select2-default");
2469
+
2470
+ if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
2471
+ this.container.addClass("select2-allowclear");
2472
+ }
2473
+ },
2474
+
2475
+ // single
2476
+ val: function () {
2477
+ var val,
2478
+ triggerChange = false,
2479
+ data = null,
2480
+ self = this,
2481
+ oldData = this.data();
2482
+
2483
+ if (arguments.length === 0) {
2484
+ return this.opts.element.val();
2485
+ }
2486
+
2487
+ val = arguments[0];
2488
+
2489
+ if (arguments.length > 1) {
2490
+ triggerChange = arguments[1];
2491
+ }
2492
+
2493
+ if (this.select) {
2494
+ this.select
2495
+ .val(val)
2496
+ .find("option").filter(function() { return this.selected }).each2(function (i, elm) {
2497
+ data = self.optionToData(elm);
2498
+ return false;
2499
+ });
2500
+ this.updateSelection(data);
2501
+ this.setPlaceholder();
2502
+ if (triggerChange) {
2503
+ this.triggerChange({added: data, removed:oldData});
2504
+ }
2505
+ } else {
2506
+ // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
2507
+ if (!val && val !== 0) {
2508
+ this.clear(triggerChange);
2509
+ return;
2510
+ }
2511
+ if (this.opts.initSelection === undefined) {
2512
+ throw new Error("cannot call val() if initSelection() is not defined");
2513
+ }
2514
+ this.opts.element.val(val);
2515
+ this.opts.initSelection(this.opts.element, function(data){
2516
+ self.opts.element.val(!data ? "" : self.id(data));
2517
+ self.updateSelection(data);
2518
+ self.setPlaceholder();
2519
+ if (triggerChange) {
2520
+ self.triggerChange({added: data, removed:oldData});
2521
+ }
2522
+ });
2523
+ }
2524
+ },
2525
+
2526
+ // single
2527
+ clearSearch: function () {
2528
+ this.search.val("");
2529
+ this.focusser.val("");
2530
+ },
2531
+
2532
+ // single
2533
+ data: function(value) {
2534
+ var data,
2535
+ triggerChange = false;
2536
+
2537
+ if (arguments.length === 0) {
2538
+ data = this.selection.data("select2-data");
2539
+ if (data == undefined) data = null;
2540
+ return data;
2541
+ } else {
2542
+ if (arguments.length > 1) {
2543
+ triggerChange = arguments[1];
2544
+ }
2545
+ if (!value) {
2546
+ this.clear(triggerChange);
2547
+ } else {
2548
+ data = this.data();
2549
+ this.opts.element.val(!value ? "" : this.id(value));
2550
+ this.updateSelection(value);
2551
+ if (triggerChange) {
2552
+ this.triggerChange({added: value, removed:data});
2553
+ }
2554
+ }
2555
+ }
2556
+ }
2557
+ });
2558
+
2559
+ MultiSelect2 = clazz(AbstractSelect2, {
2560
+
2561
+ // multi
2562
+ createContainer: function () {
2563
+ var container = $(document.createElement("div")).attr({
2564
+ "class": "select2-container select2-container-multi"
2565
+ }).html([
2566
+ "<ul class='select2-choices'>",
2567
+ " <li class='select2-search-field'>",
2568
+ " <label for='' class='select2-offscreen'></label>",
2569
+ " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
2570
+ " </li>",
2571
+ "</ul>",
2572
+ "<div class='select2-drop select2-drop-multi select2-display-none'>",
2573
+ " <ul class='select2-results'>",
2574
+ " </ul>",
2575
+ "</div>"].join(""));
2576
+ return container;
2577
+ },
2578
+
2579
+ // multi
2580
+ prepareOpts: function () {
2581
+ var opts = this.parent.prepareOpts.apply(this, arguments),
2582
+ self=this;
2583
+
2584
+ // TODO validate placeholder is a string if specified
2585
+
2586
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
2587
+ // install the selection initializer
2588
+ opts.initSelection = function (element, callback) {
2589
+
2590
+ var data = [];
2591
+
2592
+ element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) {
2593
+ data.push(self.optionToData(elm));
2594
+ });
2595
+ callback(data);
2596
+ };
2597
+ } else if ("data" in opts) {
2598
+ // install default initSelection when applied to hidden input and data is local
2599
+ opts.initSelection = opts.initSelection || function (element, callback) {
2600
+ var ids = splitVal(element.val(), opts.separator);
2601
+ //search in data by array of ids, storing matching items in a list
2602
+ var matches = [];
2603
+ opts.query({
2604
+ matcher: function(term, text, el){
2605
+ var is_match = $.grep(ids, function(id) {
2606
+ return equal(id, opts.id(el));
2607
+ }).length;
2608
+ if (is_match) {
2609
+ matches.push(el);
2610
+ }
2611
+ return is_match;
2612
+ },
2613
+ callback: !$.isFunction(callback) ? $.noop : function() {
2614
+ // reorder matches based on the order they appear in the ids array because right now
2615
+ // they are in the order in which they appear in data array
2616
+ var ordered = [];
2617
+ for (var i = 0; i < ids.length; i++) {
2618
+ var id = ids[i];
2619
+ for (var j = 0; j < matches.length; j++) {
2620
+ var match = matches[j];
2621
+ if (equal(id, opts.id(match))) {
2622
+ ordered.push(match);
2623
+ matches.splice(j, 1);
2624
+ break;
2625
+ }
2626
+ }
2627
+ }
2628
+ callback(ordered);
2629
+ }
2630
+ });
2631
+ };
2632
+ }
2633
+
2634
+ return opts;
2635
+ },
2636
+
2637
+ // multi
2638
+ selectChoice: function (choice) {
2639
+
2640
+ var selected = this.container.find(".select2-search-choice-focus");
2641
+ if (selected.length && choice && choice[0] == selected[0]) {
2642
+
2643
+ } else {
2644
+ if (selected.length) {
2645
+ this.opts.element.trigger("choice-deselected", selected);
2646
+ }
2647
+ selected.removeClass("select2-search-choice-focus");
2648
+ if (choice && choice.length) {
2649
+ this.close();
2650
+ choice.addClass("select2-search-choice-focus");
2651
+ this.opts.element.trigger("choice-selected", choice);
2652
+ }
2653
+ }
2654
+ },
2655
+
2656
+ // multi
2657
+ destroy: function() {
2658
+ $("label[for='" + this.search.attr('id') + "']")
2659
+ .attr('for', this.opts.element.attr("id"));
2660
+ this.parent.destroy.apply(this, arguments);
2661
+
2662
+ cleanupJQueryElements.call(this,
2663
+ "searchContainer",
2664
+ "selection"
2665
+ );
2666
+ },
2667
+
2668
+ // multi
2669
+ initContainer: function () {
2670
+
2671
+ var selector = ".select2-choices", selection;
2672
+
2673
+ this.searchContainer = this.container.find(".select2-search-field");
2674
+ this.selection = selection = this.container.find(selector);
2675
+
2676
+ var _this = this;
2677
+ this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
2678
+ //killEvent(e);
2679
+ _this.search[0].focus();
2680
+ _this.selectChoice($(this));
2681
+ });
2682
+
2683
+ // rewrite labels from original element to focusser
2684
+ this.search.attr("id", "s2id_autogen"+nextUid());
2685
+
2686
+ this.search.prev()
2687
+ .text($("label[for='" + this.opts.element.attr("id") + "']").text())
2688
+ .attr('for', this.search.attr('id'));
2689
+
2690
+ this.search.on("input paste", this.bind(function() {
2691
+ if (this.search.attr('placeholder') && this.search.val().length == 0) return;
2692
+ if (!this.isInterfaceEnabled()) return;
2693
+ if (!this.opened()) {
2694
+ this.open();
2695
+ }
2696
+ }));
2697
+
2698
+ this.search.attr("tabindex", this.elementTabIndex);
2699
+
2700
+ this.keydowns = 0;
2701
+ this.search.on("keydown", this.bind(function (e) {
2702
+ if (!this.isInterfaceEnabled()) return;
2703
+
2704
+ ++this.keydowns;
2705
+ var selected = selection.find(".select2-search-choice-focus");
2706
+ var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
2707
+ var next = selected.next(".select2-search-choice:not(.select2-locked)");
2708
+ var pos = getCursorInfo(this.search);
2709
+
2710
+ if (selected.length &&
2711
+ (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
2712
+ var selectedChoice = selected;
2713
+ if (e.which == KEY.LEFT && prev.length) {
2714
+ selectedChoice = prev;
2715
+ }
2716
+ else if (e.which == KEY.RIGHT) {
2717
+ selectedChoice = next.length ? next : null;
2718
+ }
2719
+ else if (e.which === KEY.BACKSPACE) {
2720
+ if (this.unselect(selected.first())) {
2721
+ this.search.width(10);
2722
+ selectedChoice = prev.length ? prev : next;
2723
+ }
2724
+ } else if (e.which == KEY.DELETE) {
2725
+ if (this.unselect(selected.first())) {
2726
+ this.search.width(10);
2727
+ selectedChoice = next.length ? next : null;
2728
+ }
2729
+ } else if (e.which == KEY.ENTER) {
2730
+ selectedChoice = null;
2731
+ }
2732
+
2733
+ this.selectChoice(selectedChoice);
2734
+ killEvent(e);
2735
+ if (!selectedChoice || !selectedChoice.length) {
2736
+ this.open();
2737
+ }
2738
+ return;
2739
+ } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
2740
+ || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
2741
+
2742
+ this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
2743
+ killEvent(e);
2744
+ return;
2745
+ } else {
2746
+ this.selectChoice(null);
2747
+ }
2748
+
2749
+ if (this.opened()) {
2750
+ switch (e.which) {
2751
+ case KEY.UP:
2752
+ case KEY.DOWN:
2753
+ this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
2754
+ killEvent(e);
2755
+ return;
2756
+ case KEY.ENTER:
2757
+ this.selectHighlighted();
2758
+ killEvent(e);
2759
+ return;
2760
+ case KEY.TAB:
2761
+ this.selectHighlighted({noFocus:true});
2762
+ this.close();
2763
+ return;
2764
+ case KEY.ESC:
2765
+ this.cancel(e);
2766
+ killEvent(e);
2767
+ return;
2768
+ }
2769
+ }
2770
+
2771
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
2772
+ || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
2773
+ return;
2774
+ }
2775
+
2776
+ if (e.which === KEY.ENTER) {
2777
+ if (this.opts.openOnEnter === false) {
2778
+ return;
2779
+ } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2780
+ return;
2781
+ }
2782
+ }
2783
+
2784
+ this.open();
2785
+
2786
+ if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
2787
+ // prevent the page from scrolling
2788
+ killEvent(e);
2789
+ }
2790
+
2791
+ if (e.which === KEY.ENTER) {
2792
+ // prevent form from being submitted
2793
+ killEvent(e);
2794
+ }
2795
+
2796
+ }));
2797
+
2798
+ this.search.on("keyup", this.bind(function (e) {
2799
+ this.keydowns = 0;
2800
+ this.resizeSearch();
2801
+ })
2802
+ );
2803
+
2804
+ this.search.on("blur", this.bind(function(e) {
2805
+ this.container.removeClass("select2-container-active");
2806
+ this.search.removeClass("select2-focused");
2807
+ this.selectChoice(null);
2808
+ if (!this.opened()) this.clearSearch();
2809
+ e.stopImmediatePropagation();
2810
+ this.opts.element.trigger($.Event("select2-blur"));
2811
+ }));
2812
+
2813
+ this.container.on("click", selector, this.bind(function (e) {
2814
+ if (!this.isInterfaceEnabled()) return;
2815
+ if ($(e.target).closest(".select2-search-choice").length > 0) {
2816
+ // clicked inside a select2 search choice, do not open
2817
+ return;
2818
+ }
2819
+ this.selectChoice(null);
2820
+ this.clearPlaceholder();
2821
+ if (!this.container.hasClass("select2-container-active")) {
2822
+ this.opts.element.trigger($.Event("select2-focus"));
2823
+ }
2824
+ this.open();
2825
+ this.focusSearch();
2826
+ e.preventDefault();
2827
+ }));
2828
+
2829
+ this.container.on("focus", selector, this.bind(function () {
2830
+ if (!this.isInterfaceEnabled()) return;
2831
+ if (!this.container.hasClass("select2-container-active")) {
2832
+ this.opts.element.trigger($.Event("select2-focus"));
2833
+ }
2834
+ this.container.addClass("select2-container-active");
2835
+ this.dropdown.addClass("select2-drop-active");
2836
+ this.clearPlaceholder();
2837
+ }));
2838
+
2839
+ this.initContainerWidth();
2840
+ this.opts.element.addClass("select2-offscreen");
2841
+
2842
+ // set the placeholder if necessary
2843
+ this.clearSearch();
2844
+ },
2845
+
2846
+ // multi
2847
+ enableInterface: function() {
2848
+ if (this.parent.enableInterface.apply(this, arguments)) {
2849
+ this.search.prop("disabled", !this.isInterfaceEnabled());
2850
+ }
2851
+ },
2852
+
2853
+ // multi
2854
+ initSelection: function () {
2855
+ var data;
2856
+ if (this.opts.element.val() === "" && this.opts.element.text() === "") {
2857
+ this.updateSelection([]);
2858
+ this.close();
2859
+ // set the placeholder if necessary
2860
+ this.clearSearch();
2861
+ }
2862
+ if (this.select || this.opts.element.val() !== "") {
2863
+ var self = this;
2864
+ this.opts.initSelection.call(null, this.opts.element, function(data){
2865
+ if (data !== undefined && data !== null) {
2866
+ self.updateSelection(data);
2867
+ self.close();
2868
+ // set the placeholder if necessary
2869
+ self.clearSearch();
2870
+ }
2871
+ });
2872
+ }
2873
+ },
2874
+
2875
+ // multi
2876
+ clearSearch: function () {
2877
+ var placeholder = this.getPlaceholder(),
2878
+ maxWidth = this.getMaxSearchWidth();
2879
+
2880
+ if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
2881
+ this.search.val(placeholder).addClass("select2-default");
2882
+ // stretch the search box to full width of the container so as much of the placeholder is visible as possible
2883
+ // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944
2884
+ this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
2885
+ } else {
2886
+ this.search.val("").width(10);
2887
+ }
2888
+ },
2889
+
2890
+ // multi
2891
+ clearPlaceholder: function () {
2892
+ if (this.search.hasClass("select2-default")) {
2893
+ this.search.val("").removeClass("select2-default");
2894
+ }
2895
+ },
2896
+
2897
+ // multi
2898
+ opening: function () {
2899
+ this.clearPlaceholder(); // should be done before super so placeholder is not used to search
2900
+ this.resizeSearch();
2901
+
2902
+ this.parent.opening.apply(this, arguments);
2903
+
2904
+ this.focusSearch();
2905
+
2906
+ // initializes search's value with nextSearchTerm (if defined by user)
2907
+ // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
2908
+ if(this.search.val() === "") {
2909
+ if(this.nextSearchTerm != undefined){
2910
+ this.search.val(this.nextSearchTerm);
2911
+ this.search.select();
2912
+ }
2913
+ }
2914
+
2915
+ this.updateResults(true);
2916
+ if (this.opts.shouldFocusInput(this)) {
2917
+ this.search.focus();
2918
+ }
2919
+ this.opts.element.trigger($.Event("select2-open"));
2920
+ },
2921
+
2922
+ // multi
2923
+ close: function () {
2924
+ if (!this.opened()) return;
2925
+ this.parent.close.apply(this, arguments);
2926
+ },
2927
+
2928
+ // multi
2929
+ focus: function () {
2930
+ this.close();
2931
+ this.search.focus();
2932
+ },
2933
+
2934
+ // multi
2935
+ isFocused: function () {
2936
+ return this.search.hasClass("select2-focused");
2937
+ },
2938
+
2939
+ // multi
2940
+ updateSelection: function (data) {
2941
+ var ids = [], filtered = [], self = this;
2942
+
2943
+ // filter out duplicates
2944
+ $(data).each(function () {
2945
+ if (indexOf(self.id(this), ids) < 0) {
2946
+ ids.push(self.id(this));
2947
+ filtered.push(this);
2948
+ }
2949
+ });
2950
+ data = filtered;
2951
+
2952
+ this.selection.find(".select2-search-choice").remove();
2953
+ $(data).each(function () {
2954
+ self.addSelectedChoice(this);
2955
+ });
2956
+ self.postprocessResults();
2957
+ },
2958
+
2959
+ // multi
2960
+ tokenize: function() {
2961
+ var input = this.search.val();
2962
+ input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
2963
+ if (input != null && input != undefined) {
2964
+ this.search.val(input);
2965
+ if (input.length > 0) {
2966
+ this.open();
2967
+ }
2968
+ }
2969
+
2970
+ },
2971
+
2972
+ // multi
2973
+ onSelect: function (data, options) {
2974
+
2975
+ if (!this.triggerSelect(data) || data.text === "") { return; }
2976
+
2977
+ this.addSelectedChoice(data);
2978
+
2979
+ this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
2980
+
2981
+ // keep track of the search's value before it gets cleared
2982
+ this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
2983
+
2984
+ this.clearSearch();
2985
+ this.updateResults();
2986
+
2987
+ if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
2988
+
2989
+ if (this.opts.closeOnSelect) {
2990
+ this.close();
2991
+ this.search.width(10);
2992
+ } else {
2993
+ if (this.countSelectableResults()>0) {
2994
+ this.search.width(10);
2995
+ this.resizeSearch();
2996
+ if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
2997
+ // if we reached max selection size repaint the results so choices
2998
+ // are replaced with the max selection reached message
2999
+ this.updateResults(true);
3000
+ } else {
3001
+ // initializes search's value with nextSearchTerm and update search result
3002
+ if(this.nextSearchTerm != undefined){
3003
+ this.search.val(this.nextSearchTerm);
3004
+ this.updateResults();
3005
+ this.search.select();
3006
+ }
3007
+ }
3008
+ this.positionDropdown();
3009
+ } else {
3010
+ // if nothing left to select close
3011
+ this.close();
3012
+ this.search.width(10);
3013
+ }
3014
+ }
3015
+
3016
+ // since its not possible to select an element that has already been
3017
+ // added we do not need to check if this is a new element before firing change
3018
+ this.triggerChange({ added: data });
3019
+
3020
+ if (!options || !options.noFocus)
3021
+ this.focusSearch();
3022
+ },
3023
+
3024
+ // multi
3025
+ cancel: function () {
3026
+ this.close();
3027
+ this.focusSearch();
3028
+ },
3029
+
3030
+ addSelectedChoice: function (data) {
3031
+ var enableChoice = !data.locked,
3032
+ enabledItem = $(
3033
+ "<li class='select2-search-choice'>" +
3034
+ " <div></div>" +
3035
+ " <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
3036
+ "</li>"),
3037
+ disabledItem = $(
3038
+ "<li class='select2-search-choice select2-locked'>" +
3039
+ "<div></div>" +
3040
+ "</li>");
3041
+ var choice = enableChoice ? enabledItem : disabledItem,
3042
+ id = this.id(data),
3043
+ val = this.getVal(),
3044
+ formatted,
3045
+ cssClass;
3046
+
3047
+ formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
3048
+ if (formatted != undefined) {
3049
+ choice.find("div").replaceWith("<div>"+formatted+"</div>");
3050
+ }
3051
+ cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
3052
+ if (cssClass != undefined) {
3053
+ choice.addClass(cssClass);
3054
+ }
3055
+
3056
+ if(enableChoice){
3057
+ choice.find(".select2-search-choice-close")
3058
+ .on("mousedown", killEvent)
3059
+ .on("click dblclick", this.bind(function (e) {
3060
+ if (!this.isInterfaceEnabled()) return;
3061
+
3062
+ this.unselect($(e.target));
3063
+ this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
3064
+ killEvent(e);
3065
+ this.close();
3066
+ this.focusSearch();
3067
+ })).on("focus", this.bind(function () {
3068
+ if (!this.isInterfaceEnabled()) return;
3069
+ this.container.addClass("select2-container-active");
3070
+ this.dropdown.addClass("select2-drop-active");
3071
+ }));
3072
+ }
3073
+
3074
+ choice.data("select2-data", data);
3075
+ choice.insertBefore(this.searchContainer);
3076
+
3077
+ val.push(id);
3078
+ this.setVal(val);
3079
+ },
3080
+
3081
+ // multi
3082
+ unselect: function (selected) {
3083
+ var val = this.getVal(),
3084
+ data,
3085
+ index;
3086
+ selected = selected.closest(".select2-search-choice");
3087
+
3088
+ if (selected.length === 0) {
3089
+ throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
3090
+ }
3091
+
3092
+ data = selected.data("select2-data");
3093
+
3094
+ if (!data) {
3095
+ // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
3096
+ // and invoked on an element already removed
3097
+ return;
3098
+ }
3099
+
3100
+ var evt = $.Event("select2-removing");
3101
+ evt.val = this.id(data);
3102
+ evt.choice = data;
3103
+ this.opts.element.trigger(evt);
3104
+
3105
+ if (evt.isDefaultPrevented()) {
3106
+ return false;
3107
+ }
3108
+
3109
+ while((index = indexOf(this.id(data), val)) >= 0) {
3110
+ val.splice(index, 1);
3111
+ this.setVal(val);
3112
+ if (this.select) this.postprocessResults();
3113
+ }
3114
+
3115
+ selected.remove();
3116
+
3117
+ this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
3118
+ this.triggerChange({ removed: data });
3119
+
3120
+ return true;
3121
+ },
3122
+
3123
+ // multi
3124
+ postprocessResults: function (data, initial, noHighlightUpdate) {
3125
+ var val = this.getVal(),
3126
+ choices = this.results.find(".select2-result"),
3127
+ compound = this.results.find(".select2-result-with-children"),
3128
+ self = this;
3129
+
3130
+ choices.each2(function (i, choice) {
3131
+ var id = self.id(choice.data("select2-data"));
3132
+ if (indexOf(id, val) >= 0) {
3133
+ choice.addClass("select2-selected");
3134
+ // mark all children of the selected parent as selected
3135
+ choice.find(".select2-result-selectable").addClass("select2-selected");
3136
+ }
3137
+ });
3138
+
3139
+ compound.each2(function(i, choice) {
3140
+ // hide an optgroup if it doesn't have any selectable children
3141
+ if (!choice.is('.select2-result-selectable')
3142
+ && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
3143
+ choice.addClass("select2-selected");
3144
+ }
3145
+ });
3146
+
3147
+ if (this.highlight() == -1 && noHighlightUpdate !== false){
3148
+ self.highlight(0);
3149
+ }
3150
+
3151
+ //If all results are chosen render formatNoMatches
3152
+ if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
3153
+ if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
3154
+ if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
3155
+ this.results.append("<li class='select2-no-results'>" + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "</li>");
3156
+ }
3157
+ }
3158
+ }
3159
+
3160
+ },
3161
+
3162
+ // multi
3163
+ getMaxSearchWidth: function() {
3164
+ return this.selection.width() - getSideBorderPadding(this.search);
3165
+ },
3166
+
3167
+ // multi
3168
+ resizeSearch: function () {
3169
+ var minimumWidth, left, maxWidth, containerLeft, searchWidth,
3170
+ sideBorderPadding = getSideBorderPadding(this.search);
3171
+
3172
+ minimumWidth = measureTextWidth(this.search) + 10;
3173
+
3174
+ left = this.search.offset().left;
3175
+
3176
+ maxWidth = this.selection.width();
3177
+ containerLeft = this.selection.offset().left;
3178
+
3179
+ searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
3180
+
3181
+ if (searchWidth < minimumWidth) {
3182
+ searchWidth = maxWidth - sideBorderPadding;
3183
+ }
3184
+
3185
+ if (searchWidth < 40) {
3186
+ searchWidth = maxWidth - sideBorderPadding;
3187
+ }
3188
+
3189
+ if (searchWidth <= 0) {
3190
+ searchWidth = minimumWidth;
3191
+ }
3192
+
3193
+ this.search.width(Math.floor(searchWidth));
3194
+ },
3195
+
3196
+ // multi
3197
+ getVal: function () {
3198
+ var val;
3199
+ if (this.select) {
3200
+ val = this.select.val();
3201
+ return val === null ? [] : val;
3202
+ } else {
3203
+ val = this.opts.element.val();
3204
+ return splitVal(val, this.opts.separator);
3205
+ }
3206
+ },
3207
+
3208
+ // multi
3209
+ setVal: function (val) {
3210
+ var unique;
3211
+ if (this.select) {
3212
+ this.select.val(val);
3213
+ } else {
3214
+ unique = [];
3215
+ // filter out duplicates
3216
+ $(val).each(function () {
3217
+ if (indexOf(this, unique) < 0) unique.push(this);
3218
+ });
3219
+ this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
3220
+ }
3221
+ },
3222
+
3223
+ // multi
3224
+ buildChangeDetails: function (old, current) {
3225
+ var current = current.slice(0),
3226
+ old = old.slice(0);
3227
+
3228
+ // remove intersection from each array
3229
+ for (var i = 0; i < current.length; i++) {
3230
+ for (var j = 0; j < old.length; j++) {
3231
+ if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
3232
+ current.splice(i, 1);
3233
+ if(i>0){
3234
+ i--;
3235
+ }
3236
+ old.splice(j, 1);
3237
+ j--;
3238
+ }
3239
+ }
3240
+ }
3241
+
3242
+ return {added: current, removed: old};
3243
+ },
3244
+
3245
+
3246
+ // multi
3247
+ val: function (val, triggerChange) {
3248
+ var oldData, self=this;
3249
+
3250
+ if (arguments.length === 0) {
3251
+ return this.getVal();
3252
+ }
3253
+
3254
+ oldData=this.data();
3255
+ if (!oldData.length) oldData=[];
3256
+
3257
+ // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
3258
+ if (!val && val !== 0) {
3259
+ this.opts.element.val("");
3260
+ this.updateSelection([]);
3261
+ this.clearSearch();
3262
+ if (triggerChange) {
3263
+ this.triggerChange({added: this.data(), removed: oldData});
3264
+ }
3265
+ return;
3266
+ }
3267
+
3268
+ // val is a list of ids
3269
+ this.setVal(val);
3270
+
3271
+ if (this.select) {
3272
+ this.opts.initSelection(this.select, this.bind(this.updateSelection));
3273
+ if (triggerChange) {
3274
+ this.triggerChange(this.buildChangeDetails(oldData, this.data()));
3275
+ }
3276
+ } else {
3277
+ if (this.opts.initSelection === undefined) {
3278
+ throw new Error("val() cannot be called if initSelection() is not defined");
3279
+ }
3280
+
3281
+ this.opts.initSelection(this.opts.element, function(data){
3282
+ var ids=$.map(data, self.id);
3283
+ self.setVal(ids);
3284
+ self.updateSelection(data);
3285
+ self.clearSearch();
3286
+ if (triggerChange) {
3287
+ self.triggerChange(self.buildChangeDetails(oldData, self.data()));
3288
+ }
3289
+ });
3290
+ }
3291
+ this.clearSearch();
3292
+ },
3293
+
3294
+ // multi
3295
+ onSortStart: function() {
3296
+ if (this.select) {
3297
+ throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
3298
+ }
3299
+
3300
+ // collapse search field into 0 width so its container can be collapsed as well
3301
+ this.search.width(0);
3302
+ // hide the container
3303
+ this.searchContainer.hide();
3304
+ },
3305
+
3306
+ // multi
3307
+ onSortEnd:function() {
3308
+
3309
+ var val=[], self=this;
3310
+
3311
+ // show search and move it to the end of the list
3312
+ this.searchContainer.show();
3313
+ // make sure the search container is the last item in the list
3314
+ this.searchContainer.appendTo(this.searchContainer.parent());
3315
+ // since we collapsed the width in dragStarted, we resize it here
3316
+ this.resizeSearch();
3317
+
3318
+ // update selection
3319
+ this.selection.find(".select2-search-choice").each(function() {
3320
+ val.push(self.opts.id($(this).data("select2-data")));
3321
+ });
3322
+ this.setVal(val);
3323
+ this.triggerChange();
3324
+ },
3325
+
3326
+ // multi
3327
+ data: function(values, triggerChange) {
3328
+ var self=this, ids, old;
3329
+ if (arguments.length === 0) {
3330
+ return this.selection
3331
+ .children(".select2-search-choice")
3332
+ .map(function() { return $(this).data("select2-data"); })
3333
+ .get();
3334
+ } else {
3335
+ old = this.data();
3336
+ if (!values) { values = []; }
3337
+ ids = $.map(values, function(e) { return self.opts.id(e); });
3338
+ this.setVal(ids);
3339
+ this.updateSelection(values);
3340
+ this.clearSearch();
3341
+ if (triggerChange) {
3342
+ this.triggerChange(this.buildChangeDetails(old, this.data()));
3343
+ }
3344
+ }
3345
+ }
3346
+ });
3347
+
3348
+ $.fn.select2 = function () {
3349
+
3350
+ var args = Array.prototype.slice.call(arguments, 0),
3351
+ opts,
3352
+ select2,
3353
+ method, value, multiple,
3354
+ allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
3355
+ valueMethods = ["opened", "isFocused", "container", "dropdown"],
3356
+ propertyMethods = ["val", "data"],
3357
+ methodsMap = { search: "externalSearch" };
3358
+
3359
+ this.each(function () {
3360
+ if (args.length === 0 || typeof(args[0]) === "object") {
3361
+ opts = args.length === 0 ? {} : $.extend({}, args[0]);
3362
+ opts.element = $(this);
3363
+
3364
+ if (opts.element.get(0).tagName.toLowerCase() === "select") {
3365
+ multiple = opts.element.prop("multiple");
3366
+ } else {
3367
+ multiple = opts.multiple || false;
3368
+ if ("tags" in opts) {opts.multiple = multiple = true;}
3369
+ }
3370
+
3371
+ select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single();
3372
+ select2.init(opts);
3373
+ } else if (typeof(args[0]) === "string") {
3374
+
3375
+ if (indexOf(args[0], allowedMethods) < 0) {
3376
+ throw "Unknown method: " + args[0];
3377
+ }
3378
+
3379
+ value = undefined;
3380
+ select2 = $(this).data("select2");
3381
+ if (select2 === undefined) return;
3382
+
3383
+ method=args[0];
3384
+
3385
+ if (method === "container") {
3386
+ value = select2.container;
3387
+ } else if (method === "dropdown") {
3388
+ value = select2.dropdown;
3389
+ } else {
3390
+ if (methodsMap[method]) method = methodsMap[method];
3391
+
3392
+ value = select2[method].apply(select2, args.slice(1));
3393
+ }
3394
+ if (indexOf(args[0], valueMethods) >= 0
3395
+ || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) {
3396
+ return false; // abort the iteration, ready to return first matched value
3397
+ }
3398
+ } else {
3399
+ throw "Invalid arguments to select2 plugin: " + args;
3400
+ }
3401
+ });
3402
+ return (value === undefined) ? this : value;
3403
+ };
3404
+
3405
+ // plugin defaults, accessible to users
3406
+ $.fn.select2.defaults = {
3407
+ width: "copy",
3408
+ loadMorePadding: 0,
3409
+ closeOnSelect: true,
3410
+ openOnEnter: true,
3411
+ containerCss: {},
3412
+ dropdownCss: {},
3413
+ containerCssClass: "",
3414
+ dropdownCssClass: "",
3415
+ formatResult: function(result, container, query, escapeMarkup) {
3416
+ var markup=[];
3417
+ markMatch(result.text, query.term, markup, escapeMarkup);
3418
+ return markup.join("");
3419
+ },
3420
+ formatSelection: function (data, container, escapeMarkup) {
3421
+ return data ? escapeMarkup(data.text) : undefined;
3422
+ },
3423
+ sortResults: function (results, container, query) {
3424
+ return results;
3425
+ },
3426
+ formatResultCssClass: function(data) {return data.css;},
3427
+ formatSelectionCssClass: function(data, container) {return undefined;},
3428
+ minimumResultsForSearch: 0,
3429
+ minimumInputLength: 0,
3430
+ maximumInputLength: null,
3431
+ maximumSelectionSize: 0,
3432
+ id: function (e) { return e == undefined ? null : e.id; },
3433
+ matcher: function(term, text) {
3434
+ return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
3435
+ },
3436
+ separator: ",",
3437
+ tokenSeparators: [],
3438
+ tokenizer: defaultTokenizer,
3439
+ escapeMarkup: defaultEscapeMarkup,
3440
+ blurOnChange: false,
3441
+ selectOnBlur: false,
3442
+ adaptContainerCssClass: function(c) { return c; },
3443
+ adaptDropdownCssClass: function(c) { return null; },
3444
+ nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; },
3445
+ searchInputPlaceholder: '',
3446
+ createSearchChoicePosition: 'top',
3447
+ shouldFocusInput: function (instance) {
3448
+ // Attempt to detect touch devices
3449
+ var supportsTouchEvents = (('ontouchstart' in window) ||
3450
+ (navigator.msMaxTouchPoints > 0));
3451
+
3452
+ // Only devices which support touch events should be special cased
3453
+ if (!supportsTouchEvents) {
3454
+ return true;
3455
+ }
3456
+
3457
+ // Never focus the input if search is disabled
3458
+ if (instance.opts.minimumResultsForSearch < 0) {
3459
+ return false;
3460
+ }
3461
+
3462
+ return true;
3463
+ }
3464
+ };
3465
+
3466
+ $.fn.select2.locales = [];
3467
+
3468
+ $.fn.select2.locales['en'] = {
3469
+ formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; },
3470
+ formatNoMatches: function () { return "No matches found"; },
3471
+ formatAjaxError: function (jqXHR, textStatus, errorThrown) { return "Loading failed"; },
3472
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1 ? "" : "s"); },
3473
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },
3474
+ formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
3475
+ formatLoadMore: function (pageNumber) { return "Loading more results…"; },
3476
+ formatSearching: function () { return "Searching…"; },
3477
+ };
3478
+
3479
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['en']);
3480
+
3481
+ $.fn.select2.ajaxDefaults = {
3482
+ transport: $.ajax,
3483
+ params: {
3484
+ type: "GET",
3485
+ cache: false,
3486
+ dataType: "json"
3487
+ }
3488
+ };
3489
+
3490
+ // exports
3491
+ window.Select2 = {
3492
+ query: {
3493
+ ajax: ajax,
3494
+ local: local,
3495
+ tags: tags
3496
+ }, util: {
3497
+ debounce: debounce,
3498
+ markMatch: markMatch,
3499
+ escapeMarkup: defaultEscapeMarkup,
3500
+ stripDiacritics: stripDiacritics
3501
+ }, "class": {
3502
+ "abstract": AbstractSelect2,
3503
+ "single": SingleSelect2,
3504
+ "multi": MultiSelect2
3505
+ }
3506
+ };
3507
+
3508
+ }(jQuery));
js/select2/select2.min.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Copyright 2014 Igor Vaynberg
3
+
4
+ Version: 3.5.1 Timestamp: Tue Jul 22 18:58:56 EDT 2014
5
+
6
+ This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
7
+ General Public License version 2 (the "GPL License"). You may choose either license to govern your
8
+ use of this software only upon the condition that you accept all of the terms of either the Apache
9
+ License or the GPL License.
10
+
11
+ You may obtain a copy of the Apache License and the GPL License at:
12
+
13
+ http://www.apache.org/licenses/LICENSE-2.0
14
+ http://www.gnu.org/licenses/gpl-2.0.html
15
+
16
+ Unless required by applicable law or agreed to in writing, software distributed under the Apache License
17
+ or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
18
+ either express or implied. See the Apache License and the GPL License for the specific language governing
19
+ permissions and limitations under the Apache License and the GPL License.
20
+ */
21
+ !function(a){"undefined"==typeof a.fn.each2&&a.extend(a.fn,{each2:function(b){for(var c=a([0]),d=-1,e=this.length;++d<e&&(c.context=c[0]=this[d])&&b.call(c[0],d,c)!==!1;);return this}})}(jQuery),function(a,b){"use strict";function n(b){var c=a(document.createTextNode(""));b.before(c),c.before(b),c.remove()}function o(a){function b(a){return m[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function p(a,b){for(var c=0,d=b.length;d>c;c+=1)if(r(a,b[c]))return c;return-1}function q(){var b=a(l);b.appendTo("body");var c={width:b.width()-b[0].clientWidth,height:b.height()-b[0].clientHeight};return b.remove(),c}function r(a,c){return a===c?!0:a===b||c===b?!1:null===a||null===c?!1:a.constructor===String?a+""==c+"":c.constructor===String?c+""==a+"":!1}function s(b,c){var d,e,f;if(null===b||b.length<1)return[];for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d}function t(a){return a.outerWidth(!1)-a.width()}function u(c){var d="keyup-change-value";c.on("keydown",function(){a.data(c,d)===b&&a.data(c,d,c.val())}),c.on("keyup",function(){var e=a.data(c,d);e!==b&&c.val()!==e&&(a.removeData(c,d),c.trigger("keyup-change"))})}function v(c){c.on("mousemove",function(c){var d=i;(d===b||d.x!==c.pageX||d.y!==c.pageY)&&a(c.target).trigger("mousemove-filtered",c)})}function w(a,c,d){d=d||b;var e;return function(){var b=arguments;window.clearTimeout(e),e=window.setTimeout(function(){c.apply(d,b)},a)}}function x(a,b){var c=w(a,function(a){b.trigger("scroll-debounced",a)});b.on("scroll",function(a){p(a.target,b.get())>=0&&c(a)})}function y(a){a[0]!==document.activeElement&&window.setTimeout(function(){var d,b=a[0],c=a.val().length;a.focus();var e=b.offsetWidth>0||b.offsetHeight>0;e&&b===document.activeElement&&(b.setSelectionRange?b.setSelectionRange(c,c):b.createTextRange&&(d=b.createTextRange(),d.collapse(!1),d.select()))},0)}function z(b){b=a(b)[0];var c=0,d=0;if("selectionStart"in b)c=b.selectionStart,d=b.selectionEnd-c;else if("selection"in document){b.focus();var e=document.selection.createRange();d=document.selection.createRange().text.length,e.moveStart("character",-b.value.length),c=e.text.length-d}return{offset:c,length:d}}function A(a){a.preventDefault(),a.stopPropagation()}function B(a){a.preventDefault(),a.stopImmediatePropagation()}function C(b){if(!h){var c=b[0].currentStyle||window.getComputedStyle(b[0],null);h=a(document.createElement("div")).css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),h.attr("class","select2-sizer"),a("body").append(h)}return h.text(b.val()),h.width()}function D(b,c,d){var e,g,f=[];e=a.trim(b.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each2(function(){0===this.indexOf("select2-")&&f.push(this)})),e=a.trim(c.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each2(function(){0!==this.indexOf("select2-")&&(g=d(this),g&&f.push(g))})),b.attr("class",f.join(" "))}function E(a,b,c,d){var e=o(a.toUpperCase()).indexOf(o(b.toUpperCase())),f=b.length;return 0>e?(c.push(d(a)),void 0):(c.push(d(a.substring(0,e))),c.push("<span class='select2-match'>"),c.push(d(a.substring(e,e+f))),c.push("</span>"),c.push(d(a.substring(e+f,a.length))),void 0)}function F(a){var b={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};return String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})}function G(c){var d,e=null,f=c.quietMillis||100,g=c.url,h=this;return function(i){window.clearTimeout(d),d=window.setTimeout(function(){var d=c.data,f=g,j=c.transport||a.fn.select2.ajaxDefaults.transport,k={type:c.type||"GET",cache:c.cache||!1,jsonpCallback:c.jsonpCallback||b,dataType:c.dataType||"json"},l=a.extend({},a.fn.select2.ajaxDefaults.params,k);d=d?d.call(h,i.term,i.page,i.context):null,f="function"==typeof f?f.call(h,i.term,i.page,i.context):f,e&&"function"==typeof e.abort&&e.abort(),c.params&&(a.isFunction(c.params)?a.extend(l,c.params.call(h)):a.extend(l,c.params)),a.extend(l,{url:f,dataType:c.dataType,data:d,success:function(a){var b=c.results(a,i.page,i);i.callback(b)},error:function(a,b,c){var d={hasError:!0,jqXHR:a,textStatus:b,errorThrown:c};i.callback(d)}}),e=j.call(h,l)},f)}}function H(b){var d,e,c=b,f=function(a){return""+a.text};a.isArray(c)&&(e=c,c={results:e}),a.isFunction(c)===!1&&(e=c,c=function(){return e});var g=c();return g.text&&(f=g.text,a.isFunction(f)||(d=g.text,f=function(a){return a[d]})),function(b){var g,d=b.term,e={results:[]};return""===d?(b.callback(c()),void 0):(g=function(c,e){var h,i;if(c=c[0],c.children){h={};for(i in c)c.hasOwnProperty(i)&&(h[i]=c[i]);h.children=[],a(c.children).each2(function(a,b){g(b,h.children)}),(h.children.length||b.matcher(d,f(h),c))&&e.push(h)}else b.matcher(d,f(c),c)&&e.push(c)},a(c().results).each2(function(a,b){g(b,e.results)}),b.callback(e),void 0)}}function I(c){var d=a.isFunction(c);return function(e){var f=e.term,g={results:[]},h=d?c(e):c;a.isArray(h)&&(a(h).each(function(){var a=this.text!==b,c=a?this.text:this;(""===f||e.matcher(f,c))&&g.results.push(a?this:{id:this,text:this})}),e.callback(g))}}function J(b,c){if(a.isFunction(b))return!0;if(!b)return!1;if("string"==typeof b)return!0;throw new Error(c+" must be a string, function, or falsy value")}function K(b,c){if(a.isFunction(b)){var d=Array.prototype.slice.call(arguments,2);return b.apply(c,d)}return b}function L(b){var c=0;return a.each(b,function(a,b){b.children?c+=L(b.children):c++}),c}function M(a,c,d,e){var h,i,j,k,l,f=a,g=!1;if(!e.createSearchChoice||!e.tokenSeparators||e.tokenSeparators.length<1)return b;for(;;){for(i=-1,j=0,k=e.tokenSeparators.length;k>j&&(l=e.tokenSeparators[j],i=a.indexOf(l),!(i>=0));j++);if(0>i)break;if(h=a.substring(0,i),a=a.substring(i+l.length),h.length>0&&(h=e.createSearchChoice.call(this,h,c),h!==b&&null!==h&&e.id(h)!==b&&null!==e.id(h))){for(g=!1,j=0,k=c.length;k>j;j++)if(r(e.id(h),e.id(c[j]))){g=!0;break}g||d(h)}}return f!==a?a:void 0}function N(){var b=this;a.each(arguments,function(a,c){b[c].remove(),b[c]=null})}function O(b,c){var d=function(){};return d.prototype=new b,d.prototype.constructor=d,d.prototype.parent=b.prototype,d.prototype=a.extend(d.prototype,c),d}if(window.Select2===b){var c,d,e,f,g,h,j,k,i={x:0,y:0},c={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(a){switch(a=a.which?a.which:a){case c.LEFT:case c.RIGHT:case c.UP:case c.DOWN:return!0}return!1},isControl:function(a){var b=a.which;switch(b){case c.SHIFT:case c.CTRL:case c.ALT:return!0}return a.metaKey?!0:!1},isFunctionKey:function(a){return a=a.which?a.which:a,a>=112&&123>=a}},l="<div class='select2-measure-scrollbar'></div>",m={"\u24b6":"A","\uff21":"A","\xc0":"A","\xc1":"A","\xc2":"A","\u1ea6":"A","\u1ea4":"A","\u1eaa":"A","\u1ea8":"A","\xc3":"A","\u0100":"A","\u0102":"A","\u1eb0":"A","\u1eae":"A","\u1eb4":"A","\u1eb2":"A","\u0226":"A","\u01e0":"A","\xc4":"A","\u01de":"A","\u1ea2":"A","\xc5":"A","\u01fa":"A","\u01cd":"A","\u0200":"A","\u0202":"A","\u1ea0":"A","\u1eac":"A","\u1eb6":"A","\u1e00":"A","\u0104":"A","\u023a":"A","\u2c6f":"A","\ua732":"AA","\xc6":"AE","\u01fc":"AE","\u01e2":"AE","\ua734":"AO","\ua736":"AU","\ua738":"AV","\ua73a":"AV","\ua73c":"AY","\u24b7":"B","\uff22":"B","\u1e02":"B","\u1e04":"B","\u1e06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24b8":"C","\uff23":"C","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\xc7":"C","\u1e08":"C","\u0187":"C","\u023b":"C","\ua73e":"C","\u24b9":"D","\uff24":"D","\u1e0a":"D","\u010e":"D","\u1e0c":"D","\u1e10":"D","\u1e12":"D","\u1e0e":"D","\u0110":"D","\u018b":"D","\u018a":"D","\u0189":"D","\ua779":"D","\u01f1":"DZ","\u01c4":"DZ","\u01f2":"Dz","\u01c5":"Dz","\u24ba":"E","\uff25":"E","\xc8":"E","\xc9":"E","\xca":"E","\u1ec0":"E","\u1ebe":"E","\u1ec4":"E","\u1ec2":"E","\u1ebc":"E","\u0112":"E","\u1e14":"E","\u1e16":"E","\u0114":"E","\u0116":"E","\xcb":"E","\u1eba":"E","\u011a":"E","\u0204":"E","\u0206":"E","\u1eb8":"E","\u1ec6":"E","\u0228":"E","\u1e1c":"E","\u0118":"E","\u1e18":"E","\u1e1a":"E","\u0190":"E","\u018e":"E","\u24bb":"F","\uff26":"F","\u1e1e":"F","\u0191":"F","\ua77b":"F","\u24bc":"G","\uff27":"G","\u01f4":"G","\u011c":"G","\u1e20":"G","\u011e":"G","\u0120":"G","\u01e6":"G","\u0122":"G","\u01e4":"G","\u0193":"G","\ua7a0":"G","\ua77d":"G","\ua77e":"G","\u24bd":"H","\uff28":"H","\u0124":"H","\u1e22":"H","\u1e26":"H","\u021e":"H","\u1e24":"H","\u1e28":"H","\u1e2a":"H","\u0126":"H","\u2c67":"H","\u2c75":"H","\ua78d":"H","\u24be":"I","\uff29":"I","\xcc":"I","\xcd":"I","\xce":"I","\u0128":"I","\u012a":"I","\u012c":"I","\u0130":"I","\xcf":"I","\u1e2e":"I","\u1ec8":"I","\u01cf":"I","\u0208":"I","\u020a":"I","\u1eca":"I","\u012e":"I","\u1e2c":"I","\u0197":"I","\u24bf":"J","\uff2a":"J","\u0134":"J","\u0248":"J","\u24c0":"K","\uff2b":"K","\u1e30":"K","\u01e8":"K","\u1e32":"K","\u0136":"K","\u1e34":"K","\u0198":"K","\u2c69":"K","\ua740":"K","\ua742":"K","\ua744":"K","\ua7a2":"K","\u24c1":"L","\uff2c":"L","\u013f":"L","\u0139":"L","\u013d":"L","\u1e36":"L","\u1e38":"L","\u013b":"L","\u1e3c":"L","\u1e3a":"L","\u0141":"L","\u023d":"L","\u2c62":"L","\u2c60":"L","\ua748":"L","\ua746":"L","\ua780":"L","\u01c7":"LJ","\u01c8":"Lj","\u24c2":"M","\uff2d":"M","\u1e3e":"M","\u1e40":"M","\u1e42":"M","\u2c6e":"M","\u019c":"M","\u24c3":"N","\uff2e":"N","\u01f8":"N","\u0143":"N","\xd1":"N","\u1e44":"N","\u0147":"N","\u1e46":"N","\u0145":"N","\u1e4a":"N","\u1e48":"N","\u0220":"N","\u019d":"N","\ua790":"N","\ua7a4":"N","\u01ca":"NJ","\u01cb":"Nj","\u24c4":"O","\uff2f":"O","\xd2":"O","\xd3":"O","\xd4":"O","\u1ed2":"O","\u1ed0":"O","\u1ed6":"O","\u1ed4":"O","\xd5":"O","\u1e4c":"O","\u022c":"O","\u1e4e":"O","\u014c":"O","\u1e50":"O","\u1e52":"O","\u014e":"O","\u022e":"O","\u0230":"O","\xd6":"O","\u022a":"O","\u1ece":"O","\u0150":"O","\u01d1":"O","\u020c":"O","\u020e":"O","\u01a0":"O","\u1edc":"O","\u1eda":"O","\u1ee0":"O","\u1ede":"O","\u1ee2":"O","\u1ecc":"O","\u1ed8":"O","\u01ea":"O","\u01ec":"O","\xd8":"O","\u01fe":"O","\u0186":"O","\u019f":"O","\ua74a":"O","\ua74c":"O","\u01a2":"OI","\ua74e":"OO","\u0222":"OU","\u24c5":"P","\uff30":"P","\u1e54":"P","\u1e56":"P","\u01a4":"P","\u2c63":"P","\ua750":"P","\ua752":"P","\ua754":"P","\u24c6":"Q","\uff31":"Q","\ua756":"Q","\ua758":"Q","\u024a":"Q","\u24c7":"R","\uff32":"R","\u0154":"R","\u1e58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1e5a":"R","\u1e5c":"R","\u0156":"R","\u1e5e":"R","\u024c":"R","\u2c64":"R","\ua75a":"R","\ua7a6":"R","\ua782":"R","\u24c8":"S","\uff33":"S","\u1e9e":"S","\u015a":"S","\u1e64":"S","\u015c":"S","\u1e60":"S","\u0160":"S","\u1e66":"S","\u1e62":"S","\u1e68":"S","\u0218":"S","\u015e":"S","\u2c7e":"S","\ua7a8":"S","\ua784":"S","\u24c9":"T","\uff34":"T","\u1e6a":"T","\u0164":"T","\u1e6c":"T","\u021a":"T","\u0162":"T","\u1e70":"T","\u1e6e":"T","\u0166":"T","\u01ac":"T","\u01ae":"T","\u023e":"T","\ua786":"T","\ua728":"TZ","\u24ca":"U","\uff35":"U","\xd9":"U","\xda":"U","\xdb":"U","\u0168":"U","\u1e78":"U","\u016a":"U","\u1e7a":"U","\u016c":"U","\xdc":"U","\u01db":"U","\u01d7":"U","\u01d5":"U","\u01d9":"U","\u1ee6":"U","\u016e":"U","\u0170":"U","\u01d3":"U","\u0214":"U","\u0216":"U","\u01af":"U","\u1eea":"U","\u1ee8":"U","\u1eee":"U","\u1eec":"U","\u1ef0":"U","\u1ee4":"U","\u1e72":"U","\u0172":"U","\u1e76":"U","\u1e74":"U","\u0244":"U","\u24cb":"V","\uff36":"V","\u1e7c":"V","\u1e7e":"V","\u01b2":"V","\ua75e":"V","\u0245":"V","\ua760":"VY","\u24cc":"W","\uff37":"W","\u1e80":"W","\u1e82":"W","\u0174":"W","\u1e86":"W","\u1e84":"W","\u1e88":"W","\u2c72":"W","\u24cd":"X","\uff38":"X","\u1e8a":"X","\u1e8c":"X","\u24ce":"Y","\uff39":"Y","\u1ef2":"Y","\xdd":"Y","\u0176":"Y","\u1ef8":"Y","\u0232":"Y","\u1e8e":"Y","\u0178":"Y","\u1ef6":"Y","\u1ef4":"Y","\u01b3":"Y","\u024e":"Y","\u1efe":"Y","\u24cf":"Z","\uff3a":"Z","\u0179":"Z","\u1e90":"Z","\u017b":"Z","\u017d":"Z","\u1e92":"Z","\u1e94":"Z","\u01b5":"Z","\u0224":"Z","\u2c7f":"Z","\u2c6b":"Z","\ua762":"Z","\u24d0":"a","\uff41":"a","\u1e9a":"a","\xe0":"a","\xe1":"a","\xe2":"a","\u1ea7":"a","\u1ea5":"a","\u1eab":"a","\u1ea9":"a","\xe3":"a","\u0101":"a","\u0103":"a","\u1eb1":"a","\u1eaf":"a","\u1eb5":"a","\u1eb3":"a","\u0227":"a","\u01e1":"a","\xe4":"a","\u01df":"a","\u1ea3":"a","\xe5":"a","\u01fb":"a","\u01ce":"a","\u0201":"a","\u0203":"a","\u1ea1":"a","\u1ead":"a","\u1eb7":"a","\u1e01":"a","\u0105":"a","\u2c65":"a","\u0250":"a","\ua733":"aa","\xe6":"ae","\u01fd":"ae","\u01e3":"ae","\ua735":"ao","\ua737":"au","\ua739":"av","\ua73b":"av","\ua73d":"ay","\u24d1":"b","\uff42":"b","\u1e03":"b","\u1e05":"b","\u1e07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24d2":"c","\uff43":"c","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\xe7":"c","\u1e09":"c","\u0188":"c","\u023c":"c","\ua73f":"c","\u2184":"c","\u24d3":"d","\uff44":"d","\u1e0b":"d","\u010f":"d","\u1e0d":"d","\u1e11":"d","\u1e13":"d","\u1e0f":"d","\u0111":"d","\u018c":"d","\u0256":"d","\u0257":"d","\ua77a":"d","\u01f3":"dz","\u01c6":"dz","\u24d4":"e","\uff45":"e","\xe8":"e","\xe9":"e","\xea":"e","\u1ec1":"e","\u1ebf":"e","\u1ec5":"e","\u1ec3":"e","\u1ebd":"e","\u0113":"e","\u1e15":"e","\u1e17":"e","\u0115":"e","\u0117":"e","\xeb":"e","\u1ebb":"e","\u011b":"e","\u0205":"e","\u0207":"e","\u1eb9":"e","\u1ec7":"e","\u0229":"e","\u1e1d":"e","\u0119":"e","\u1e19":"e","\u1e1b":"e","\u0247":"e","\u025b":"e","\u01dd":"e","\u24d5":"f","\uff46":"f","\u1e1f":"f","\u0192":"f","\ua77c":"f","\u24d6":"g","\uff47":"g","\u01f5":"g","\u011d":"g","\u1e21":"g","\u011f":"g","\u0121":"g","\u01e7":"g","\u0123":"g","\u01e5":"g","\u0260":"g","\ua7a1":"g","\u1d79":"g","\ua77f":"g","\u24d7":"h","\uff48":"h","\u0125":"h","\u1e23":"h","\u1e27":"h","\u021f":"h","\u1e25":"h","\u1e29":"h","\u1e2b":"h","\u1e96":"h","\u0127":"h","\u2c68":"h","\u2c76":"h","\u0265":"h","\u0195":"hv","\u24d8":"i","\uff49":"i","\xec":"i","\xed":"i","\xee":"i","\u0129":"i","\u012b":"i","\u012d":"i","\xef":"i","\u1e2f":"i","\u1ec9":"i","\u01d0":"i","\u0209":"i","\u020b":"i","\u1ecb":"i","\u012f":"i","\u1e2d":"i","\u0268":"i","\u0131":"i","\u24d9":"j","\uff4a":"j","\u0135":"j","\u01f0":"j","\u0249":"j","\u24da":"k","\uff4b":"k","\u1e31":"k","\u01e9":"k","\u1e33":"k","\u0137":"k","\u1e35":"k","\u0199":"k","\u2c6a":"k","\ua741":"k","\ua743":"k","\ua745":"k","\ua7a3":"k","\u24db":"l","\uff4c":"l","\u0140":"l","\u013a":"l","\u013e":"l","\u1e37":"l","\u1e39":"l","\u013c":"l","\u1e3d":"l","\u1e3b":"l","\u017f":"l","\u0142":"l","\u019a":"l","\u026b":"l","\u2c61":"l","\ua749":"l","\ua781":"l","\ua747":"l","\u01c9":"lj","\u24dc":"m","\uff4d":"m","\u1e3f":"m","\u1e41":"m","\u1e43":"m","\u0271":"m","\u026f":"m","\u24dd":"n","\uff4e":"n","\u01f9":"n","\u0144":"n","\xf1":"n","\u1e45":"n","\u0148":"n","\u1e47":"n","\u0146":"n","\u1e4b":"n","\u1e49":"n","\u019e":"n","\u0272":"n","\u0149":"n","\ua791":"n","\ua7a5":"n","\u01cc":"nj","\u24de":"o","\uff4f":"o","\xf2":"o","\xf3":"o","\xf4":"o","\u1ed3":"o","\u1ed1":"o","\u1ed7":"o","\u1ed5":"o","\xf5":"o","\u1e4d":"o","\u022d":"o","\u1e4f":"o","\u014d":"o","\u1e51":"o","\u1e53":"o","\u014f":"o","\u022f":"o","\u0231":"o","\xf6":"o","\u022b":"o","\u1ecf":"o","\u0151":"o","\u01d2":"o","\u020d":"o","\u020f":"o","\u01a1":"o","\u1edd":"o","\u1edb":"o","\u1ee1":"o","\u1edf":"o","\u1ee3":"o","\u1ecd":"o","\u1ed9":"o","\u01eb":"o","\u01ed":"o","\xf8":"o","\u01ff":"o","\u0254":"o","\ua74b":"o","\ua74d":"o","\u0275":"o","\u01a3":"oi","\u0223":"ou","\ua74f":"oo","\u24df":"p","\uff50":"p","\u1e55":"p","\u1e57":"p","\u01a5":"p","\u1d7d":"p","\ua751":"p","\ua753":"p","\ua755":"p","\u24e0":"q","\uff51":"q","\u024b":"q","\ua757":"q","\ua759":"q","\u24e1":"r","\uff52":"r","\u0155":"r","\u1e59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1e5b":"r","\u1e5d":"r","\u0157":"r","\u1e5f":"r","\u024d":"r","\u027d":"r","\ua75b":"r","\ua7a7":"r","\ua783":"r","\u24e2":"s","\uff53":"s","\xdf":"s","\u015b":"s","\u1e65":"s","\u015d":"s","\u1e61":"s","\u0161":"s","\u1e67":"s","\u1e63":"s","\u1e69":"s","\u0219":"s","\u015f":"s","\u023f":"s","\ua7a9":"s","\ua785":"s","\u1e9b":"s","\u24e3":"t","\uff54":"t","\u1e6b":"t","\u1e97":"t","\u0165":"t","\u1e6d":"t","\u021b":"t","\u0163":"t","\u1e71":"t","\u1e6f":"t","\u0167":"t","\u01ad":"t","\u0288":"t","\u2c66":"t","\ua787":"t","\ua729":"tz","\u24e4":"u","\uff55":"u","\xf9":"u","\xfa":"u","\xfb":"u","\u0169":"u","\u1e79":"u","\u016b":"u","\u1e7b":"u","\u016d":"u","\xfc":"u","\u01dc":"u","\u01d8":"u","\u01d6":"u","\u01da":"u","\u1ee7":"u","\u016f":"u","\u0171":"u","\u01d4":"u","\u0215":"u","\u0217":"u","\u01b0":"u","\u1eeb":"u","\u1ee9":"u","\u1eef":"u","\u1eed":"u","\u1ef1":"u","\u1ee5":"u","\u1e73":"u","\u0173":"u","\u1e77":"u","\u1e75":"u","\u0289":"u","\u24e5":"v","\uff56":"v","\u1e7d":"v","\u1e7f":"v","\u028b":"v","\ua75f":"v","\u028c":"v","\ua761":"vy","\u24e6":"w","\uff57":"w","\u1e81":"w","\u1e83":"w","\u0175":"w","\u1e87":"w","\u1e85":"w","\u1e98":"w","\u1e89":"w","\u2c73":"w","\u24e7":"x","\uff58":"x","\u1e8b":"x","\u1e8d":"x","\u24e8":"y","\uff59":"y","\u1ef3":"y","\xfd":"y","\u0177":"y","\u1ef9":"y","\u0233":"y","\u1e8f":"y","\xff":"y","\u1ef7":"y","\u1e99":"y","\u1ef5":"y","\u01b4":"y","\u024f":"y","\u1eff":"y","\u24e9":"z","\uff5a":"z","\u017a":"z","\u1e91":"z","\u017c":"z","\u017e":"z","\u1e93":"z","\u1e95":"z","\u01b6":"z","\u0225":"z","\u0240":"z","\u2c6c":"z","\ua763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038a":"\u0399","\u03aa":"\u0399","\u038c":"\u039f","\u038e":"\u03a5","\u03ab":"\u03a5","\u038f":"\u03a9","\u03ac":"\u03b1","\u03ad":"\u03b5","\u03ae":"\u03b7","\u03af":"\u03b9","\u03ca":"\u03b9","\u0390":"\u03b9","\u03cc":"\u03bf","\u03cd":"\u03c5","\u03cb":"\u03c5","\u03b0":"\u03c5","\u03c9":"\u03c9","\u03c2":"\u03c3"};j=a(document),g=function(){var a=1;return function(){return a++}}(),d=O(Object,{bind:function(a){var b=this;return function(){a.apply(b,arguments)}},init:function(c){var d,e,f=".select2-results";this.opts=c=this.prepareOpts(c),this.id=c.id,c.element.data("select2")!==b&&null!==c.element.data("select2")&&c.element.data("select2").destroy(),this.container=this.createContainer(),this.liveRegion=a("<span>",{role:"status","aria-live":"polite"}).addClass("select2-hidden-accessible").appendTo(document.body),this.containerId="s2id_"+(c.element.attr("id")||"autogen"+g()),this.containerEventName=this.containerId.replace(/([.])/g,"_").replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1"),this.container.attr("id",this.containerId),this.container.attr("title",c.element.attr("title")),this.body=a("body"),D(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.attr("style",c.element.attr("style")),this.container.css(K(c.containerCss,this.opts.element)),this.container.addClass(K(c.containerCssClass,this.opts.element)),this.elementTabIndex=this.opts.element.attr("tabindex"),this.opts.element.data("select2",this).attr("tabindex","-1").before(this.container).on("click.select2",A),this.container.data("select2",this),this.dropdown=this.container.find(".select2-drop"),D(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(K(c.dropdownCssClass,this.opts.element)),this.dropdown.data("select2",this),this.dropdown.on("click",A),this.results=d=this.container.find(f),this.search=e=this.container.find("input.select2-input"),this.queryCount=0,this.resultsPage=0,this.context=null,this.initContainer(),this.container.on("click",A),v(this.results),this.dropdown.on("mousemove-filtered",f,this.bind(this.highlightUnderEvent)),this.dropdown.on("touchstart touchmove touchend",f,this.bind(function(a){this._touchEvent=!0,this.highlightUnderEvent(a)})),this.dropdown.on("touchmove",f,this.bind(this.touchMoved)),this.dropdown.on("touchstart touchend",f,this.bind(this.clearTouchMoved)),this.dropdown.on("click",this.bind(function(){this._touchEvent&&(this._touchEvent=!1,this.selectHighlighted())})),x(80,this.results),this.dropdown.on("scroll-debounced",f,this.bind(this.loadMoreIfNeeded)),a(this.container).on("change",".select2-input",function(a){a.stopPropagation()}),a(this.dropdown).on("change",".select2-input",function(a){a.stopPropagation()}),a.fn.mousewheel&&d.mousewheel(function(a,b,c,e){var f=d.scrollTop();e>0&&0>=f-e?(d.scrollTop(0),A(a)):0>e&&d.get(0).scrollHeight-d.scrollTop()+e<=d.height()&&(d.scrollTop(d.get(0).scrollHeight-d.height()),A(a))}),u(e),e.on("keyup-change input paste",this.bind(this.updateResults)),e.on("focus",function(){e.addClass("select2-focused")}),e.on("blur",function(){e.removeClass("select2-focused")}),this.dropdown.on("mouseup",f,this.bind(function(b){a(b.target).closest(".select2-result-selectable").length>0&&(this.highlightUnderEvent(b),this.selectHighlighted(b))})),this.dropdown.on("click mouseup mousedown touchstart touchend focusin",function(a){a.stopPropagation()}),this.nextSearchTerm=b,a.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource()),null!==c.maximumInputLength&&this.search.attr("maxlength",c.maximumInputLength);var h=c.element.prop("disabled");h===b&&(h=!1),this.enable(!h);var i=c.element.prop("readonly");i===b&&(i=!1),this.readonly(i),k=k||q(),this.autofocus=c.element.prop("autofocus"),c.element.prop("autofocus",!1),this.autofocus&&this.focus(),this.search.attr("placeholder",c.searchInputPlaceholder)},destroy:function(){var a=this.opts.element,c=a.data("select2"),d=this;this.close(),a.length&&a[0].detachEvent&&a.each(function(){this.detachEvent("onpropertychange",d._sync)}),this.propertyObserver&&(this.propertyObserver.disconnect(),this.propertyObserver=null),this._sync=null,c!==b&&(c.container.remove(),c.liveRegion.remove(),c.dropdown.remove(),a.removeClass("select2-offscreen").removeData("select2").off(".select2").prop("autofocus",this.autofocus||!1),this.elementTabIndex?a.attr({tabindex:this.elementTabIndex}):a.removeAttr("tabindex"),a.show()),N.call(this,"container","liveRegion","dropdown","results","search")},optionToData:function(a){return a.is("option")?{id:a.prop("value"),text:a.text(),element:a.get(),css:a.attr("class"),disabled:a.prop("disabled"),locked:r(a.attr("locked"),"locked")||r(a.data("locked"),!0)}:a.is("optgroup")?{text:a.attr("label"),children:[],element:a.get(),css:a.attr("class")}:void 0},prepareOpts:function(c){var d,e,f,h,i=this;if(d=c.element,"select"===d.get(0).tagName.toLowerCase()&&(this.select=e=c.element),e&&a.each(["id","multiple","ajax","query","createSearchChoice","initSelection","data","tags"],function(){if(this in c)throw new Error("Option '"+this+"' is not allowed for Select2 when attached to a <select> element.")}),c=a.extend({},{populateResults:function(d,e,f){var h,j=this.opts.id,k=this.liveRegion;h=function(d,e,l){var m,n,o,p,q,r,s,t,u,v;d=c.sortResults(d,e,f);var w=[];for(m=0,n=d.length;n>m;m+=1)o=d[m],q=o.disabled===!0,p=!q&&j(o)!==b,r=o.children&&o.children.length>0,s=a("<li></li>"),s.addClass("select2-results-dept-"+l),s.addClass("select2-result"),s.addClass(p?"select2-result-selectable":"select2-result-unselectable"),q&&s.addClass("select2-disabled"),r&&s.addClass("select2-result-with-children"),s.addClass(i.opts.formatResultCssClass(o)),s.attr("role","presentation"),t=a(document.createElement("div")),t.addClass("select2-result-label"),t.attr("id","select2-result-label-"+g()),t.attr("role","option"),v=c.formatResult(o,t,f,i.opts.escapeMarkup),v!==b&&(t.html(v),s.append(t)),r&&(u=a("<ul></ul>"),u.addClass("select2-result-sub"),h(o.children,u,l+1),s.append(u)),s.data("select2-data",o),w.push(s[0]);e.append(w),k.text(c.formatMatches(d.length))},h(e,d,0)}},a.fn.select2.defaults,c),"function"!=typeof c.id&&(f=c.id,c.id=function(a){return a[f]}),a.isArray(c.element.data("select2Tags"))){if("tags"in c)throw"tags specified as both an attribute 'data-select2-tags' and in options of Select2 "+c.element.attr("id");c.tags=c.element.data("select2Tags")}if(e?(c.query=this.bind(function(a){var f,g,h,c={results:[],more:!1},e=a.term;h=function(b,c){var d;b.is("option")?a.matcher(e,b.text(),b)&&c.push(i.optionToData(b)):b.is("optgroup")&&(d=i.optionToData(b),b.children().each2(function(a,b){h(b,d.children)}),d.children.length>0&&c.push(d))},f=d.children(),this.getPlaceholder()!==b&&f.length>0&&(g=this.getPlaceholderOption(),g&&(f=f.not(g))),f.each2(function(a,b){h(b,c.results)}),a.callback(c)}),c.id=function(a){return a.id}):"query"in c||("ajax"in c?(h=c.element.data("ajax-url"),h&&h.length>0&&(c.ajax.url=h),c.query=G.call(c.element,c.ajax)):"data"in c?c.query=H(c.data):"tags"in c&&(c.query=I(c.tags),c.createSearchChoice===b&&(c.createSearchChoice=function(b){return{id:a.trim(b),text:a.trim(b)}}),c.initSelection===b&&(c.initSelection=function(b,d){var e=[];a(s(b.val(),c.separator)).each(function(){var b={id:this,text:this},d=c.tags;a.isFunction(d)&&(d=d()),a(d).each(function(){return r(this.id,b.id)?(b=this,!1):void 0}),e.push(b)}),d(e)}))),"function"!=typeof c.query)throw"query function not defined for Select2 "+c.element.attr("id");if("top"===c.createSearchChoicePosition)c.createSearchChoicePosition=function(a,b){a.unshift(b)};else if("bottom"===c.createSearchChoicePosition)c.createSearchChoicePosition=function(a,b){a.push(b)};else if("function"!=typeof c.createSearchChoicePosition)throw"invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";return c},monitorSource:function(){var d,c=this.opts.element,e=this;c.on("change.select2",this.bind(function(){this.opts.element.data("select2-change-triggered")!==!0&&this.initSelection()})),this._sync=this.bind(function(){var a=c.prop("disabled");a===b&&(a=!1),this.enable(!a);var d=c.prop("readonly");d===b&&(d=!1),this.readonly(d),D(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.addClass(K(this.opts.containerCssClass,this.opts.element)),D(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(K(this.opts.dropdownCssClass,this.opts.element))}),c.length&&c[0].attachEvent&&c.each(function(){this.attachEvent("onpropertychange",e._sync)}),d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,d!==b&&(this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),this.propertyObserver=new d(function(b){a.each(b,e._sync)}),this.propertyObserver.observe(c.get(0),{attributes:!0,subtree:!1}))},triggerSelect:function(b){var c=a.Event("select2-selecting",{val:this.id(b),object:b,choice:b});return this.opts.element.trigger(c),!c.isDefaultPrevented()},triggerChange:function(b){b=b||{},b=a.extend({},b,{type:"change",val:this.val()}),this.opts.element.data("select2-change-triggered",!0),this.opts.element.trigger(b),this.opts.element.data("select2-change-triggered",!1),this.opts.element.click(),this.opts.blurOnChange&&this.opts.element.blur()},isInterfaceEnabled:function(){return this.enabledInterface===!0},enableInterface:function(){var a=this._enabled&&!this._readonly,b=!a;return a===this.enabledInterface?!1:(this.container.toggleClass("select2-container-disabled",b),this.close(),this.enabledInterface=a,!0)},enable:function(a){a===b&&(a=!0),this._enabled!==a&&(this._enabled=a,this.opts.element.prop("disabled",!a),this.enableInterface())},disable:function(){this.enable(!1)},readonly:function(a){a===b&&(a=!1),this._readonly!==a&&(this._readonly=a,this.opts.element.prop("readonly",a),this.enableInterface())},opened:function(){return this.container?this.container.hasClass("select2-dropdown-open"):!1},positionDropdown:function(){var t,u,v,w,x,b=this.dropdown,c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),g=a(window),h=g.width(),i=g.height(),j=g.scrollLeft()+h,l=g.scrollTop()+i,m=c.top+d,n=c.left,o=l>=m+f,p=c.top-f>=g.scrollTop(),q=b.outerWidth(!1),r=j>=n+q,s=b.hasClass("select2-drop-above");s?(u=!0,!p&&o&&(v=!0,u=!1)):(u=!1,!o&&p&&(v=!0,u=!0)),v&&(b.hide(),c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),j=g.scrollLeft()+h,l=g.scrollTop()+i,m=c.top+d,n=c.left,q=b.outerWidth(!1),r=j>=n+q,b.show(),this.focusSearch()),this.opts.dropdownAutoWidth?(x=a(".select2-results",b)[0],b.addClass("select2-drop-auto-width"),b.css("width",""),q=b.outerWidth(!1)+(x.scrollHeight===x.clientHeight?0:k.width),q>e?e=q:q=e,f=b.outerHeight(!1),r=j>=n+q):this.container.removeClass("select2-drop-auto-width"),"static"!==this.body.css("position")&&(t=this.body.offset(),m-=t.top,n-=t.left),r||(n=c.left+this.container.outerWidth(!1)-q),w={left:n,width:e},u?(w.top=c.top-f,w.bottom="auto",this.container.addClass("select2-drop-above"),b.addClass("select2-drop-above")):(w.top=m,w.bottom="auto",this.container.removeClass("select2-drop-above"),b.removeClass("select2-drop-above")),w=a.extend(w,K(this.opts.dropdownCss,this.opts.element)),b.css(w)},shouldOpen:function(){var b;return this.opened()?!1:this._enabled===!1||this._readonly===!0?!1:(b=a.Event("select2-opening"),this.opts.element.trigger(b),!b.isDefaultPrevented())},clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above")},open:function(){return this.shouldOpen()?(this.opening(),j.on("mousemove.select2Event",function(a){i.x=a.pageX,i.y=a.pageY}),!0):!1},opening:function(){var f,b=this.containerEventName,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.addClass("select2-dropdown-open").addClass("select2-container-active"),this.clearDropdownAlignmentPreference(),this.dropdown[0]!==this.body.children().last()[0]&&this.dropdown.detach().appendTo(this.body),f=a("#select2-drop-mask"),0==f.length&&(f=a(document.createElement("div")),f.attr("id","select2-drop-mask").attr("class","select2-drop-mask"),f.hide(),f.appendTo(this.body),f.on("mousedown touchstart click",function(b){n(f);var d,c=a("#select2-drop");c.length>0&&(d=c.data("select2"),d.opts.selectOnBlur&&d.selectHighlighted({noFocus:!0}),d.close(),b.preventDefault(),b.stopPropagation())})),this.dropdown.prev()[0]!==f[0]&&this.dropdown.before(f),a("#select2-drop").removeAttr("id"),this.dropdown.attr("id","select2-drop"),f.show(),this.positionDropdown(),this.dropdown.show(),this.positionDropdown(),this.dropdown.addClass("select2-drop-active");var g=this;this.container.parents().add(window).each(function(){a(this).on(d+" "+c+" "+e,function(){g.opened()&&g.positionDropdown()})})},close:function(){if(this.opened()){var b=this.containerEventName,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.parents().add(window).each(function(){a(this).off(c).off(d).off(e)}),this.clearDropdownAlignmentPreference(),a("#select2-drop-mask").hide(),this.dropdown.removeAttr("id"),this.dropdown.hide(),this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active"),this.results.empty(),j.off("mousemove.select2Event"),this.clearSearch(),this.search.removeClass("select2-active"),this.opts.element.trigger(a.Event("select2-close"))}},externalSearch:function(a){this.open(),this.search.val(a),this.updateResults(!1)},clearSearch:function(){},getMaximumSelectionSize:function(){return K(this.opts.maximumSelectionSize,this.opts.element)},ensureHighlightVisible:function(){var c,d,e,f,g,h,i,j,b=this.results;if(d=this.highlight(),!(0>d)){if(0==d)return b.scrollTop(0),void 0;c=this.findHighlightableChoices().find(".select2-result-label"),e=a(c[d]),j=(e.offset()||{}).top||0,f=j+e.outerHeight(!0),d===c.length-1&&(i=b.find("li.select2-more-results"),i.length>0&&(f=i.offset().top+i.outerHeight(!0))),g=b.offset().top+b.outerHeight(!0),f>g&&b.scrollTop(b.scrollTop()+(f-g)),h=j-b.offset().top,0>h&&"none"!=e.css("display")&&b.scrollTop(b.scrollTop()+h)}},findHighlightableChoices:function(){return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)")},moveHighlight:function(b){for(var c=this.findHighlightableChoices(),d=this.highlight();d>-1&&d<c.length;){d+=b;var e=a(c[d]);if(e.hasClass("select2-result-selectable")&&!e.hasClass("select2-disabled")&&!e.hasClass("select2-selected")){this.highlight(d);
22
+ break}}},highlight:function(b){var d,e,c=this.findHighlightableChoices();return 0===arguments.length?p(c.filter(".select2-highlighted")[0],c.get()):(b>=c.length&&(b=c.length-1),0>b&&(b=0),this.removeHighlight(),d=a(c[b]),d.addClass("select2-highlighted"),this.search.attr("aria-activedescendant",d.find(".select2-result-label").attr("id")),this.ensureHighlightVisible(),this.liveRegion.text(d.text()),e=d.data("select2-data"),e&&this.opts.element.trigger({type:"select2-highlight",val:this.id(e),choice:e}),void 0)},removeHighlight:function(){this.results.find(".select2-highlighted").removeClass("select2-highlighted")},touchMoved:function(){this._touchMoved=!0},clearTouchMoved:function(){this._touchMoved=!1},countSelectableResults:function(){return this.findHighlightableChoices().length},highlightUnderEvent:function(b){var c=a(b.target).closest(".select2-result-selectable");if(c.length>0&&!c.is(".select2-highlighted")){var d=this.findHighlightableChoices();this.highlight(d.index(c))}else 0==c.length&&this.removeHighlight()},loadMoreIfNeeded:function(){var c,a=this.results,b=a.find("li.select2-more-results"),d=this.resultsPage+1,e=this,f=this.search.val(),g=this.context;0!==b.length&&(c=b.offset().top-a.offset().top-a.height(),c<=this.opts.loadMorePadding&&(b.addClass("select2-active"),this.opts.query({element:this.opts.element,term:f,page:d,context:g,matcher:this.opts.matcher,callback:this.bind(function(c){e.opened()&&(e.opts.populateResults.call(this,a,c.results,{term:f,page:d,context:g}),e.postprocessResults(c,!1,!1),c.more===!0?(b.detach().appendTo(a).text(K(e.opts.formatLoadMore,e.opts.element,d+1)),window.setTimeout(function(){e.loadMoreIfNeeded()},10)):b.remove(),e.positionDropdown(),e.resultsPage=d,e.context=c.context,this.opts.element.trigger({type:"select2-loaded",items:c}))})})))},tokenize:function(){},updateResults:function(c){function m(){d.removeClass("select2-active"),h.positionDropdown(),e.find(".select2-no-results,.select2-selection-limit,.select2-searching").length?h.liveRegion.text(e.text()):h.liveRegion.text(h.opts.formatMatches(e.find(".select2-result-selectable").length))}function n(a){e.html(a),m()}var g,i,l,d=this.search,e=this.results,f=this.opts,h=this,j=d.val(),k=a.data(this.container,"select2-last-term");if((c===!0||!k||!r(j,k))&&(a.data(this.container,"select2-last-term",j),c===!0||this.showSearchInput!==!1&&this.opened())){l=++this.queryCount;var o=this.getMaximumSelectionSize();if(o>=1&&(g=this.data(),a.isArray(g)&&g.length>=o&&J(f.formatSelectionTooBig,"formatSelectionTooBig")))return n("<li class='select2-selection-limit'>"+K(f.formatSelectionTooBig,f.element,o)+"</li>"),void 0;if(d.val().length<f.minimumInputLength)return J(f.formatInputTooShort,"formatInputTooShort")?n("<li class='select2-no-results'>"+K(f.formatInputTooShort,f.element,d.val(),f.minimumInputLength)+"</li>"):n(""),c&&this.showSearch&&this.showSearch(!0),void 0;if(f.maximumInputLength&&d.val().length>f.maximumInputLength)return J(f.formatInputTooLong,"formatInputTooLong")?n("<li class='select2-no-results'>"+K(f.formatInputTooLong,f.element,d.val(),f.maximumInputLength)+"</li>"):n(""),void 0;f.formatSearching&&0===this.findHighlightableChoices().length&&n("<li class='select2-searching'>"+K(f.formatSearching,f.element)+"</li>"),d.addClass("select2-active"),this.removeHighlight(),i=this.tokenize(),i!=b&&null!=i&&d.val(i),this.resultsPage=1,f.query({element:f.element,term:d.val(),page:this.resultsPage,context:null,matcher:f.matcher,callback:this.bind(function(g){var i;if(l==this.queryCount){if(!this.opened())return this.search.removeClass("select2-active"),void 0;if(g.hasError!==b&&J(f.formatAjaxError,"formatAjaxError"))return n("<li class='select2-ajax-error'>"+K(f.formatAjaxError,f.element,g.jqXHR,g.textStatus,g.errorThrown)+"</li>"),void 0;if(this.context=g.context===b?null:g.context,this.opts.createSearchChoice&&""!==d.val()&&(i=this.opts.createSearchChoice.call(h,d.val(),g.results),i!==b&&null!==i&&h.id(i)!==b&&null!==h.id(i)&&0===a(g.results).filter(function(){return r(h.id(this),h.id(i))}).length&&this.opts.createSearchChoicePosition(g.results,i)),0===g.results.length&&J(f.formatNoMatches,"formatNoMatches"))return n("<li class='select2-no-results'>"+K(f.formatNoMatches,f.element,d.val())+"</li>"),void 0;e.empty(),h.opts.populateResults.call(this,e,g.results,{term:d.val(),page:this.resultsPage,context:null}),g.more===!0&&J(f.formatLoadMore,"formatLoadMore")&&(e.append("<li class='select2-more-results'>"+f.escapeMarkup(K(f.formatLoadMore,f.element,this.resultsPage))+"</li>"),window.setTimeout(function(){h.loadMoreIfNeeded()},10)),this.postprocessResults(g,c),m(),this.opts.element.trigger({type:"select2-loaded",items:g})}})})}},cancel:function(){this.close()},blur:function(){this.opts.selectOnBlur&&this.selectHighlighted({noFocus:!0}),this.close(),this.container.removeClass("select2-container-active"),this.search[0]===document.activeElement&&this.search.blur(),this.clearSearch(),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){y(this.search)},selectHighlighted:function(a){if(this._touchMoved)return this.clearTouchMoved(),void 0;var b=this.highlight(),c=this.results.find(".select2-highlighted"),d=c.closest(".select2-result").data("select2-data");d?(this.highlight(b),this.onSelect(d,a)):a&&a.noFocus&&this.close()},getPlaceholder:function(){var a;return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")||this.opts.placeholder||((a=this.getPlaceholderOption())!==b?a.text():b)},getPlaceholderOption:function(){if(this.select){var c=this.select.children("option").first();if(this.opts.placeholderOption!==b)return"first"===this.opts.placeholderOption&&c||"function"==typeof this.opts.placeholderOption&&this.opts.placeholderOption(this.select);if(""===a.trim(c.text())&&""===c.val())return c}},initContainerWidth:function(){function c(){var c,d,e,f,g,h;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){if(c=this.opts.element.attr("style"),c!==b)for(d=c.split(";"),f=0,g=d.length;g>f;f+=1)if(h=d[f].replace(/\s/g,""),e=h.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i),null!==e&&e.length>=1)return e[1];return"resolve"===this.opts.width?(c=this.opts.element.css("width"),c.indexOf("%")>0?c:0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px"):null}return a.isFunction(this.opts.width)?this.opts.width():this.opts.width}var d=c.call(this);null!==d&&this.container.css("width",d)}}),e=O(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container"}).html(["<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>"," <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>"," <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>","</a>","<label for='' class='select2-offscreen'></label>","<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />","<div class='select2-drop select2-display-none'>"," <div class='select2-search'>"," <label for='' class='select2-offscreen'></label>"," <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'"," aria-autocomplete='list' />"," </div>"," <ul class='select2-results' role='listbox'>"," </ul>","</div>"].join(""));return b},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.focusser.prop("disabled",!this.isInterfaceEnabled())},opening:function(){var c,d,e;this.opts.minimumResultsForSearch>=0&&this.showSearch(!0),this.parent.opening.apply(this,arguments),this.showSearchInput!==!1&&this.search.val(this.focusser.val()),this.opts.shouldFocusInput(this)&&(this.search.focus(),c=this.search.get(0),c.createTextRange?(d=c.createTextRange(),d.collapse(!1),d.select()):c.setSelectionRange&&(e=this.search.val().length,c.setSelectionRange(e,e))),""===this.search.val()&&this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.search.select()),this.focusser.prop("disabled",!0).val(""),this.updateResults(!0),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus())},focus:function(){this.opened()?this.close():(this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus())},isFocused:function(){return this.container.hasClass("select2-container-active")},cancel:function(){this.parent.cancel.apply(this,arguments),this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus()},destroy:function(){a("label[for='"+this.focusser.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments),N.call(this,"selection","focusser")},initContainer:function(){var b,h,d=this.container,e=this.dropdown,f=g();this.opts.minimumResultsForSearch<0?this.showSearch(!1):this.showSearch(!0),this.selection=b=d.find(".select2-choice"),this.focusser=d.find(".select2-focusser"),b.find(".select2-chosen").attr("id","select2-chosen-"+f),this.focusser.attr("aria-labelledby","select2-chosen-"+f),this.results.attr("id","select2-results-"+f),this.search.attr("aria-owns","select2-results-"+f),this.focusser.attr("id","s2id_autogen"+f),h=a("label[for='"+this.opts.element.attr("id")+"']"),this.focusser.prev().text(h.text()).attr("for",this.focusser.attr("id"));var i=this.opts.element.attr("title");this.opts.element.attr("title",i||h.text()),this.focusser.attr("tabindex",this.elementTabIndex),this.search.attr("id",this.focusser.attr("id")+"_search"),this.search.prev().text(a("label[for='"+this.focusser.attr("id")+"']").text()).attr("for",this.search.attr("id")),this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()&&229!=a.keyCode){if(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)return A(a),void 0;switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),A(a),void 0;case c.ENTER:return this.selectHighlighted(),A(a),void 0;case c.TAB:return this.selectHighlighted({noFocus:!0}),void 0;case c.ESC:return this.cancel(a),A(a),void 0}}})),this.search.on("blur",this.bind(function(){document.activeElement===this.body.get(0)&&window.setTimeout(this.bind(function(){this.opened()&&this.search.focus()}),0)})),this.focusser.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()&&a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.ESC){if(this.opts.openOnEnter===!1&&a.which===c.ENTER)return A(a),void 0;if(a.which==c.DOWN||a.which==c.UP||a.which==c.ENTER&&this.opts.openOnEnter){if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return;return this.open(),A(a),void 0}return a.which==c.DELETE||a.which==c.BACKSPACE?(this.opts.allowClear&&this.clear(),A(a),void 0):void 0}})),u(this.focusser),this.focusser.on("keyup-change input",this.bind(function(a){if(this.opts.minimumResultsForSearch>=0){if(a.stopPropagation(),this.opened())return;this.open()}})),b.on("mousedown touchstart","abbr",this.bind(function(a){this.isInterfaceEnabled()&&(this.clear(),B(a),this.close(),this.selection.focus())})),b.on("mousedown touchstart",this.bind(function(c){n(b),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.opened()?this.close():this.isInterfaceEnabled()&&this.open(),A(c)})),e.on("mousedown touchstart",this.bind(function(){this.opts.shouldFocusInput(this)&&this.search.focus()})),b.on("focus",this.bind(function(a){A(a)})),this.focusser.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})).on("blur",this.bind(function(){this.opened()||(this.container.removeClass("select2-container-active"),this.opts.element.trigger(a.Event("select2-blur")))})),this.search.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.setPlaceholder()},clear:function(b){var c=this.selection.data("select2-data");if(c){var d=a.Event("select2-clearing");if(this.opts.element.trigger(d),d.isDefaultPrevented())return;var e=this.getPlaceholderOption();this.opts.element.val(e?e.val():""),this.selection.find(".select2-chosen").empty(),this.selection.removeData("select2-data"),this.setPlaceholder(),b!==!1&&(this.opts.element.trigger({type:"select2-removed",val:this.id(c),choice:c}),this.triggerChange({removed:c}))}},initSelection:function(){if(this.isPlaceholderOptionSelected())this.updateSelection(null),this.close(),this.setPlaceholder();else{var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.setPlaceholder(),c.nextSearchTerm=c.opts.nextSearchTerm(a,c.search.val()))})}},isPlaceholderOptionSelected:function(){var a;return this.getPlaceholder()===b?!1:(a=this.getPlaceholderOption())!==b&&a.prop("selected")||""===this.opts.element.val()||this.opts.element.val()===b||null===this.opts.element.val()},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=a.find("option").filter(function(){return this.selected&&!this.disabled});b(c.optionToData(d))}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=c.val(),f=null;b.query({matcher:function(a,c,d){var g=r(e,b.id(d));return g&&(f=d),g},callback:a.isFunction(d)?function(){d(f)}:a.noop})}),b},getPlaceholder:function(){return this.select&&this.getPlaceholderOption()===b?b:this.parent.getPlaceholder.apply(this,arguments)},setPlaceholder:function(){var a=this.getPlaceholder();if(this.isPlaceholderOptionSelected()&&a!==b){if(this.select&&this.getPlaceholderOption()===b)return;this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.container.removeClass("select2-allowclear")}},postprocessResults:function(a,b,c){var d=0,e=this;if(this.findHighlightableChoices().each2(function(a,b){return r(e.id(b.data("select2-data")),e.opts.element.val())?(d=a,!1):void 0}),c!==!1&&(b===!0&&d>=0?this.highlight(d):this.highlight(0)),b===!0){var g=this.opts.minimumResultsForSearch;g>=0&&this.showSearch(L(a.results)>=g)}},showSearch:function(b){this.showSearchInput!==b&&(this.showSearchInput=b,this.dropdown.find(".select2-search").toggleClass("select2-search-hidden",!b),this.dropdown.find(".select2-search").toggleClass("select2-offscreen",!b),a(this.dropdown,this.container).toggleClass("select2-with-searchbox",b))},onSelect:function(a,b){if(this.triggerSelect(a)){var c=this.opts.element.val(),d=this.data();this.opts.element.val(this.id(a)),this.updateSelection(a),this.opts.element.trigger({type:"select2-selected",val:this.id(a),choice:a}),this.nextSearchTerm=this.opts.nextSearchTerm(a,this.search.val()),this.close(),b&&b.noFocus||!this.opts.shouldFocusInput(this)||this.focusser.focus(),r(c,this.id(a))||this.triggerChange({added:a,removed:d})}},updateSelection:function(a){var d,e,c=this.selection.find(".select2-chosen");this.selection.data("select2-data",a),c.empty(),null!==a&&(d=this.opts.formatSelection(a,c,this.opts.escapeMarkup)),d!==b&&c.append(d),e=this.opts.formatSelectionCssClass(a,c),e!==b&&c.addClass(e),this.selection.removeClass("select2-default"),this.opts.allowClear&&this.getPlaceholder()!==b&&this.container.addClass("select2-allowclear")},val:function(){var a,c=!1,d=null,e=this,f=this.data();if(0===arguments.length)return this.opts.element.val();if(a=arguments[0],arguments.length>1&&(c=arguments[1]),this.select)this.select.val(a).find("option").filter(function(){return this.selected}).each2(function(a,b){return d=e.optionToData(b),!1}),this.updateSelection(d),this.setPlaceholder(),c&&this.triggerChange({added:d,removed:f});else{if(!a&&0!==a)return this.clear(c),void 0;if(this.opts.initSelection===b)throw new Error("cannot call val() if initSelection() is not defined");this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){e.opts.element.val(a?e.id(a):""),e.updateSelection(a),e.setPlaceholder(),c&&e.triggerChange({added:a,removed:f})})}},clearSearch:function(){this.search.val(""),this.focusser.val("")},data:function(a){var c,d=!1;return 0===arguments.length?(c=this.selection.data("select2-data"),c==b&&(c=null),c):(arguments.length>1&&(d=arguments[1]),a?(c=this.data(),this.opts.element.val(a?this.id(a):""),this.updateSelection(a),d&&this.triggerChange({added:a,removed:c})):this.clear(d),void 0)}}),f=O(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container select2-container-multi"}).html(["<ul class='select2-choices'>"," <li class='select2-search-field'>"," <label for='' class='select2-offscreen'></label>"," <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>"," </li>","</ul>","<div class='select2-drop select2-drop-multi select2-display-none'>"," <ul class='select2-results'>"," </ul>","</div>"].join(""));return b},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=[];a.find("option").filter(function(){return this.selected&&!this.disabled}).each2(function(a,b){d.push(c.optionToData(b))}),b(d)}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=s(c.val(),b.separator),f=[];b.query({matcher:function(c,d,g){var h=a.grep(e,function(a){return r(a,b.id(g))}).length;return h&&f.push(g),h},callback:a.isFunction(d)?function(){for(var a=[],c=0;c<e.length;c++)for(var g=e[c],h=0;h<f.length;h++){var i=f[h];if(r(g,b.id(i))){a.push(i),f.splice(h,1);break}}d(a)}:a.noop})}),b},selectChoice:function(a){var b=this.container.find(".select2-search-choice-focus");b.length&&a&&a[0]==b[0]||(b.length&&this.opts.element.trigger("choice-deselected",b),b.removeClass("select2-search-choice-focus"),a&&a.length&&(this.close(),a.addClass("select2-search-choice-focus"),this.opts.element.trigger("choice-selected",a)))},destroy:function(){a("label[for='"+this.search.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments),N.call(this,"searchContainer","selection")},initContainer:function(){var d,b=".select2-choices";this.searchContainer=this.container.find(".select2-search-field"),this.selection=d=this.container.find(b);var e=this;this.selection.on("click",".select2-search-choice:not(.select2-locked)",function(){e.search[0].focus(),e.selectChoice(a(this))}),this.search.attr("id","s2id_autogen"+g()),this.search.prev().text(a("label[for='"+this.opts.element.attr("id")+"']").text()).attr("for",this.search.attr("id")),this.search.on("input paste",this.bind(function(){this.search.attr("placeholder")&&0==this.search.val().length||this.isInterfaceEnabled()&&(this.opened()||this.open())})),this.search.attr("tabindex",this.elementTabIndex),this.keydowns=0,this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){++this.keydowns;var b=d.find(".select2-search-choice-focus"),e=b.prev(".select2-search-choice:not(.select2-locked)"),f=b.next(".select2-search-choice:not(.select2-locked)"),g=z(this.search);if(b.length&&(a.which==c.LEFT||a.which==c.RIGHT||a.which==c.BACKSPACE||a.which==c.DELETE||a.which==c.ENTER)){var h=b;return a.which==c.LEFT&&e.length?h=e:a.which==c.RIGHT?h=f.length?f:null:a.which===c.BACKSPACE?this.unselect(b.first())&&(this.search.width(10),h=e.length?e:f):a.which==c.DELETE?this.unselect(b.first())&&(this.search.width(10),h=f.length?f:null):a.which==c.ENTER&&(h=null),this.selectChoice(h),A(a),h&&h.length||this.open(),void 0}if((a.which===c.BACKSPACE&&1==this.keydowns||a.which==c.LEFT)&&0==g.offset&&!g.length)return this.selectChoice(d.find(".select2-search-choice:not(.select2-locked)").last()),A(a),void 0;if(this.selectChoice(null),this.opened())switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),A(a),void 0;case c.ENTER:return this.selectHighlighted(),A(a),void 0;case c.TAB:return this.selectHighlighted({noFocus:!0}),this.close(),void 0;case c.ESC:return this.cancel(a),A(a),void 0}if(a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.BACKSPACE&&a.which!==c.ESC){if(a.which===c.ENTER){if(this.opts.openOnEnter===!1)return;if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return}this.open(),(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)&&A(a),a.which===c.ENTER&&A(a)}}})),this.search.on("keyup",this.bind(function(){this.keydowns=0,this.resizeSearch()})),this.search.on("blur",this.bind(function(b){this.container.removeClass("select2-container-active"),this.search.removeClass("select2-focused"),this.selectChoice(null),this.opened()||this.clearSearch(),b.stopImmediatePropagation(),this.opts.element.trigger(a.Event("select2-blur"))})),this.container.on("click",b,this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").length>0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.open(),this.focusSearch(),b.preventDefault()))})),this.container.on("focus",b,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.clearSearch())})}},clearSearch:function(){var a=this.getPlaceholder(),c=this.getMaxSearchWidth();a!==b&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(a).addClass("select2-default"),this.search.width(c>0?c:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),""===this.search.val()&&this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.search.select()),this.updateResults(!0),this.opts.shouldFocusInput(this)&&this.search.focus(),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(b){var c=[],d=[],e=this;a(b).each(function(){p(e.id(this),c)<0&&(c.push(e.id(this)),d.push(this))}),b=d,this.selection.find(".select2-search-choice").remove(),a(b).each(function(){e.addSelectedChoice(this)}),e.postprocessResults()},tokenize:function(){var a=this.search.val();a=this.opts.tokenizer.call(this,a,this.data(),this.bind(this.onSelect),this.opts),null!=a&&a!=b&&(this.search.val(a),a.length>0&&this.open())},onSelect:function(a,c){this.triggerSelect(a)&&""!==a.text&&(this.addSelectedChoice(a),this.opts.element.trigger({type:"selected",val:this.id(a),choice:a}),this.nextSearchTerm=this.opts.nextSearchTerm(a,this.search.val()),this.clearSearch(),this.updateResults(),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(a,!1,this.opts.closeOnSelect===!0),this.opts.closeOnSelect?(this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()?this.updateResults(!0):this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.updateResults(),this.search.select()),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:a}),c&&c.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(c){var j,k,d=!c.locked,e=a("<li class='select2-search-choice'> <div></div> <a href='#' class='select2-search-choice-close' tabindex='-1'></a></li>"),f=a("<li class='select2-search-choice select2-locked'><div></div></li>"),g=d?e:f,h=this.id(c),i=this.getVal();j=this.opts.formatSelection(c,g.find("div"),this.opts.escapeMarkup),j!=b&&g.find("div").replaceWith("<div>"+j+"</div>"),k=this.opts.formatSelectionCssClass(c,g.find("div")),k!=b&&g.addClass(k),d&&g.find(".select2-search-choice-close").on("mousedown",A).on("click dblclick",this.bind(function(b){this.isInterfaceEnabled()&&(this.unselect(a(b.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),A(b),this.close(),this.focusSearch())})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),g.data("select2-data",c),g.insertBefore(this.searchContainer),i.push(h),this.setVal(i)},unselect:function(b){var d,e,c=this.getVal();if(b=b.closest(".select2-search-choice"),0===b.length)throw"Invalid argument: "+b+". Must be .select2-search-choice";if(d=b.data("select2-data")){var f=a.Event("select2-removing");if(f.val=this.id(d),f.choice=d,this.opts.element.trigger(f),f.isDefaultPrevented())return!1;for(;(e=p(this.id(d),c))>=0;)c.splice(e,1),this.setVal(c),this.select&&this.postprocessResults();return b.remove(),this.opts.element.trigger({type:"select2-removed",val:this.id(d),choice:d}),this.triggerChange({removed:d}),!0}},postprocessResults:function(a,b,c){var d=this.getVal(),e=this.results.find(".select2-result"),f=this.results.find(".select2-result-with-children"),g=this;e.each2(function(a,b){var c=g.id(b.data("select2-data"));p(c,d)>=0&&(b.addClass("select2-selected"),b.find(".select2-result-selectable").addClass("select2-selected"))}),f.each2(function(a,b){b.is(".select2-result-selectable")||0!==b.find(".select2-result-selectable:not(.select2-selected)").length||b.addClass("select2-selected")}),-1==this.highlight()&&c!==!1&&g.highlight(0),!this.opts.createSearchChoice&&!e.filter(".select2-result:not(.select2-selected)").length>0&&(!a||a&&!a.more&&0===this.results.find(".select2-no-results").length)&&J(g.opts.formatNoMatches,"formatNoMatches")&&this.results.append("<li class='select2-no-results'>"+K(g.opts.formatNoMatches,g.opts.element,g.search.val())+"</li>")},getMaxSearchWidth:function(){return this.selection.width()-t(this.search)},resizeSearch:function(){var a,b,c,d,e,f=t(this.search);a=C(this.search)+10,b=this.search.offset().left,c=this.selection.width(),d=this.selection.offset().left,e=c-(b-d)-f,a>e&&(e=c-f),40>e&&(e=c-f),0>=e&&(e=a),this.search.width(Math.floor(e))},getVal:function(){var a;return this.select?(a=this.select.val(),null===a?[]:a):(a=this.opts.element.val(),s(a,this.opts.separator))},setVal:function(b){var c;this.select?this.select.val(b):(c=[],a(b).each(function(){p(this,c)<0&&c.push(this)}),this.opts.element.val(0===c.length?"":c.join(this.opts.separator)))},buildChangeDetails:function(a,b){for(var b=b.slice(0),a=a.slice(0),c=0;c<b.length;c++)for(var d=0;d<a.length;d++)r(this.opts.id(b[c]),this.opts.id(a[d]))&&(b.splice(c,1),c>0&&c--,a.splice(d,1),d--);return{added:b,removed:a}},val:function(c,d){var e,f=this;if(0===arguments.length)return this.getVal();if(e=this.data(),e.length||(e=[]),!c&&0!==c)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),d&&this.triggerChange({added:this.data(),removed:e}),void 0;if(this.setVal(c),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),d&&this.triggerChange(this.buildChangeDetails(e,this.data()));else{if(this.opts.initSelection===b)throw new Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(b){var c=a.map(b,f.id);f.setVal(c),f.updateSelection(b),f.clearSearch(),d&&f.triggerChange(f.buildChangeDetails(e,f.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var b=[],c=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){b.push(c.opts.id(a(this).data("select2-data")))}),this.setVal(b),this.triggerChange()},data:function(b,c){var e,f,d=this;return 0===arguments.length?this.selection.children(".select2-search-choice").map(function(){return a(this).data("select2-data")}).get():(f=this.data(),b||(b=[]),e=a.map(b,function(a){return d.opts.id(a)}),this.setVal(e),this.updateSelection(b),this.clearSearch(),c&&this.triggerChange(this.buildChangeDetails(f,this.data())),void 0)}}),a.fn.select2=function(){var d,e,f,g,h,c=Array.prototype.slice.call(arguments,0),i=["val","destroy","opened","open","close","focus","isFocused","container","dropdown","onSortStart","onSortEnd","enable","disable","readonly","positionDropdown","data","search"],j=["opened","isFocused","container","dropdown"],k=["val","data"],l={search:"externalSearch"};return this.each(function(){if(0===c.length||"object"==typeof c[0])d=0===c.length?{}:a.extend({},c[0]),d.element=a(this),"select"===d.element.get(0).tagName.toLowerCase()?h=d.element.prop("multiple"):(h=d.multiple||!1,"tags"in d&&(d.multiple=h=!0)),e=h?new window.Select2["class"].multi:new window.Select2["class"].single,e.init(d);else{if("string"!=typeof c[0])throw"Invalid arguments to select2 plugin: "+c;if(p(c[0],i)<0)throw"Unknown method: "+c[0];if(g=b,e=a(this).data("select2"),e===b)return;if(f=c[0],"container"===f?g=e.container:"dropdown"===f?g=e.dropdown:(l[f]&&(f=l[f]),g=e[f].apply(e,c.slice(1))),p(c[0],j)>=0||p(c[0],k)>=0&&1==c.length)return!1}}),g===b?this:g},a.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c,d){var e=[];return E(a.text,c.term,e,d),e.join("")},formatSelection:function(a,c,d){return a?d(a.text):b},sortResults:function(a){return a},formatResultCssClass:function(a){return a.css},formatSelectionCssClass:function(){return b},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(a){return a==b?null:a.id},matcher:function(a,b){return o(""+b).toUpperCase().indexOf(o(""+a).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:M,escapeMarkup:F,blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(a){return a},adaptDropdownCssClass:function(){return null},nextSearchTerm:function(){return b},searchInputPlaceholder:"",createSearchChoicePosition:"top",shouldFocusInput:function(a){var b="ontouchstart"in window||navigator.msMaxTouchPoints>0;return b?a.opts.minimumResultsForSearch<0?!1:!0:!0}},a.fn.select2.locales=[],a.fn.select2.locales.en={formatMatches:function(a){return 1===a?"One result is available, press enter to select it.":a+" results are available, use up and down arrow keys to navigate."
23
+ },formatNoMatches:function(){return"No matches found"},formatAjaxError:function(){return"Loading failed"},formatInputTooShort:function(a,b){var c=b-a.length;return"Please enter "+c+" or more character"+(1==c?"":"s")},formatInputTooLong:function(a,b){var c=a.length-b;return"Please delete "+c+" character"+(1==c?"":"s")},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results\u2026"},formatSearching:function(){return"Searching\u2026"}},a.extend(a.fn.select2.defaults,a.fn.select2.locales.en),a.fn.select2.ajaxDefaults={transport:a.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:G,local:H,tags:I},util:{debounce:w,markMatch:E,escapeMarkup:F,stripDiacritics:o},"class":{"abstract":d,single:e,multi:f}}}}(jQuery);
js/select2/select2.png ADDED
Binary file
js/select2/select2_locale_ar.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Arabic translation.
3
+ *
4
+ * Author: Adel KEDJOUR <adel@kedjour.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['ar'] = {
10
+ formatNoMatches: function () { return "لم يتم العثور على مطابقات"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; if (n == 1){ return "الرجاء إدخال حرف واحد على الأكثر"; } return n == 2 ? "الرجاء إدخال حرفين على الأكثر" : "الرجاء إدخال " + n + " على الأكثر"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; if (n == 1){ return "الرجاء إدخال حرف واحد على الأقل"; } return n == 2 ? "الرجاء إدخال حرفين على الأقل" : "الرجاء إدخال " + n + " على الأقل "; },
13
+ formatSelectionTooBig: function (limit) { if (n == 1){ return "يمكنك أن تختار إختيار واحد فقط"; } return n == 2 ? "يمكنك أن تختار إختيارين فقط" : "يمكنك أن تختار " + n + " إختيارات فقط"; },
14
+ formatLoadMore: function (pageNumber) { return "تحميل المزيد من النتائج…"; },
15
+ formatSearching: function () { return "البحث…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ar']);
19
+ })(jQuery);
js/select2/select2_locale_az.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Azerbaijani translation.
3
+ *
4
+ * Author: Farhad Safarov <farhad.safarov@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['az'] = {
10
+ formatMatches: function (matches) { return matches + " nəticə mövcuddur, hərəkət etdirmək üçün yuxarı və aşağı düymələrindən istifadə edin."; },
11
+ formatNoMatches: function () { return "Nəticə tapılmadı"; },
12
+ formatInputTooShort: function (input, min) { var n = min - input.length; return n + " simvol daxil edin"; },
13
+ formatInputTooLong: function (input, max) { var n = input.length - max; return n + " simvol silin"; },
14
+ formatSelectionTooBig: function (limit) { return "Sadəcə " + limit + " element seçə bilərsiniz"; },
15
+ formatLoadMore: function (pageNumber) { return "Daha çox nəticə yüklənir…"; },
16
+ formatSearching: function () { return "Axtarılır…"; }
17
+ };
18
+
19
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['az']);
20
+ })(jQuery);
js/select2/select2_locale_bg.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Bulgarian translation.
3
+ *
4
+ * @author Lubomir Vikev <lubomirvikev@gmail.com>
5
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
6
+ */
7
+ (function ($) {
8
+ "use strict";
9
+
10
+ $.fn.select2.locales['bg'] = {
11
+ formatNoMatches: function () { return "Няма намерени съвпадения"; },
12
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Моля въведете още " + n + " символ" + (n > 1 ? "а" : ""); },
13
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Моля въведете с " + n + " по-малко символ" + (n > 1 ? "а" : ""); },
14
+ formatSelectionTooBig: function (limit) { return "Можете да направите до " + limit + (limit > 1 ? " избора" : " избор"); },
15
+ formatLoadMore: function (pageNumber) { return "Зареждат се още…"; },
16
+ formatSearching: function () { return "Търсене…"; }
17
+ };
18
+
19
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['bg']);
20
+ })(jQuery);
js/select2/select2_locale_ca.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Catalan translation.
3
+ *
4
+ * Author: David Planella <david.planella@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['ca'] = {
10
+ formatNoMatches: function () { return "No s'ha trobat cap coincidència"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduïu " + n + " caràcter" + (n == 1 ? "" : "s") + " més"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Introduïu " + n + " caràcter" + (n == 1? "" : "s") + "menys"; },
13
+ formatSelectionTooBig: function (limit) { return "Només podeu seleccionar " + limit + " element" + (limit == 1 ? "" : "s"); },
14
+ formatLoadMore: function (pageNumber) { return "S'estan carregant més resultats…"; },
15
+ formatSearching: function () { return "S'està cercant…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ca']);
19
+ })(jQuery);
js/select2/select2_locale_cs.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Czech translation.
3
+ *
4
+ * Author: Michal Marek <ahoj@michal-marek.cz>
5
+ * Author - sklonovani: David Vallner <david@vallner.net>
6
+ */
7
+ (function ($) {
8
+ "use strict";
9
+ // use text for the numbers 2 through 4
10
+ var smallNumbers = {
11
+ 2: function(masc) { return (masc ? "dva" : "dvě"); },
12
+ 3: function() { return "tři"; },
13
+ 4: function() { return "čtyři"; }
14
+ }
15
+ $.fn.select2.locales['cs'] = {
16
+ formatNoMatches: function () { return "Nenalezeny žádné položky"; },
17
+ formatInputTooShort: function (input, min) {
18
+ var n = min - input.length;
19
+ if (n == 1) {
20
+ return "Prosím zadejte ještě jeden znak";
21
+ } else if (n <= 4) {
22
+ return "Prosím zadejte ještě další "+smallNumbers[n](true)+" znaky";
23
+ } else {
24
+ return "Prosím zadejte ještě dalších "+n+" znaků";
25
+ }
26
+ },
27
+ formatInputTooLong: function (input, max) {
28
+ var n = input.length - max;
29
+ if (n == 1) {
30
+ return "Prosím zadejte o jeden znak méně";
31
+ } else if (n <= 4) {
32
+ return "Prosím zadejte o "+smallNumbers[n](true)+" znaky méně";
33
+ } else {
34
+ return "Prosím zadejte o "+n+" znaků méně";
35
+ }
36
+ },
37
+ formatSelectionTooBig: function (limit) {
38
+ if (limit == 1) {
39
+ return "Můžete zvolit jen jednu položku";
40
+ } else if (limit <= 4) {
41
+ return "Můžete zvolit maximálně "+smallNumbers[limit](false)+" položky";
42
+ } else {
43
+ return "Můžete zvolit maximálně "+limit+" položek";
44
+ }
45
+ },
46
+ formatLoadMore: function (pageNumber) { return "Načítají se další výsledky…"; },
47
+ formatSearching: function () { return "Vyhledávání…"; }
48
+ };
49
+
50
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['cs']);
51
+ })(jQuery);
js/select2/select2_locale_da.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Danish translation.
3
+ *
4
+ * Author: Anders Jenbo <anders@jenbo.dk>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['da'] = {
10
+ formatNoMatches: function () { return "Ingen resultater fundet"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Angiv venligst " + n + " tegn mere"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Angiv venligst " + n + " tegn mindre"; },
13
+ formatSelectionTooBig: function (limit) { return "Du kan kun vælge " + limit + " emne" + (limit === 1 ? "" : "r"); },
14
+ formatLoadMore: function (pageNumber) { return "Indlæser flere resultater…"; },
15
+ formatSearching: function () { return "Søger…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['da']);
19
+ })(jQuery);
js/select2/select2_locale_de.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 German translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['de'] = {
8
+ formatNoMatches: function () { return "Keine Übereinstimmungen gefunden"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Bitte " + n + " Zeichen mehr eingeben"; },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Bitte " + n + " Zeichen weniger eingeben"; },
11
+ formatSelectionTooBig: function (limit) { return "Sie können nur " + limit + " Eintr" + (limit === 1 ? "ag" : "äge") + " auswählen"; },
12
+ formatLoadMore: function (pageNumber) { return "Lade mehr Ergebnisse…"; },
13
+ formatSearching: function () { return "Suche…"; },
14
+ formatMatches: function (matches) { return matches + " Ergebnis " + (matches > 1 ? "se" : "") + " verfügbar, zum Navigieren die Hoch-/Runter-Pfeiltasten verwenden."; }
15
+ };
16
+
17
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['de']);
18
+ })(jQuery);
js/select2/select2_locale_el.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Greek translation.
3
+ *
4
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['el'] = {
10
+ formatNoMatches: function () { return "Δεν βρέθηκαν αποτελέσματα"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Παρακαλούμε εισάγετε " + n + " περισσότερο" + (n > 1 ? "υς" : "") + " χαρακτήρ" + (n > 1 ? "ες" : "α"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Παρακαλούμε διαγράψτε " + n + " χαρακτήρ" + (n > 1 ? "ες" : "α"); },
13
+ formatSelectionTooBig: function (limit) { return "Μπορείτε να επιλέξετε μόνο " + limit + " αντικείμεν" + (limit > 1 ? "α" : "ο"); },
14
+ formatLoadMore: function (pageNumber) { return "Φόρτωση περισσότερων…"; },
15
+ formatSearching: function () { return "Αναζήτηση…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['el']);
19
+ })(jQuery);
js/select2/select2_locale_en.js.template ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 <Language> translation.
3
+ *
4
+ * Author: Your Name <your@email>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['en'] = {
10
+ formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; },
11
+ formatNoMatches: function () { return "No matches found"; },
12
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1 ? "" : "s"); },
13
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); },
14
+ formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
15
+ formatLoadMore: function (pageNumber) { return "Loading more results…"; },
16
+ formatSearching: function () { return "Searching…"; }
17
+ };
18
+
19
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['en']);
20
+ })(jQuery);
js/select2/select2_locale_es.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Spanish translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['es'] = {
8
+ formatNoMatches: function () { return "No se encontraron resultados"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Por favor, introduzca " + n + " car" + (n == 1? "ácter" : "acteres"); },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Por favor, elimine " + n + " car" + (n == 1? "ácter" : "acteres"); },
11
+ formatSelectionTooBig: function (limit) { return "Sólo puede seleccionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
12
+ formatLoadMore: function (pageNumber) { return "Cargando más resultados…"; },
13
+ formatSearching: function () { return "Buscando…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['es']);
17
+ })(jQuery);
js/select2/select2_locale_et.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Estonian translation.
3
+ *
4
+ * Author: Kuldar Kalvik <kuldar@kalvik.ee>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['et'] = {
10
+ formatNoMatches: function () { return "Tulemused puuduvad"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Sisesta " + n + " täht" + (n == 1 ? "" : "e") + " rohkem"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Sisesta " + n + " täht" + (n == 1? "" : "e") + " vähem"; },
13
+ formatSelectionTooBig: function (limit) { return "Saad vaid " + limit + " tulemus" + (limit == 1 ? "e" : "t") + " valida"; },
14
+ formatLoadMore: function (pageNumber) { return "Laen tulemusi.."; },
15
+ formatSearching: function () { return "Otsin.."; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['et']);
19
+ })(jQuery);
js/select2/select2_locale_eu.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Basque translation.
3
+ *
4
+ * Author: Julen Ruiz Aizpuru <julenx at gmail dot com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['eu'] = {
10
+ formatNoMatches: function () {
11
+ return "Ez da bat datorrenik aurkitu";
12
+ },
13
+ formatInputTooShort: function (input, min) {
14
+ var n = min - input.length;
15
+ if (n === 1) {
16
+ return "Idatzi karaktere bat gehiago";
17
+ } else {
18
+ return "Idatzi " + n + " karaktere gehiago";
19
+ }
20
+ },
21
+ formatInputTooLong: function (input, max) {
22
+ var n = input.length - max;
23
+ if (n === 1) {
24
+ return "Idatzi karaktere bat gutxiago";
25
+ } else {
26
+ return "Idatzi " + n + " karaktere gutxiago";
27
+ }
28
+ },
29
+ formatSelectionTooBig: function (limit) {
30
+ if (limit === 1 ) {
31
+ return "Elementu bakarra hauta dezakezu";
32
+ } else {
33
+ return limit + " elementu hauta ditzakezu soilik";
34
+ }
35
+ },
36
+ formatLoadMore: function (pageNumber) {
37
+ return "Emaitza gehiago kargatzen…";
38
+ },
39
+ formatSearching: function () {
40
+ return "Bilatzen…";
41
+ }
42
+ };
43
+
44
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['eu']);
45
+ })(jQuery);
js/select2/select2_locale_fa.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Persian translation.
3
+ *
4
+ * Author: Ali Choopan <choopan@arsh.co>
5
+ * Author: Ebrahim Byagowi <ebrahim@gnu.org>
6
+ */
7
+ (function ($) {
8
+ "use strict";
9
+
10
+ $.fn.select2.locales['fa'] = {
11
+ formatMatches: function (matches) { return matches + " نتیجه موجود است، کلیدهای جهت بالا و پایین را برای گشتن استفاده کنید."; },
12
+ formatNoMatches: function () { return "نتیجه‌ای یافت نشد."; },
13
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "لطفاً " + n + " نویسه بیشتر وارد نمایید"; },
14
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "لطفاً " + n + " نویسه را حذف کنید."; },
15
+ formatSelectionTooBig: function (limit) { return "شما فقط می‌توانید " + limit + " مورد را انتخاب کنید"; },
16
+ formatLoadMore: function (pageNumber) { return "در حال بارگیری موارد بیشتر…"; },
17
+ formatSearching: function () { return "در حال جستجو…"; }
18
+ };
19
+
20
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['fa']);
21
+ })(jQuery);
js/select2/select2_locale_fi.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Finnish translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+ $.fn.select2.locales['fi'] = {
7
+ formatNoMatches: function () {
8
+ return "Ei tuloksia";
9
+ },
10
+ formatInputTooShort: function (input, min) {
11
+ var n = min - input.length;
12
+ return "Ole hyvä ja anna " + n + " merkkiä lisää";
13
+ },
14
+ formatInputTooLong: function (input, max) {
15
+ var n = input.length - max;
16
+ return "Ole hyvä ja anna " + n + " merkkiä vähemmän";
17
+ },
18
+ formatSelectionTooBig: function (limit) {
19
+ return "Voit valita ainoastaan " + limit + " kpl";
20
+ },
21
+ formatLoadMore: function (pageNumber) {
22
+ return "Ladataan lisää tuloksia…";
23
+ },
24
+ formatSearching: function () {
25
+ return "Etsitään…";
26
+ }
27
+ };
28
+
29
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['fi']);
30
+ })(jQuery);
js/select2/select2_locale_fr.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 French translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['fr'] = {
8
+ formatMatches: function (matches) { return matches + " résultats sont disponibles, utilisez les flèches haut et bas pour naviguer."; },
9
+ formatNoMatches: function () { return "Aucun résultat trouvé"; },
10
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Saisissez " + n + " caractère" + (n == 1? "" : "s") + " supplémentaire" + (n == 1? "" : "s") ; },
11
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Supprimez " + n + " caractère" + (n == 1? "" : "s"); },
12
+ formatSelectionTooBig: function (limit) { return "Vous pouvez seulement sélectionner " + limit + " élément" + (limit == 1 ? "" : "s"); },
13
+ formatLoadMore: function (pageNumber) { return "Chargement de résultats supplémentaires…"; },
14
+ formatSearching: function () { return "Recherche en cours…"; }
15
+ };
16
+
17
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['fr']);
18
+ })(jQuery);
js/select2/select2_locale_gl.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Galician translation
3
+ *
4
+ * Author: Leandro Regueiro <leandro.regueiro@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['gl'] = {
10
+ formatNoMatches: function () {
11
+ return "Non se atoparon resultados";
12
+ },
13
+ formatInputTooShort: function (input, min) {
14
+ var n = min - input.length;
15
+ if (n === 1) {
16
+ return "Engada un carácter";
17
+ } else {
18
+ return "Engada " + n + " caracteres";
19
+ }
20
+ },
21
+ formatInputTooLong: function (input, max) {
22
+ var n = input.length - max;
23
+ if (n === 1) {
24
+ return "Elimine un carácter";
25
+ } else {
26
+ return "Elimine " + n + " caracteres";
27
+ }
28
+ },
29
+ formatSelectionTooBig: function (limit) {
30
+ if (limit === 1 ) {
31
+ return "Só pode seleccionar un elemento";
32
+ } else {
33
+ return "Só pode seleccionar " + limit + " elementos";
34
+ }
35
+ },
36
+ formatLoadMore: function (pageNumber) {
37
+ return "Cargando máis resultados…";
38
+ },
39
+ formatSearching: function () {
40
+ return "Buscando…";
41
+ }
42
+ };
43
+
44
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['gl']);
45
+ })(jQuery);
js/select2/select2_locale_he.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Hebrew translation.
3
+ *
4
+ * Author: Yakir Sitbon <http://www.yakirs.net/>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['he'] = {
10
+ formatNoMatches: function () { return "לא נמצאו התאמות"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "נא להזין עוד " + n + " תווים נוספים"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "נא להזין פחות " + n + " תווים"; },
13
+ formatSelectionTooBig: function (limit) { return "ניתן לבחור " + limit + " פריטים"; },
14
+ formatLoadMore: function (pageNumber) { return "טוען תוצאות נוספות…"; },
15
+ formatSearching: function () { return "מחפש…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['he']);
19
+ })(jQuery);
js/select2/select2_locale_hr.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Croatian translation.
3
+ *
4
+ * @author Edi Modrić <edi.modric@gmail.com>
5
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
6
+ */
7
+ (function ($) {
8
+ "use strict";
9
+
10
+ $.fn.select2.locales['hr'] = {
11
+ formatNoMatches: function () { return "Nema rezultata"; },
12
+ formatInputTooShort: function (input, min) { return "Unesite još" + character(min - input.length); },
13
+ formatInputTooLong: function (input, max) { return "Unesite" + character(input.length - max) + " manje"; },
14
+ formatSelectionTooBig: function (limit) { return "Maksimalan broj odabranih stavki je " + limit; },
15
+ formatLoadMore: function (pageNumber) { return "Učitavanje rezultata…"; },
16
+ formatSearching: function () { return "Pretraga…"; }
17
+ };
18
+
19
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['hr']);
20
+
21
+ function character (n) {
22
+ return " " + n + " znak" + (n%10 < 5 && n%10 > 0 && (n%100 < 5 || n%100 > 19) ? n%10 > 1 ? "a" : "" : "ova");
23
+ }
24
+ })(jQuery);
js/select2/select2_locale_hu.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Hungarian translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['hu'] = {
8
+ formatNoMatches: function () { return "Nincs találat."; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Túl rövid. Még " + n + " karakter hiányzik."; },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Túl hosszú. " + n + " karakterrel több, mint kellene."; },
11
+ formatSelectionTooBig: function (limit) { return "Csak " + limit + " elemet lehet kiválasztani."; },
12
+ formatLoadMore: function (pageNumber) { return "Töltés…"; },
13
+ formatSearching: function () { return "Keresés…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['hu']);
17
+ })(jQuery);
js/select2/select2_locale_id.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Indonesian translation.
3
+ *
4
+ * Author: Ibrahim Yusuf <ibrahim7usuf@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['id'] = {
10
+ formatNoMatches: function () { return "Tidak ada data yang sesuai"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Masukkan " + n + " huruf lagi" + (n == 1 ? "" : "s"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Hapus " + n + " huruf" + (n == 1 ? "" : "s"); },
13
+ formatSelectionTooBig: function (limit) { return "Anda hanya dapat memilih " + limit + " pilihan" + (limit == 1 ? "" : "s"); },
14
+ formatLoadMore: function (pageNumber) { return "Mengambil data…"; },
15
+ formatSearching: function () { return "Mencari…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['id']);
19
+ })(jQuery);
js/select2/select2_locale_is.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Icelandic translation.
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['is'] = {
8
+ formatNoMatches: function () { return "Ekkert fannst"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vinsamlegast skrifið " + n + " staf" + (n > 1 ? "i" : "") + " í viðbót"; },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vinsamlegast styttið texta um " + n + " staf" + (n > 1 ? "i" : ""); },
11
+ formatSelectionTooBig: function (limit) { return "Þú getur aðeins valið " + limit + " atriði"; },
12
+ formatLoadMore: function (pageNumber) { return "Sæki fleiri niðurstöður…"; },
13
+ formatSearching: function () { return "Leita…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['is']);
17
+ })(jQuery);
js/select2/select2_locale_it.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Italian translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['it'] = {
8
+ formatNoMatches: function () { return "Nessuna corrispondenza trovata"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Inserisci ancora " + n + " caratter" + (n == 1? "e" : "i"); },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Inserisci " + n + " caratter" + (n == 1? "e" : "i") + " in meno"; },
11
+ formatSelectionTooBig: function (limit) { return "Puoi selezionare solo " + limit + " element" + (limit == 1 ? "o" : "i"); },
12
+ formatLoadMore: function (pageNumber) { return "Caricamento in corso…"; },
13
+ formatSearching: function () { return "Ricerca…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['it']);
17
+ })(jQuery);
js/select2/select2_locale_ja.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Japanese translation.
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['ja'] = {
8
+ formatNoMatches: function () { return "該当なし"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "後" + n + "文字入れてください"; },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "検索文字列が" + n + "文字長すぎます"; },
11
+ formatSelectionTooBig: function (limit) { return "最多で" + limit + "項目までしか選択できません"; },
12
+ formatLoadMore: function (pageNumber) { return "読込中・・・"; },
13
+ formatSearching: function () { return "検索中・・・"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ja']);
17
+ })(jQuery);
js/select2/select2_locale_ka.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Georgian (Kartuli) translation.
3
+ *
4
+ * Author: Dimitri Kurashvili dimakura@gmail.com
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['ka'] = {
10
+ formatNoMatches: function () { return "ვერ მოიძებნა"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "გთხოვთ შეიყვანოთ კიდევ " + n + " სიმბოლო"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "გთხოვთ წაშალოთ " + n + " სიმბოლო"; },
13
+ formatSelectionTooBig: function (limit) { return "თქვენ შეგიძლიათ მხოლოდ " + limit + " ჩანაწერის მონიშვნა"; },
14
+ formatLoadMore: function (pageNumber) { return "შედეგის ჩატვირთვა…"; },
15
+ formatSearching: function () { return "ძებნა…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ka']);
19
+ })(jQuery);
js/select2/select2_locale_ko.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Korean translation.
3
+ *
4
+ * @author Swen Mun <longfinfunnel@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['ko'] = {
10
+ formatNoMatches: function () { return "결과 없음"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "너무 짧습니다. "+n+"글자 더 입력해주세요."; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "너무 깁니다. "+n+"글자 지워주세요."; },
13
+ formatSelectionTooBig: function (limit) { return "최대 "+limit+"개까지만 선택하실 수 있습니다."; },
14
+ formatLoadMore: function (pageNumber) { return "불러오는 중…"; },
15
+ formatSearching: function () { return "검색 중…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ko']);
19
+ })(jQuery);
js/select2/select2_locale_lt.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Lithuanian translation.
3
+ *
4
+ * @author CRONUS Karmalakas <cronus dot karmalakas at gmail dot com>
5
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
6
+ */
7
+ (function ($) {
8
+ "use strict";
9
+
10
+ $.fn.select2.locales['lt'] = {
11
+ formatNoMatches: function () { return "Atitikmenų nerasta"; },
12
+ formatInputTooShort: function (input, min) { return "Įrašykite dar" + character(min - input.length); },
13
+ formatInputTooLong: function (input, max) { return "Pašalinkite" + character(input.length - max); },
14
+ formatSelectionTooBig: function (limit) {
15
+ return "Jūs galite pasirinkti tik " + limit + " element" + ((limit%100 > 9 && limit%100 < 21) || limit%10 == 0 ? "ų" : limit%10 > 1 ? "us" : "ą");
16
+ },
17
+ formatLoadMore: function (pageNumber) { return "Kraunama daugiau rezultatų…"; },
18
+ formatSearching: function () { return "Ieškoma…"; }
19
+ };
20
+
21
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['lt']);
22
+
23
+ function character (n) {
24
+ return " " + n + " simbol" + ((n%100 > 9 && n%100 < 21) || n%10 == 0 ? "ių" : n%10 > 1 ? "ius" : "į");
25
+ }
26
+ })(jQuery);
js/select2/select2_locale_lv.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Latvian translation.
3
+ *
4
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['lv'] = {
10
+ formatNoMatches: function () { return "Sakritību nav"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Lūdzu ievadiet vēl " + n + " simbol" + (n == 11 ? "us" : n%10 == 1 ? "u" : "us"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Lūdzu ievadiet par " + n + " simbol" + (n == 11 ? "iem" : n%10 == 1 ? "u" : "iem") + " mazāk"; },
13
+ formatSelectionTooBig: function (limit) { return "Jūs varat izvēlēties ne vairāk kā " + limit + " element" + (limit == 11 ? "us" : limit%10 == 1 ? "u" : "us"); },
14
+ formatLoadMore: function (pageNumber) { return "Datu ielāde…"; },
15
+ formatSearching: function () { return "Meklēšana…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['lv']);
19
+ })(jQuery);
js/select2/select2_locale_mk.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Macedonian translation.
3
+ *
4
+ * Author: Marko Aleksic <psybaron@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['mk'] = {
10
+ formatNoMatches: function () { return "Нема пронајдено совпаѓања"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Ве молиме внесете уште " + n + " карактер" + (n == 1 ? "" : "и"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Ве молиме внесете " + n + " помалку карактер" + (n == 1? "" : "и"); },
13
+ formatSelectionTooBig: function (limit) { return "Можете да изберете само " + limit + " ставк" + (limit == 1 ? "а" : "и"); },
14
+ formatLoadMore: function (pageNumber) { return "Вчитување резултати…"; },
15
+ formatSearching: function () { return "Пребарување…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['mk']);
19
+ })(jQuery);
js/select2/select2_locale_ms.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Malay translation.
3
+ *
4
+ * Author: Kepoweran <kepoweran@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['ms'] = {
10
+ formatNoMatches: function () { return "Tiada padanan yang ditemui"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Sila masukkan " + n + " aksara lagi"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Sila hapuskan " + n + " aksara"; },
13
+ formatSelectionTooBig: function (limit) { return "Anda hanya boleh memilih " + limit + " pilihan"; },
14
+ formatLoadMore: function (pageNumber) { return "Sedang memuatkan keputusan…"; },
15
+ formatSearching: function () { return "Mencari…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ms']);
19
+ })(jQuery);
js/select2/select2_locale_nl.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Dutch translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['nl'] = {
8
+ formatNoMatches: function () { return "Geen resultaten gevonden"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vul nog " + n + " karakter" + (n == 1? "" : "s") + " in"; },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Haal " + n + " karakter" + (n == 1? "" : "s") + " weg"; },
11
+ formatSelectionTooBig: function (limit) { return "Maximaal " + limit + " item" + (limit == 1 ? "" : "s") + " toegestaan"; },
12
+ formatLoadMore: function (pageNumber) { return "Meer resultaten laden…"; },
13
+ formatSearching: function () { return "Zoeken…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['nl']);
17
+ })(jQuery);
js/select2/select2_locale_no.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Norwegian translation.
3
+ *
4
+ * Author: Torgeir Veimo <torgeir.veimo@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['no'] = {
10
+ formatNoMatches: function () { return "Ingen treff"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vennligst skriv inn " + n + (n>1 ? " flere tegn" : " tegn til"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vennligst fjern " + n + " tegn"; },
13
+ formatSelectionTooBig: function (limit) { return "Du kan velge maks " + limit + " elementer"; },
14
+ formatLoadMore: function (pageNumber) { return "Laster flere resultater…"; },
15
+ formatSearching: function () { return "Søker…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['no']);
19
+ })(jQuery);
20
+
js/select2/select2_locale_pl.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Polish translation.
3
+ *
4
+ * @author Jan Kondratowicz <jan@kondratowicz.pl>
5
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
6
+ * @author Michał Połtyn <mike@poltyn.com>
7
+ */
8
+ (function ($) {
9
+ "use strict";
10
+
11
+ $.fn.select2.locales['pl'] = {
12
+ formatNoMatches: function () { return "Brak wyników"; },
13
+ formatInputTooShort: function (input, min) { return "Wpisz co najmniej" + character(min - input.length, "znak", "i"); },
14
+ formatInputTooLong: function (input, max) { return "Wpisana fraza jest za długa o" + character(input.length - max, "znak", "i"); },
15
+ formatSelectionTooBig: function (limit) { return "Możesz zaznaczyć najwyżej" + character(limit, "element", "y"); },
16
+ formatLoadMore: function (pageNumber) { return "Ładowanie wyników…"; },
17
+ formatSearching: function () { return "Szukanie…"; }
18
+ };
19
+
20
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['pl']);
21
+
22
+ function character (n, word, pluralSuffix) {
23
+ return " " + n + " " + word + (n == 1 ? "" : n%10 < 5 && n%10 > 1 && (n%100 < 5 || n%100 > 20) ? pluralSuffix : "ów");
24
+ }
25
+ })(jQuery);
js/select2/select2_locale_pt-BR.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Brazilian Portuguese translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['pt-BR'] = {
8
+ formatNoMatches: function () { return "Nenhum resultado encontrado"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Digite mais " + n + " caracter" + (n == 1? "" : "es"); },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " caracter" + (n == 1? "" : "es"); },
11
+ formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
12
+ formatLoadMore: function (pageNumber) { return "Carregando mais resultados…"; },
13
+ formatSearching: function () { return "Buscando…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['pt-BR']);
17
+ })(jQuery);
js/select2/select2_locale_pt-PT.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Portuguese (Portugal) translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['pt-PT'] = {
8
+ formatNoMatches: function () { return "Nenhum resultado encontrado"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduza " + n + " car" + (n == 1 ? "ácter" : "acteres"); },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " car" + (n == 1 ? "ácter" : "acteres"); },
11
+ formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); },
12
+ formatLoadMore: function (pageNumber) { return "A carregar mais resultados…"; },
13
+ formatSearching: function () { return "A pesquisar…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['pt-PT']);
17
+ })(jQuery);
js/select2/select2_locale_ro.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Romanian translation.
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+
7
+ $.fn.select2.locales['ro'] = {
8
+ formatNoMatches: function () { return "Nu a fost găsit nimic"; },
9
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vă rugăm să introduceți incă " + n + " caracter" + (n == 1 ? "" : "e"); },
10
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vă rugăm să introduceți mai puțin de " + n + " caracter" + (n == 1? "" : "e"); },
11
+ formatSelectionTooBig: function (limit) { return "Aveți voie să selectați cel mult " + limit + " element" + (limit == 1 ? "" : "e"); },
12
+ formatLoadMore: function (pageNumber) { return "Se încarcă…"; },
13
+ formatSearching: function () { return "Căutare…"; }
14
+ };
15
+
16
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ro']);
17
+ })(jQuery);
js/select2/select2_locale_rs.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Serbian translation.
3
+ *
4
+ * @author Limon Monte <limon.monte@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['rs'] = {
10
+ formatNoMatches: function () { return "Ništa nije pronađeno"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Ukucajte bar još " + n + " simbol" + (n % 10 == 1 && n % 100 != 11 ? "" : "a"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Obrišite " + n + " simbol" + (n % 10 == 1 && n % 100 != 11 ? "" : "a"); },
13
+ formatSelectionTooBig: function (limit) { return "Možete izabrati samo " + limit + " stavk" + (limit % 10 == 1 && limit % 100 != 11 ? "u" : (limit % 10 >= 2 && limit % 10 <= 4 && (limit % 100 < 12 || limit % 100 > 14)? "e" : "i")); },
14
+ formatLoadMore: function (pageNumber) { return "Preuzimanje još rezultata…"; },
15
+ formatSearching: function () { return "Pretraga…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['rs']);
19
+ })(jQuery);
js/select2/select2_locale_ru.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Russian translation.
3
+ *
4
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['ru'] = {
10
+ formatNoMatches: function () { return "Совпадений не найдено"; },
11
+ formatInputTooShort: function (input, min) { return "Пожалуйста, введите еще хотя бы" + character(min - input.length); },
12
+ formatInputTooLong: function (input, max) { return "Пожалуйста, введите на" + character(input.length - max) + " меньше"; },
13
+ formatSelectionTooBig: function (limit) { return "Вы можете выбрать не более " + limit + " элемент" + (limit%10 == 1 && limit%100 != 11 ? "а" : "ов"); },
14
+ formatLoadMore: function (pageNumber) { return "Загрузка данных…"; },
15
+ formatSearching: function () { return "Поиск…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ru']);
19
+
20
+ function character (n) {
21
+ return " " + n + " символ" + (n%10 < 5 && n%10 > 0 && (n%100 < 5 || n%100 > 20) ? n%10 > 1 ? "a" : "" : "ов");
22
+ }
23
+ })(jQuery);
js/select2/select2_locale_sk.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Slovak translation.
3
+ *
4
+ * Author: David Vallner <david@vallner.net>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+ // use text for the numbers 2 through 4
9
+ var smallNumbers = {
10
+ 2: function(masc) { return (masc ? "dva" : "dve"); },
11
+ 3: function() { return "tri"; },
12
+ 4: function() { return "štyri"; }
13
+ };
14
+ $.fn.select2.locales['sk'] = {
15
+ formatNoMatches: function () { return "Nenašli sa žiadne položky"; },
16
+ formatInputTooShort: function (input, min) {
17
+ var n = min - input.length;
18
+ if (n == 1) {
19
+ return "Prosím, zadajte ešte jeden znak";
20
+ } else if (n <= 4) {
21
+ return "Prosím, zadajte ešte ďalšie "+smallNumbers[n](true)+" znaky";
22
+ } else {
23
+ return "Prosím, zadajte ešte ďalších "+n+" znakov";
24
+ }
25
+ },
26
+ formatInputTooLong: function (input, max) {
27
+ var n = input.length - max;
28
+ if (n == 1) {
29
+ return "Prosím, zadajte o jeden znak menej";
30
+ } else if (n <= 4) {
31
+ return "Prosím, zadajte o "+smallNumbers[n](true)+" znaky menej";
32
+ } else {
33
+ return "Prosím, zadajte o "+n+" znakov menej";
34
+ }
35
+ },
36
+ formatSelectionTooBig: function (limit) {
37
+ if (limit == 1) {
38
+ return "Môžete zvoliť len jednu položku";
39
+ } else if (limit <= 4) {
40
+ return "Môžete zvoliť najviac "+smallNumbers[limit](false)+" položky";
41
+ } else {
42
+ return "Môžete zvoliť najviac "+limit+" položiek";
43
+ }
44
+ },
45
+ formatLoadMore: function (pageNumber) { return "Načítavajú sa ďalšie výsledky…"; },
46
+ formatSearching: function () { return "Vyhľadávanie…"; }
47
+ };
48
+
49
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['sk']);
50
+ })(jQuery);
js/select2/select2_locale_sv.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Swedish translation.
3
+ *
4
+ * Author: Jens Rantil <jens.rantil@telavox.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['sv'] = {
10
+ formatNoMatches: function () { return "Inga träffar"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Var god skriv in " + n + (n>1 ? " till tecken" : " tecken till"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Var god sudda ut " + n + " tecken"; },
13
+ formatSelectionTooBig: function (limit) { return "Du kan max välja " + limit + " element"; },
14
+ formatLoadMore: function (pageNumber) { return "Laddar fler resultat…"; },
15
+ formatSearching: function () { return "Söker…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['sv']);
19
+ })(jQuery);
js/select2/select2_locale_th.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Thai translation.
3
+ *
4
+ * Author: Atsawin Chaowanakritsanakul <joke@nakhon.net>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['th'] = {
10
+ formatNoMatches: function () { return "ไม่พบข้อมูล"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "โปรดพิมพ์เพิ่มอีก " + n + " ตัวอักษร"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "โปรดลบออก " + n + " ตัวอักษร"; },
13
+ formatSelectionTooBig: function (limit) { return "คุณสามารถเลือกได้ไม่เกิน " + limit + " รายการ"; },
14
+ formatLoadMore: function (pageNumber) { return "กำลังค้นข้อมูลเพิ่ม…"; },
15
+ formatSearching: function () { return "กำลังค้นข้อมูล…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['th']);
19
+ })(jQuery);
js/select2/select2_locale_tr.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Turkish translation.
3
+ *
4
+ * Author: Salim KAYABAŞI <salim.kayabasi@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['tr'] = {
10
+ formatNoMatches: function () { return "Sonuç bulunamadı"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "En az " + n + " karakter daha girmelisiniz"; },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return n + " karakter azaltmalısınız"; },
13
+ formatSelectionTooBig: function (limit) { return "Sadece " + limit + " seçim yapabilirsiniz"; },
14
+ formatLoadMore: function (pageNumber) { return "Daha fazla…"; },
15
+ formatSearching: function () { return "Aranıyor…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['tr']);
19
+ })(jQuery);
js/select2/select2_locale_ug-CN.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Uyghur translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+ $.fn.select2.locales['ug-CN'] = {
7
+ formatNoMatches: function () { return "ماس كېلىدىغان ئۇچۇر تېپىلمىدى"; },
8
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "يەنە " + n + " ھەرپ كىرگۈزۈڭ";},
9
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "" + n + "ھەرپ ئۆچۈرۈڭ";},
10
+ formatSelectionTooBig: function (limit) { return "ئەڭ كۆپ بولغاندا" + limit + " تال ئۇچۇر تاللىيالايسىز"; },
11
+ formatLoadMore: function (pageNumber) { return "ئۇچۇرلار ئوقۇلىۋاتىدۇ…"; },
12
+ formatSearching: function () { return "ئىزدەۋاتىدۇ…"; }
13
+ };
14
+
15
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['ug-CN']);
16
+ })(jQuery);
js/select2/select2_locale_uk.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Ukrainian translation.
3
+ *
4
+ * @author bigmihail <bigmihail@bigmir.net>
5
+ * @author Uriy Efremochkin <efremochkin@uriy.me>
6
+ */
7
+ (function ($) {
8
+ "use strict";
9
+
10
+ $.fn.select2.locales['uk'] = {
11
+ formatMatches: function (matches) { return character(matches, "результат") + " знайдено, використовуйте клавіші зі стрілками вверх та вниз для навігації."; },
12
+ formatNoMatches: function () { return "Нічого не знайдено"; },
13
+ formatInputTooShort: function (input, min) { return "Введіть буль ласка ще " + character(min - input.length, "символ"); },
14
+ formatInputTooLong: function (input, max) { return "Введіть буль ласка на " + character(input.length - max, "символ") + " менше"; },
15
+ formatSelectionTooBig: function (limit) { return "Ви можете вибрати лише " + character(limit, "елемент"); },
16
+ formatLoadMore: function (pageNumber) { return "Завантаження даних…"; },
17
+ formatSearching: function () { return "Пошук…"; }
18
+ };
19
+
20
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['uk']);
21
+
22
+ function character (n, word) {
23
+ return n + " " + word + (n%10 < 5 && n%10 > 0 && (n%100 < 5 || n%100 > 19) ? n%10 > 1 ? "и" : "" : "ів");
24
+ }
25
+ })(jQuery);
js/select2/select2_locale_vi.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Vietnamese translation.
3
+ *
4
+ * Author: Long Nguyen <olragon@gmail.com>
5
+ */
6
+ (function ($) {
7
+ "use strict";
8
+
9
+ $.fn.select2.locales['vi'] = {
10
+ formatNoMatches: function () { return "Không tìm thấy kết quả"; },
11
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "Vui lòng nhập nhiều hơn " + n + " ký tự" + (n == 1 ? "" : "s"); },
12
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "Vui lòng nhập ít hơn " + n + " ký tự" + (n == 1? "" : "s"); },
13
+ formatSelectionTooBig: function (limit) { return "Chỉ có thể chọn được " + limit + " tùy chọn" + (limit == 1 ? "" : "s"); },
14
+ formatLoadMore: function (pageNumber) { return "Đang lấy thêm kết quả…"; },
15
+ formatSearching: function () { return "Đang tìm…"; }
16
+ };
17
+
18
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['vi']);
19
+ })(jQuery);
20
+
js/select2/select2_locale_zh-CN.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Chinese translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+ $.fn.select2.locales['zh-CN'] = {
7
+ formatNoMatches: function () { return "没有找到匹配项"; },
8
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "请再输入" + n + "个字符";},
9
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "请删掉" + n + "个字符";},
10
+ formatSelectionTooBig: function (limit) { return "你只能选择最多" + limit + "项"; },
11
+ formatLoadMore: function (pageNumber) { return "加载结果中…"; },
12
+ formatSearching: function () { return "搜索中…"; }
13
+ };
14
+
15
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['zh-CN']);
16
+ })(jQuery);
js/select2/select2_locale_zh-TW.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Select2 Traditional Chinese translation
3
+ */
4
+ (function ($) {
5
+ "use strict";
6
+ $.fn.select2.locales['zh-TW'] = {
7
+ formatNoMatches: function () { return "沒有找到相符的項目"; },
8
+ formatInputTooShort: function (input, min) { var n = min - input.length; return "請再輸入" + n + "個字元";},
9
+ formatInputTooLong: function (input, max) { var n = input.length - max; return "請刪掉" + n + "個字元";},
10
+ formatSelectionTooBig: function (limit) { return "你只能選擇最多" + limit + "項"; },
11
+ formatLoadMore: function (pageNumber) { return "載入中…"; },
12
+ formatSearching: function () { return "搜尋中…"; }
13
+ };
14
+
15
+ $.extend($.fn.select2.defaults, $.fn.select2.locales['zh-TW']);
16
+ })(jQuery);
js/select2/select2x2.png ADDED
Binary file
languages/simple-history-sv_SE.mo CHANGED
Binary file
languages/simple-history-sv_SE.po CHANGED
@@ -1,374 +1,1342 @@
 
 
1
  msgid ""
2
  msgstr ""
3
- "Project-Id-Version: Simple History\n"
4
- "Report-Msgid-Bugs-To: \n"
5
- "POT-Creation-Date: 2012-02-07 13:48+0100\n"
6
- "PO-Revision-Date: 2012-02-09 13:50+0100\n"
7
- "Last-Translator: Jocke Gustin <jocke.gustin@gmail.com>\n"
8
  "Language-Team: \n"
 
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "Plural-Forms: nplurals=2; plural=n != 1;\n"
13
- "X-Poedit-Language: Swedish\n"
14
- "X-Poedit-Country: SWEDEN\n"
15
- "X-Poedit-SourceCharset: utf-8\n"
16
- "X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;\n"
17
- "X-Poedit-Basepath: .\n"
18
- "X-Poedit-Bookmarks: \n"
19
- "X-Poedit-SearchPath-0: ..\n"
20
- "X-Textdomain-Support: yes"
21
-
22
- #: index.php:132
23
- #: index.php:174
24
- #: index_orig.php:132
25
- #: index_orig.php:174
26
- #, php-format
27
- #@ simple-history
28
- msgid "Simple History for %s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  msgstr "Historik för %s"
30
 
31
- #: index.php:133
32
- #: index.php:175
33
- #: index_orig.php:133
34
- #: index_orig.php:175
35
- #, php-format
36
- #@ simple-history
37
  msgid "WordPress History for %s"
38
- msgstr "WordPress historik för %s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- #: index.php:145
41
- #: index.php:1201
42
- #: index_orig.php:145
43
- #, php-format
44
- #@ simple-history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  msgid "By %s"
46
  msgstr "Av %s"
47
 
48
- #: index.php:149
49
- #: index_orig.php:149
50
- #, php-format
51
- #@ simple-history
52
  msgid "%d occasions"
53
  msgstr "%d tillfällen"
54
 
55
- #: index.php:179
56
- #: index_orig.php:179
57
- #@ simple-history
58
- msgid "Wrong RSS secret"
59
- msgstr "Fel RSS lösenord"
60
 
61
- #: index.php:180
62
- #: index_orig.php:180
63
- #@ simple-history
64
- msgid "Your RSS secret for Simple History RSS feed is wrong. Please see WordPress settings for current link to the RSS feed."
65
- msgstr "Ditt RSS lösenord är fel. Vänligen gå till WordPress inställningsmeny för en länk till RSS-feed."
66
 
67
- #: index.php:254
68
- #: index_orig.php:254
69
- #@ simple-history
70
- msgid "on the dashboard"
71
- msgstr "på adminpanelen"
72
-
73
- #: index.php:259
74
- #: index_orig.php:259
75
- #@ simple-history
76
- msgid "as a page under the tools menu"
77
- msgstr "som en sida under verktygsmenyn"
78
-
79
- #: index.php:296
80
- #: index_orig.php:296
81
- #@ simple-history
82
- msgid "Created new secret RSS adress"
83
- msgstr "Skapade en ny hemlig RSS address"
84
-
85
- #: index.php:307
86
- #: index_orig.php:307
87
- #@ simple-history
88
- msgid "This is a secret RSS feed for Simple History. Only share the link with people you trust"
89
- msgstr "Detta är en hemlig RSS feed för \"Webbplatshistorik\". Dela den bara med personer du litar på."
90
-
91
- #: index.php:310
92
- #: index_orig.php:310
93
- #, fuzzy, php-format, php-format, php-format, php-format, php-format, php-format, php-format, php-format
94
- #@ simple-history
95
- msgid "You can <a href='%s#simple-history-settings-page'>generate a new address</a> for the RSS feed. This is useful if you think that the address has fallen into the wrong hands."
96
- msgstr "Du kan <a href='%s'>generera en ny adress</a> för din hemliga RSS feed. Detta är användbart ifall du tror att adressen kommit i fel händer."
97
-
98
- #: index.php:333
99
- #: index.php:348
100
- #: index.php:379
101
- #: index_orig.php:333
102
- #: index_orig.php:348
103
- #: index_orig.php:379
104
- #, php-format
105
- #@ default
106
- msgid "From %1$s on %2$s"
107
- msgstr "Från %1$s klockan %2$s"
108
-
109
- #: index.php:45
110
- #: index.php:792
111
- #: index_orig.php:786
112
- #@ simple-history
113
- msgid "By all users"
114
- msgstr "Av alla användare"
115
-
116
- #: index.php:811
117
- #: index_orig.php:805
118
- #@ simple-history
119
- msgid "Search"
120
- msgstr "Sök"
121
-
122
- #: index.php:1051
123
- #: index_orig.php:1041
124
- #@ simple-history
125
- msgid "Unknown or deleted user"
126
- msgstr "Okänd eller borttagen användare"
127
-
128
- #: index.php:476
129
- #: index.php:549
130
- #: index_orig.php:1069
131
- #@ simple-history
132
- msgid "created"
133
- msgstr "skapades"
134
-
135
- #: index.php:457
136
- #: index.php:469
137
- #: index.php:557
138
- #: index_orig.php:1071
139
- #: index_orig.php:1160
140
- #@ simple-history
141
- msgid "updated"
142
- msgstr "uppdaterades"
143
-
144
- #: index.php:351
145
- #: index.php:462
146
- #: index.php:483
147
- #: index.php:512
148
- #: index.php:554
149
- #: index_orig.php:1073
150
- #: index_orig.php:1162
151
- #@ simple-history
152
- msgid "deleted"
153
- msgstr "kastades i papperskorgen"
154
-
155
- #: index.php:451
156
- #: index_orig.php:1158
157
- #@ simple-history
158
- msgid "added"
159
- msgstr "lades till"
160
-
161
- #: index_orig.php:1164
162
- #@ simple-history
163
- msgid "logged in"
164
- msgstr "loggade in"
165
-
166
- #: index_orig.php:1166
167
- #@ simple-history
168
- msgid "logged out"
169
- msgstr "loggade ut"
170
-
171
- #: index.php:1203
172
- #: index_orig.php:1193
173
- #, php-format
174
- #@ default
175
- msgid "%s ago"
176
- msgstr "%s sedan"
177
-
178
- #: index.php:1212
179
- #: index_orig.php:1202
180
- #@ simple-history
181
- msgid "+ 1 occasion"
182
- msgstr "+1 tillfälle"
183
-
184
- #: index.php:1215
185
- #: index_orig.php:1205
186
- #, php-format
187
- #@ simple-history
188
- msgid "+ %d occasions"
189
- msgstr "+ %d tillfällen"
190
-
191
- #: index.php:1223
192
- #: index_orig.php:1213
193
- #, php-format
194
- #@ simple-history
195
- msgid "%s ago (%s at %s)"
196
- msgstr "%s sedan (%s den %s)"
197
-
198
- #: index.php:1246
199
- #: index_orig.php:1236
200
- #, fuzzy
201
- #@ simple-history
202
- msgid "Show 5 more"
203
- msgstr "Visa 5 till"
204
-
205
- #: index.php:1247
206
- #: index_orig.php:1237
207
- #, fuzzy
208
- #@ simple-history
209
- msgid "Show 15 more"
210
- msgstr "Visa 15 till"
211
-
212
- #: index.php:1248
213
- #: index_orig.php:1238
214
- #, fuzzy
215
- #@ simple-history
216
- msgid "Show 50 more"
217
- msgstr "Visa 50 till"
218
-
219
- #: index.php:1249
220
- #: index_orig.php:1239
221
- #, fuzzy
222
- #@ simple-history
223
- msgid "Show 100 more"
224
- msgstr "Visa 100 till"
225
-
226
- #: index.php:1252
227
- #: index_orig.php:1242
228
- #@ simple-history
229
- msgid "Loading..."
230
- msgstr "Laddar..."
231
-
232
- #: index.php:1253
233
- #: index_orig.php:1243
234
- #@ simple-history
235
- msgid "No more history items found."
236
- msgstr "Ingen historik hittad."
237
-
238
- #: index.php:1254
239
- #: index_orig.php:1244
240
- #@ simple-history
241
- msgid "Simple History RSS feed"
242
- msgstr "Webbplatshistorik RSS feed"
243
-
244
- #: index.php:1256
245
- #: index_orig.php:1246
246
- #@ simple-history
247
- msgid "Show"
248
- msgstr "Visa"
249
-
250
- #: index.php:1270
251
- #: index_orig.php:1260
252
- #@ simple-history
253
- msgid "No history items found."
254
- msgstr "Inga händelser hittade."
255
-
256
- #: index.php:1271
257
- #: index_orig.php:1261
258
- #@ simple-history
259
- msgid "Please note that Simple History only records things that happen after this plugin have been installed."
260
- msgstr "Vänligen notera att \"Webbplatshistorik\" enbart visar händelser efter detta plugin aktiverats."
261
-
262
- #: index.php:1095
263
- #@ simple-history
264
- msgid "attachment"
265
- msgstr "bilaga"
266
-
267
- #: index.php:1134
268
- #@ simple-history
269
- msgid "user"
270
- msgstr "användare"
271
-
272
- #: index.php:29
273
- #@ simple-history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  msgid "Simple History"
275
- msgstr "Webbplatshistorik"
 
 
 
 
276
 
277
- #: index.php:317
278
- #: index.php:321
279
- #: index.php:691
280
- #@ default
281
- msgid "Plugin"
282
  msgstr ""
 
 
283
 
284
- #: index.php:336
285
- #: index.php:351
286
- #: index.php:382
287
- #@ default
288
- msgid "Comment"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
- #: index.php:451
292
- #: index.php:457
293
- #: index.php:462
294
- #@ simple-history
295
- msgid "Attachment"
296
- msgstr "Filuppladdning"
297
-
298
- #: index.php:469
299
- #: index.php:476
300
- #: index.php:483
301
- #: index.php:497
302
- #: index.php:504
303
- #@ simple-history
304
- msgid "User"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  msgstr "Användare"
306
 
307
- #: index.php:512
308
- #@ default
309
- msgid "Post"
310
- msgstr ""
311
-
312
- #: index.php:42
313
- #: index.php:726
314
- #@ simple-history
315
- msgid "All types"
316
- msgstr "All historik"
317
-
318
- #: index.php:317
319
- #: index.php:691
320
- #@ simple-history
321
- msgid "activated"
322
- msgstr "aktiverades"
323
-
324
- #: index.php:321
325
- #@ simple-history
326
- msgid "deactivated"
327
- msgstr "inaktiverades"
328
-
329
- #: index.php:336
330
- #@ simple-history
331
- msgid "edited"
332
- msgstr "redigerade"
333
-
334
- #: index.php:366
335
- #@ simple-history
336
- msgid "approved"
337
- msgstr "godkände"
338
-
339
- #: index.php:368
340
- #@ simple-history
341
- msgid "unapproved"
342
- msgstr "nekade"
343
-
344
- #: index.php:370
345
- #@ simple-history
346
- msgid "marked as spam"
347
- msgstr "markerade som skräppost"
348
-
349
- #: index.php:372
350
- #@ simple-history
351
- msgid "trashed"
352
- msgstr "slängde"
353
-
354
- #: index.php:374
355
- #@ simple-history
356
- msgid "untrashed"
357
- msgstr "återställde"
358
-
359
- #: index.php:497
360
- #@ simple-history
361
- msgid "logged_in"
362
- msgstr "loggade in"
363
-
364
- #: index.php:504
365
- #@ simple-history
366
- msgid "logged_out"
367
- msgstr "loggade ut"
368
-
369
- #: index.php:1205
370
- #, php-format
371
- #@ simple-history
372
- msgid "%s at %s"
373
- msgstr "%s klockan %s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
 
 
1
+ # Copyright (C) 2014 Simple History
2
+ # This file is distributed under the same license as the Simple History package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Simple History 2\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/support/plugin/Simple-History\n"
7
+ "POT-Creation-Date: 2014-12-01 19:54:25+00:00\n"
8
+ "PO-Revision-Date: 2014-12-01 21:09+0100\n"
9
+ "Last-Translator: Pär Thernström <par.thernstrom@gmail.com>\n"
10
  "Language-Team: \n"
11
+ "Language: sv\n"
12
  "MIME-Version: 1.0\n"
13
  "Content-Type: text/plain; charset=UTF-8\n"
14
  "Content-Transfer-Encoding: 8bit\n"
15
+ "X-Generator: Poedit 1.6.10\n"
16
+ "X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;"
17
+ "_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;"
18
+ "esc_html_x:1,2c\n"
19
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+ "X-Poedit-SourceCharset: UTF-8\n"
21
+ "X-Poedit-Basepath: ../\n"
22
+ "X-Textdomain-Support: yes\n"
23
+ "X-Poedit-SearchPath-0: .\n"
24
+
25
+ #: SimpleHistory.php:423 SimpleHistory.php:662
26
+ msgid "Settings"
27
+ msgstr "Inställningar"
28
+
29
+ #: SimpleHistory.php:434
30
+ msgid "Log (debug)"
31
+ msgstr "Logg (debug)"
32
+
33
+ #: SimpleHistory.php:439
34
+ msgid "Styles example (debug)"
35
+ msgstr "Stilexempel (debug)"
36
+
37
+ #: SimpleHistory.php:677
38
+ msgid "History"
39
+ msgstr "Historik"
40
+
41
+ #: SimpleHistory.php:753
42
+ msgid "Remove all log items?"
43
+ msgstr "Ta bort alla händelser?"
44
+
45
+ #: SimpleHistory.php:755
46
+ msgid "Go to the first page"
47
+ msgstr "Gå till första sidan"
48
+
49
+ #: SimpleHistory.php:756
50
+ msgid "Go to the previous page"
51
+ msgstr "Gå till föregående sida"
52
+
53
+ #: SimpleHistory.php:757
54
+ msgid "Go to the next page"
55
+ msgstr "Gå till nästa sida"
56
+
57
+ #: SimpleHistory.php:758
58
+ msgid "Go to the last page"
59
+ msgstr "Gå till sista sidan"
60
+
61
+ #: SimpleHistory.php:759
62
+ msgid "Current page"
63
+ msgstr "Aktuell sida"
64
+
65
+ #: SimpleHistory.php:761
66
+ msgid "Oups, the log could not be loaded right now."
67
+ msgstr "Hoppsan, historiken kunde inte laddas just nu."
68
+
69
+ #: SimpleHistory.php:762
70
+ msgid "Your search did not match any history events."
71
+ msgstr "Din sökning matchade inte några händelser i historiken."
72
+
73
+ #: SimpleHistory.php:1070 SimpleHistory.php:1173
74
+ msgid "Simple History Settings"
75
+ msgstr "Inställningar för Simple History"
76
+
77
+ #: SimpleHistory.php:1104
78
+ msgid "No valid callback found"
79
+ msgstr "Inget giltigt callback hittades."
80
+
81
+ #: SimpleHistory.php:1194
82
+ msgid "Cleared database"
83
+ msgstr "Databasen rensades"
84
+
85
+ #: SimpleHistory.php:1221
86
+ msgid "Show history"
87
+ msgstr "Visa historik"
88
+
89
+ #: SimpleHistory.php:1234
90
+ msgid "Number of items per page"
91
+ msgstr "Antal händelser per sida"
92
+
93
+ #: SimpleHistory.php:1246
94
+ msgid "Clear log"
95
+ msgstr "Rensa logg"
96
+
97
+ #: SimpleHistory.php:1466
98
+ msgid "on the dashboard"
99
+ msgstr "i panelen"
100
+
101
+ #: SimpleHistory.php:1471
102
+ msgid "as a page under the dashboard menu"
103
+ msgstr "som en sida under panel-menyn"
104
+
105
+ #: SimpleHistory.php:1487
106
+ msgid "Items in the database are automatically removed after %1$s days."
107
+ msgstr "Händelser i databasen tas automatiskt bort efter %1$s dagar."
108
+
109
+ #: SimpleHistory.php:1489
110
+ msgid "Items in the database are kept forever."
111
+ msgstr "Händelser i databasen lagras för evigt."
112
+
113
+ #: SimpleHistory.php:1493
114
+ msgid "Clear log now"
115
+ msgstr "Rensa loggen nu"
116
+
117
+ #: SimpleHistory.php:1713
118
+ msgid "+%1$s more"
119
+ msgstr "+%1$s fler"
120
+
121
+ #: SimpleHistory.php:1720
122
+ msgid "Loading…"
123
+ msgstr "Laddar historik..."
124
+
125
+ #: SimpleHistory.php:1727
126
+ msgid "Showing %1$s more"
127
+ msgstr "+%1$s fler"
128
+
129
+ #: SimpleHistory.php:1745
130
+ msgid "Context data"
131
+ msgstr "Kontextuell data"
132
+
133
+ #: SimpleHistory.php:1746
134
+ msgid "This is potentially useful meta data that a logger have saved."
135
+ msgstr "Detta eventuellt användbar metadata som en logger har sparat."
136
+
137
+ #: SimpleHistory.php:2067
138
+ msgid "No events today so far."
139
+ msgstr "Inga händelser idag ännu."
140
+
141
+ #: SimpleHistory.php:2071
142
+ msgid "%1$d event today from one user."
143
+ msgstr "%1$d händelse idag från en användare."
144
+
145
+ #: SimpleHistory.php:2075
146
+ msgid "%1$d events today from %2$d users."
147
+ msgstr "%1$d händelser idag från %2$d användare."
148
+
149
+ #: SimpleHistory.php:2079
150
+ msgid "%1$d events today from one user."
151
+ msgstr "%1$d händelser idag från en användare."
152
+
153
+ #: dropins/SimpleHistoryDonateDropin.php:36
154
+ msgid "Donate"
155
+ msgstr "Donera"
156
+
157
+ #: dropins/SimpleHistoryDonateDropin.php:72
158
+ msgid ""
159
+ "If you find Simple History useful please <a href=\"%1$s\">donate</a> or <a "
160
+ "href=\"%2$s\">buy me something from my Amazon wish list</a>."
161
+ msgstr ""
162
+ "Om Simple History är användbar för dig så <a href=\"%1$s\">donera</a> eller "
163
+ "<a href=\"%2$s\">köp mig något från min Amazon wish list</a>."
164
+
165
+ #: dropins/SimpleHistoryFilterDropin.php:42
166
+ msgid "Filter history"
167
+ msgstr "Filtrera historik"
168
+
169
+ #: dropins/SimpleHistoryFilterDropin.php:49
170
+ msgid "All log levels"
171
+ msgstr "Alla loggnivåer"
172
+
173
+ #: dropins/SimpleHistoryFilterDropin.php:63
174
+ msgid "All messages"
175
+ msgstr "Alla meddelanden"
176
+
177
+ #: dropins/SimpleHistoryFilterDropin.php:117
178
+ msgid "All users"
179
+ msgstr "Alla användare"
180
+
181
+ #: dropins/SimpleHistoryFilterDropin.php:138
182
+ msgid "All dates"
183
+ msgstr "Alla datum"
184
+
185
+ #: dropins/SimpleHistoryFilterDropin.php:152
186
+ msgid "Filter"
187
+ msgstr "Filtrera"
188
+
189
+ #: dropins/SimpleHistoryNewRowsNotifier.php:80
190
+ msgid "1 new row"
191
+ msgid_plural "%d new rows"
192
+ msgstr[0] "1 ny rad"
193
+ msgstr[1] "%d nya rader"
194
+
195
+ #: dropins/SimpleHistoryRSSDropin.php:55
196
+ msgid "Address"
197
+ msgstr "Adress"
198
+
199
+ #: dropins/SimpleHistoryRSSDropin.php:64
200
+ msgid "Regenerate"
201
+ msgstr "Skapa ny"
202
+
203
+ #: dropins/SimpleHistoryRSSDropin.php:81
204
+ msgid "Created new secret RSS address"
205
+ msgstr "Skapade en ny hemlig RSS-adress"
206
+
207
+ #: dropins/SimpleHistoryRSSDropin.php:148
208
+ #: dropins/SimpleHistoryRSSDropin.php:256
209
+ msgid "History for %s"
210
  msgstr "Historik för %s"
211
 
212
+ #: dropins/SimpleHistoryRSSDropin.php:149
213
+ #: dropins/SimpleHistoryRSSDropin.php:257
 
 
 
 
214
  msgid "WordPress History for %s"
215
+ msgstr "WordPress-historik för %s"
216
+
217
+ #: dropins/SimpleHistoryRSSDropin.php:196
218
+ msgid "+%1$s occasion"
219
+ msgid_plural "+%1$s occasions"
220
+ msgstr[0] "+%1$s tillfälle"
221
+ msgstr[1] "+%1$s tillfällen"
222
+
223
+ #: dropins/SimpleHistoryRSSDropin.php:260
224
+ msgid "Wrong RSS secret"
225
+ msgstr "Fel RSS-lösenord"
226
+
227
+ #: dropins/SimpleHistoryRSSDropin.php:261
228
+ msgid ""
229
+ "Your RSS secret for Simple History RSS feed is wrong. Please see WordPress "
230
+ "settings for current link to the RSS feed."
231
+ msgstr ""
232
+ "Ditt RSS-lösenord för RSS-flödet för Simple History är felaktigt. Gå till "
233
+ "WordPress-inställningarna för att få en aktuell länk till RSS-flöded."
234
+
235
+ #: dropins/SimpleHistoryRSSDropin.php:312
236
+ msgid ""
237
+ "You can generate a new address for the RSS feed. This is useful if you think "
238
+ "that the address has fallen into the wrong hands."
239
+ msgstr ""
240
+ "Du kan skapa en ny adress för RSS-flödet. Detta är användbart om du "
241
+ "misstänker att adressen kommit i orätta händer."
242
+
243
+ #: dropins/SimpleHistoryRSSDropin.php:315
244
+ msgid "Generate new address"
245
+ msgstr "Skapa ny adress"
246
+
247
+ #: dropins/SimpleHistoryRSSDropin.php:343
248
+ msgid ""
249
+ "Simple History has a RSS feed which you can subscribe to and receive log "
250
+ "updates. Make sure you only share the feed with people you trust, since it "
251
+ "can contain sensitive or confidential information."
252
+ msgstr ""
253
+ "Simple History har ett RSS-flöde som du kan prenumerera på. Dela bara RSS-"
254
+ "adressen med personer du litar på, eftersom den kan innehåll känslig eller "
255
+ "konfidentiell information."
256
+
257
+ #: dropins/SimpleHistorySettingsLogtestDropin.php:20
258
+ msgid "Test data (debug)"
259
+ msgstr "Testdata (debug)"
260
+
261
+ #: dropins/SimpleHistorySettingsStatsDropin.php:27
262
+ msgid "Stats"
263
+ msgstr "Statistik"
264
 
265
+ #: index.php:54
266
+ msgid ""
267
+ "Simple History is a great plugin, but to use it your server must have at "
268
+ "least PHP 5.3 installed."
269
+ msgstr ""
270
+ "Simple History är en rackarns bra plugin, men för att använda den måste din "
271
+ "server ha minst PHP 5.3 installerat."
272
+
273
+ #: loggers/SimpleCommentsLogger.php:680
274
+ msgid "Spam"
275
+ msgstr "Skräp"
276
+
277
+ #: loggers/SimpleCommentsLogger.php:682
278
+ msgid "Approved"
279
+ msgstr "Godkänd"
280
+
281
+ #: loggers/SimpleCommentsLogger.php:684
282
+ msgid "Pending"
283
+ msgstr "Väntande"
284
+
285
+ #: loggers/SimpleCommentsLogger.php:698
286
+ msgid "Trackback"
287
+ msgstr "Trackback"
288
+
289
+ #: loggers/SimpleCommentsLogger.php:700
290
+ msgid "Pingback"
291
+ msgstr "Pingback"
292
+
293
+ #: loggers/SimpleCommentsLogger.php:702
294
+ msgid "Comment"
295
+ msgstr "Kommentar"
296
+
297
+ #: loggers/SimpleCoreUpdatesLogger.php:29
298
+ msgid "Updated WordPress from {prev_version} to {new_version}"
299
+ msgstr "Uppdaterade WordPress från {prev_version} till {new_version}"
300
+
301
+ #: loggers/SimpleCoreUpdatesLogger.php:30
302
+ msgid "WordPress auto-updated from {prev_version} to {new_version}"
303
+ msgstr ""
304
+ "WordPress uppdaterades automatiskt från {prev_version} till {new_version}"
305
+
306
+ #: loggers/SimpleExportLogger.php:23
307
+ msgid "Created XML export"
308
+ msgstr "Skapade XML-export"
309
+
310
+ #: loggers/SimpleLegacyLogger.php:88
311
  msgid "By %s"
312
  msgstr "Av %s"
313
 
314
+ #: loggers/SimpleLegacyLogger.php:93
 
 
 
315
  msgid "%d occasions"
316
  msgstr "%d tillfällen"
317
 
318
+ #: loggers/SimpleLogger.php:205
319
+ msgid "Deleted user (had id %1$s, email %2$s, login %3$s)"
320
+ msgstr "Raderad användare (hade id %1$s, epost %2$s, login %3$s)"
 
 
321
 
322
+ #: loggers/SimpleLogger.php:220
323
+ msgid "Anonymous web user"
324
+ msgstr "Anonym webbanvändare"
 
 
325
 
326
+ #: loggers/SimpleLogger.php:224
327
+ msgid "Anonymous user from %1$s"
328
+ msgstr "Anonym användare från %1$s"
329
+
330
+ #: loggers/SimpleLogger.php:291
331
+ msgid "Just now"
332
+ msgstr "Just nu"
333
+
334
+ #. translators: Date format for log row header, see http:php.net/date
335
+ #: loggers/SimpleLogger.php:296
336
+ msgid "M j, Y \\a\\t G:i"
337
+ msgstr "M j, Y \\a\\t G:i"
338
+
339
+ #. translators: 1: last modified date and time in human time diff-format
340
+ #: loggers/SimpleLogger.php:304
341
+ msgid "%1$s ago"
342
+ msgstr "%1$s sedan"
343
+
344
+ #: loggers/SimpleMediaLogger.php:23
345
+ msgid "Created {post_type} \"{attachment_title}\""
346
+ msgstr "Skapade {post_type} \"{attachment_title}\""
347
+
348
+ #: loggers/SimpleMediaLogger.php:24
349
+ msgid "Edited {post_type} \"{attachment_title}\""
350
+ msgstr "Redigerade {post_type} \"{attachment_title}\""
351
+
352
+ #: loggers/SimpleMediaLogger.php:25
353
+ msgid "Deleted {post_type} \"{attachment_title}\" (\"{attachment_filename}\")"
354
+ msgstr ""
355
+ "Raderade {post_type} \"{attachment_title}\" (\"{attachment_filename}\")"
356
+
357
+ #: loggers/SimpleMediaLogger.php:81
358
+ msgid "Edited {post_type} <a href=\"{edit_link}\">\"{attachment_title}\"</a>"
359
+ msgstr ""
360
+ "Redigerade {post_type} <a href=\"{edit_link}\">\"{attachment_title}\"</a>"
361
+
362
+ #: loggers/SimpleMediaLogger.php:85
363
+ msgid "Uploaded {post_type} <a href=\"{edit_link}\">\"{attachment_title}\"</a>"
364
+ msgstr ""
365
+ "Laddade upp {post_type} <a href=\"{edit_link}\">\"{attachment_title}\"</a>"
366
+
367
+ #: loggers/SimpleMediaLogger.php:180
368
+ msgid "{attachment_thumb}"
369
+ msgstr "{attachment_thumb}"
370
+
371
+ #: loggers/SimpleMediaLogger.php:189
372
+ msgid "{attachment_size_format}"
373
+ msgstr "{attachment_size_format}"
374
+
375
+ #: loggers/SimpleMediaLogger.php:190
376
+ msgid "{attachment_filetype_extension}"
377
+ msgstr "{attachment_filetype_extension}"
378
+
379
+ #: loggers/SimpleMediaLogger.php:192
380
+ msgid "{full_image_width} × {full_image_height}"
381
+ msgstr "{full_image_width} × {full_image_height}"
382
+
383
+ #: loggers/SimpleMenuLogger.php:23
384
+ msgid "Created menu \"{menu_name}\""
385
+ msgstr "Skapade meny \"{menu_name}\""
386
+
387
+ #: loggers/SimpleMenuLogger.php:24
388
+ msgid "Edited menu \"{menu_name}\""
389
+ msgstr "Redigerade menyn \"{menu_name}\""
390
+
391
+ #: loggers/SimpleMenuLogger.php:25
392
+ msgid "Deleted menu \"{menu_name}\""
393
+ msgstr "Raderade meny \"{menu_name}\""
394
+
395
+ #: loggers/SimpleMenuLogger.php:26
396
+ msgid "Edited a menu item"
397
+ msgstr "Redigerade ett menyalternativ"
398
+
399
+ #: loggers/SimpleMenuLogger.php:27
400
+ msgid "Updated menu locations"
401
+ msgstr "Uppdaterade meny-platser"
402
+
403
+ #: loggers/SimpleOptionsLogger.php:140
404
+ msgid "Updated option \"{option}\""
405
+ msgstr "Uppdaterade inställningen \"{option}\""
406
+
407
+ #: loggers/SimpleOptionsLogger.php:242 loggers/SimpleThemeLogger.php:570
408
+ msgid "New value"
409
+ msgstr "Nytt värde"
410
+
411
+ #: loggers/SimpleOptionsLogger.php:253 loggers/SimpleThemeLogger.php:582
412
+ msgid "Old value"
413
+ msgstr "Gammalt värde"
414
+
415
+ #: loggers/SimpleOptionsLogger.php:268 loggers/SimpleOptionsLogger.php:285
416
+ msgid "Settings page"
417
+ msgstr "Inställningssida"
418
+
419
+ #: loggers/SimplePostLogger.php:30
420
+ msgid "Created {post_type} \"{post_title}\""
421
+ msgstr "Skapade {post_type} \"{post_title}\""
422
+
423
+ #: loggers/SimplePostLogger.php:31
424
+ msgid "Updated {post_type} \"{post_title}\""
425
+ msgstr "Uppdaterade {post_type} \"{post_title}\""
426
+
427
+ #: loggers/SimplePostLogger.php:32
428
+ msgid "Restored {post_type} \"{post_title}\" from trash"
429
+ msgstr "Återställde {post_type} \"{post_title}\" från papperskorgen"
430
+
431
+ #: loggers/SimplePostLogger.php:33 loggers/SimplePostLogger.php:236
432
+ msgid "Deleted {post_type} \"{post_title}\""
433
+ msgstr "Raderade {post_type} \"{post_title}\""
434
+
435
+ #: loggers/SimplePostLogger.php:34
436
+ msgid "Moved {post_type} \"{post_title}\" to the trash"
437
+ msgstr "Flyttade {post_type} \"{post_title}\" till papperskorgen"
438
+
439
+ #: loggers/SimplePostLogger.php:232
440
+ msgid "Updated {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a>"
441
+ msgstr "Uppdaterade {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a>"
442
+
443
+ #: loggers/SimplePostLogger.php:240
444
+ msgid "Created {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a>"
445
+ msgstr "Skapade {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a>"
446
+
447
+ #: loggers/SimplePostLogger.php:245
448
+ msgid ""
449
+ "Moved {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a> to the trash"
450
+ msgstr ""
451
+ "Flyttade {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a> till "
452
+ "papperskorgen"
453
+
454
+ #: loggers/SimpleThemeLogger.php:26
455
+ msgid "Switched theme to \"{theme_name}\" from \"{prev_theme_name}\""
456
+ msgstr "Bytte tema till \"{theme_name}\" från \"{prev_theme_name}\""
457
+
458
+ #: loggers/SimpleThemeLogger.php:27
459
+ msgid "Customized theme appearance \"{setting_id}\""
460
+ msgstr "Ändrade temats inställning för \"{setting_id}\""
461
+
462
+ #: loggers/SimpleThemeLogger.php:28
463
+ msgid "Removed widget \"{widget_id_base}\" from sidebar \"{sidebar_id}\""
464
+ msgstr "Tog bort widget \"{widget_id_base}\" från sidebar \"{sidebar_id}\""
465
+
466
+ #: loggers/SimpleThemeLogger.php:29
467
+ msgid "Added widget \"{widget_id_base}\" to sidebar \"{sidebar_id}\""
468
+ msgstr "Lade till widget \"{widget_id_base}\" till sidebar \"{sidebar_id}\""
469
+
470
+ #: loggers/SimpleThemeLogger.php:30
471
+ msgid "Changed widget order \"{widget_id_base}\" in sidebar \"{sidebar_id}\""
472
+ msgstr ""
473
+ "Ändrade ordningen på widgeten \"{widget_id_base}\" i sidebar \"{sidebar_id}\""
474
+
475
+ #: loggers/SimpleThemeLogger.php:31
476
+ msgid "Changed widget \"{widget_id_base}\" in sidebar \"{sidebar_id}\""
477
+ msgstr "Ändrade widgeten \"{widget_id_base}\" i sidebar \"{sidebar_id}\""
478
+
479
+ #: loggers/SimpleThemeLogger.php:32
480
+ msgid "Changed settings for the theme custom background"
481
+ msgstr "Ändrade inställningar för temats anpassade bakgrund"
482
+
483
+ #: loggers/SimpleThemeLogger.php:532
484
+ msgid "Section"
485
+ msgstr "Sektion"
486
+
487
+ #: loggers/SimpleUserLogger.php:23
488
+ msgid ""
489
+ "Failed to login to account with username \"{login_user_login}\" because an "
490
+ "incorrect password was entered"
491
+ msgstr ""
492
+ "Misslyckades att logga in på kontot med användarnamnet "
493
+ "\"{login_user_login}\" eftersom ett felaktigt lösenord angavs"
494
+
495
+ #: loggers/SimpleUserLogger.php:24
496
+ msgid ""
497
+ "Failed to login with username \"{failed_login_username}\" because no user "
498
+ "with that username exist"
499
+ msgstr ""
500
+ "Misslyckades att logga in med användarnamn \"{failed_login_username}\" "
501
+ "eftersom ingen användare med det användarnamnet finns"
502
+
503
+ #: loggers/SimpleUserLogger.php:25
504
+ msgid "Logged in"
505
+ msgstr "Loggade in"
506
+
507
+ #: loggers/SimpleUserLogger.php:26
508
+ msgid "Unknown user logged in"
509
+ msgstr "Okänd användare loggade in"
510
+
511
+ #: loggers/SimpleUserLogger.php:27
512
+ msgid "Logged out"
513
+ msgstr "Loggade ut"
514
+
515
+ #: loggers/SimpleUserLogger.php:28
516
+ msgid "Edited the profile for user {edited_user_login} ({edited_user_email})"
517
+ msgstr ""
518
+ "Redigerade profilen för användaren {edited_user_login} ({edited_user_email})"
519
+
520
+ #: loggers/SimpleUserLogger.php:29
521
+ msgid ""
522
+ "Created user {created_user_login} ({created_user_email}) with role "
523
+ "{created_user_role}"
524
+ msgstr ""
525
+ "Skapade användare {created_user_login} ({created_user_email}) med rollen "
526
+ "{created_user_role}"
527
+
528
+ #: loggers/SimpleUserLogger.php:30
529
+ msgid "Deleted user {deleted_user_login} ({deleted_user_email})"
530
+ msgstr "Raderade användaren {deleted_user_login} ({deleted_user_email})"
531
+
532
+ #: loggers/SimpleUserLogger.php:153
533
+ msgid "Edited <a href=\"{edit_profile_link}\">your profile</a>"
534
+ msgstr "Redigerade <a href=\"{edit_profile_link}\">din profl</a>"
535
+
536
+ #: loggers/SimpleUserLogger.php:157
537
+ msgid "Edited <a href=\"{edit_profile_link}\">their profile</a>"
538
+ msgstr "Redigerade <a href=\"{edit_profile_link}\">sin profil</a>"
539
+
540
+ #: loggers/SimpleUserLogger.php:166
541
+ msgid "Edited your profile"
542
+ msgstr "Redigerade din profil"
543
+
544
+ #: loggers/SimpleUserLogger.php:177
545
+ msgid ""
546
+ "Edited the profile for user <a href="
547
+ "\"{edit_profile_link}\">{edited_user_login} ({edited_user_email})</a>"
548
+ msgstr ""
549
+ "Redigerade profilen för användaren <a href="
550
+ "\"{edit_profile_link}\">{edited_user_login} ({edited_user_email})</a>"
551
+
552
+ #: node_modules/grunt-wp-i18n/test/fixtures/basic-theme/exclude/file.php:3
553
+ #: node_modules/grunt-wp-i18n/test/fixtures/plugin-include/plugin-include.php:6
554
+ msgid "Exclude"
555
+ msgstr "Exkludera"
556
+
557
+ #: node_modules/grunt-wp-i18n/test/fixtures/plugin-include/include/file.php:2
558
+ msgid "Include"
559
+ msgstr "Inkludera"
560
+
561
+ #: node_modules/grunt-wp-i18n/test/fixtures/text-domains/add-domain.php:2
562
+ #: node_modules/grunt-wp-i18n/test/fixtures/text-domains/update-domains.php:2
563
+ #: node_modules/grunt-wp-i18n/test/fixtures/text-domains/update-domains.php:3
564
+ msgid "String"
565
+ msgstr "Sträng"
566
+
567
+ #: templates/settings-statsIntro.php:19
568
+ msgid "<b>%1$s rows</b> have been logged the last <b>%2$s days</b>"
569
+ msgstr "<b>%1$s händelser</b> har loggats de senaste <b>%2$s dagarna</b>"
570
+
571
+ #: templates/settings-statsLogLevels.php:4
572
+ msgid "Log levels"
573
+ msgstr "Loggnivåer"
574
+
575
+ #: templates/settings-statsLogLevels.php:6
576
+ msgid "Number of rows logged for each log level."
577
+ msgstr "Antal loggade händelser för respektive loglevel."
578
+
579
+ #: templates/settings-statsLoggers.php:4
580
+ msgid "Loggers"
581
+ msgstr "Loggare"
582
+
583
+ #: templates/settings-statsRowsPerDay.php:4
584
+ msgid "Rows per day"
585
+ msgstr "Händelser per dag"
586
+
587
+ #: templates/settings-statsUsers.php:4
588
+ msgid "Users"
589
+ msgstr "Användare"
590
+
591
+ #: templates/settings-statsUsers.php:6
592
+ msgid "Number of logged items for the 5 users with most logged rows."
593
+ msgstr ""
594
+ "Antal loggade händelser för dom 5 användarna med mest loggade händelser."
595
+
596
+ #: templates/settings-statsUsers.php:7
597
+ msgid "Deleted users are also included."
598
+ msgstr "Raderade användare är också inkluderade."
599
+
600
+ #. Plugin Name of the plugin/theme
601
  msgid "Simple History"
602
+ msgstr "Simple History"
603
+
604
+ #. Plugin URI of the plugin/theme
605
+ msgid "http://simple-history.com"
606
+ msgstr "http://simple-history.com"
607
 
608
+ #. Description of the plugin/theme
609
+ msgid ""
610
+ "Plugin that logs various things that occur in WordPress and then presents "
611
+ "those events in a very nice GUI."
 
612
  msgstr ""
613
+ "Plugin that logs various things that occur in WordPress and then presents "
614
+ "those events in a very nice GUI."
615
 
616
+ #. Author of the plugin/theme
617
+ msgid "Pär Thernström"
618
+ msgstr "Pär Thernström"
619
+
620
+ #. Author URI of the plugin/theme
621
+ msgid "http://simple-history.com/"
622
+ msgstr "http://simple-history.com"
623
+
624
+ #: SimpleHistory.php:166
625
+ msgctxt ""
626
+ "Message visible while waiting for log to load from server the first time"
627
+ msgid "Loading history..."
628
+ msgstr "Laddar historik..."
629
+
630
+ #: SimpleHistory.php:203
631
+ msgctxt "page n of n"
632
+ msgid "of"
633
+ msgstr "av"
634
+
635
+ #: SimpleHistory.php:274
636
+ msgctxt "API: not enought arguments passed"
637
+ msgid "Not enough args specified"
638
+ msgstr "Inte tillräckligt med arguments specifierade"
639
+
640
+ #: SimpleHistory.php:1158
641
+ msgctxt "dashboard menu name"
642
+ msgid "Simple History"
643
+ msgstr "Simple History"
644
+
645
+ #: SimpleHistory.php:1283
646
+ msgctxt "history page headline"
647
+ msgid "Simple History"
648
+ msgstr "Simple History"
649
+
650
+ #: dropins/SimpleHistoryDonateDropin.php:51
651
+ msgctxt "donate settings headline"
652
+ msgid "Donate"
653
+ msgstr "Donera"
654
+
655
+ #: dropins/SimpleHistoryNewRowsNotifier.php:38
656
+ msgctxt "New rows notifier: error while checking for new rows"
657
+ msgid "An error occured while checking for new log rows"
658
+ msgstr "Ett fel uppstod vid uppdatering av nya händelser"
659
+
660
+ #: dropins/SimpleHistoryRSSDropin.php:47
661
+ msgctxt "rss settings headline"
662
+ msgid "RSS feed"
663
+ msgstr "RSS-flöde"
664
+
665
+ #: loggers/SimpleCommentsLogger.php:93
666
+ msgctxt "A comment was added to the database by a non-logged in internet user"
667
+ msgid "Added a comment to {comment_post_type} \"{comment_post_title}\""
668
+ msgstr ""
669
+ "Lade till en kommentar till {comment_post_type} \"{comment_post_title}\""
670
+
671
+ #: loggers/SimpleCommentsLogger.php:99
672
+ msgctxt "A comment was added to the database by a logged in user"
673
+ msgid "Added a comment to {comment_post_type} \"{comment_post_title}\""
674
+ msgstr ""
675
+ "Lade till en kommentar till {comment_post_type} \"{comment_post_title}\""
676
+
677
+ #: loggers/SimpleCommentsLogger.php:105
678
+ msgctxt "A comment was approved"
679
+ msgid ""
680
+ "Approved a comment to \"{comment_post_title}\" by {comment_author} "
681
+ "({comment_author_email})"
682
+ msgstr ""
683
+ "Godkände en kommentar till \"{comment_post_title}\" av {comment_author} "
684
+ "({comment_author_email})"
685
+
686
+ #: loggers/SimpleCommentsLogger.php:111
687
+ msgctxt "A comment was was unapproved"
688
+ msgid ""
689
+ "Unapproved a comment to \"{comment_post_title}\" by {comment_author} "
690
+ "({comment_author_email})"
691
+ msgstr ""
692
+ "Godkände inte en kommentar till \"{comment_post_title}\" av {comment_author} "
693
+ "({comment_author_email})"
694
+
695
+ #: loggers/SimpleCommentsLogger.php:117
696
+ msgctxt "A comment was marked as spam"
697
+ msgid "Marked a comment to post \"{comment_post_title}\" as spam"
698
+ msgstr "Markerade en kommentar till \"{comment_post_title}\" som spam"
699
+
700
+ #: loggers/SimpleCommentsLogger.php:123
701
+ msgctxt "A comment was marked moved to the trash"
702
+ msgid ""
703
+ "Trashed a comment to \"{comment_post_title}\" by {comment_author} "
704
+ "({comment_author_email})"
705
+ msgstr ""
706
+ "Kastade en kommentar till \"{comment_post_title}\" av {comment_author} "
707
+ "({comment_author_email}) i papperskorgen"
708
+
709
+ #: loggers/SimpleCommentsLogger.php:129
710
+ msgctxt "A comment was restored from the trash"
711
+ msgid ""
712
+ "Restored a comment to \"{comment_post_title}\" by {comment_author} "
713
+ "({comment_author_email}) from the trash"
714
  msgstr ""
715
+ "Återställde en kommentar till \"{comment_post_title}\" av {comment_author} "
716
+ "({comment_author_email}) från papperskorgen"
717
+
718
+ #: loggers/SimpleCommentsLogger.php:135
719
+ msgctxt "A comment was deleted"
720
+ msgid ""
721
+ "Deleted a comment to \"{comment_post_title}\" by {comment_author} "
722
+ "({comment_author_email})"
723
+ msgstr ""
724
+ "Raderade en kommentar kommentar till \"{comment_post_title}\" av "
725
+ "{comment_author} ({comment_author_email})"
726
+
727
+ #: loggers/SimpleCommentsLogger.php:141
728
+ msgctxt "A comment was edited"
729
+ msgid ""
730
+ "Edited a comment to \"{comment_post_title}\" by {comment_author} "
731
+ "({comment_author_email})"
732
+ msgstr ""
733
+ "Redigerade en kommentar till \"{comment_post_title}\" av {comment_author} "
734
+ "({comment_author_email})"
735
+
736
+ #: loggers/SimpleCommentsLogger.php:148
737
+ msgctxt ""
738
+ "A trackback was added to the database by a non-logged in internet user"
739
+ msgid "Added a trackback to {comment_post_type} \"{comment_post_title}\""
740
+ msgstr ""
741
+ "Lade till en trackback till {comment_post_type} \"{comment_post_title}\""
742
+
743
+ #: loggers/SimpleCommentsLogger.php:203
744
+ msgctxt ""
745
+ "A trackback was added to the database by a non-logged in internet user"
746
+ msgid "Added a pingback to {comment_post_type} \"{comment_post_title}\""
747
+ msgstr ""
748
+ "Lade till en pingback till {comment_post_type} \"{comment_post_title}\""
749
+
750
+ #: loggers/SimpleCommentsLogger.php:154
751
+ msgctxt "A trackback was added to the database by a logged in user"
752
+ msgid "Added a trackback to {comment_post_type} \"{comment_post_title}\""
753
+ msgstr ""
754
+ "Lade till en trackback till {comment_post_type} \"{comment_post_title}\""
755
+
756
+ #: loggers/SimpleCommentsLogger.php:160
757
+ msgctxt "A trackback was approved"
758
+ msgid ""
759
+ "Approved a trackback to \"{comment_post_title}\" by {comment_author} "
760
+ "({comment_author_email})"
761
+ msgstr ""
762
+ "Godkände en trackback till \"{comment_post_title}\" av {comment_author} "
763
+ "({comment_author_email})"
764
+
765
+ #: loggers/SimpleCommentsLogger.php:166
766
+ msgctxt "A trackback was was unapproved"
767
+ msgid ""
768
+ "Unapproved a trackback to \"{comment_post_title}\" by {comment_author} "
769
+ "({comment_author_email})"
770
+ msgstr ""
771
+ "Godkände inte en trackback till \"{comment_post_title}\" av {comment_author} "
772
+ "({comment_author_email})"
773
+
774
+ #: loggers/SimpleCommentsLogger.php:172
775
+ msgctxt "A trackback was marked as spam"
776
+ msgid "Marked a trackback to post \"{comment_post_title}\" as spam"
777
+ msgstr "Markerade en trackback till \"{comment_post_title}\" som spam"
778
+
779
+ #: loggers/SimpleCommentsLogger.php:178
780
+ msgctxt "A trackback was marked moved to the trash"
781
+ msgid ""
782
+ "Trashed a trackback to \"{comment_post_title}\" by {comment_author} "
783
+ "({comment_author_email})"
784
+ msgstr ""
785
+ "Kastade en trackback till \"{comment_post_title}\" av {comment_author} "
786
+ "({comment_author_email}) i papperskorgen"
787
+
788
+ #: loggers/SimpleCommentsLogger.php:184
789
+ msgctxt "A trackback was restored from the trash"
790
+ msgid ""
791
+ "Restored a trackback to \"{comment_post_title}\" by {comment_author} "
792
+ "({comment_author_email}) from the trash"
793
+ msgstr ""
794
+ "Återställde en trackback till \"{comment_post_title}\" av {comment_author} "
795
+ "({comment_author_email}) från papperskorgen"
796
+
797
+ #: loggers/SimpleCommentsLogger.php:190
798
+ msgctxt "A trackback was deleted"
799
+ msgid ""
800
+ "Deleted a trackback to \"{comment_post_title}\" by {comment_author} "
801
+ "({comment_author_email})"
802
+ msgstr ""
803
+ "Raderade en trackback till \"{comment_post_title}\" av {comment_author} "
804
+ "({comment_author_email})"
805
+
806
+ #: loggers/SimpleCommentsLogger.php:196
807
+ msgctxt "A trackback was edited"
808
+ msgid ""
809
+ "Edited a trackback to \"{comment_post_title}\" by {comment_author} "
810
+ "({comment_author_email})"
811
+ msgstr ""
812
+ "Redigerade en trackback till \"{comment_post_title}\" av {comment_author} "
813
+ "({comment_author_email})"
814
+
815
+ #: loggers/SimpleCommentsLogger.php:209
816
+ msgctxt "A pingback was added to the database by a logged in user"
817
+ msgid "Added a pingback to {comment_post_type} \"{comment_post_title}\""
818
+ msgstr ""
819
+ "Lade till en pingback till {comment_post_type} \"{comment_post_title}\""
820
+
821
+ #: loggers/SimpleCommentsLogger.php:215
822
+ msgctxt "A pingback was approved"
823
+ msgid ""
824
+ "Approved a pingback to \"{comment_post_title}\" by "
825
+ "\"{comment_author}\"\" ({comment_author_email})"
826
+ msgstr ""
827
+ "Godkände en pingback till \"{comment_post_title}\" av {comment_author} "
828
+ "({comment_author_email}) från papperskorgen"
829
+
830
+ #: loggers/SimpleCommentsLogger.php:221
831
+ msgctxt "A pingback was was unapproved"
832
+ msgid ""
833
+ "Unapproved a pingback to \"{comment_post_title}\" by "
834
+ "\"{comment_author}\" ({comment_author_email})"
835
+ msgstr ""
836
+ "Ångrade godkännande av en pingack till \"{comment_post_title}\" av "
837
+ "{comment_author} ({comment_author_email})"
838
+
839
+ #: loggers/SimpleCommentsLogger.php:227
840
+ msgctxt "A pingback was marked as spam"
841
+ msgid "Marked a pingback to post \"{comment_post_title}\" as spam"
842
+ msgstr "Markerade en pingback till \"{comment_post_title}\" som spam"
843
+
844
+ #: loggers/SimpleCommentsLogger.php:233
845
+ msgctxt "A pingback was marked moved to the trash"
846
+ msgid ""
847
+ "Trashed a pingback to \"{comment_post_title}\" by {comment_author} "
848
+ "({comment_author_email})"
849
+ msgstr ""
850
+ "Kastade en pingback till \"{comment_post_title}\" av {comment_author} "
851
+ "({comment_author_email}) i papperskorgen"
852
+
853
+ #: loggers/SimpleCommentsLogger.php:239
854
+ msgctxt "A pingback was restored from the trash"
855
+ msgid ""
856
+ "Restored a pingback to \"{comment_post_title}\" by {comment_author} "
857
+ "({comment_author_email}) from the trash"
858
+ msgstr ""
859
+ "Ångrade godkännandet av en kommentar till \"{comment_post_title}\" av "
860
+ "{comment_author} ({comment_author_email})"
861
+
862
+ #: loggers/SimpleCommentsLogger.php:245
863
+ msgctxt "A pingback was deleted"
864
+ msgid ""
865
+ "Deleted a pingback to \"{comment_post_title}\" by {comment_author} "
866
+ "({comment_author_email})"
867
+ msgstr ""
868
+ "Raderade en pingback till \"{comment_post_title}\" av {comment_author} "
869
+ "({comment_author_email})"
870
+
871
+ #: loggers/SimpleCommentsLogger.php:251
872
+ msgctxt "A pingback was edited"
873
+ msgid ""
874
+ "Edited a pingback to \"{comment_post_title}\" by {comment_author} "
875
+ "({comment_author_email})"
876
+ msgstr ""
877
+ "Redigerade en pingback till \"{comment_post_title}\" av {comment_author} "
878
+ "({comment_author_email})"
879
+
880
+ #: loggers/SimpleCommentsLogger.php:262
881
+ msgctxt "Comments logger: search"
882
+ msgid "Comments"
883
+ msgstr "Kommentarer"
884
+
885
+ #: loggers/SimpleCommentsLogger.php:263
886
+ msgctxt "Comments logger: search"
887
+ msgid "All comments activity"
888
+ msgstr "Alla kommentarshändelser"
889
+
890
+ #: loggers/SimpleCommentsLogger.php:265
891
+ msgctxt "Comments logger: search"
892
+ msgid "Added comments"
893
+ msgstr "Tillagda kommentarer"
894
+
895
+ #: loggers/SimpleCommentsLogger.php:273
896
+ msgctxt "Comments logger: search"
897
+ msgid "Edited comments"
898
+ msgstr "Redigerade kommentarer"
899
+
900
+ #: loggers/SimpleCommentsLogger.php:278
901
+ msgctxt "Comments logger: search"
902
+ msgid "Approved comments"
903
+ msgstr "Godkända kommentarer"
904
+
905
+ #: loggers/SimpleCommentsLogger.php:283
906
+ msgctxt "Comments logger: search"
907
+ msgid "Held comments"
908
+ msgstr "Tillbakahållna kommentar"
909
+
910
+ #: loggers/SimpleCommentsLogger.php:288
911
+ msgctxt "Comments logger: search"
912
+ msgid "Comments status changed to spam"
913
+ msgstr "Status på kommentar ändrad till spam"
914
+
915
+ #: loggers/SimpleCommentsLogger.php:293
916
+ msgctxt "Comments logger: search"
917
+ msgid "Trashed comments"
918
+ msgstr "Kastade kommentarer"
919
+
920
+ #: loggers/SimpleCommentsLogger.php:298
921
+ msgctxt "Comments logger: search"
922
+ msgid "Untrashed comments"
923
+ msgstr "Kommentarer tillbakatagna från papperskorgen"
924
+
925
+ #: loggers/SimpleCommentsLogger.php:303
926
+ msgctxt "Comments logger: search"
927
+ msgid "Deleted comments"
928
+ msgstr "Borttagna kommentarer"
929
 
930
+ #: loggers/SimpleCommentsLogger.php:597 loggers/SimpleCommentsLogger.php:610
931
+ #: loggers/SimpleCommentsLogger.php:624
932
+ msgctxt "comments logger - detailed output comment status"
933
+ msgid "Status"
934
+ msgstr "Status"
935
+
936
+ #: loggers/SimpleCommentsLogger.php:599 loggers/SimpleCommentsLogger.php:612
937
+ #: loggers/SimpleCommentsLogger.php:626
938
+ msgctxt "comments logger - detailed output author"
939
+ msgid "Name"
940
+ msgstr "Namn"
941
+
942
+ #: loggers/SimpleCommentsLogger.php:600 loggers/SimpleCommentsLogger.php:613
943
+ #: loggers/SimpleCommentsLogger.php:627
944
+ msgctxt "comments logger - detailed output email"
945
+ msgid "Email"
946
+ msgstr "E-post"
947
+
948
+ #: loggers/SimpleCommentsLogger.php:601 loggers/SimpleCommentsLogger.php:614
949
+ msgctxt "comments logger - detailed output content"
950
+ msgid "Content"
951
+ msgstr "Innehåll"
952
+
953
+ #: loggers/SimpleCommentsLogger.php:628
954
+ msgctxt "comments logger - detailed output content"
955
+ msgid "Comment"
956
+ msgstr "Kommentar"
957
+
958
+ #: loggers/SimpleCommentsLogger.php:754
959
+ msgctxt "comments logger - edit comment"
960
+ msgid "View/Edit"
961
+ msgstr "Visa/Redigera"
962
+
963
+ #: loggers/SimpleCoreUpdatesLogger.php:34
964
+ msgctxt "User logger: search"
965
+ msgid "WordPress Core"
966
+ msgstr "WordPress Core"
967
+
968
+ #: loggers/SimpleCoreUpdatesLogger.php:36
969
+ msgctxt "User logger: search"
970
+ msgid "WordPress core updates"
971
+ msgstr "WordPress core uppdateringar"
972
+
973
+ #: loggers/SimpleUserLogger.php:35
974
+ msgctxt "User logger: search"
975
+ msgid "Users"
976
  msgstr "Användare"
977
 
978
+ #: loggers/SimpleUserLogger.php:36
979
+ msgctxt "User logger: search"
980
+ msgid "All user activity"
981
+ msgstr "All användaraktivitet"
982
+
983
+ #: loggers/SimpleUserLogger.php:38
984
+ msgctxt "User logger: search"
985
+ msgid "Successful user logins"
986
+ msgstr "Lyckade inloggningar av användare"
987
+
988
+ #: loggers/SimpleUserLogger.php:42
989
+ msgctxt "User logger: search"
990
+ msgid "Failed user logins"
991
+ msgstr "Misslyckade inloggningar av användare"
992
+
993
+ #: loggers/SimpleUserLogger.php:46
994
+ msgctxt "User logger: search"
995
+ msgid "User logouts"
996
+ msgstr "Utloggning av användare"
997
+
998
+ #: loggers/SimpleUserLogger.php:49
999
+ msgctxt "User logger: search"
1000
+ msgid "Created users"
1001
+ msgstr "Skapade användare"
1002
+
1003
+ #: loggers/SimpleUserLogger.php:52
1004
+ msgctxt "User logger: search"
1005
+ msgid "User profile updates"
1006
+ msgstr "Uppdateringar av användarprofiler"
1007
+
1008
+ #: loggers/SimpleUserLogger.php:55
1009
+ msgctxt "User logger: search"
1010
+ msgid "User deletions"
1011
+ msgstr "Radering av användare"
1012
+
1013
+ #: loggers/SimpleExportLogger.php:27
1014
+ msgctxt "Export logger: search"
1015
+ msgid "Export"
1016
+ msgstr "Export"
1017
+
1018
+ #: loggers/SimpleExportLogger.php:29
1019
+ msgctxt "Export logger: search"
1020
+ msgid "Created exports"
1021
+ msgstr "Skapade exporter"
1022
+
1023
+ #: loggers/SimpleLogger.php:192
1024
+ msgctxt "header output when initiator is the currently logged in user"
1025
+ msgid "You"
1026
+ msgstr "Du"
1027
+
1028
+ #: loggers/SimpleMediaLogger.php:29
1029
+ msgctxt "Media logger: search"
1030
+ msgid "Media"
1031
+ msgstr "Media"
1032
+
1033
+ #: loggers/SimpleMediaLogger.php:31
1034
+ msgctxt "Media logger: search"
1035
+ msgid "Added media"
1036
+ msgstr "Tillagda media"
1037
+
1038
+ #: loggers/SimpleMediaLogger.php:34
1039
+ msgctxt "Media logger: search"
1040
+ msgid "Updated media"
1041
+ msgstr "Uppdaterade medier"
1042
+
1043
+ #: loggers/SimpleMediaLogger.php:37
1044
+ msgctxt "Media logger: search"
1045
+ msgid "Deleted media"
1046
+ msgstr "Raderade media"
1047
+
1048
+ #: loggers/SimpleMenuLogger.php:31
1049
+ msgctxt "Menu logger: search"
1050
+ msgid "Menus"
1051
+ msgstr "Menyer"
1052
+
1053
+ #: loggers/SimpleMenuLogger.php:33
1054
+ msgctxt "Menu updates logger: search"
1055
+ msgid "Created menus"
1056
+ msgstr "Skapade menyer"
1057
+
1058
+ #: loggers/SimpleMenuLogger.php:36
1059
+ msgctxt "Menu updates logger: search"
1060
+ msgid "Edited menus"
1061
+ msgstr "Ändrade menyer"
1062
+
1063
+ #: loggers/SimpleMenuLogger.php:41
1064
+ msgctxt "Menu updates logger: search"
1065
+ msgid "Deleted menus"
1066
+ msgstr "Raderade menyer"
1067
+
1068
+ #: loggers/SimpleMenuLogger.php:326
1069
+ msgctxt "menu logger"
1070
+ msgid "%1$s menu item added"
1071
+ msgid_plural "%1$s menu items added"
1072
+ msgstr[0] "%1$s menyval tillagt"
1073
+ msgstr[1] "%1$s menyval tillagda"
1074
+
1075
+ #: loggers/SimpleMenuLogger.php:333
1076
+ msgctxt "menu logger"
1077
+ msgid "%1$s menu item removed"
1078
+ msgid_plural "%1$s menu items removed"
1079
+ msgstr[0] "%1$s menyval borttaget"
1080
+ msgstr[1] "%1$s menyval borttagna"
1081
+
1082
+ #: loggers/SimpleOptionsLogger.php:153
1083
+ msgctxt "Options logger: search"
1084
+ msgid "Options"
1085
+ msgstr "Inställningar"
1086
+
1087
+ #: loggers/SimpleOptionsLogger.php:155
1088
+ msgctxt "Options logger: search"
1089
+ msgid "Changed options"
1090
+ msgstr "Ändrade inställningar"
1091
+
1092
+ #: loggers/SimplePluginLogger.php:51
1093
+ msgctxt "Plugin was non-silently activated by a user"
1094
+ msgid "Activated plugin \"{plugin_name}\""
1095
+ msgstr "Aktiverade plugin \"{plugin_name}\""
1096
+
1097
+ #: loggers/SimplePluginLogger.php:57
1098
+ msgctxt "Plugin was non-silently deactivated by a user"
1099
+ msgid "Deactivated plugin \"{plugin_name}\""
1100
+ msgstr "Inaktiverade plugin \"{plugin_name}\""
1101
+
1102
+ #: loggers/SimplePluginLogger.php:63
1103
+ msgctxt "Plugin was installed"
1104
+ msgid "Installed plugin \"{plugin_name}\""
1105
+ msgstr "Installerade plugin \"{plugin_name}\""
1106
+
1107
+ #: loggers/SimplePluginLogger.php:69
1108
+ msgctxt "Plugin failed to install"
1109
+ msgid "Failed to install plugin \"{plugin_name}\""
1110
+ msgstr "Misslyckades med att installera pluginen \"{plugin_name}\""
1111
+
1112
+ #: loggers/SimplePluginLogger.php:75
1113
+ msgctxt "Plugin was updated"
1114
+ msgid ""
1115
+ "Updated plugin \"{plugin_name}\" from {plugin_prev_version} to "
1116
+ "{plugin_version}"
1117
+ msgstr ""
1118
+ "Uppdaterade plugin \"{plugin_name}\" från {plugin_prev_version} till "
1119
+ "{plugin_version}"
1120
+
1121
+ #: loggers/SimplePluginLogger.php:81
1122
+ msgctxt "Plugin update failed"
1123
+ msgid "Updated plugin \"{plugin_name}\""
1124
+ msgstr "Uppdaterade plugin \"{plugin_name}\""
1125
+
1126
+ #: loggers/SimplePluginLogger.php:87
1127
+ msgctxt "Plugin file edited"
1128
+ msgid "Edited plugin file \"{plugin_edited_file}\""
1129
+ msgstr "Redigerade plugin-filen \"{plugin_edited_file}\""
1130
+
1131
+ #: loggers/SimplePluginLogger.php:93
1132
+ msgctxt "Plugin files was deleted"
1133
+ msgid "Deleted plugin \"{plugin_name}\""
1134
+ msgstr "Raderade plugin \"{plugin_name}\""
1135
+
1136
+ #: loggers/SimplePluginLogger.php:100
1137
+ msgctxt "Plugin was updated in bulk"
1138
+ msgid ""
1139
+ "Updated plugin \"{plugin_name}\" from {plugin_prev_version} to "
1140
+ "{plugin_version}"
1141
+ msgstr ""
1142
+ "Uppdaterade plugin \"{plugin_name}\" från {plugin_prev_version} till "
1143
+ "{plugin_version}"
1144
+
1145
+ #: loggers/SimplePluginLogger.php:108
1146
+ msgctxt "Plugin logger: search"
1147
+ msgid "Plugins"
1148
+ msgstr "Tillägg"
1149
+
1150
+ #: loggers/SimplePluginLogger.php:110
1151
+ msgctxt "Plugin logger: search"
1152
+ msgid "Activated plugins"
1153
+ msgstr "Aktiverade plugins"
1154
+
1155
+ #: loggers/SimplePluginLogger.php:113
1156
+ msgctxt "Plugin logger: search"
1157
+ msgid "Deactivated plugins"
1158
+ msgstr "Inaktiverade plugins"
1159
+
1160
+ #: loggers/SimplePluginLogger.php:116
1161
+ msgctxt "Plugin logger: search"
1162
+ msgid "Installed plugins"
1163
+ msgstr "Installerade plugins"
1164
+
1165
+ #: loggers/SimplePluginLogger.php:119
1166
+ msgctxt "Plugin logger: search"
1167
+ msgid "Failed plugin installs"
1168
+ msgstr "Misslyckade installationer av plugins"
1169
+
1170
+ #: loggers/SimplePluginLogger.php:122
1171
+ msgctxt "Plugin logger: search"
1172
+ msgid "Updated plugins"
1173
+ msgstr "Uppdaterade plugins"
1174
+
1175
+ #: loggers/SimplePluginLogger.php:126
1176
+ msgctxt "Plugin logger: search"
1177
+ msgid "Failed plugin updates"
1178
+ msgstr "Misslyckades uppdateringar av plugins"
1179
+
1180
+ #: loggers/SimplePluginLogger.php:129
1181
+ msgctxt "Plugin logger: search"
1182
+ msgid "Edited plugin files"
1183
+ msgstr "Redigerade pluginfiler"
1184
+
1185
+ #: loggers/SimplePluginLogger.php:132
1186
+ msgctxt "Plugin logger: search"
1187
+ msgid "Deleted plugins"
1188
+ msgstr "Raderade plugins"
1189
+
1190
+ #: loggers/SimplePluginLogger.php:863
1191
+ msgctxt "plugin logger - detailed output version"
1192
+ msgid "Version"
1193
+ msgstr "Version"
1194
+
1195
+ #: loggers/SimplePluginLogger.php:865
1196
+ msgctxt "plugin logger - detailed output author"
1197
+ msgid "Author"
1198
+ msgstr "Författare"
1199
+
1200
+ #: loggers/SimplePluginLogger.php:867
1201
+ msgctxt "plugin logger - detailed output author"
1202
+ msgid "Requires"
1203
+ msgstr "Kräver"
1204
+
1205
+ #: loggers/SimplePluginLogger.php:866
1206
+ msgctxt "plugin logger - detailed output url"
1207
+ msgid "URL"
1208
+ msgstr "URL"
1209
+
1210
+ #: loggers/SimplePluginLogger.php:868
1211
+ msgctxt "plugin logger - detailed output compatible"
1212
+ msgid "Compatible up to"
1213
+ msgstr "Kompatibel upp till"
1214
+
1215
+ #: loggers/SimplePluginLogger.php:869
1216
+ msgctxt "plugin logger - detailed output downloaded"
1217
+ msgid "Downloads"
1218
+ msgstr "Nedladdningar"
1219
+
1220
+ #: loggers/SimplePluginLogger.php:929
1221
+ msgctxt "plugin logger: plugin info thickbox title view all info"
1222
+ msgid "View plugin info"
1223
+ msgstr "Visa information om plugin"
1224
+
1225
+ #: loggers/SimplePluginLogger.php:944
1226
+ msgctxt "plugin logger: plugin info thickbox title"
1227
+ msgid "View plugin info"
1228
+ msgstr "Visa information om plugin"
1229
+
1230
+ #: loggers/SimplePluginLogger.php:948
1231
+ msgctxt "plugin logger: plugin info thickbox title"
1232
+ msgid "View changelog"
1233
+ msgstr "Visa ändringslogg"
1234
+
1235
+ #: loggers/SimplePostLogger.php:38
1236
+ msgctxt "Post logger: search"
1237
+ msgid "Posts & Pages"
1238
+ msgstr "Poster & sidor"
1239
+
1240
+ #: loggers/SimplePostLogger.php:40
1241
+ msgctxt "Post logger: search"
1242
+ msgid "Posts created"
1243
+ msgstr "Poster skapade"
1244
+
1245
+ #: loggers/SimplePostLogger.php:43
1246
+ msgctxt "Post logger: search"
1247
+ msgid "Posts updated"
1248
+ msgstr "Poster uppdaterade"
1249
+
1250
+ #: loggers/SimplePostLogger.php:46
1251
+ msgctxt "Post logger: search"
1252
+ msgid "Posts trashed"
1253
+ msgstr "Poster kastade"
1254
+
1255
+ #: loggers/SimplePostLogger.php:49
1256
+ msgctxt "Post logger: search"
1257
+ msgid "Posts deleted"
1258
+ msgstr "Poster raderade"
1259
+
1260
+ #: loggers/SimplePostLogger.php:52
1261
+ msgctxt "Post logger: search"
1262
+ msgid "Posts restored"
1263
+ msgstr "Posterar återställda"
1264
+
1265
+ #: loggers/SimpleThemeLogger.php:36
1266
+ msgctxt "Theme logger: search"
1267
+ msgid "Themes & Widgets"
1268
+ msgstr "Teman & Widgets"
1269
+
1270
+ #: loggers/SimpleThemeLogger.php:38
1271
+ msgctxt "Theme logger: search"
1272
+ msgid "Switched themes"
1273
+ msgstr "Byten av teman"
1274
+
1275
+ #: loggers/SimpleThemeLogger.php:41
1276
+ msgctxt "Theme logger: search"
1277
+ msgid "Changed appearance of themes"
1278
+ msgstr "Ändrade utseende av teman"
1279
+
1280
+ #: loggers/SimpleThemeLogger.php:44
1281
+ msgctxt "Theme logger: search"
1282
+ msgid "Added widgets"
1283
+ msgstr "Tillagda widgets"
1284
+
1285
+ #: loggers/SimpleThemeLogger.php:47
1286
+ msgctxt "Theme logger: search"
1287
+ msgid "Removed widgets"
1288
+ msgstr "Borttagna widgets"
1289
+
1290
+ #: loggers/SimpleThemeLogger.php:50
1291
+ msgctxt "Theme logger: search"
1292
+ msgid "Changed widgets order"
1293
+ msgstr "Ändrad ordning av widgets"
1294
+
1295
+ #: loggers/SimpleThemeLogger.php:53
1296
+ msgctxt "Theme logger: search"
1297
+ msgid "Edited widgets"
1298
+ msgstr "Ändrade widgets"
1299
+
1300
+ #: loggers/SimpleThemeLogger.php:56
1301
+ msgctxt "Theme logger: search"
1302
+ msgid "Background of themes changed"
1303
+ msgstr "Ändrade bakgrunder för teman"
1304
+
1305
+ #: templates/settings-statsRowsPerDay.php:36
1306
+ msgctxt "stats: date in rows per day chart"
1307
+ msgid "M j"
1308
+ msgstr "M j"
1309
+
1310
+ #~ msgctxt "dashboard menu name"
1311
+ #~ msgid "History"
1312
+ #~ msgstr "Historik"
1313
+
1314
+ #~ msgctxt "history page headline"
1315
+ #~ msgid "History"
1316
+ #~ msgstr "Historik"
1317
+
1318
+ #~ msgctxt "comments logger - detailed output comment type"
1319
+ #~ msgid "Comment type"
1320
+ #~ msgstr "Typ av kommentar"
1321
+
1322
+ #~ msgctxt "comments logger - detailed output version"
1323
+ #~ msgid "Author name"
1324
+ #~ msgstr "Författarens namn"
1325
+
1326
+ #~ msgctxt "comments logger - detailed output url"
1327
+ #~ msgid "Author email"
1328
+ #~ msgstr "Författarens epost"
1329
+
1330
+ #~ msgctxt "comments logger - detailed output author"
1331
+ #~ msgid "Comment"
1332
+ #~ msgstr "Kommentar"
1333
+
1334
+ #~ msgid ""
1335
+ #~ "Get a log/history/audit log/version history of the changes made by users "
1336
+ #~ "in WordPress."
1337
+ #~ msgstr ""
1338
+ #~ "Get a log/history/audit log/version history of the changes made by users "
1339
+ #~ "in WordPress."
1340
 
1341
+ #~ msgid "Log"
1342
+ #~ msgstr "Logg"
languages/simple-history.pot CHANGED
@@ -1,708 +1,1230 @@
1
- # Copyright (C) 2013
2
- # This file is distributed under the same license as the package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: \n"
6
- "Report-Msgid-Bugs-To: http://wordpress.org/tag/simple-history\n"
7
- "POT-Creation-Date: 2013-05-22 13:16:18+00:00\n"
8
  "MIME-Version: 1.0\n"
9
- "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2013-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- #: index.php:88 index.php:105
16
- msgid "added"
17
  msgstr ""
18
 
19
- #: index.php:89
20
- msgid "approved"
21
  msgstr ""
22
 
23
- #: index.php:90
24
- msgid "unapproved"
25
  msgstr ""
26
 
27
- #: index.php:91 simple-history-extender/class.simple-history-extend.php:92
28
- msgid "marked as spam"
29
  msgstr ""
30
 
31
- #: index.php:92 simple-history-extender/class.simple-history-extend.php:94
32
- msgid "trashed"
33
  msgstr ""
34
 
35
- #: index.php:93 simple-history-extender/class.simple-history-extend.php:95
36
- msgid "untrashed"
37
  msgstr ""
38
 
39
- #: index.php:94 simple-history-extender/class.simple-history-extend.php:89
40
- msgid "created"
41
  msgstr ""
42
 
43
- #: index.php:95 simple-history-extender/class.simple-history-extend.php:91
44
- msgid "deleted"
45
  msgstr ""
46
 
47
- #: index.php:96 simple-history-extender/class.simple-history-extend.php:239
48
- msgid "updated"
49
  msgstr ""
50
 
51
- #: index.php:97
52
- msgid "nav_menu_item"
53
  msgstr ""
54
 
55
- #: index.php:98 index.php:1770
56
- msgid "attachment"
57
  msgstr ""
58
 
59
- #: index.php:99 index.php:1851
60
- msgid "user"
61
  msgstr ""
62
 
63
- #: index.php:100
64
- msgid "settings page"
65
  msgstr ""
66
 
67
- #: index.php:101 simple-history-extender/class.simple-history-extend.php:90
68
- msgid "edited"
69
  msgstr ""
70
 
71
- #: index.php:102
72
- msgid "comment"
73
  msgstr ""
74
 
75
- #: index.php:103
76
- msgid "logged in"
77
  msgstr ""
78
 
79
- #: index.php:104
80
- msgid "logged out"
81
  msgstr ""
82
 
83
- #: index.php:106
84
- msgid "modified"
85
  msgstr ""
86
 
87
- #: index.php:107
88
- msgid "upgraded it\\'s database"
89
  msgstr ""
90
 
91
- #: index.php:108
92
- msgid "plugin"
93
  msgstr ""
94
 
95
- #: index.php:121 index.php:283 index.php:1078
96
- msgid "History"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  msgstr ""
98
 
99
- #: index.php:181
100
- msgid "WordPress %1$s"
101
  msgstr ""
102
 
103
- #: index.php:194 index.php:299
 
 
 
 
104
  msgid "Donate"
105
  msgstr ""
106
 
107
- #: index.php:269 index.php:290
108
- msgid "Simple History Settings"
 
 
109
  msgstr ""
110
 
111
- #: index.php:295
112
- msgid "Show Simple History"
113
  msgstr ""
114
 
115
- #: index.php:296
116
- msgid "Number of items per page"
117
  msgstr ""
118
 
119
- #: index.php:297 index.php:2024
120
- msgid "RSS feed"
121
  msgstr ""
122
 
123
- #: index.php:298
124
- msgid "Clear log"
125
  msgstr ""
126
 
127
- #: index.php:315
128
- msgid "failed to log in because they entered the wrong password"
129
  msgstr ""
130
 
131
- #: index.php:391 index.php:450
132
- msgid "History for %s"
133
  msgstr ""
134
 
135
- #: index.php:392 index.php:451
136
- msgid "WordPress History for %s"
 
 
 
 
 
 
137
  msgstr ""
138
 
139
- #: index.php:415
140
- msgid "By %s"
141
  msgstr ""
142
 
143
- #: index.php:419
144
- msgid "%d occasions"
145
  msgstr ""
146
 
147
- #: index.php:454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  msgid "Wrong RSS secret"
149
  msgstr ""
150
 
151
- #: index.php:455
152
  msgid ""
153
  "Your RSS secret for Simple History RSS feed is wrong. Please see WordPress "
154
  "settings for current link to the RSS feed."
155
  msgstr ""
156
 
157
- #: index.php:534 index.php:1448
158
- msgid "One item"
159
- msgid_plural "%1$d items"
160
- msgstr[0] ""
161
- msgstr[1] ""
162
 
163
- #: index.php:597
164
- msgid "on the dashboard"
165
  msgstr ""
166
 
167
- #: index.php:602
168
- msgid "as a page under the dashboard menu"
 
 
 
169
  msgstr ""
170
 
171
- #: index.php:617
172
- msgid "Cleared database"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  msgstr ""
174
 
175
- #: index.php:625
176
- msgid "Items in the database are automatically removed after 60 days."
177
  msgstr ""
178
 
179
- #: index.php:627
180
- msgid "Clear it now."
181
  msgstr ""
182
 
183
- #: index.php:641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  msgid ""
185
- "\n"
186
- "\t\t\tPlease\n"
187
- "\t\t\t<a href=\"http://eskapism.se/sida/donate/?"
188
- "utm_source=wordpress&utm_medium=settingpage&utm_campaign=simplehistory\">\n"
189
- "\t\t\tdonate\n"
190
- "\t\t\t</a> to support the development of this plugin and to keep it free.\n"
191
- "\t\t\tThanks!\n"
192
- "\t\t\t"
193
  msgstr ""
194
 
195
- #: index.php:675
196
- msgid "Created new secret RSS address"
 
 
 
 
 
 
 
 
 
 
197
  msgstr ""
198
 
199
- #: index.php:686
 
 
 
 
 
 
 
 
200
  msgid ""
201
- "This is a secret RSS feed for Simple History. Only share the link with "
202
- "people you trust"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  msgstr ""
204
 
205
- #: index.php:689
206
  msgid ""
207
- "You can <a href='%s'>generate a new address</a> for the RSS feed. This is "
208
- "useful if you think that the address has fallen into the wrong hands."
209
  msgstr ""
210
 
211
- #: index.php:740 index.php:756 index.php:788
212
- msgid "From %1$s on %2$s"
 
213
  msgstr ""
214
 
215
- #: index.php:1195
216
- msgid "All types"
217
  msgstr ""
218
 
219
- #: index.php:1356 index.php:1360
220
- msgid "By all users"
 
 
221
  msgstr ""
222
 
223
- #: index.php:1414
224
- msgid "Search"
225
  msgstr ""
226
 
227
- #: index.php:1452
228
- msgid "Go to the first page"
229
  msgstr ""
230
 
231
- #: index.php:1453
232
- msgid "Go to the previous page"
233
  msgstr ""
234
 
235
- #: index.php:1454
236
- msgid "Current page"
237
  msgstr ""
238
 
239
- #: index.php:1455
240
- msgid "of"
241
  msgstr ""
242
 
243
- #: index.php:1456
244
- msgid "Go to the next page"
245
  msgstr ""
246
 
247
- #: index.php:1457
248
- msgid "Go to the last page"
 
 
 
 
 
 
 
 
249
  msgstr ""
250
 
251
- #: index.php:1724
252
- msgid "Unknown or deleted user"
253
  msgstr ""
254
 
255
- #: index.php:1819
256
- msgid "File name:"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  msgstr ""
258
 
259
- #: index.php:1820
260
- msgid "File size:"
 
261
  msgstr ""
262
 
263
- #: index.php:1822
264
- msgid "Dimensions:"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  msgstr ""
266
 
267
- #: index.php:1823
268
- msgid "Length:"
 
269
  msgstr ""
270
 
271
- #: index.php:1894 simple-history-extender/simple-history-extender.php:274
272
- msgid "activated"
 
273
  msgstr ""
274
 
275
- #: index.php:1897 simple-history-extender/simple-history-extender.php:274
276
- msgid "deactivated"
 
277
  msgstr ""
278
 
279
- #: index.php:1900
280
- msgid "enabled"
 
281
  msgstr ""
282
 
283
- #: index.php:1903
284
- msgid "disabled"
 
 
 
285
  msgstr ""
286
 
287
- #: index.php:1919
288
- msgid "<span class=\"when\">%1$s ago</span> by %2$s"
 
 
 
289
  msgstr ""
290
 
291
- #: index.php:1921
292
- msgid "%s at %s"
 
293
  msgstr ""
294
 
295
- #: index.php:1933 index.php:1981
296
- msgid "Details"
 
 
 
297
  msgstr ""
298
 
299
- #: index.php:1957
300
- msgid "+ 1 occasion"
 
 
 
301
  msgstr ""
302
 
303
- #: index.php:1960
304
- msgid "+ %d occasions"
 
 
 
305
  msgstr ""
306
 
307
- #: index.php:1974
308
- msgid "%s ago (%s at %s)"
 
 
 
309
  msgstr ""
310
 
311
- #: index.php:2017
312
- msgid "Show 5 more"
 
313
  msgstr ""
314
 
315
- #: index.php:2018
316
- msgid "Show 15 more"
 
317
  msgstr ""
318
 
319
- #: index.php:2019
320
- msgid "Show 50 more"
 
321
  msgstr ""
322
 
323
- #: index.php:2020
324
- msgid "Show 100 more"
 
 
 
325
  msgstr ""
326
 
327
- #: index.php:2023
328
- msgid "No matching items found."
 
 
 
329
  msgstr ""
330
 
331
- #: index.php:2026
332
- msgid "Show"
 
333
  msgstr ""
334
 
335
- #: index.php:2032
336
- msgid "Loading..."
 
 
 
337
  msgstr ""
338
 
339
- #: index.php:2051
340
- msgid "No history items found."
 
 
 
341
  msgstr ""
342
 
343
- #: index.php:2052
 
344
  msgid ""
345
- "Please note that Simple History only records things that happen after this "
346
- "plugin have been installed."
347
  msgstr ""
348
 
349
- #: index.php:2065
350
- msgid "General Settings"
 
 
 
351
  msgstr ""
352
 
353
- #: index.php:2066
354
- msgid "Writing Settings"
 
355
  msgstr ""
356
 
357
- #: index.php:2067
358
- msgid "Reading Settings"
 
 
 
359
  msgstr ""
360
 
361
- #: index.php:2068
362
- msgid "Discussion Settings"
 
 
 
363
  msgstr ""
364
 
365
- #: index.php:2069
366
- msgid "Media Settings"
 
367
  msgstr ""
368
 
369
- #: index.php:2070
370
- msgid "Privacy Settings"
 
 
 
371
  msgstr ""
372
 
373
- #: index.php:2086
374
- msgid "Permalink Settings"
 
 
 
375
  msgstr ""
376
 
377
- #: simple-history-extender/class.simple-history-extend.php:68
378
- msgid "Log events for the %s plugin."
 
 
 
379
  msgstr ""
380
 
381
- #: simple-history-extender/class.simple-history-extend.php:69
382
- msgid "Log events for %s."
 
 
 
383
  msgstr ""
384
 
385
- #: simple-history-extender/class.simple-history-extend.php:93
386
- msgid "unmarked as spam"
 
387
  msgstr ""
388
 
389
- #: simple-history-extender/class.simple-history-extend.php:96
390
- msgid "submitted"
 
391
  msgstr ""
392
 
393
- #: simple-history-extender/class.simple-history-extend.php:150
394
- msgid "The %s module logs the following events:"
 
395
  msgstr ""
396
 
397
- #: simple-history-extender/class.simple-history-extend.php:165
398
- msgid "The %s module does not support the following events:"
 
399
  msgstr ""
400
 
401
- #: simple-history-extender/class.simple-history-extend.php:263
402
- msgid "User"
 
403
  msgstr ""
404
 
405
- #: simple-history-extender/modules/bbpress.php:28
406
- msgid "BBPress"
 
407
  msgstr ""
408
 
409
- #: simple-history-extender/modules/bbpress.php:32
410
- msgid "Creating, editing and deleting a forum, topic, reply."
 
411
  msgstr ""
412
 
413
- #: simple-history-extender/modules/bbpress.php:33
414
- msgid "Setting the type of a forum to category or forum."
 
415
  msgstr ""
416
 
417
- #: simple-history-extender/modules/bbpress.php:34
418
- msgid "Setting the status of a forum, topic to open or closed."
 
419
  msgstr ""
420
 
421
- #: simple-history-extender/modules/bbpress.php:35
422
- msgid "Setting the forum visibility to public, private or hidden."
 
423
  msgstr ""
424
 
425
- #: simple-history-extender/modules/bbpress.php:36
426
- msgid "Trashing and untrashing a forum, topic, reply."
 
 
427
  msgstr ""
428
 
429
- #: simple-history-extender/modules/bbpress.php:37
430
- msgid "Marking and unmarking a topic, reply as spam."
 
 
431
  msgstr ""
432
 
433
- #: simple-history-extender/modules/bbpress.php:38
434
- msgid "Marking and unmarking a topic as sticky."
 
 
435
  msgstr ""
436
 
437
- #: simple-history-extender/modules/bbpress.php:39
438
- msgid "Merging and splitting a topic."
 
439
  msgstr ""
440
 
441
- #: simple-history-extender/modules/bbpress.php:40
442
- msgid "Updating, merging and deleting a topic tag."
 
443
  msgstr ""
444
 
445
- #: simple-history-extender/modules/bbpress.php:41
446
- msgid "A user (un)favoriting and (un)subscribing to a topic."
 
447
  msgstr ""
448
 
449
- #: simple-history-extender/modules/bbpress.php:42
450
- msgid "A user saving his/her profile."
 
451
  msgstr ""
452
 
453
- #: simple-history-extender/modules/bbpress.php:53
454
- msgid "closed"
 
455
  msgstr ""
456
 
457
- #: simple-history-extender/modules/bbpress.php:54
458
- msgid "opened"
 
459
  msgstr ""
460
 
461
- #: simple-history-extender/modules/bbpress.php:55
462
- msgid "marked as sticky"
 
463
  msgstr ""
464
 
465
- #: simple-history-extender/modules/bbpress.php:56
466
- msgid "marked as super sticky"
 
467
  msgstr ""
468
 
469
- #: simple-history-extender/modules/bbpress.php:57
470
- msgid "unmarked as sticky"
 
471
  msgstr ""
472
 
473
- #: simple-history-extender/modules/bbpress.php:58
474
- msgid "set to category type"
 
475
  msgstr ""
476
 
477
- #: simple-history-extender/modules/bbpress.php:59
478
- msgid "set to forum type"
 
479
  msgstr ""
480
 
481
- #: simple-history-extender/modules/bbpress.php:60
482
- msgid "set to public"
 
483
  msgstr ""
484
 
485
- #: simple-history-extender/modules/bbpress.php:61
486
- msgid "set to private"
 
487
  msgstr ""
488
 
489
- #: simple-history-extender/modules/bbpress.php:62
490
- msgid "set to hidden"
 
491
  msgstr ""
492
 
493
- #: simple-history-extender/modules/bbpress.php:63
494
- msgid "in forum %s merged into %s"
 
495
  msgstr ""
496
 
497
- #: simple-history-extender/modules/bbpress.php:64
498
- msgid "in forum %s split from reply %s by %s into %s in forum %s"
 
499
  msgstr ""
500
 
501
- #: simple-history-extender/modules/bbpress.php:131
502
- msgid "Forum"
 
503
  msgstr ""
504
 
505
- #: simple-history-extender/modules/bbpress.php:141
506
- msgid "Topic"
 
507
  msgstr ""
508
 
509
- #: simple-history-extender/modules/bbpress.php:150
510
- msgid "Topic Tag"
 
511
  msgstr ""
512
 
513
- #: simple-history-extender/modules/bbpress.php:161
514
- msgid "by %s"
 
515
  msgstr ""
516
 
517
- #: simple-history-extender/modules/bbpress.php:162
518
- msgid "Reply"
 
519
  msgstr ""
520
 
521
- #: simple-history-extender/modules/bbpress.php:174
522
- msgid "as child of %s"
 
523
  msgstr ""
524
 
525
- #: simple-history-extender/modules/bbpress.php:235
526
- #: simple-history-extender/modules/bbpress.php:245
527
- msgid "in forum %s"
528
  msgstr ""
529
 
530
- #: simple-history-extender/modules/bbpress.php:358
531
- msgid "favorited"
 
532
  msgstr ""
533
 
534
- #: simple-history-extender/modules/bbpress.php:362
535
- msgid "unfavorited"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  msgstr ""
537
 
538
- #: simple-history-extender/modules/bbpress.php:366
539
- msgid "subscribed"
 
540
  msgstr ""
541
 
542
- #: simple-history-extender/modules/bbpress.php:370
543
- msgid "unsubscribed"
 
544
  msgstr ""
545
 
546
- #: simple-history-extender/modules/bbpress.php:374
547
- msgid "profile updated"
 
548
  msgstr ""
549
 
550
- #: simple-history-extender/modules/bbpress.php:378
551
- msgid "registered"
 
552
  msgstr ""
553
 
554
- #: simple-history-extender/modules/bbpress.php:390
555
- msgid "changed forum role to %s"
 
556
  msgstr ""
557
 
558
- #: simple-history-extender/modules/bbpress.php:390
559
- msgid "none"
 
 
 
560
  msgstr ""
561
 
562
- #: simple-history-extender/modules/gravityforms.php:28
563
- msgid "Gravity Forms"
 
564
  msgstr ""
565
 
566
- #: simple-history-extender/modules/gravityforms.php:32
567
- msgid "Creating, editing and deleting a form."
 
568
  msgstr ""
569
 
570
- #: simple-history-extender/modules/gravityforms.php:33
571
- msgid "Deleting a field from an existing form."
 
572
  msgstr ""
573
 
574
- #: simple-history-extender/modules/gravityforms.php:34
575
- msgid "Submitting, editing and deleting an entry."
 
 
 
576
  msgstr ""
577
 
578
- #: simple-history-extender/modules/gravityforms.php:35
579
- msgid "Changing the status of an entry, including read/unread and star/unstar."
 
580
  msgstr ""
581
 
582
- #: simple-history-extender/modules/gravityforms.php:38
583
- msgid "Duplicating a form."
 
584
  msgstr ""
585
 
586
- #: simple-history-extender/modules/gravityforms.php:39
587
- msgid "Setting a form to active/inactive."
 
588
  msgstr ""
589
 
590
- #: simple-history-extender/modules/gravityforms.php:48
591
- msgid "starred"
 
592
  msgstr ""
593
 
594
- #: simple-history-extender/modules/gravityforms.php:49
595
- msgid "unstarred"
 
596
  msgstr ""
597
 
598
- #: simple-history-extender/modules/gravityforms.php:50
599
- msgid "marked as read"
 
600
  msgstr ""
601
 
602
- #: simple-history-extender/modules/gravityforms.php:51
603
- msgid "marked as unread"
 
604
  msgstr ""
605
 
606
- #: simple-history-extender/modules/gravityforms.php:110
607
- msgid "from %s"
 
608
  msgstr ""
609
 
610
- #: simple-history-extender/modules/gravityforms.php:112
611
- msgid "from unknown"
 
612
  msgstr ""
613
 
614
- #: simple-history-extender/modules/gravityforms.php:120
615
- msgid "Form"
 
616
  msgstr ""
617
 
618
- #: simple-history-extender/modules/gravityforms.php:129
619
- msgid "Form entry"
 
620
  msgstr ""
621
 
622
- #: simple-history-extender/modules/gravityforms.php:150
623
- msgid "without entries deleted"
 
624
  msgstr ""
625
 
626
- #: simple-history-extender/modules/gravityforms.php:151
627
- msgid "with %d entries deleted"
 
628
  msgstr ""
629
 
630
- #: simple-history-extender/modules/gravityforms.php:160
631
- msgid "field %s deleted"
 
632
  msgstr ""
633
 
634
- #: simple-history-extender/modules/gravityforms.php:201
635
- msgid "restored"
 
636
  msgstr ""
637
 
638
- #: simple-history-extender/modules/gravityforms.php:206
639
- msgid "changed status"
 
640
  msgstr ""
641
 
642
- #: simple-history-extender/modules/widgets.php:25
643
- msgid "Widgets"
 
644
  msgstr ""
645
 
646
- #: simple-history-extender/modules/widgets.php:27
647
- msgid "Log events for the Widgets section of your WP install."
 
648
  msgstr ""
649
 
650
- #: simple-history-extender/modules/widgets.php:30
651
- msgid "Adding, updating and deleting widgets in/from a sidebar."
 
652
  msgstr ""
653
 
654
- #: simple-history-extender/modules/widgets.php:33
655
- msgid "Moving widgets between sidebars."
 
656
  msgstr ""
657
 
658
- #: simple-history-extender/modules/widgets.php:34
659
- msgid "Setting a widget to active/inactive."
 
660
  msgstr ""
661
 
662
- #: simple-history-extender/modules/widgets.php:86
663
- msgid "removed from sidebar %s"
 
664
  msgstr ""
665
 
666
- #: simple-history-extender/modules/widgets.php:88
667
- msgid "updated in sidebar %s"
 
668
  msgstr ""
669
 
670
- #: simple-history-extender/modules/widgets.php:90
671
- msgid "added to sidebar %s"
 
672
  msgstr ""
673
 
674
- #: simple-history-extender/modules/widgets.php:95
675
- msgid "Widget"
 
676
  msgstr ""
677
 
678
- #: simple-history-extender/simple-history-extender.php:139
679
- msgid "Settings"
 
680
  msgstr ""
681
 
682
- #: simple-history-extender/simple-history-extender.php:162
683
- msgid ""
684
- "The Simple History Extender plugin was deactivated because the Simple "
685
- "History plugin was not found installed or active."
686
  msgstr ""
687
 
688
- #: simple-history-extender/simple-history-extender.php:163
689
- msgid "The Simple History Extender plugin was deactivated."
 
690
  msgstr ""
691
 
692
- #: simple-history-extender/simple-history-extender.php:167
693
- msgid "Return"
 
694
  msgstr ""
695
 
696
- #: simple-history-extender/simple-history-extender.php:226
697
- msgid "Simple History Extender Modules"
 
698
  msgstr ""
699
 
700
- #: simple-history-extender/simple-history-extender.php:236
701
- msgid ""
702
- "Activate or deactivate the events you want to log. Read the Help tab if you "
703
- "want to know which actions are supported and which aren't."
704
  msgstr ""
705
 
706
- #: simple-history-extender/simple-history-extender.php:275
707
- msgid "Simple History Extender Module"
 
708
  msgstr ""
 
 
 
 
 
1
+ # Copyright (C) 2014 Simple History
2
+ # This file is distributed under the same license as the Simple History package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Simple History 2\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/support/plugin/Simple-History\n"
7
+ "POT-Creation-Date: 2014-12-01 19:54:25+00:00\n"
8
  "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=utf-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2014-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
14
+ "X-Generator: grunt-wp-i18n 0.4.9\n"
15
+ "X-Poedit-KeywordsList: "
16
+ "__;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;_nx_noop:1,2,3c;esc_"
17
+ "attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;esc_html_x:1,2c;\n"
18
+ "Language: en\n"
19
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+ "X-Poedit-Country: United States\n"
21
+ "X-Poedit-SourceCharset: UTF-8\n"
22
+ "X-Poedit-Basepath: ../\n"
23
+ "X-Poedit-SearchPath-0: .\n"
24
+ "X-Poedit-Bookmarks: \n"
25
+ "X-Textdomain-Support: yes\n"
26
+
27
+ #: SimpleHistory.php:423 SimpleHistory.php:662
28
+ msgid "Settings"
29
+ msgstr ""
30
 
31
+ #: SimpleHistory.php:434
32
+ msgid "Log (debug)"
33
  msgstr ""
34
 
35
+ #: SimpleHistory.php:439
36
+ msgid "Styles example (debug)"
37
  msgstr ""
38
 
39
+ #: SimpleHistory.php:677
40
+ msgid "History"
41
  msgstr ""
42
 
43
+ #: SimpleHistory.php:753
44
+ msgid "Remove all log items?"
45
  msgstr ""
46
 
47
+ #: SimpleHistory.php:755
48
+ msgid "Go to the first page"
49
  msgstr ""
50
 
51
+ #: SimpleHistory.php:756
52
+ msgid "Go to the previous page"
53
  msgstr ""
54
 
55
+ #: SimpleHistory.php:757
56
+ msgid "Go to the next page"
57
  msgstr ""
58
 
59
+ #: SimpleHistory.php:758
60
+ msgid "Go to the last page"
61
  msgstr ""
62
 
63
+ #: SimpleHistory.php:759
64
+ msgid "Current page"
65
  msgstr ""
66
 
67
+ #: SimpleHistory.php:761
68
+ msgid "Oups, the log could not be loaded right now."
69
  msgstr ""
70
 
71
+ #: SimpleHistory.php:762
72
+ msgid "Your search did not match any history events."
73
  msgstr ""
74
 
75
+ #: SimpleHistory.php:1070 SimpleHistory.php:1173
76
+ msgid "Simple History Settings"
77
  msgstr ""
78
 
79
+ #: SimpleHistory.php:1104
80
+ msgid "No valid callback found"
81
  msgstr ""
82
 
83
+ #: SimpleHistory.php:1194
84
+ msgid "Cleared database"
85
  msgstr ""
86
 
87
+ #: SimpleHistory.php:1221
88
+ msgid "Show history"
89
  msgstr ""
90
 
91
+ #: SimpleHistory.php:1234
92
+ msgid "Number of items per page"
93
  msgstr ""
94
 
95
+ #: SimpleHistory.php:1246
96
+ msgid "Clear log"
97
  msgstr ""
98
 
99
+ #: SimpleHistory.php:1466
100
+ msgid "on the dashboard"
101
  msgstr ""
102
 
103
+ #: SimpleHistory.php:1471
104
+ msgid "as a page under the dashboard menu"
105
  msgstr ""
106
 
107
+ #: SimpleHistory.php:1487
108
+ msgid "Items in the database are automatically removed after %1$s days."
109
  msgstr ""
110
 
111
+ #: SimpleHistory.php:1489
112
+ msgid "Items in the database are kept forever."
113
+ msgstr ""
114
+
115
+ #: SimpleHistory.php:1493
116
+ msgid "Clear log now"
117
+ msgstr ""
118
+
119
+ #: SimpleHistory.php:1713
120
+ msgid "+%1$s more"
121
+ msgstr ""
122
+
123
+ #: SimpleHistory.php:1720
124
+ msgid "Loading…"
125
+ msgstr ""
126
+
127
+ #: SimpleHistory.php:1727
128
+ msgid "Showing %1$s more"
129
+ msgstr ""
130
+
131
+ #: SimpleHistory.php:1745
132
+ msgid "Context data"
133
+ msgstr ""
134
+
135
+ #: SimpleHistory.php:1746
136
+ msgid "This is potentially useful meta data that a logger have saved."
137
+ msgstr ""
138
+
139
+ #: SimpleHistory.php:2067
140
+ msgid "No events today so far."
141
+ msgstr ""
142
+
143
+ #: SimpleHistory.php:2071
144
+ msgid "%1$d event today from one user."
145
  msgstr ""
146
 
147
+ #: SimpleHistory.php:2075
148
+ msgid "%1$d events today from %2$d users."
149
  msgstr ""
150
 
151
+ #: SimpleHistory.php:2079
152
+ msgid "%1$d events today from one user."
153
+ msgstr ""
154
+
155
+ #: dropins/SimpleHistoryDonateDropin.php:36
156
  msgid "Donate"
157
  msgstr ""
158
 
159
+ #: dropins/SimpleHistoryDonateDropin.php:72
160
+ msgid ""
161
+ "If you find Simple History useful please <a href=\"%1$s\">donate</a> or <a "
162
+ "href=\"%2$s\">buy me something from my Amazon wish list</a>."
163
  msgstr ""
164
 
165
+ #: dropins/SimpleHistoryFilterDropin.php:42
166
+ msgid "Filter history"
167
  msgstr ""
168
 
169
+ #: dropins/SimpleHistoryFilterDropin.php:49
170
+ msgid "All log levels"
171
  msgstr ""
172
 
173
+ #: dropins/SimpleHistoryFilterDropin.php:63
174
+ msgid "All messages"
175
  msgstr ""
176
 
177
+ #: dropins/SimpleHistoryFilterDropin.php:117
178
+ msgid "All users"
179
  msgstr ""
180
 
181
+ #: dropins/SimpleHistoryFilterDropin.php:138
182
+ msgid "All dates"
183
  msgstr ""
184
 
185
+ #: dropins/SimpleHistoryFilterDropin.php:152
186
+ msgid "Filter"
187
  msgstr ""
188
 
189
+ #: dropins/SimpleHistoryNewRowsNotifier.php:80
190
+ msgid "1 new row"
191
+ msgid_plural "%d new rows"
192
+ msgstr[0] ""
193
+ msgstr[1] ""
194
+
195
+ #: dropins/SimpleHistoryRSSDropin.php:55
196
+ msgid "Address"
197
  msgstr ""
198
 
199
+ #: dropins/SimpleHistoryRSSDropin.php:64
200
+ msgid "Regenerate"
201
  msgstr ""
202
 
203
+ #: dropins/SimpleHistoryRSSDropin.php:81
204
+ msgid "Created new secret RSS address"
205
  msgstr ""
206
 
207
+ #: dropins/SimpleHistoryRSSDropin.php:148
208
+ #: dropins/SimpleHistoryRSSDropin.php:256
209
+ msgid "History for %s"
210
+ msgstr ""
211
+
212
+ #: dropins/SimpleHistoryRSSDropin.php:149
213
+ #: dropins/SimpleHistoryRSSDropin.php:257
214
+ msgid "WordPress History for %s"
215
+ msgstr ""
216
+
217
+ #: dropins/SimpleHistoryRSSDropin.php:196
218
+ msgid "+%1$s occasion"
219
+ msgid_plural "+%1$s occasions"
220
+ msgstr[0] ""
221
+ msgstr[1] ""
222
+
223
+ #: dropins/SimpleHistoryRSSDropin.php:260
224
  msgid "Wrong RSS secret"
225
  msgstr ""
226
 
227
+ #: dropins/SimpleHistoryRSSDropin.php:261
228
  msgid ""
229
  "Your RSS secret for Simple History RSS feed is wrong. Please see WordPress "
230
  "settings for current link to the RSS feed."
231
  msgstr ""
232
 
233
+ #: dropins/SimpleHistoryRSSDropin.php:312
234
+ msgid ""
235
+ "You can generate a new address for the RSS feed. This is useful if you "
236
+ "think that the address has fallen into the wrong hands."
237
+ msgstr ""
238
 
239
+ #: dropins/SimpleHistoryRSSDropin.php:315
240
+ msgid "Generate new address"
241
  msgstr ""
242
 
243
+ #: dropins/SimpleHistoryRSSDropin.php:343
244
+ msgid ""
245
+ "Simple History has a RSS feed which you can subscribe to and receive log "
246
+ "updates. Make sure you only share the feed with people you trust, since it "
247
+ "can contain sensitive or confidential information."
248
  msgstr ""
249
 
250
+ #: dropins/SimpleHistorySettingsLogtestDropin.php:20
251
+ msgid "Test data (debug)"
252
+ msgstr ""
253
+
254
+ #: dropins/SimpleHistorySettingsStatsDropin.php:27
255
+ msgid "Stats"
256
+ msgstr ""
257
+
258
+ #: index.php:54
259
+ msgid ""
260
+ "Simple History is a great plugin, but to use it your server must have at "
261
+ "least PHP 5.3 installed."
262
+ msgstr ""
263
+
264
+ #: loggers/SimpleCommentsLogger.php:680
265
+ msgid "Spam"
266
+ msgstr ""
267
+
268
+ #: loggers/SimpleCommentsLogger.php:682
269
+ msgid "Approved"
270
+ msgstr ""
271
+
272
+ #: loggers/SimpleCommentsLogger.php:684
273
+ msgid "Pending"
274
+ msgstr ""
275
+
276
+ #: loggers/SimpleCommentsLogger.php:698
277
+ msgid "Trackback"
278
+ msgstr ""
279
+
280
+ #: loggers/SimpleCommentsLogger.php:700
281
+ msgid "Pingback"
282
+ msgstr ""
283
+
284
+ #: loggers/SimpleCommentsLogger.php:702
285
+ msgid "Comment"
286
+ msgstr ""
287
+
288
+ #: loggers/SimpleCoreUpdatesLogger.php:29
289
+ msgid "Updated WordPress from {prev_version} to {new_version}"
290
+ msgstr ""
291
+
292
+ #: loggers/SimpleCoreUpdatesLogger.php:30
293
+ msgid "WordPress auto-updated from {prev_version} to {new_version}"
294
+ msgstr ""
295
+
296
+ #: loggers/SimpleExportLogger.php:23
297
+ msgid "Created XML export"
298
+ msgstr ""
299
+
300
+ #: loggers/SimpleLegacyLogger.php:88
301
+ msgid "By %s"
302
+ msgstr ""
303
+
304
+ #: loggers/SimpleLegacyLogger.php:93
305
+ msgid "%d occasions"
306
+ msgstr ""
307
+
308
+ #: loggers/SimpleLogger.php:205
309
+ msgid "Deleted user (had id %1$s, email %2$s, login %3$s)"
310
+ msgstr ""
311
+
312
+ #: loggers/SimpleLogger.php:220
313
+ msgid "Anonymous web user"
314
+ msgstr ""
315
+
316
+ #: loggers/SimpleLogger.php:224
317
+ msgid "Anonymous user from %1$s"
318
+ msgstr ""
319
+
320
+ #: loggers/SimpleLogger.php:291
321
+ msgid "Just now"
322
+ msgstr ""
323
+
324
+ #: loggers/SimpleLogger.php:296
325
+ #. translators: Date format for log row header, see http:php.net/date
326
+ msgid "M j, Y \\a\\t G:i"
327
+ msgstr ""
328
+
329
+ #: loggers/SimpleLogger.php:304
330
+ #. translators: 1: last modified date and time in human time diff-format
331
+ msgid "%1$s ago"
332
+ msgstr ""
333
+
334
+ #: loggers/SimpleMediaLogger.php:23
335
+ msgid "Created {post_type} \"{attachment_title}\""
336
  msgstr ""
337
 
338
+ #: loggers/SimpleMediaLogger.php:24
339
+ msgid "Edited {post_type} \"{attachment_title}\""
340
  msgstr ""
341
 
342
+ #: loggers/SimpleMediaLogger.php:25
343
+ msgid "Deleted {post_type} \"{attachment_title}\" (\"{attachment_filename}\")"
344
  msgstr ""
345
 
346
+ #: loggers/SimpleMediaLogger.php:81
347
+ msgid "Edited {post_type} <a href=\"{edit_link}\">\"{attachment_title}\"</a>"
348
+ msgstr ""
349
+
350
+ #: loggers/SimpleMediaLogger.php:85
351
+ msgid "Uploaded {post_type} <a href=\"{edit_link}\">\"{attachment_title}\"</a>"
352
+ msgstr ""
353
+
354
+ #: loggers/SimpleMediaLogger.php:180
355
+ msgid "{attachment_thumb}"
356
+ msgstr ""
357
+
358
+ #: loggers/SimpleMediaLogger.php:189
359
+ msgid "{attachment_size_format}"
360
+ msgstr ""
361
+
362
+ #: loggers/SimpleMediaLogger.php:190
363
+ msgid "{attachment_filetype_extension}"
364
+ msgstr ""
365
+
366
+ #: loggers/SimpleMediaLogger.php:192
367
+ msgid "{full_image_width} × {full_image_height}"
368
+ msgstr ""
369
+
370
+ #: loggers/SimpleMenuLogger.php:23
371
+ msgid "Created menu \"{menu_name}\""
372
+ msgstr ""
373
+
374
+ #: loggers/SimpleMenuLogger.php:24
375
+ msgid "Edited menu \"{menu_name}\""
376
+ msgstr ""
377
+
378
+ #: loggers/SimpleMenuLogger.php:25
379
+ msgid "Deleted menu \"{menu_name}\""
380
+ msgstr ""
381
+
382
+ #: loggers/SimpleMenuLogger.php:26
383
+ msgid "Edited a menu item"
384
+ msgstr ""
385
+
386
+ #: loggers/SimpleMenuLogger.php:27
387
+ msgid "Updated menu locations"
388
+ msgstr ""
389
+
390
+ #: loggers/SimpleOptionsLogger.php:140
391
+ msgid "Updated option \"{option}\""
392
+ msgstr ""
393
+
394
+ #: loggers/SimpleOptionsLogger.php:242 loggers/SimpleThemeLogger.php:570
395
+ msgid "New value"
396
+ msgstr ""
397
+
398
+ #: loggers/SimpleOptionsLogger.php:253 loggers/SimpleThemeLogger.php:582
399
+ msgid "Old value"
400
+ msgstr ""
401
+
402
+ #: loggers/SimpleOptionsLogger.php:268 loggers/SimpleOptionsLogger.php:285
403
+ msgid "Settings page"
404
+ msgstr ""
405
+
406
+ #: loggers/SimplePostLogger.php:30
407
+ msgid "Created {post_type} \"{post_title}\""
408
+ msgstr ""
409
+
410
+ #: loggers/SimplePostLogger.php:31
411
+ msgid "Updated {post_type} \"{post_title}\""
412
+ msgstr ""
413
+
414
+ #: loggers/SimplePostLogger.php:32
415
+ msgid "Restored {post_type} \"{post_title}\" from trash"
416
+ msgstr ""
417
+
418
+ #: loggers/SimplePostLogger.php:33 loggers/SimplePostLogger.php:236
419
+ msgid "Deleted {post_type} \"{post_title}\""
420
+ msgstr ""
421
+
422
+ #: loggers/SimplePostLogger.php:34
423
+ msgid "Moved {post_type} \"{post_title}\" to the trash"
424
+ msgstr ""
425
+
426
+ #: loggers/SimplePostLogger.php:232
427
+ msgid "Updated {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a>"
428
+ msgstr ""
429
+
430
+ #: loggers/SimplePostLogger.php:240
431
+ msgid "Created {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a>"
432
+ msgstr ""
433
+
434
+ #: loggers/SimplePostLogger.php:245
435
+ msgid "Moved {post_type} <a href=\"{edit_link}\">\"{post_title}\"</a> to the trash"
436
+ msgstr ""
437
+
438
+ #: loggers/SimpleThemeLogger.php:26
439
+ msgid "Switched theme to \"{theme_name}\" from \"{prev_theme_name}\""
440
+ msgstr ""
441
+
442
+ #: loggers/SimpleThemeLogger.php:27
443
+ msgid "Customized theme appearance \"{setting_id}\""
444
+ msgstr ""
445
+
446
+ #: loggers/SimpleThemeLogger.php:28
447
+ msgid "Removed widget \"{widget_id_base}\" from sidebar \"{sidebar_id}\""
448
+ msgstr ""
449
+
450
+ #: loggers/SimpleThemeLogger.php:29
451
+ msgid "Added widget \"{widget_id_base}\" to sidebar \"{sidebar_id}\""
452
+ msgstr ""
453
+
454
+ #: loggers/SimpleThemeLogger.php:30
455
+ msgid "Changed widget order \"{widget_id_base}\" in sidebar \"{sidebar_id}\""
456
+ msgstr ""
457
+
458
+ #: loggers/SimpleThemeLogger.php:31
459
+ msgid "Changed widget \"{widget_id_base}\" in sidebar \"{sidebar_id}\""
460
+ msgstr ""
461
+
462
+ #: loggers/SimpleThemeLogger.php:32
463
+ msgid "Changed settings for the theme custom background"
464
+ msgstr ""
465
+
466
+ #: loggers/SimpleThemeLogger.php:532
467
+ msgid "Section"
468
+ msgstr ""
469
+
470
+ #: loggers/SimpleUserLogger.php:23
471
  msgid ""
472
+ "Failed to login to account with username \"{login_user_login}\" because an "
473
+ "incorrect password was entered"
 
 
 
 
 
 
474
  msgstr ""
475
 
476
+ #: loggers/SimpleUserLogger.php:24
477
+ msgid ""
478
+ "Failed to login with username \"{failed_login_username}\" because no user "
479
+ "with that username exist"
480
+ msgstr ""
481
+
482
+ #: loggers/SimpleUserLogger.php:25
483
+ msgid "Logged in"
484
+ msgstr ""
485
+
486
+ #: loggers/SimpleUserLogger.php:26
487
+ msgid "Unknown user logged in"
488
  msgstr ""
489
 
490
+ #: loggers/SimpleUserLogger.php:27
491
+ msgid "Logged out"
492
+ msgstr ""
493
+
494
+ #: loggers/SimpleUserLogger.php:28
495
+ msgid "Edited the profile for user {edited_user_login} ({edited_user_email})"
496
+ msgstr ""
497
+
498
+ #: loggers/SimpleUserLogger.php:29
499
  msgid ""
500
+ "Created user {created_user_login} ({created_user_email}) with role "
501
+ "{created_user_role}"
502
+ msgstr ""
503
+
504
+ #: loggers/SimpleUserLogger.php:30
505
+ msgid "Deleted user {deleted_user_login} ({deleted_user_email})"
506
+ msgstr ""
507
+
508
+ #: loggers/SimpleUserLogger.php:153
509
+ msgid "Edited <a href=\"{edit_profile_link}\">your profile</a>"
510
+ msgstr ""
511
+
512
+ #: loggers/SimpleUserLogger.php:157
513
+ msgid "Edited <a href=\"{edit_profile_link}\">their profile</a>"
514
+ msgstr ""
515
+
516
+ #: loggers/SimpleUserLogger.php:166
517
+ msgid "Edited your profile"
518
  msgstr ""
519
 
520
+ #: loggers/SimpleUserLogger.php:177
521
  msgid ""
522
+ "Edited the profile for user <a "
523
+ "href=\"{edit_profile_link}\">{edited_user_login} ({edited_user_email})</a>"
524
  msgstr ""
525
 
526
+ #: node_modules/grunt-wp-i18n/test/fixtures/basic-theme/exclude/file.php:3
527
+ #: node_modules/grunt-wp-i18n/test/fixtures/plugin-include/plugin-include.php:6
528
+ msgid "Exclude"
529
  msgstr ""
530
 
531
+ #: node_modules/grunt-wp-i18n/test/fixtures/plugin-include/include/file.php:2
532
+ msgid "Include"
533
  msgstr ""
534
 
535
+ #: node_modules/grunt-wp-i18n/test/fixtures/text-domains/add-domain.php:2
536
+ #: node_modules/grunt-wp-i18n/test/fixtures/text-domains/update-domains.php:2
537
+ #: node_modules/grunt-wp-i18n/test/fixtures/text-domains/update-domains.php:3
538
+ msgid "String"
539
  msgstr ""
540
 
541
+ #: templates/settings-statsIntro.php:19
542
+ msgid "<b>%1$s rows</b> have been logged the last <b>%2$s days</b>"
543
  msgstr ""
544
 
545
+ #: templates/settings-statsLogLevels.php:4
546
+ msgid "Log levels"
547
  msgstr ""
548
 
549
+ #: templates/settings-statsLogLevels.php:6
550
+ msgid "Number of rows logged for each log level."
551
  msgstr ""
552
 
553
+ #: templates/settings-statsLoggers.php:4
554
+ msgid "Loggers"
555
  msgstr ""
556
 
557
+ #: templates/settings-statsRowsPerDay.php:4
558
+ msgid "Rows per day"
559
  msgstr ""
560
 
561
+ #: templates/settings-statsUsers.php:4
562
+ msgid "Users"
563
  msgstr ""
564
 
565
+ #: templates/settings-statsUsers.php:6
566
+ msgid "Number of logged items for the 5 users with most logged rows."
567
+ msgstr ""
568
+
569
+ #: templates/settings-statsUsers.php:7
570
+ msgid "Deleted users are also included."
571
+ msgstr ""
572
+
573
+ #. Plugin Name of the plugin/theme
574
+ msgid "Simple History"
575
  msgstr ""
576
 
577
+ #. Plugin URI of the plugin/theme
578
+ msgid "http://simple-history.com"
579
  msgstr ""
580
 
581
+ #. Description of the plugin/theme
582
+ msgid ""
583
+ "Plugin that logs various things that occur in WordPress and then presents "
584
+ "those events in a very nice GUI."
585
+ msgstr ""
586
+
587
+ #. Author of the plugin/theme
588
+ msgid "Pär Thernström"
589
+ msgstr ""
590
+
591
+ #. Author URI of the plugin/theme
592
+ msgid "http://simple-history.com/"
593
+ msgstr ""
594
+
595
+ #: SimpleHistory.php:166
596
+ msgctxt "Message visible while waiting for log to load from server the first time"
597
+ msgid "Loading history..."
598
  msgstr ""
599
 
600
+ #: SimpleHistory.php:203
601
+ msgctxt "page n of n"
602
+ msgid "of"
603
  msgstr ""
604
 
605
+ #: SimpleHistory.php:274
606
+ msgctxt "API: not enought arguments passed"
607
+ msgid "Not enough args specified"
608
+ msgstr ""
609
+
610
+ #: SimpleHistory.php:1158
611
+ msgctxt "dashboard menu name"
612
+ msgid "Simple History"
613
+ msgstr ""
614
+
615
+ #: SimpleHistory.php:1283
616
+ msgctxt "history page headline"
617
+ msgid "Simple History"
618
+ msgstr ""
619
+
620
+ #: dropins/SimpleHistoryDonateDropin.php:51
621
+ msgctxt "donate settings headline"
622
+ msgid "Donate"
623
  msgstr ""
624
 
625
+ #: dropins/SimpleHistoryNewRowsNotifier.php:38
626
+ msgctxt "New rows notifier: error while checking for new rows"
627
+ msgid "An error occured while checking for new log rows"
628
  msgstr ""
629
 
630
+ #: dropins/SimpleHistoryRSSDropin.php:47
631
+ msgctxt "rss settings headline"
632
+ msgid "RSS feed"
633
  msgstr ""
634
 
635
+ #: loggers/SimpleCommentsLogger.php:93
636
+ msgctxt "A comment was added to the database by a non-logged in internet user"
637
+ msgid "Added a comment to {comment_post_type} \"{comment_post_title}\""
638
  msgstr ""
639
 
640
+ #: loggers/SimpleCommentsLogger.php:99
641
+ msgctxt "A comment was added to the database by a logged in user"
642
+ msgid "Added a comment to {comment_post_type} \"{comment_post_title}\""
643
  msgstr ""
644
 
645
+ #: loggers/SimpleCommentsLogger.php:105
646
+ msgctxt "A comment was approved"
647
+ msgid ""
648
+ "Approved a comment to \"{comment_post_title}\" by {comment_author} "
649
+ "({comment_author_email})"
650
  msgstr ""
651
 
652
+ #: loggers/SimpleCommentsLogger.php:111
653
+ msgctxt "A comment was was unapproved"
654
+ msgid ""
655
+ "Unapproved a comment to \"{comment_post_title}\" by {comment_author} "
656
+ "({comment_author_email})"
657
  msgstr ""
658
 
659
+ #: loggers/SimpleCommentsLogger.php:117
660
+ msgctxt "A comment was marked as spam"
661
+ msgid "Marked a comment to post \"{comment_post_title}\" as spam"
662
  msgstr ""
663
 
664
+ #: loggers/SimpleCommentsLogger.php:123
665
+ msgctxt "A comment was marked moved to the trash"
666
+ msgid ""
667
+ "Trashed a comment to \"{comment_post_title}\" by {comment_author} "
668
+ "({comment_author_email})"
669
  msgstr ""
670
 
671
+ #: loggers/SimpleCommentsLogger.php:129
672
+ msgctxt "A comment was restored from the trash"
673
+ msgid ""
674
+ "Restored a comment to \"{comment_post_title}\" by {comment_author} "
675
+ "({comment_author_email}) from the trash"
676
  msgstr ""
677
 
678
+ #: loggers/SimpleCommentsLogger.php:135
679
+ msgctxt "A comment was deleted"
680
+ msgid ""
681
+ "Deleted a comment to \"{comment_post_title}\" by {comment_author} "
682
+ "({comment_author_email})"
683
  msgstr ""
684
 
685
+ #: loggers/SimpleCommentsLogger.php:141
686
+ msgctxt "A comment was edited"
687
+ msgid ""
688
+ "Edited a comment to \"{comment_post_title}\" by {comment_author} "
689
+ "({comment_author_email})"
690
  msgstr ""
691
 
692
+ #: loggers/SimpleCommentsLogger.php:148
693
+ msgctxt "A trackback was added to the database by a non-logged in internet user"
694
+ msgid "Added a trackback to {comment_post_type} \"{comment_post_title}\""
695
  msgstr ""
696
 
697
+ #: loggers/SimpleCommentsLogger.php:203
698
+ msgctxt "A trackback was added to the database by a non-logged in internet user"
699
+ msgid "Added a pingback to {comment_post_type} \"{comment_post_title}\""
700
  msgstr ""
701
 
702
+ #: loggers/SimpleCommentsLogger.php:154
703
+ msgctxt "A trackback was added to the database by a logged in user"
704
+ msgid "Added a trackback to {comment_post_type} \"{comment_post_title}\""
705
  msgstr ""
706
 
707
+ #: loggers/SimpleCommentsLogger.php:160
708
+ msgctxt "A trackback was approved"
709
+ msgid ""
710
+ "Approved a trackback to \"{comment_post_title}\" by {comment_author} "
711
+ "({comment_author_email})"
712
  msgstr ""
713
 
714
+ #: loggers/SimpleCommentsLogger.php:166
715
+ msgctxt "A trackback was was unapproved"
716
+ msgid ""
717
+ "Unapproved a trackback to \"{comment_post_title}\" by {comment_author} "
718
+ "({comment_author_email})"
719
  msgstr ""
720
 
721
+ #: loggers/SimpleCommentsLogger.php:172
722
+ msgctxt "A trackback was marked as spam"
723
+ msgid "Marked a trackback to post \"{comment_post_title}\" as spam"
724
  msgstr ""
725
 
726
+ #: loggers/SimpleCommentsLogger.php:178
727
+ msgctxt "A trackback was marked moved to the trash"
728
+ msgid ""
729
+ "Trashed a trackback to \"{comment_post_title}\" by {comment_author} "
730
+ "({comment_author_email})"
731
  msgstr ""
732
 
733
+ #: loggers/SimpleCommentsLogger.php:184
734
+ msgctxt "A trackback was restored from the trash"
735
+ msgid ""
736
+ "Restored a trackback to \"{comment_post_title}\" by {comment_author} "
737
+ "({comment_author_email}) from the trash"
738
  msgstr ""
739
 
740
+ #: loggers/SimpleCommentsLogger.php:190
741
+ msgctxt "A trackback was deleted"
742
  msgid ""
743
+ "Deleted a trackback to \"{comment_post_title}\" by {comment_author} "
744
+ "({comment_author_email})"
745
  msgstr ""
746
 
747
+ #: loggers/SimpleCommentsLogger.php:196
748
+ msgctxt "A trackback was edited"
749
+ msgid ""
750
+ "Edited a trackback to \"{comment_post_title}\" by {comment_author} "
751
+ "({comment_author_email})"
752
  msgstr ""
753
 
754
+ #: loggers/SimpleCommentsLogger.php:209
755
+ msgctxt "A pingback was added to the database by a logged in user"
756
+ msgid "Added a pingback to {comment_post_type} \"{comment_post_title}\""
757
  msgstr ""
758
 
759
+ #: loggers/SimpleCommentsLogger.php:215
760
+ msgctxt "A pingback was approved"
761
+ msgid ""
762
+ "Approved a pingback to \"{comment_post_title}\" by \"{comment_author}\"\" "
763
+ "({comment_author_email})"
764
  msgstr ""
765
 
766
+ #: loggers/SimpleCommentsLogger.php:221
767
+ msgctxt "A pingback was was unapproved"
768
+ msgid ""
769
+ "Unapproved a pingback to \"{comment_post_title}\" by \"{comment_author}\" "
770
+ "({comment_author_email})"
771
  msgstr ""
772
 
773
+ #: loggers/SimpleCommentsLogger.php:227
774
+ msgctxt "A pingback was marked as spam"
775
+ msgid "Marked a pingback to post \"{comment_post_title}\" as spam"
776
  msgstr ""
777
 
778
+ #: loggers/SimpleCommentsLogger.php:233
779
+ msgctxt "A pingback was marked moved to the trash"
780
+ msgid ""
781
+ "Trashed a pingback to \"{comment_post_title}\" by {comment_author} "
782
+ "({comment_author_email})"
783
  msgstr ""
784
 
785
+ #: loggers/SimpleCommentsLogger.php:239
786
+ msgctxt "A pingback was restored from the trash"
787
+ msgid ""
788
+ "Restored a pingback to \"{comment_post_title}\" by {comment_author} "
789
+ "({comment_author_email}) from the trash"
790
  msgstr ""
791
 
792
+ #: loggers/SimpleCommentsLogger.php:245
793
+ msgctxt "A pingback was deleted"
794
+ msgid ""
795
+ "Deleted a pingback to \"{comment_post_title}\" by {comment_author} "
796
+ "({comment_author_email})"
797
  msgstr ""
798
 
799
+ #: loggers/SimpleCommentsLogger.php:251
800
+ msgctxt "A pingback was edited"
801
+ msgid ""
802
+ "Edited a pingback to \"{comment_post_title}\" by {comment_author} "
803
+ "({comment_author_email})"
804
  msgstr ""
805
 
806
+ #: loggers/SimpleCommentsLogger.php:262
807
+ msgctxt "Comments logger: search"
808
+ msgid "Comments"
809
  msgstr ""
810
 
811
+ #: loggers/SimpleCommentsLogger.php:263
812
+ msgctxt "Comments logger: search"
813
+ msgid "All comments activity"
814
  msgstr ""
815
 
816
+ #: loggers/SimpleCommentsLogger.php:265
817
+ msgctxt "Comments logger: search"
818
+ msgid "Added comments"
819
  msgstr ""
820
 
821
+ #: loggers/SimpleCommentsLogger.php:273
822
+ msgctxt "Comments logger: search"
823
+ msgid "Edited comments"
824
  msgstr ""
825
 
826
+ #: loggers/SimpleCommentsLogger.php:278
827
+ msgctxt "Comments logger: search"
828
+ msgid "Approved comments"
829
  msgstr ""
830
 
831
+ #: loggers/SimpleCommentsLogger.php:283
832
+ msgctxt "Comments logger: search"
833
+ msgid "Held comments"
834
  msgstr ""
835
 
836
+ #: loggers/SimpleCommentsLogger.php:288
837
+ msgctxt "Comments logger: search"
838
+ msgid "Comments status changed to spam"
839
  msgstr ""
840
 
841
+ #: loggers/SimpleCommentsLogger.php:293
842
+ msgctxt "Comments logger: search"
843
+ msgid "Trashed comments"
844
  msgstr ""
845
 
846
+ #: loggers/SimpleCommentsLogger.php:298
847
+ msgctxt "Comments logger: search"
848
+ msgid "Untrashed comments"
849
  msgstr ""
850
 
851
+ #: loggers/SimpleCommentsLogger.php:303
852
+ msgctxt "Comments logger: search"
853
+ msgid "Deleted comments"
854
  msgstr ""
855
 
856
+ #: loggers/SimpleCommentsLogger.php:597 loggers/SimpleCommentsLogger.php:610
857
+ #: loggers/SimpleCommentsLogger.php:624
858
+ msgctxt "comments logger - detailed output comment status"
859
+ msgid "Status"
860
  msgstr ""
861
 
862
+ #: loggers/SimpleCommentsLogger.php:599 loggers/SimpleCommentsLogger.php:612
863
+ #: loggers/SimpleCommentsLogger.php:626
864
+ msgctxt "comments logger - detailed output author"
865
+ msgid "Name"
866
  msgstr ""
867
 
868
+ #: loggers/SimpleCommentsLogger.php:600 loggers/SimpleCommentsLogger.php:613
869
+ #: loggers/SimpleCommentsLogger.php:627
870
+ msgctxt "comments logger - detailed output email"
871
+ msgid "Email"
872
  msgstr ""
873
 
874
+ #: loggers/SimpleCommentsLogger.php:601 loggers/SimpleCommentsLogger.php:614
875
+ msgctxt "comments logger - detailed output content"
876
+ msgid "Content"
877
  msgstr ""
878
 
879
+ #: loggers/SimpleCommentsLogger.php:628
880
+ msgctxt "comments logger - detailed output content"
881
+ msgid "Comment"
882
  msgstr ""
883
 
884
+ #: loggers/SimpleCommentsLogger.php:754
885
+ msgctxt "comments logger - edit comment"
886
+ msgid "View/Edit"
887
  msgstr ""
888
 
889
+ #: loggers/SimpleCoreUpdatesLogger.php:34
890
+ msgctxt "User logger: search"
891
+ msgid "WordPress Core"
892
  msgstr ""
893
 
894
+ #: loggers/SimpleCoreUpdatesLogger.php:36
895
+ msgctxt "User logger: search"
896
+ msgid "WordPress core updates"
897
  msgstr ""
898
 
899
+ #: loggers/SimpleUserLogger.php:35
900
+ msgctxt "User logger: search"
901
+ msgid "Users"
902
  msgstr ""
903
 
904
+ #: loggers/SimpleUserLogger.php:36
905
+ msgctxt "User logger: search"
906
+ msgid "All user activity"
907
  msgstr ""
908
 
909
+ #: loggers/SimpleUserLogger.php:38
910
+ msgctxt "User logger: search"
911
+ msgid "Successful user logins"
912
  msgstr ""
913
 
914
+ #: loggers/SimpleUserLogger.php:42
915
+ msgctxt "User logger: search"
916
+ msgid "Failed user logins"
917
  msgstr ""
918
 
919
+ #: loggers/SimpleUserLogger.php:46
920
+ msgctxt "User logger: search"
921
+ msgid "User logouts"
922
  msgstr ""
923
 
924
+ #: loggers/SimpleUserLogger.php:49
925
+ msgctxt "User logger: search"
926
+ msgid "Created users"
927
  msgstr ""
928
 
929
+ #: loggers/SimpleUserLogger.php:52
930
+ msgctxt "User logger: search"
931
+ msgid "User profile updates"
932
  msgstr ""
933
 
934
+ #: loggers/SimpleUserLogger.php:55
935
+ msgctxt "User logger: search"
936
+ msgid "User deletions"
937
  msgstr ""
938
 
939
+ #: loggers/SimpleExportLogger.php:27
940
+ msgctxt "Export logger: search"
941
+ msgid "Export"
942
  msgstr ""
943
 
944
+ #: loggers/SimpleExportLogger.php:29
945
+ msgctxt "Export logger: search"
946
+ msgid "Created exports"
947
  msgstr ""
948
 
949
+ #: loggers/SimpleLogger.php:192
950
+ msgctxt "header output when initiator is the currently logged in user"
951
+ msgid "You"
952
  msgstr ""
953
 
954
+ #: loggers/SimpleMediaLogger.php:29
955
+ msgctxt "Media logger: search"
956
+ msgid "Media"
957
  msgstr ""
958
 
959
+ #: loggers/SimpleMediaLogger.php:31
960
+ msgctxt "Media logger: search"
961
+ msgid "Added media"
962
  msgstr ""
963
 
964
+ #: loggers/SimpleMediaLogger.php:34
965
+ msgctxt "Media logger: search"
966
+ msgid "Updated media"
967
  msgstr ""
968
 
969
+ #: loggers/SimpleMediaLogger.php:37
970
+ msgctxt "Media logger: search"
971
+ msgid "Deleted media"
972
  msgstr ""
973
 
974
+ #: loggers/SimpleMenuLogger.php:31
975
+ msgctxt "Menu logger: search"
976
+ msgid "Menus"
977
  msgstr ""
978
 
979
+ #: loggers/SimpleMenuLogger.php:33
980
+ msgctxt "Menu updates logger: search"
981
+ msgid "Created menus"
982
  msgstr ""
983
 
984
+ #: loggers/SimpleMenuLogger.php:36
985
+ msgctxt "Menu updates logger: search"
986
+ msgid "Edited menus"
987
  msgstr ""
988
 
989
+ #: loggers/SimpleMenuLogger.php:41
990
+ msgctxt "Menu updates logger: search"
991
+ msgid "Deleted menus"
992
  msgstr ""
993
 
994
+ #: loggers/SimpleMenuLogger.php:326
995
+ msgctxt "menu logger"
996
+ msgid "%1$s menu item added"
997
+ msgid_plural "%1$s menu items added"
998
+ msgstr[0] ""
999
+ msgstr[1] ""
1000
+
1001
+ #: loggers/SimpleMenuLogger.php:333
1002
+ msgctxt "menu logger"
1003
+ msgid "%1$s menu item removed"
1004
+ msgid_plural "%1$s menu items removed"
1005
+ msgstr[0] ""
1006
+ msgstr[1] ""
1007
+
1008
+ #: loggers/SimpleOptionsLogger.php:153
1009
+ msgctxt "Options logger: search"
1010
+ msgid "Options"
1011
  msgstr ""
1012
 
1013
+ #: loggers/SimpleOptionsLogger.php:155
1014
+ msgctxt "Options logger: search"
1015
+ msgid "Changed options"
1016
  msgstr ""
1017
 
1018
+ #: loggers/SimplePluginLogger.php:51
1019
+ msgctxt "Plugin was non-silently activated by a user"
1020
+ msgid "Activated plugin \"{plugin_name}\""
1021
  msgstr ""
1022
 
1023
+ #: loggers/SimplePluginLogger.php:57
1024
+ msgctxt "Plugin was non-silently deactivated by a user"
1025
+ msgid "Deactivated plugin \"{plugin_name}\""
1026
  msgstr ""
1027
 
1028
+ #: loggers/SimplePluginLogger.php:63
1029
+ msgctxt "Plugin was installed"
1030
+ msgid "Installed plugin \"{plugin_name}\""
1031
  msgstr ""
1032
 
1033
+ #: loggers/SimplePluginLogger.php:69
1034
+ msgctxt "Plugin failed to install"
1035
+ msgid "Failed to install plugin \"{plugin_name}\""
1036
  msgstr ""
1037
 
1038
+ #: loggers/SimplePluginLogger.php:75
1039
+ msgctxt "Plugin was updated"
1040
+ msgid ""
1041
+ "Updated plugin \"{plugin_name}\" from {plugin_prev_version} to "
1042
+ "{plugin_version}"
1043
  msgstr ""
1044
 
1045
+ #: loggers/SimplePluginLogger.php:81
1046
+ msgctxt "Plugin update failed"
1047
+ msgid "Updated plugin \"{plugin_name}\""
1048
  msgstr ""
1049
 
1050
+ #: loggers/SimplePluginLogger.php:87
1051
+ msgctxt "Plugin file edited"
1052
+ msgid "Edited plugin file \"{plugin_edited_file}\""
1053
  msgstr ""
1054
 
1055
+ #: loggers/SimplePluginLogger.php:93
1056
+ msgctxt "Plugin files was deleted"
1057
+ msgid "Deleted plugin \"{plugin_name}\""
1058
  msgstr ""
1059
 
1060
+ #: loggers/SimplePluginLogger.php:100
1061
+ msgctxt "Plugin was updated in bulk"
1062
+ msgid ""
1063
+ "Updated plugin \"{plugin_name}\" from {plugin_prev_version} to "
1064
+ "{plugin_version}"
1065
  msgstr ""
1066
 
1067
+ #: loggers/SimplePluginLogger.php:108
1068
+ msgctxt "Plugin logger: search"
1069
+ msgid "Plugins"
1070
  msgstr ""
1071
 
1072
+ #: loggers/SimplePluginLogger.php:110
1073
+ msgctxt "Plugin logger: search"
1074
+ msgid "Activated plugins"
1075
  msgstr ""
1076
 
1077
+ #: loggers/SimplePluginLogger.php:113
1078
+ msgctxt "Plugin logger: search"
1079
+ msgid "Deactivated plugins"
1080
  msgstr ""
1081
 
1082
+ #: loggers/SimplePluginLogger.php:116
1083
+ msgctxt "Plugin logger: search"
1084
+ msgid "Installed plugins"
1085
  msgstr ""
1086
 
1087
+ #: loggers/SimplePluginLogger.php:119
1088
+ msgctxt "Plugin logger: search"
1089
+ msgid "Failed plugin installs"
1090
  msgstr ""
1091
 
1092
+ #: loggers/SimplePluginLogger.php:122
1093
+ msgctxt "Plugin logger: search"
1094
+ msgid "Updated plugins"
1095
  msgstr ""
1096
 
1097
+ #: loggers/SimplePluginLogger.php:126
1098
+ msgctxt "Plugin logger: search"
1099
+ msgid "Failed plugin updates"
1100
  msgstr ""
1101
 
1102
+ #: loggers/SimplePluginLogger.php:129
1103
+ msgctxt "Plugin logger: search"
1104
+ msgid "Edited plugin files"
1105
  msgstr ""
1106
 
1107
+ #: loggers/SimplePluginLogger.php:132
1108
+ msgctxt "Plugin logger: search"
1109
+ msgid "Deleted plugins"
1110
  msgstr ""
1111
 
1112
+ #: loggers/SimplePluginLogger.php:863
1113
+ msgctxt "plugin logger - detailed output version"
1114
+ msgid "Version"
1115
  msgstr ""
1116
 
1117
+ #: loggers/SimplePluginLogger.php:865
1118
+ msgctxt "plugin logger - detailed output author"
1119
+ msgid "Author"
1120
  msgstr ""
1121
 
1122
+ #: loggers/SimplePluginLogger.php:867
1123
+ msgctxt "plugin logger - detailed output author"
1124
+ msgid "Requires"
1125
  msgstr ""
1126
 
1127
+ #: loggers/SimplePluginLogger.php:866
1128
+ msgctxt "plugin logger - detailed output url"
1129
+ msgid "URL"
1130
  msgstr ""
1131
 
1132
+ #: loggers/SimplePluginLogger.php:868
1133
+ msgctxt "plugin logger - detailed output compatible"
1134
+ msgid "Compatible up to"
1135
  msgstr ""
1136
 
1137
+ #: loggers/SimplePluginLogger.php:869
1138
+ msgctxt "plugin logger - detailed output downloaded"
1139
+ msgid "Downloads"
1140
  msgstr ""
1141
 
1142
+ #: loggers/SimplePluginLogger.php:929
1143
+ msgctxt "plugin logger: plugin info thickbox title view all info"
1144
+ msgid "View plugin info"
1145
  msgstr ""
1146
 
1147
+ #: loggers/SimplePluginLogger.php:944
1148
+ msgctxt "plugin logger: plugin info thickbox title"
1149
+ msgid "View plugin info"
1150
  msgstr ""
1151
 
1152
+ #: loggers/SimplePluginLogger.php:948
1153
+ msgctxt "plugin logger: plugin info thickbox title"
1154
+ msgid "View changelog"
1155
  msgstr ""
1156
 
1157
+ #: loggers/SimplePostLogger.php:38
1158
+ msgctxt "Post logger: search"
1159
+ msgid "Posts & Pages"
1160
  msgstr ""
1161
 
1162
+ #: loggers/SimplePostLogger.php:40
1163
+ msgctxt "Post logger: search"
1164
+ msgid "Posts created"
1165
  msgstr ""
1166
 
1167
+ #: loggers/SimplePostLogger.php:43
1168
+ msgctxt "Post logger: search"
1169
+ msgid "Posts updated"
1170
  msgstr ""
1171
 
1172
+ #: loggers/SimplePostLogger.php:46
1173
+ msgctxt "Post logger: search"
1174
+ msgid "Posts trashed"
1175
  msgstr ""
1176
 
1177
+ #: loggers/SimplePostLogger.php:49
1178
+ msgctxt "Post logger: search"
1179
+ msgid "Posts deleted"
1180
  msgstr ""
1181
 
1182
+ #: loggers/SimplePostLogger.php:52
1183
+ msgctxt "Post logger: search"
1184
+ msgid "Posts restored"
1185
  msgstr ""
1186
 
1187
+ #: loggers/SimpleThemeLogger.php:36
1188
+ msgctxt "Theme logger: search"
1189
+ msgid "Themes & Widgets"
1190
  msgstr ""
1191
 
1192
+ #: loggers/SimpleThemeLogger.php:38
1193
+ msgctxt "Theme logger: search"
1194
+ msgid "Switched themes"
1195
  msgstr ""
1196
 
1197
+ #: loggers/SimpleThemeLogger.php:41
1198
+ msgctxt "Theme logger: search"
1199
+ msgid "Changed appearance of themes"
 
1200
  msgstr ""
1201
 
1202
+ #: loggers/SimpleThemeLogger.php:44
1203
+ msgctxt "Theme logger: search"
1204
+ msgid "Added widgets"
1205
  msgstr ""
1206
 
1207
+ #: loggers/SimpleThemeLogger.php:47
1208
+ msgctxt "Theme logger: search"
1209
+ msgid "Removed widgets"
1210
  msgstr ""
1211
 
1212
+ #: loggers/SimpleThemeLogger.php:50
1213
+ msgctxt "Theme logger: search"
1214
+ msgid "Changed widgets order"
1215
  msgstr ""
1216
 
1217
+ #: loggers/SimpleThemeLogger.php:53
1218
+ msgctxt "Theme logger: search"
1219
+ msgid "Edited widgets"
 
1220
  msgstr ""
1221
 
1222
+ #: loggers/SimpleThemeLogger.php:56
1223
+ msgctxt "Theme logger: search"
1224
+ msgid "Background of themes changed"
1225
  msgstr ""
1226
+
1227
+ #: templates/settings-statsRowsPerDay.php:36
1228
+ msgctxt "stats: date in rows per day chart"
1229
+ msgid "M j"
1230
+ msgstr ""
loggers/SimpleCommentsLogger.php ADDED
@@ -0,0 +1,789 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs things related to comments
5
+ */
6
+ class SimpleCommentsLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ function __construct($sh) {
12
+
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
+
19
+ }
20
+
21
+ /**
22
+ * Modify sql query to exclude comments of type spam
23
+ *
24
+ * @param string $where sql query where
25
+ */
26
+ function maybe_modify_log_query_sql_where($where) {
27
+
28
+ $include_spam = false;
29
+
30
+ /**
31
+ * Filter option to include spam or not in the gui
32
+ * By default spam is not included, because it can fill the log
33
+ * with too much events
34
+ *
35
+ * @since 2.0
36
+ *
37
+ * @param bool $include_spam Default false
38
+ */
39
+ $include_spam = apply_filters("simple_history/comments_logger/include_spam", $include_spam);
40
+
41
+ if ( $include_spam ) {
42
+ return $where;
43
+ }
44
+
45
+ $where .= sprintf('
46
+ AND id NOT IN (
47
+
48
+ SELECT id
49
+ # , c1.history_id, c2.history_id
50
+ FROM %1$s AS h
51
+
52
+ INNER JOIN %2$s AS c1
53
+ ON c1.history_id = h.id
54
+ AND c1.key = "_message_key"
55
+ AND c1.value IN (
56
+ "comment_deleted",
57
+ "pingback_deleted",
58
+ "trackback_deleted",
59
+ "anon_comment_added",
60
+ "anon_pingback_added",
61
+ "anon_trackback_added"
62
+ )
63
+
64
+ INNER JOIN %2$s AS c2
65
+ ON c2.history_id = h.id
66
+ AND c2.key = "comment_approved"
67
+ AND c2.value = "spam"
68
+
69
+ WHERE logger = "%3$s"
70
+
71
+ )
72
+ ', $this->db_table, $this->db_table_contexts, $this->slug);
73
+
74
+ #echo $where;
75
+
76
+ return $where;
77
+
78
+ }
79
+
80
+ /**
81
+ * Get array with information about this logger
82
+ *
83
+ * @return array
84
+ */
85
+ function getInfo() {
86
+
87
+ $arr_info = array(
88
+ "name" => "Comments Logger",
89
+ "description" => "Logs comments, and modifications to them",
90
+ "capability" => "moderate_comments",
91
+ "messages" => array(
92
+
93
+ // Comments
94
+ 'anon_comment_added' => _x(
95
+ 'Added a comment to {comment_post_type} "{comment_post_title}"',
96
+ 'A comment was added to the database by a non-logged in internet user',
97
+ 'simple-history'
98
+ ),
99
+
100
+ 'user_comment_added' => _x(
101
+ 'Added a comment to {comment_post_type} "{comment_post_title}"',
102
+ 'A comment was added to the database by a logged in user',
103
+ 'simple-history'
104
+ ),
105
+
106
+ 'comment_status_approve' => _x(
107
+ 'Approved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
108
+ 'A comment was approved',
109
+ 'simple-history'
110
+ ),
111
+
112
+ 'comment_status_hold' => _x(
113
+ 'Unapproved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
114
+ 'A comment was was unapproved',
115
+ 'simple-history'
116
+ ),
117
+
118
+ 'comment_status_spam' => _x(
119
+ 'Marked a comment to post "{comment_post_title}" as spam',
120
+ 'A comment was marked as spam',
121
+ 'simple-history'
122
+ ),
123
+
124
+ 'comment_status_trash' => _x(
125
+ 'Trashed a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
126
+ 'A comment was marked moved to the trash',
127
+ 'simple-history'
128
+ ),
129
+
130
+ 'comment_untrashed' => _x(
131
+ 'Restored a comment to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
132
+ 'A comment was restored from the trash',
133
+ 'simple-history'
134
+ ),
135
+
136
+ 'comment_deleted' => _x(
137
+ 'Deleted a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
138
+ 'A comment was deleted',
139
+ 'simple-history'
140
+ ),
141
+
142
+ 'comment_edited' => _x(
143
+ 'Edited a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})',
144
+ 'A comment was edited',
145
+ 'simple-history'
146
+ ),
147
+
148
+ // Trackbacks
149
+ 'anon_trackback_added' => _x(
150
+ 'Added a trackback to {comment_post_type} "{comment_post_title}"',
151
+ 'A trackback was added to the database by a non-logged in internet user',
152
+ 'simple-history'
153
+ ),
154
+
155
+ 'user_trackback_added' => _x(
156
+ 'Added a trackback to {comment_post_type} "{comment_post_title}"',
157
+ 'A trackback was added to the database by a logged in user',
158
+ 'simple-history'
159
+ ),
160
+
161
+ 'trackback_status_approve' => _x(
162
+ 'Approved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
163
+ 'A trackback was approved',
164
+ 'simple-history'
165
+ ),
166
+
167
+ 'trackback_status_hold' => _x(
168
+ 'Unapproved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
169
+ 'A trackback was was unapproved',
170
+ 'simple-history'
171
+ ),
172
+
173
+ 'trackback_status_spam' => _x(
174
+ 'Marked a trackback to post "{comment_post_title}" as spam',
175
+ 'A trackback was marked as spam',
176
+ 'simple-history'
177
+ ),
178
+
179
+ 'trackback_status_trash' => _x(
180
+ 'Trashed a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
181
+ 'A trackback was marked moved to the trash',
182
+ 'simple-history'
183
+ ),
184
+
185
+ 'trackback_untrashed' => _x(
186
+ 'Restored a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
187
+ 'A trackback was restored from the trash',
188
+ 'simple-history'
189
+ ),
190
+
191
+ 'trackback_deleted' => _x(
192
+ 'Deleted a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
193
+ 'A trackback was deleted',
194
+ 'simple-history'
195
+ ),
196
+
197
+ 'trackback_edited' => _x(
198
+ 'Edited a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
199
+ 'A trackback was edited',
200
+ 'simple-history'
201
+ ),
202
+
203
+ // Pingbacks
204
+ 'anon_pingback_added' => _x(
205
+ 'Added a pingback to {comment_post_type} "{comment_post_title}"',
206
+ 'A trackback was added to the database by a non-logged in internet user',
207
+ 'simple-history'
208
+ ),
209
+
210
+ 'user_pingback_added' => _x(
211
+ 'Added a pingback to {comment_post_type} "{comment_post_title}"',
212
+ 'A pingback was added to the database by a logged in user',
213
+ 'simple-history'
214
+ ),
215
+
216
+ 'pingback_status_approve' => _x(
217
+ 'Approved a pingback to "{comment_post_title}" by "{comment_author}"" ({comment_author_email})',
218
+ 'A pingback was approved',
219
+ 'simple-history'
220
+ ),
221
+
222
+ 'pingback_status_hold' => _x(
223
+ 'Unapproved a pingback to "{comment_post_title}" by "{comment_author}" ({comment_author_email})',
224
+ 'A pingback was was unapproved',
225
+ 'simple-history'
226
+ ),
227
+
228
+ 'pingback_status_spam' => _x(
229
+ 'Marked a pingback to post "{comment_post_title}" as spam',
230
+ 'A pingback was marked as spam',
231
+ 'simple-history'
232
+ ),
233
+
234
+ 'pingback_status_trash' => _x(
235
+ 'Trashed a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
236
+ 'A pingback was marked moved to the trash',
237
+ 'simple-history'
238
+ ),
239
+
240
+ 'pingback_untrashed' => _x(
241
+ 'Restored a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash',
242
+ 'A pingback was restored from the trash',
243
+ 'simple-history'
244
+ ),
245
+
246
+ 'pingback_deleted' => _x(
247
+ 'Deleted a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
248
+ 'A pingback was deleted',
249
+ 'simple-history'
250
+ ),
251
+
252
+ 'pingback_edited' => _x(
253
+ 'Edited a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})',
254
+ 'A pingback was edited',
255
+ 'simple-history'
256
+ ),
257
+
258
+ ), // end messages
259
+
260
+ "labels" => array(
261
+
262
+ "search" => array(
263
+ "label" => _x("Comments", "Comments logger: search", "simple-history"),
264
+ "label_all" => _x("All comments activity", "Comments logger: search", "simple-history"),
265
+ "options" => array(
266
+ _x("Added comments", "Comments logger: search", "simple-history") => array(
267
+ "anon_comment_added",
268
+ "user_comment_added",
269
+ "anon_trackback_added",
270
+ "user_trackback_added",
271
+ "anon_pingback_added",
272
+ "user_pingback_added"
273
+ ),
274
+ _x("Edited comments", "Comments logger: search", "simple-history") => array(
275
+ "comment_edited",
276
+ "trackback_edited",
277
+ "pingback_edited"
278
+ ),
279
+ _x("Approved comments", "Comments logger: search", "simple-history") => array(
280
+ "comment_status_approve",
281
+ "trackback_status_approve",
282
+ "pingback_status_approve"
283
+ ),
284
+ _x("Held comments", "Comments logger: search", "simple-history") => array(
285
+ "comment_status_hold",
286
+ "trackback_status_hold",
287
+ "pingback_status_hold"
288
+ ),
289
+ _x("Comments status changed to spam", "Comments logger: search", "simple-history") => array(
290
+ "comment_status_spam",
291
+ "trackback_status_spam",
292
+ "pingback_status_spam"
293
+ ),
294
+ _x("Trashed comments", "Comments logger: search", "simple-history") => array(
295
+ "comment_status_trash",
296
+ "trackback_status_trash",
297
+ "pingback_status_trash"
298
+ ),
299
+ _x("Untrashed comments", "Comments logger: search", "simple-history") => array(
300
+ "comment_untrashed",
301
+ "trackback_untrashed",
302
+ "pingback_untrashed"
303
+ ),
304
+ _x("Deleted comments", "Comments logger: search", "simple-history") => array(
305
+ "comment_deleted",
306
+ "trackback_deleted",
307
+ "pingback_deleted"
308
+ ),
309
+ )
310
+ ) // end search
311
+
312
+ ) // labels
313
+
314
+ );
315
+
316
+ return $arr_info;
317
+
318
+ }
319
+
320
+ public function loaded() {
321
+
322
+ /**
323
+ * Fires immediately after a comment is inserted into the database.
324
+ */
325
+ add_action( 'comment_post', array( $this, 'on_comment_post'), 10, 2 );
326
+
327
+ /**
328
+ * Fires after a comment status has been updated in the database.
329
+ * The hook also fires immediately before comment status transition hooks are fired.
330
+ */
331
+ add_action( "wp_set_comment_status", array( $this, 'on_wp_set_comment_status'), 10, 2 );
332
+
333
+ /**
334
+ *Fires immediately after a comment is restored from the Trash.
335
+ */
336
+ add_action( "untrashed_comment", array( $this, 'on_untrashed_comment'), 10, 1 );
337
+
338
+ /**
339
+ * Fires immediately before a comment is deleted from the database.
340
+ */
341
+ add_action( "delete_comment", array( $this, 'on_delete_comment'), 10, 1 );
342
+
343
+ /**
344
+ * Fires immediately after a comment is updated in the database.
345
+ * The hook also fires immediately before comment status transition hooks are fired.
346
+ */
347
+ add_action( "edit_comment", array( $this, 'on_edit_comment'), 10, 1 );
348
+
349
+
350
+ }
351
+
352
+ /**
353
+ * Get comments context
354
+ *
355
+ * @param int $comment_ID
356
+ * @return mixed array with context if comment found, false if comment not found
357
+ */
358
+ public function get_context_for_comment($comment_ID) {
359
+
360
+ // get_comment passes comment_ID by reference, so it can be unset by that function
361
+ $comment_ID_original = $comment_ID;
362
+ $comment_data = get_comment( $comment_ID );
363
+
364
+ if ( is_null( $comment_data ) ) {
365
+ return false;
366
+ }
367
+
368
+ $comment_parent_post = get_post( $comment_data->comment_post_ID );
369
+
370
+ $context = array(
371
+ "comment_ID" => $comment_ID_original,
372
+ "comment_author" => $comment_data->comment_author,
373
+ "comment_author_email" => $comment_data->comment_author_email,
374
+ "comment_author_url" => $comment_data->comment_author_url,
375
+ "comment_author_IP" => $comment_data->comment_author_IP,
376
+ "comment_content" => $comment_data->comment_content,
377
+ "comment_approved" => $comment_data->comment_approved,
378
+ "comment_agent" => $comment_data->comment_agent,
379
+ "comment_type" => $comment_data->comment_type,
380
+ "comment_parent" => $comment_data->comment_parent,
381
+ "comment_post_ID" => $comment_data->comment_post_ID,
382
+ "comment_post_title" => $comment_parent_post->post_title,
383
+ "comment_post_type" => $comment_parent_post->post_type,
384
+ );
385
+
386
+ return $context;
387
+
388
+ }
389
+
390
+ public function on_edit_comment($comment_ID) {
391
+
392
+ $context = $this->get_context_for_comment($comment_ID);
393
+ if ( ! $context ) {
394
+ return;
395
+ }
396
+
397
+ $this->infoMessage(
398
+ "{$context["comment_type"]}_edited",
399
+ $context
400
+ );
401
+
402
+ }
403
+
404
+ public function on_delete_comment($comment_ID) {
405
+
406
+ $context = $this->get_context_for_comment($comment_ID);
407
+ if ( ! $context ) {
408
+ return;
409
+ }
410
+
411
+
412
+ $comment_data = get_comment( $comment_ID );
413
+
414
+ // add occasions if comment was considered spam
415
+ // if not added, spam comments can easily flood the log
416
+ // Deletions of spam easiy flood log
417
+ if ( isset( $comment_data->comment_approved ) && "spam" === $comment_data->comment_approved ) {
418
+ $context["_occasionsID"] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_deleted/type:spam";
419
+ }
420
+
421
+ $this->infoMessage(
422
+ "{$context["comment_type"]}_deleted",
423
+ $context
424
+ );
425
+
426
+ }
427
+
428
+ public function on_untrashed_comment($comment_ID) {
429
+
430
+ $context = $this->get_context_for_comment($comment_ID);
431
+ if ( ! $context ) {
432
+ return;
433
+ }
434
+
435
+ $this->infoMessage(
436
+ "{$context["comment_type"]}_untrashed",
437
+ $context
438
+ );
439
+
440
+ }
441
+
442
+ /**
443
+ * Fires after a comment status has been updated in the database.
444
+ * The hook also fires immediately before comment status transition hooks are fired.
445
+ * @param int $comment_id The comment ID.
446
+ * @param string|bool $comment_status The comment status. Possible values include 'hold',
447
+ * 'approve', 'spam', 'trash', or false.
448
+ * do_action( 'wp_set_comment_status', $comment_id, $comment_status );
449
+ */
450
+ public function on_wp_set_comment_status($comment_ID, $comment_status) {
451
+
452
+ $context = $this->get_context_for_comment($comment_ID);
453
+ if ( ! $context ) {
454
+ return;
455
+ }
456
+
457
+ /*
458
+ $comment_status:
459
+ approve
460
+ comment was approved
461
+ spam
462
+ comment was marked as spam
463
+ trash
464
+ comment was trashed
465
+ hold
466
+ comment was un-approved
467
+ */
468
+ // sf_d($comment_status);exit;
469
+ $message = "{$context["comment_type"]}_status_{$comment_status}";
470
+
471
+ $this->infoMessage(
472
+ $message,
473
+ $context
474
+ );
475
+
476
+ }
477
+
478
+ /**
479
+ * Fires immediately after a comment is inserted into the database.
480
+ */
481
+ public function on_comment_post($comment_ID, $comment_approved) {
482
+
483
+ $context = $this->get_context_for_comment($comment_ID);
484
+
485
+ if ( ! $context ) {
486
+ return;
487
+ }
488
+
489
+ $comment_data = get_comment( $comment_ID );
490
+
491
+ $message = "";
492
+
493
+ if ( $comment_data->user_id ) {
494
+
495
+ // comment was from a logged in user
496
+ $message = "user_{$context["comment_type"]}_added";
497
+
498
+ } else {
499
+
500
+ // comment was from a non-logged in user
501
+ $message = "anon_{$context["comment_type"]}_added";
502
+ $context["_initiator"] = SimpleLoggerLogInitiators::WEB_USER;
503
+
504
+ // add occasions if comment is considered spam
505
+ // if not added, spam comments can easily flood the log
506
+ if ( isset( $comment_data->comment_approved ) && "spam" === $comment_data->comment_approved ) {
507
+ $context["_occasionsID"] = __CLASS__ . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_added/type:spam";
508
+ }
509
+
510
+ }
511
+
512
+ // @TODO: group comments by comment_type comment | trackback | pingback
513
+ // OR: different messages for different comment types?
514
+ /*
515
+ if ( isset( $comment_data["comment_type"] ) ) {
516
+
517
+ $comment_type = $comment_data["comment_type"];
518
+
519
+ }
520
+ */
521
+
522
+ $this->infoMessage(
523
+ $message,
524
+ $context
525
+ );
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
+ $message = $row->message;
537
+ $context = $row->context;
538
+ $message_key = $context["_message_key"];
539
+
540
+ // Wrap links around {comment_post_title}
541
+ $comment_post_ID = isset( $context["comment_post_ID"] ) ? (int) $context["comment_post_ID"] : null;
542
+ if ( $comment_post_ID && $comment_post = get_post( $comment_post_ID ) ) {
543
+
544
+ $edit_post_link = get_edit_post_link( $comment_post_ID );
545
+
546
+ if ( $edit_post_link ) {
547
+
548
+ $message = str_replace(
549
+ '"{comment_post_title}"',
550
+ "<a href='{$edit_post_link}'>\"{comment_post_title}\"</a>",
551
+ $message
552
+ );
553
+
554
+ }
555
+
556
+ }
557
+
558
+ return $this->interpolate($message, $context);
559
+
560
+ }
561
+
562
+
563
+ /**
564
+ * Get output for detailed log section
565
+ */
566
+ function getLogRowDetailsOutput($row) {
567
+
568
+ $context = $row->context;
569
+ $message_key = $context["_message_key"];
570
+ $output = "";
571
+
572
+ /*
573
+ if ( 'spam' !== $commentdata['comment_approved'] ) { // If it's spam save it silently for later crunching
574
+ if ( '0' == $commentdata['comment_approved'] ) { // comment not spam, but not auto-approved
575
+ wp_notify_moderator( $comment_ID );
576
+ */
577
+ /*if ( isset( $context["comment_approved"] ) && $context["comment_approved"] == '0' ) {
578
+ $output .= "<br>comment was automatically approved";
579
+ } else {
580
+ $output .= "<br>comment was not automatically approved";
581
+ }*/
582
+
583
+ $comment_text = "";
584
+ if ( isset( $context["comment_content"] ) && $context["comment_content"] ) {
585
+ $comment_text = $context["comment_content"];
586
+ $comment_text = wp_trim_words( $comment_text, 20 );
587
+ $comment_text = wpautop( $comment_text );
588
+ }
589
+
590
+ // Keys to show
591
+ $arr_plugin_keys = array();
592
+ $comment_type = isset( $context["comment_type"] ) ? $context["comment_type"] : "";
593
+
594
+ switch ( $comment_type ) {
595
+
596
+ case "trackback";
597
+
598
+ $arr_plugin_keys = array(
599
+ "trackback_status" => _x("Status", "comments logger - detailed output comment status", "simple-history"),
600
+ #"trackback_type" => _x("Trackback type", "comments logger - detailed output comment type", "simple-history"),
601
+ "trackback_author" => _x("Name", "comments logger - detailed output author", "simple-history"),
602
+ "trackback_author_email" => _x("Email", "comments logger - detailed output email", "simple-history"),
603
+ "trackback_content" => _x("Content", "comments logger - detailed output content", "simple-history"),
604
+ );
605
+
606
+ break;
607
+
608
+ case "pingback";
609
+
610
+ $arr_plugin_keys = array(
611
+
612
+ "pingback_status" => _x("Status", "comments logger - detailed output comment status", "simple-history"),
613
+ #"pingback_type" => _x("Pingback type", "comments logger - detailed output comment type", "simple-history"),
614
+ "pingback_author" => _x("Name", "comments logger - detailed output author", "simple-history"),
615
+ "pingback_author_email" => _x("Email", "comments logger - detailed output email", "simple-history"),
616
+ "pingback_content" => _x("Content", "comments logger - detailed output content", "simple-history"),
617
+
618
+ );
619
+
620
+ break;
621
+
622
+ case "comment";
623
+ default;
624
+
625
+ $arr_plugin_keys = array(
626
+ "comment_status" => _x("Status", "comments logger - detailed output comment status", "simple-history"),
627
+ #"comment_type" => _x("Comment type", "comments logger - detailed output comment type", "simple-history"),
628
+ "comment_author" => _x("Name", "comments logger - detailed output author", "simple-history"),
629
+ "comment_author_email" => _x("Email", "comments logger - detailed output email", "simple-history"),
630
+ "comment_content" => _x("Comment", "comments logger - detailed output content", "simple-history")
631
+ );
632
+
633
+ break;
634
+
635
+ //"comment_author_url" => _x("Author URL", "comments logger - detailed output author", "simple-history"),
636
+ //"comment_author_IP" => _x("IP number", "comments logger - detailed output IP", "simple-history"),
637
+
638
+ }
639
+
640
+ $arr_plugin_keys = apply_filters("simple_history/comments_logger/row_details_plugin_info_keys", $arr_plugin_keys);
641
+
642
+ // Start output of plugin meta data table
643
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
644
+
645
+ foreach ( $arr_plugin_keys as $key => $desc ) {
646
+
647
+ switch ( $key ) {
648
+
649
+ case "comment_content":
650
+ case "trackback_content":
651
+ case "pingback_content":
652
+ $desc_output = $comment_text;
653
+ break;
654
+
655
+ case "comment_author":
656
+ case "trackback_author":
657
+ case "pingback_author":
658
+
659
+ $desc_output = "";
660
+
661
+ $desc_output .= esc_html( $context[ $key ] );
662
+
663
+ /*
664
+ if ( isset( $context["comment_author_email"] ) ) {
665
+
666
+ $gravatar_email = $context["comment_author_email"];
667
+ $avatar = $this->simpleHistory->get_avatar( $gravatar_email, 14, "blank" );
668
+ $desc_output .= "<span class='SimpleCommentsLogger__gravatar'>{$avatar}</span>";
669
+
670
+ }
671
+ */
672
+
673
+ break;
674
+
675
+ case "comment_status":
676
+ case "trackback_status":
677
+ case "pingback_status":
678
+
679
+ if ( isset( $context["comment_approved"] ) ) {
680
+
681
+ if ( $context["comment_approved"] === "spam" ) {
682
+ $desc_output = __("Spam", "simple-history");
683
+ } else if ( $context["comment_approved"] == 1 ) {
684
+ $desc_output = __("Approved", "simple-history");
685
+ } else if ( $context["comment_approved"] == 0 ) {
686
+ $desc_output = __("Pending", "simple-history");
687
+ }
688
+
689
+ }
690
+
691
+ break;
692
+
693
+ case "comment_type":
694
+ case "trackback_type":
695
+ case "pingback_type":
696
+
697
+ if ( isset( $context["comment_type"] ) ) {
698
+
699
+ if ( $context["comment_type"] === "trackback" ) {
700
+ $desc_output = __("Trackback", "simple-history");
701
+ } else if ( $context["comment_type"] === "pingback" ) {
702
+ $desc_output = __("Pingback", "simple-history");
703
+ } else if ( $context["comment_type"] === "comment" ) {
704
+ $desc_output = __("Comment", "simple-history");
705
+ } else {
706
+ $desc_output = "";
707
+ }
708
+
709
+ }
710
+
711
+ break;
712
+
713
+ default;
714
+ $desc_output = esc_html( $context[ $key ] );
715
+ break;
716
+ }
717
+
718
+ // Skip empty rows
719
+ if (empty( $desc_output )) {
720
+ continue;
721
+ }
722
+
723
+ $output .= sprintf(
724
+ '
725
+ <tr>
726
+ <td>%1$s</td>
727
+ <td>%2$s</td>
728
+ </tr>
729
+ ',
730
+ esc_html($desc),
731
+ $desc_output
732
+ );
733
+
734
+ }
735
+
736
+ // Add link to edit comment
737
+ $comment_ID = isset( $context["comment_ID"] ) && is_numeric( $context["comment_ID"] ) ? (int) $context["comment_ID"] : false;
738
+
739
+ if ( $comment_ID ) {
740
+
741
+ // http://site.local/wp/wp-admin/comment.php?action=editcomment&c=
742
+ $edit_comment_link = get_edit_comment_link( $comment_ID );
743
+
744
+ // Edit link sometimes does not contain comment ID
745
+ // Probably because comment has been removed or something
746
+ // So only continue if link does not end with "=""
747
+ if ( $edit_comment_link && $edit_comment_link[strlen($edit_comment_link)-1] !== "=" ) {
748
+
749
+ $output .= sprintf(
750
+ '
751
+ <tr>
752
+ <td></td>
753
+ <td><a href="%2$s">%1$s</a></td>
754
+ </tr>
755
+ ',
756
+ _x("View/Edit", "comments logger - edit comment", "simple-history"),
757
+ $edit_comment_link
758
+ );
759
+
760
+ }
761
+
762
+ }
763
+
764
+ // End table
765
+ $output .= "</table>";
766
+
767
+ return $output;
768
+
769
+ }
770
+
771
+ function adminCSS() {
772
+ ?>
773
+ <style>
774
+ .SimpleCommentsLogger__gravatar {
775
+ line-height: 1;
776
+ border-radius: 50%;
777
+ overflow: hidden;
778
+ margin-right: .5em;
779
+ margin-left: .5em;
780
+ display: inline-block;
781
+ }
782
+ .SimpleCommentsLogger__gravatar img {
783
+ display: block;
784
+ }
785
+ </style>
786
+ <?php
787
+ }
788
+
789
+ }
loggers/SimpleCoreUpdatesLogger.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs WordPress core updates
5
+ */
6
+ class SimpleCoreUpdatesLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ public function loaded() {
12
+
13
+ add_action( '_core_updated_successfully', array( $this, "on_core_updated" ) );
14
+
15
+ }
16
+
17
+ /**
18
+ * Get array with information about this logger
19
+ *
20
+ * @return array
21
+ */
22
+ function getInfo() {
23
+
24
+ $arr_info = array(
25
+ "name" => "Core Updates Logger",
26
+ "description" => "Logs the update of WordPress (manual and automatic updates)",
27
+ "capability" => "update_core",
28
+ "messages" => array(
29
+ 'core_updated' => __('Updated WordPress from {prev_version} to {new_version}', 'simple-history'),
30
+ 'core_auto_updated' => __('WordPress auto-updated from {prev_version} to {new_version}', 'simple-history')
31
+ ),
32
+ "labels" => array(
33
+ "search" => array(
34
+ "label" => _x("WordPress Core", "User logger: search", "simple-history"),
35
+ "options" => array(
36
+ _x("WordPress core updates", "User logger: search", "simple-history") => array(
37
+ "core_updated",
38
+ "core_auto_updated"
39
+ ),
40
+ )
41
+ ) // end search array
42
+ ) // end labels
43
+ );
44
+
45
+ return $arr_info;
46
+
47
+ }
48
+
49
+ /**
50
+ * Called when WordPress is updated
51
+ *
52
+ * @param string $new_wp_version
53
+ */
54
+ public function on_core_updated($new_wp_version) {
55
+
56
+ $old_wp_version = $GLOBALS['wp_version'];
57
+
58
+ $auto_update = true;
59
+ if ( $GLOBALS['pagenow'] == 'update-core.php' ) {
60
+ $auto_update = false;
61
+ }
62
+
63
+ if ($auto_update) {
64
+ $message = "core_auto_updated";
65
+ } else {
66
+ $message = "core_updated";
67
+ }
68
+
69
+ $this->noticeMessage(
70
+ $message,
71
+ array(
72
+ "prev_version" => $old_wp_version,
73
+ "new_version" => $new_wp_version
74
+ )
75
+ );
76
+
77
+ }
78
+
79
+ }
loggers/SimpleExportLogger.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs WordPress exports
5
+ */
6
+ class SimpleExportLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ /**
12
+ * Get array with information about this logger
13
+ *
14
+ * @return array
15
+ */
16
+ function getInfo() {
17
+
18
+ $arr_info = array(
19
+ "name" => "Export Logger",
20
+ "description" => "Logs updates to WordPress export",
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
+
41
+ function loaded() {
42
+
43
+ add_action( 'export_wp', array($this, "on_export_wp"), 10, 1 );
44
+
45
+ }
46
+
47
+ 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
+ }
57
+
58
+ /**
59
+ * Get detailed output
60
+ */
61
+ /*
62
+ function getLogRowDetailsOutput($row) {
63
+
64
+ $context = $row->context;
65
+ $message_key = $context["_message_key"];
66
+ $output = "";
67
+
68
+ return $output;
69
+
70
+ }
71
+ */
72
+
73
+ }
loggers/SimpleLegacyLogger.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logger for events stored earlier than v2
5
+ * and for events added via simple_history_add
6
+ *
7
+ * @since 2.0
8
+ */
9
+ class SimpleLegacyLogger extends SimpleLogger
10
+ {
11
+
12
+ /**
13
+ * Unique slug for this logger
14
+ * Will be saved in DB and used to associate each log row with its logger
15
+ */
16
+ public $slug = "SimpleLegacyLogger";
17
+
18
+ public function __construct() {
19
+
20
+ // $this->info(__CLASS__ . " construct()");
21
+
22
+ }
23
+
24
+ /**
25
+ * Get array with information about this logger
26
+ *
27
+ * @return array
28
+ */
29
+ function getInfo() {
30
+
31
+ $arr_info = array(
32
+ "name" => "Legacy Logger",
33
+ "description" => "Formats old events",
34
+ "capability" => "edit_pages",
35
+ "messages" => array(
36
+ ),
37
+ /*
38
+
39
+ "labels" => array(
40
+ "search" => array(
41
+ "label" => _x("Export", "Export logger: search", "simple-history"),
42
+ "options" => array(
43
+ _x("Exports created", "Core updates logger: search", "simple-history") => array(
44
+ "created_export"
45
+ ),
46
+ )
47
+ ) // end search array
48
+ ) // end labels
49
+ */
50
+
51
+ );
52
+
53
+ return $arr_info;
54
+
55
+ }
56
+
57
+ public function getLogRowPlainTextOutput($row) {
58
+
59
+ $message = $row->message;
60
+ $context = $row->context;
61
+
62
+ $out = "";
63
+
64
+ global $wpdb;
65
+
66
+ // Get old columns for this event
67
+ $sql = sprintf('
68
+ SELECT * FROM %1$s
69
+ WHERE id = %2$d
70
+ ',
71
+ $wpdb->prefix . SimpleHistory::DBTABLE,
72
+ $row->id
73
+ );
74
+
75
+ $one_item = $wpdb->get_row($sql);
76
+
77
+ #$out .= print_r($row, true);
78
+
79
+ // Code mostly from version 1.x
80
+ $object_type = ucwords($one_item->object_type);
81
+ $object_name = esc_html($one_item->object_name);
82
+ $user = get_user_by("id", $one_item->user_id);
83
+ $user_nicename = esc_html(@$user->user_nicename);
84
+ $user_email = esc_html(@$user->user_email);
85
+ $description = "";
86
+
87
+ if ($user_nicename) {
88
+ $description .= sprintf(__("By %s", 'simple-history'), $user_nicename);
89
+
90
+ }
91
+
92
+ if ( isset( $one_item->occasions ) ) {
93
+ $description .= sprintf(__("%d occasions", 'simple-history'), sizeof($one_item->occasions));
94
+
95
+ }
96
+
97
+ $item_title = esc_html($object_type) . " \"" . esc_html($object_name) . "\" {$one_item->action}";
98
+ $item_title = html_entity_decode($item_title, ENT_COMPAT, "UTF-8");
99
+
100
+ $out .= "$item_title";
101
+ $out .= "<br>$description";
102
+
103
+ return $out;
104
+
105
+ }
106
+
107
+
108
+ }
109
+
loggers/SimpleLogger.php ADDED
@@ -0,0 +1,1111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A PSR-3 inspired logger class
5
+ * This class logs + formats logs for display in the Simple History GUI/Viewer
6
+ *
7
+ * Extend this class to make your own logger
8
+ *
9
+ * @link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md PSR-3 specification
10
+ */
11
+ class 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 = __CLASS__;
19
+
20
+ /**
21
+ * Will contain the untranslated messages from getInfo()
22
+ *
23
+ * By adding your messages here they will be stored both translated and non-translated
24
+ * You then log something like this:
25
+ * <code>
26
+ * $this->info( $this->messages["POST_UPDATED"] );
27
+ * </code>
28
+ * or with the shortcut
29
+ * <code>
30
+ * $this->infoMessage("POST_UPDATED");
31
+ * </code>
32
+ * which results in the original, untranslated, string being added to the log and database
33
+ * the translated string are then only used when showing the log in the GUI
34
+ */
35
+ public $messages;
36
+
37
+ /**
38
+ * ID of last inserted row. Used when chaining methods.
39
+ */
40
+ private $lastInsertID;
41
+
42
+ /**
43
+ * Constructor. Remember to call this as parent constructor if making a childlogger
44
+ * @param $simpleHistory history class objectinstance
45
+ */
46
+ public function __construct($simpleHistory) {
47
+
48
+ global $wpdb;
49
+
50
+ $this->db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
51
+ $this->db_table_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
52
+
53
+ $this->simpleHistory = $simpleHistory;
54
+
55
+ }
56
+
57
+ /**
58
+ * Method that is called automagically when logger is loaded by Simple History
59
+ * Add your init stuff here
60
+ */
61
+ public function loaded() {
62
+
63
+ }
64
+
65
+ /**
66
+ * Get array with information about this logger
67
+ *
68
+ * @return array
69
+ */
70
+ function getInfo() {
71
+
72
+ $arr_info = array(
73
+
74
+ // The logger slug. Defaulting to the class name is nice and logical I think
75
+ "slug" => __CLASS__,
76
+
77
+ // Shown on the info-tab in settings, use these fields to tell
78
+ // an admin what your logger is used for
79
+ "name" => "SimpleLogger",
80
+ "description" => "The built in logger for Simple History",
81
+
82
+ // Capability required to view log entries from this logger
83
+ "capability" => "edit_pages",
84
+ "messages" => array(
85
+ // No pre-defined variants
86
+ // when adding messages __() or _x() must be used
87
+ )
88
+
89
+ );
90
+
91
+ return $arr_info;
92
+
93
+ }
94
+
95
+ /**
96
+ * Returns the capability required to read log rows from this logger
97
+ *
98
+ * @return $string capability
99
+ */
100
+ public function getCapability() {
101
+
102
+ $arr_info = $this->getInfo();
103
+
104
+ return $arr_info["capability"];
105
+
106
+ }
107
+
108
+ /**
109
+ * Interpolates context values into the message placeholders.
110
+ */
111
+ function interpolate($message, $context = array())
112
+ {
113
+
114
+ if ( ! is_array($context) ) {
115
+ return $message;
116
+ }
117
+
118
+ // build a replacement array with braces around the context keys
119
+ $replace = array();
120
+ foreach ($context as $key => $val) {
121
+ $replace['{' . $key . '}'] = $val;
122
+ }
123
+
124
+ // interpolate replacement values into the message and return
125
+ return strtr($message, $replace);
126
+
127
+ }
128
+
129
+ /**
130
+ * Returns header output for a log row
131
+ * Format should be common for all log rows and should be like:
132
+ * Username (user role) · Date
133
+ * @return string HTML
134
+ */
135
+ function getLogRowHeaderOutput($row) {
136
+
137
+ // HTML for initiator
138
+ $initiator_html = "";
139
+
140
+ $initiator = $row->initiator;
141
+ $context = $row->context;
142
+
143
+ switch ( $initiator ) {
144
+
145
+ case "wp":
146
+ $initiator_html .= '<strong class="SimpleHistoryLogitem__inlineDivided">WordPress</strong> ';
147
+ break;
148
+
149
+ // wp_user = wordpress uses, but user may have been deleted since log entry was added
150
+ case "wp_user":
151
+
152
+ $user_id = isset( $row->context["_user_id"] ) ? $row->context["_user_id"] : null;
153
+
154
+ if ( $user_id > 0 && $user = get_user_by("id", $user_id) ) {
155
+
156
+ // Sender is user and user still exists
157
+ $is_current_user = ( $user_id == get_current_user_id() ) ? true : false;
158
+
159
+ // get user role, as done in user-edit.php
160
+ $user_roles = array_intersect( array_values( $user->roles ), array_keys( get_editable_roles() ) );
161
+ $user_role = array_shift( $user_roles );
162
+ $user_display_name = $user->display_name;
163
+
164
+ $tmpl_initiator_html = '
165
+ <strong class="SimpleHistoryLogitem__inlineDivided">%3$s</strong>
166
+ <span class="SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__headerEmail">%2$s</span>
167
+ ';
168
+
169
+ // If user who logged this is the currently logged in user
170
+ // we replace name and email with just "You"
171
+ if ($is_current_user) {
172
+ $tmpl_initiator_html = '
173
+ <strong class="SimpleHistoryLogitem__inlineDivided">%5$s</strong>
174
+ ';
175
+ }
176
+
177
+ /**
178
+ * Filter the format for the user output
179
+ *
180
+ * @since 2.0
181
+ *
182
+ * @param string $format.
183
+ */
184
+ $$tmpl_initiator_html = apply_filters("simple_history/header_initiator_html_existing_user", $tmpl_initiator_html);
185
+
186
+ $initiator_html .= sprintf(
187
+ $tmpl_initiator_html,
188
+ esc_html( $user->user_login ), // 1
189
+ esc_html( $user->user_email ), // 2
190
+ esc_html( $user_display_name ), // 3
191
+ $user_role, // 4
192
+ _x("You", "header output when initiator is the currently logged in user", "simple-history") // 5
193
+ );
194
+
195
+ } else if ($user_id > 0) {
196
+
197
+ // Sender was a user, but user is deleted now
198
+ // output all info we have
199
+ // _user_id
200
+ // _username
201
+ // _user_login
202
+ // _user_email
203
+ $initiator_html .= sprintf(
204
+ '<strong class="SimpleHistoryLogitem__inlineDivided">' .
205
+ __('Deleted user (had id %1$s, email %2$s, login %3$s)', "simple-history") .
206
+ '</strong>',
207
+ esc_html( $context["_user_id"] ),
208
+ esc_html( $context["_user_email"] ),
209
+ esc_html( $context["_user_login"] )
210
+ );
211
+
212
+ }
213
+
214
+ break;
215
+
216
+ case "web_user":
217
+
218
+ if ( empty( $context["_server_remote_addr"] ) ) {
219
+
220
+ $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided'>" . __("Anonymous web user") . "</strong> ";
221
+
222
+ } else {
223
+
224
+ $iplookup_link = sprintf('https://ipinfo.io/%1$s', esc_attr( $context["_server_remote_addr"] ));
225
+
226
+ $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__anonUserWithIp'>";
227
+ $initiator_html .= sprintf(
228
+ __('Anonymous user from %1$s', "simple-history"),
229
+ "<a target='_blank' href={$iplookup_link} class='SimpleHistoryLogitem__anonUserWithIp__theIp'>" . esc_attr( $context["_server_remote_addr"] ) . "</a>"
230
+ );
231
+ $initiator_html .= "</strong> ";
232
+
233
+ // $initiator_html .= "<strong>" . __("<br><br>Unknown user from {$context["_server_remote_addr"]}") . "</strong>";
234
+ // $initiator_html .= "<strong>" . __("<br><br>{$context["_server_remote_addr"]}") . "</strong>";
235
+ // $initiator_html .= "<strong>" . __("<br><br>User from IP {$context["_server_remote_addr"]}") . "</strong>";
236
+ // $initiator_html .= "<strong>" . __("<br><br>Non-logged in user from IP {$context["_server_remote_addr"]}") . "</strong>";
237
+
238
+ }
239
+
240
+ break;
241
+
242
+ case "other":
243
+ $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided'>Other</strong>";
244
+ break;
245
+
246
+ // no initiator
247
+ case null:
248
+ // $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided'>Null</strong>";
249
+ break;
250
+
251
+ default:
252
+ $initiator_html .= "<strong class='SimpleHistoryLogitem__inlineDivided'>" . esc_html( $initiator ) . "</strong>";
253
+
254
+ }
255
+
256
+ /**
257
+ * Filter generated html for the initiator row header html
258
+ *
259
+ * @since 2.0
260
+ *
261
+ * @param string $initiator_html
262
+ * @param object $row Log row
263
+ */
264
+ $initiator_html = apply_filters("simple_history/row_header_initiator_output", $initiator_html, $row);
265
+
266
+
267
+ // HTML for date
268
+ // Date (should...) always exist
269
+ // http://developers.whatwg.org/text-level-semantics.html#the-time-element
270
+ $date_html = "";
271
+ $str_when = "";
272
+ $date_datetime = new DateTime($row->date);
273
+
274
+ /**
275
+ * Filter how many seconds as most that can pass since an
276
+ * event occured to show "nn minutes ago" (human diff time-format) instead of exact date
277
+ *
278
+ * @since 2.0
279
+ *
280
+ * @param int $time_ago_max_time Seconds
281
+ */
282
+ $time_ago_max_time = DAY_IN_SECONDS * 2;
283
+ $time_ago_max_time = apply_filters("simple_history/header_time_ago_max_time", $time_ago_max_time);
284
+
285
+ /**
286
+ * Filter how many seconds as most that can pass since an
287
+ * event occured to show "just now" instead of exact date
288
+ *
289
+ * @since 2.0
290
+ *
291
+ * @param int $time_ago_max_time Seconds
292
+ */
293
+ $time_ago_just_now_max_time = 30;
294
+ $time_ago_just_now_max_time = apply_filters("simple_history/header_just_now_max_time", $time_ago_just_now_max_time);
295
+
296
+ if ( time() - $date_datetime->getTimestamp() <= $time_ago_just_now_max_time ) {
297
+
298
+ // show "just now" if event is very recent
299
+ $str_when = __("Just now", "simple-history");
300
+
301
+ } else if ( time() - $date_datetime->getTimestamp() > $time_ago_max_time ) {
302
+
303
+ /* translators: Date format for log row header, see http://php.net/date */
304
+ $datef = __( 'M j, Y \a\t G:i', "simple-history" );
305
+ $str_when = date_i18n( $datef, $date_datetime->getTimestamp() );
306
+
307
+ } else {
308
+
309
+ // Show "nn minutes ago" when event is xx seconds ago or earlier
310
+ $date_human_time_diff = human_time_diff( $date_datetime->getTimestamp(), time() );
311
+ /* translators: 1: last modified date and time in human time diff-format */
312
+ $str_when = sprintf( __( '%1$s ago', 'simple-history' ), $date_human_time_diff );
313
+
314
+ }
315
+
316
+ $item_permalink = admin_url("index.php?page=simple_history_page");
317
+ $item_permalink .= "#item/{$row->id}";
318
+
319
+ $date_html = "<span class='SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided'>";
320
+ $date_html .= "<a class='' href='{$item_permalink}'>";
321
+ $date_html .= sprintf(
322
+ '<time datetime="%1$s" title="%1$s" class="">%2$s</time>',
323
+ $date_datetime->format(DateTime::RFC3339), // 1 datetime attribute
324
+ $str_when
325
+ );
326
+ $date_html .= "</a>";
327
+ $date_html .= "</span>";
328
+
329
+ // Loglevel
330
+ // SimpleHistoryLogitem--loglevel-warning
331
+ /*
332
+ $level_html = sprintf(
333
+ '<span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%1$s</span>',
334
+ $row->level
335
+ );
336
+ */
337
+
338
+ // Glue together final result
339
+ $template = '%1$s%2$s';
340
+ #if ( ! $initiator_html ) {
341
+ # $template = '%2$s';
342
+ #}
343
+
344
+ $html = sprintf(
345
+ $template,
346
+ $initiator_html, // 1
347
+ $date_html // 2
348
+ // $level_html // 3
349
+ );
350
+
351
+ /**
352
+ * Filter generated html for the log row header
353
+ *
354
+ * @since 2.0
355
+ *
356
+ * @param string $html
357
+ * @param object $row Log row
358
+ */
359
+ $html = apply_filters("simple_history/row_header_output", $html, $row);
360
+
361
+ return $html;
362
+
363
+ }
364
+
365
+ /**
366
+ * Returns the plain text version of this entry
367
+ * Used in for example CSV-exports.
368
+ * Defaults to log message with context interpolated.
369
+ * Keep format as plain and simple as possible.
370
+ * Links are ok, for example to link to users or posts.
371
+ * Tags will be stripped when text is used for CSV-exports and so on.
372
+ * Keep it on a single line. No <p> or <br> and so on.
373
+ *
374
+ * Example output:
375
+ * Edited post "About the company"
376
+ *
377
+ * Message should sound like it's coming from the user.
378
+ * Image that the name of the user is added in front of the text:
379
+ * Jessie James: Edited post "About the company"
380
+ */
381
+ public function getLogRowPlainTextOutput($row) {
382
+
383
+ $message = $row->message;
384
+ $message_key = $row->context["_message_key"];
385
+
386
+ // Message is translated here, but translation must be added in
387
+ // plain text before
388
+
389
+ if ( empty( $message_key ) ) {
390
+
391
+ // Leave message alone
392
+
393
+ } else {
394
+
395
+ $message = $this->messages[ $message_key ]["translated_text"];
396
+
397
+ }
398
+
399
+ $html = $this->interpolate( $message, $row->context );
400
+
401
+ // All messages are escaped by default.
402
+ // If you need unescaped output override this method
403
+ // in your own logger
404
+ $html = esc_html($html);
405
+
406
+ /**
407
+ * Filter generated output for plain text output
408
+ *
409
+ * @since 2.0
410
+ *
411
+ * @param string $html
412
+ * @param object $row Log row
413
+ */
414
+ $html = apply_filters("simple_history/row_plain_text_output", $html, $row);
415
+
416
+ return $html;
417
+
418
+ }
419
+
420
+ /**
421
+ * Get output for image
422
+ * Image can be for example gravar if sender is user,
423
+ * or other images if sender i system, wordpress, and so on
424
+ */
425
+ public function getLogRowSenderImageOutput($row) {
426
+
427
+ $sender_image_html = "";
428
+ $sender_image_size = 32;
429
+
430
+ $initiator = $row->initiator;
431
+
432
+ switch ( $initiator ) {
433
+
434
+ // wp_user = wordpress uses, but user may have been deleted since log entry was added
435
+ case "wp_user":
436
+
437
+ $user_id = isset($row->context["_user_id"]) ? $row->context["_user_id"] : null;
438
+
439
+ if ( $user_id > 0 && $user = get_user_by("id", $user_id) ) {
440
+
441
+ // Sender was user
442
+ $sender_image_html = $this->simpleHistory->get_avatar( $user->user_email, $sender_image_size );
443
+
444
+ } else if ($user_id > 0) {
445
+
446
+ // Sender was a user, but user is deleted now
447
+ $sender_image_html = $this->simpleHistory->get_avatar( "", $sender_image_size );
448
+
449
+ } else {
450
+
451
+ $sender_image_html = $this->simpleHistory->get_avatar( "", $sender_image_size );
452
+
453
+ }
454
+
455
+ break;
456
+
457
+ }
458
+ /**
459
+ * Filter generated output for row image (sender image)
460
+ *
461
+ * @since 2.0
462
+ *
463
+ * @param string $sender_image_html
464
+ * @param object $row Log row
465
+ */
466
+ $sender_image_html = apply_filters("simple_history/row_sender_image_output", $sender_image_html, $row);
467
+
468
+ return $sender_image_html;
469
+
470
+ }
471
+
472
+ /**
473
+ * Use this method to output detailed output for a log row
474
+ * Example usage is if a user has uploaded an image then a
475
+ * thumbnail of that image can bo outputed here
476
+ *
477
+ * @param object $row
478
+ * @return string HTML-formatted output
479
+ */
480
+ public function getLogRowDetailsOutput($row) {
481
+
482
+ $html = "";
483
+
484
+ /**
485
+ * Filter generated output for details
486
+ *
487
+ * @since 2.0
488
+ *
489
+ * @param string $html
490
+ * @param object $row Log row
491
+ */
492
+ $html = apply_filters("simple_history/row_details_output", $html, $row);
493
+
494
+ return $html;
495
+
496
+ }
497
+
498
+
499
+ /**
500
+ * System is unusable.
501
+ *
502
+ * @param string $message
503
+ * @param array $context
504
+ * @return null
505
+ */
506
+ public static function emergency($message, array $context = array())
507
+ {
508
+
509
+ return $this->log(SimpleLoggerLogLevels::EMERGENCY, $message, $context);
510
+
511
+ }
512
+
513
+ /**
514
+ * System is unusable.
515
+ *
516
+ * @param string $message key from getInfo messages array
517
+ * @param array $context
518
+ * @return null
519
+ */
520
+ public function emergencyMessage($message, array $context = array())
521
+ {
522
+
523
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
524
+ return;
525
+ }
526
+
527
+ $context["_message_key"] = $message;
528
+ $message = $this->messages[ $message ]["untranslated_text"];
529
+
530
+ $this->log(SimpleLoggerLogLevels::EMERGENCY, $message, $context);
531
+
532
+ }
533
+
534
+
535
+ /**
536
+ * Action must be taken immediately.
537
+ *
538
+ * @param string $message
539
+ * @param array $context
540
+ * @return null
541
+ */
542
+ public static function alert($message, array $context = array())
543
+ {
544
+ return $this->log(SimpleLoggerLogLevels::ALERT, $message, $context);
545
+
546
+ }
547
+
548
+ /**
549
+ * Action must be taken immediately.
550
+ *
551
+ * @param string $message key from getInfo messages array
552
+ * @param array $context
553
+ * @return null
554
+ */
555
+ public function alertMessage($message, array $context = array())
556
+ {
557
+
558
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
559
+ return;
560
+ }
561
+
562
+ $context["_message_key"] = $message;
563
+ $message = $this->messages[ $message ]["untranslated_text"];
564
+
565
+ $this->log(SimpleLoggerLogLevels::ALERT, $message, $context);
566
+
567
+ }
568
+
569
+
570
+ /**
571
+ * Critical conditions.
572
+ *
573
+ * Example: Application component unavailable, unexpected exception.
574
+ *
575
+ * @param string $message
576
+ * @param array $context
577
+ * @return null
578
+ */
579
+ public static function critical($message, array $context = array())
580
+ {
581
+
582
+ return $this->log(SimpleLoggerLogLevels::CRITICAL, $message, $context);
583
+
584
+ }
585
+
586
+ /**
587
+ * Critical conditions.
588
+ *
589
+ * @param string $message key from getInfo messages array
590
+ * @param array $context
591
+ * @return null
592
+ */
593
+ public function criticalMessage($message, array $context = array())
594
+ {
595
+
596
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
597
+ return;
598
+ }
599
+
600
+ $context["_message_key"] = $message;
601
+ $message = $this->messages[ $message ]["untranslated_text"];
602
+
603
+ $this->log(SimpleLoggerLogLevels::CRITICAL, $message, $context);
604
+
605
+ }
606
+
607
+
608
+ /**
609
+ * Runtime errors that do not require immediate action but should typically
610
+ * be logged and monitored.
611
+ *
612
+ * @param string $message
613
+ * @param array $context
614
+ * @return null
615
+ */
616
+ public function error($message, array $context = array())
617
+ {
618
+
619
+ return $this->log(SimpleLoggerLogLevels::ERROR, $message, $context);
620
+
621
+ }
622
+
623
+ /**
624
+ * Runtime errors that do not require immediate action but should typically
625
+ * be logged and monitored.
626
+ *
627
+ * @param string $message key from getInfo messages array
628
+ * @param array $context
629
+ * @return null
630
+ */
631
+ public function errorMessage($message, array $context = array())
632
+ {
633
+
634
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
635
+ return;
636
+ }
637
+
638
+ $context["_message_key"] = $message;
639
+ $message = $this->messages[ $message ]["untranslated_text"];
640
+
641
+ $this->log(SimpleLoggerLogLevels::ERROR, $message, $context);
642
+
643
+ }
644
+
645
+
646
+ /**
647
+ * Exceptional occurrences that are not errors.
648
+ *
649
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
650
+ * that are not necessarily wrong.
651
+ *
652
+ * @param string $message
653
+ * @param array $context
654
+ * @return null
655
+ */
656
+ public function warning($message, array $context = array())
657
+ {
658
+
659
+ return $this->log(SimpleLoggerLogLevels::WARNING, $message, $context);
660
+
661
+ }
662
+
663
+ /**
664
+ * Exceptional occurrences that are not errors.
665
+ *
666
+ * @param string $message key from getInfo messages array
667
+ * @param array $context
668
+ * @return null
669
+ */
670
+ public function warningMessage($message, array $context = array())
671
+ {
672
+
673
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
674
+ return;
675
+ }
676
+
677
+ $context["_message_key"] = $message;
678
+ $message = $this->messages[ $message ]["untranslated_text"];
679
+
680
+ $this->log(SimpleLoggerLogLevels::WARNING, $message, $context);
681
+
682
+ }
683
+
684
+
685
+ /**
686
+ * Normal but significant events.
687
+ *
688
+ * @param string $message
689
+ * @param array $context
690
+ * @return null
691
+ */
692
+ public function notice($message, array $context = array())
693
+ {
694
+
695
+ return $this->log(SimpleLoggerLogLevels::NOTICE, $message, $context);
696
+
697
+ }
698
+
699
+ /**
700
+ * Normal but significant events.
701
+ *
702
+ * @param string $message key from getInfo messages array
703
+ * @param array $context
704
+ * @return null
705
+ */
706
+ public function noticeMessage($message, array $context = array())
707
+ {
708
+
709
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
710
+ return;
711
+ }
712
+
713
+ $context["_message_key"] = $message;
714
+ $message = $this->messages[ $message ]["untranslated_text"];
715
+
716
+ $this->log(SimpleLoggerLogLevels::NOTICE, $message, $context);
717
+
718
+ }
719
+
720
+
721
+ /**
722
+ * Interesting events.
723
+ *
724
+ * Example: User logs in, SQL logs.
725
+ *
726
+ * @param string $message
727
+ * @param array $context
728
+ * @return null
729
+ */
730
+ public function info($message, array $context = array())
731
+ {
732
+
733
+ return $this->log(SimpleLoggerLogLevels::INFO, $message, $context);
734
+
735
+ }
736
+
737
+ /**
738
+ * Interesting events.
739
+ *
740
+ * Example: User logs in, SQL logs.
741
+ *
742
+ * @param string $message key from getInfo messages array
743
+ * @param array $context
744
+ * @return null
745
+ */
746
+ public function infoMessage($message, array $context = array())
747
+ {
748
+
749
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
750
+ return;
751
+ }
752
+
753
+ $context["_message_key"] = $message;
754
+ $message = $this->messages[ $message ]["untranslated_text"];
755
+
756
+ $this->log(SimpleLoggerLogLevels::INFO, $message, $context);
757
+
758
+ }
759
+
760
+ /**
761
+ * Detailed debug information.
762
+ *
763
+ * @param string $message
764
+ * @param array $context
765
+ * @return null
766
+ */
767
+ public function debug($message, array $context = array())
768
+ {
769
+
770
+ return $this->log(SimpleLoggerLogLevels::DEBUG, $message, $context);
771
+
772
+ }
773
+
774
+ /**
775
+ * Detailed debug information.
776
+ *
777
+ * @param string $message key from getInfo messages array
778
+ * @param array $context
779
+ * @return null
780
+ */
781
+ public function debugMessage($message, array $context = array())
782
+ {
783
+
784
+ if ( ! isset( $this->messages[ $message ]["untranslated_text"] ) ) {
785
+ return;
786
+ }
787
+
788
+ $context["_message_key"] = $message;
789
+ $message = $this->messages[ $message ]["untranslated_text"];
790
+
791
+ $this->log(SimpleLoggerLogLevels::DEBUG, $message, $context);
792
+
793
+ }
794
+
795
+ /**
796
+ * Logs with an arbitrary level.
797
+ *
798
+ * @param mixed $level
799
+ * @param string $message
800
+ * @param array $context
801
+ * @return null
802
+ */
803
+ public function log($level, $message, array $context = array())
804
+ {
805
+
806
+ global $wpdb;
807
+
808
+ /**
809
+ * Filter arguments passed to log funtion
810
+ *
811
+ * @since 2.0
812
+ *
813
+ * @param string $level
814
+ * @param string $message
815
+ * @param array $context
816
+ */
817
+ apply_filters("simple_history/log_arguments", $level, $message, $context);
818
+
819
+ /* Store date at utc or local time?
820
+ * Some info here:
821
+ * http://www.skyverge.com/blog/down-the-rabbit-hole-wordpress-and-timezones/
822
+ * UNIX timestamp = no timezone = UTC
823
+ * anything is better than now() anyway!
824
+ * WP seems to use the local time, so I will go with that too I think
825
+ * GMT/UTC-time is: date_i18n($timezone_format, false, 'gmt'));
826
+ * local time is: date_i18n($timezone_format));
827
+ */
828
+ $localtime = current_time("mysql", 1);
829
+
830
+ $db_table = $wpdb->prefix . SimpleHistory::DBTABLE;
831
+
832
+ /**
833
+ * Filter db table used for simple history events
834
+ *
835
+ * @since 2.0
836
+ *
837
+ * @param string $db_table
838
+ */
839
+ $db_table = apply_filters("simple_history/db_table", $db_table);
840
+
841
+ $data = array(
842
+ "logger" => $this->slug,
843
+ "level" => $level,
844
+ "date" => $localtime,
845
+ "message" => $message,
846
+ );
847
+
848
+ // Allow date to be override
849
+ // Date must be in format 'Y-m-d H:i:s'
850
+ if ( isset( $context["_date"] ) ) {
851
+ $data["date"] = $context["_date"];
852
+ unset($context["_date"]);
853
+ }
854
+
855
+ // Add occasions id
856
+ $occasions_id = null;
857
+ if ( isset( $context["_occasionsID"] ) ) {
858
+
859
+ // Minimize risk of similar loggers logging same messages and such and resulting in same occasions id
860
+ // by always adding logger slug
861
+ $occasions_data = array(
862
+ "_occasionsID" => $context["_occasionsID"],
863
+ "_loggerSlug" => $this->slug
864
+ );
865
+ $occasions_id = md5( json_encode($occasions_data) );
866
+ unset( $context["_occasionsID"] );
867
+
868
+ } else {
869
+
870
+ // No occasions id specified, create one bases on the data array
871
+ $occasions_data = $data + $context;
872
+
873
+ // Don't include date in context data
874
+ unset($occasions_data["date"]);
875
+
876
+ //sf_d($occasions_data);exit;
877
+ $occasions_id = md5( json_encode($occasions_data) );
878
+
879
+ }
880
+
881
+ $data["occasionsID"] = $occasions_id;
882
+
883
+ // Log event type, defaults to other if not set
884
+ /*
885
+ if ( isset( $context["_type"] ) ) {
886
+ $data["type"] = $context["_type"];
887
+ unset( $context["_type"] );
888
+ } else {
889
+ $data["type"] = SimpleLoggerLogTypes::OTHER;
890
+ }
891
+ */
892
+
893
+ // Log initiator, defaults to current user if exists, or other if not user exist
894
+ if ( isset( $context["_initiator"] ) ) {
895
+
896
+ // Manually set in context
897
+ $data["initiator"] = $context["_initiator"];
898
+ unset( $context["_initiator"] );
899
+
900
+ } else {
901
+
902
+ // No initiator set.
903
+
904
+ $data["initiator"] = SimpleLoggerLogInitiators::OTHER;
905
+
906
+ // Check if user is responsible.
907
+ if ( function_exists("wp_get_current_user") ) {
908
+
909
+ $current_user = wp_get_current_user();
910
+
911
+ if ( isset( $current_user->ID ) && $current_user->ID) {
912
+
913
+ $data["initiator"] = SimpleLoggerLogInitiators::WP_USER;;
914
+ $context["_user_id"] = $current_user->ID;
915
+ $context["_user_login"] = $current_user->user_login;
916
+ $context["_user_email"] = $current_user->user_email;
917
+
918
+ }
919
+
920
+ }
921
+
922
+ // If cron then set WordPress as responsible
923
+ if ( defined('DOING_CRON') && DOING_CRON ) {
924
+
925
+ // Seems to be wp cron running and doing this
926
+ $data["initiator"] = SimpleLoggerLogInitiators::WORDPRESS;
927
+ $context["_wordpress_cron_running"] = true;
928
+
929
+ }
930
+
931
+ }
932
+
933
+ /**
934
+ * Filter data to be saved to db
935
+ *
936
+ * @since 2.0
937
+ *
938
+ * @param array $data
939
+ */
940
+ $data = apply_filters("simple_history/log_insert_data", $data);
941
+
942
+ // Insert data into db
943
+ // sf_d($db_table, '$db_table');exit;
944
+ $result = $wpdb->insert( $db_table, $data );
945
+
946
+ // Only save context if able to store row
947
+ if ( false === $result ) {
948
+
949
+ $history_inserted_id = null;
950
+
951
+ } else {
952
+
953
+ $history_inserted_id = $wpdb->insert_id;
954
+
955
+ $db_table_contexts = $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS;
956
+
957
+ /**
958
+ * Filter table name for contexts
959
+ *
960
+ * @since 2.0
961
+ *
962
+ * @param string $db_table_contexts
963
+ */
964
+ $db_table_contexts = apply_filters("simple_history/logger_db_table_contexts", $db_table_contexts);
965
+
966
+ if ( ! is_array($context) ) {
967
+ $context = array();
968
+ }
969
+
970
+ // Append user id to context, if not already added
971
+ if ( ! isset( $context["_user_id"] ) ) {
972
+
973
+ // wp_get_current_user is ont available early
974
+ // http://codex.wordpress.org/Function_Reference/wp_get_current_user
975
+ // https://core.trac.wordpress.org/ticket/14024
976
+ if ( function_exists("wp_get_current_user") ) {
977
+
978
+ $current_user = wp_get_current_user();
979
+
980
+ if ( isset( $current_user->ID ) && $current_user->ID) {
981
+ $context["_user_id"] = $current_user->ID;
982
+ $context["_user_login"] = $current_user->user_login;
983
+ $context["_user_email"] = $current_user->user_email;
984
+ }
985
+
986
+ }
987
+
988
+ }
989
+
990
+ // Append remote addr to context
991
+ // Good to always have
992
+ if ( ! isset( $context["_server_remote_addr"] ) ) {
993
+ $context["_server_remote_addr"] = $_SERVER["REMOTE_ADDR"];
994
+ }
995
+
996
+ // Append http referer
997
+ // Also good to always have!
998
+ if ( ! isset( $context["_server_http_referer"] ) && isset( $_SERVER["HTTP_REFERER"] ) ) {
999
+ $context["_server_http_referer"] = $_SERVER["HTTP_REFERER"];
1000
+ }
1001
+
1002
+ // Insert all context values into db
1003
+ foreach ( $context as $key => $value ) {
1004
+
1005
+ $data = array(
1006
+ "history_id" => $history_inserted_id,
1007
+ "key" => $key,
1008
+ "value" => $value,
1009
+ );
1010
+
1011
+ $result = $wpdb->insert( $db_table_contexts, $data );
1012
+
1013
+ }
1014
+
1015
+ }
1016
+
1017
+ $this->lastInsertID = $history_inserted_id;
1018
+
1019
+ // Return $this so we can chain methods
1020
+ return $this;
1021
+
1022
+ } // log
1023
+
1024
+ /**
1025
+ * Override this to add CSS in <head> for your logger.
1026
+ * The CSS that you output will only be outputed
1027
+ * on pages where Simple History is used.
1028
+ */
1029
+ function adminCSS() {
1030
+ /*
1031
+ ?>
1032
+ <style>
1033
+ body {
1034
+ border: 2px solid red;
1035
+ }
1036
+ </style>
1037
+ <?php
1038
+ */
1039
+ }
1040
+
1041
+ /**
1042
+ * Override this to add JavaScript in the footer for your logger.
1043
+ * The JS that you output will only be outputed
1044
+ * on pages where Simple History is used.
1045
+ */
1046
+ function adminJS() {
1047
+ /*
1048
+ ?>
1049
+ <script>
1050
+ console.log("This is outputed in the footer");
1051
+ </script>
1052
+ <?php
1053
+ */
1054
+ }
1055
+
1056
+ }
1057
+
1058
+ /**
1059
+ * Describes log initiator, i.e. who caused to log event to happend
1060
+ */
1061
+ class SimpleLoggerLogInitiators
1062
+ {
1063
+
1064
+ // A wordpress user that at the log event created did exist in the wp database
1065
+ // May have been deleted when the log is viewed
1066
+ const WP_USER = 'wp_user';
1067
+
1068
+ // Cron job run = wordpress initiated
1069
+ // Email sent to customer on webshop = system/wordpress/anonymous web user
1070
+ // Javascript error occured on website = anonymous web user
1071
+ const WEB_USER = 'web_user';
1072
+
1073
+ // WordPress core or plugins updated automatically via wp-cron
1074
+ const WORDPRESS = "wp";
1075
+
1076
+ // I dunno
1077
+ const OTHER = 'other';
1078
+ }
1079
+
1080
+
1081
+ /**
1082
+ * Describes log event type
1083
+ * Based on the CRUD-types
1084
+ * http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
1085
+ * More may be added later on if needed
1086
+ * Note: not in use at the moment
1087
+ */
1088
+ class SimpleLoggerLogTypes
1089
+ {
1090
+ const CREATE = 'create';
1091
+ const READ = 'read';
1092
+ const UPDATE = 'update';
1093
+ const DELETE = 'delete';
1094
+ const OTHER = 'other';
1095
+ }
1096
+
1097
+ /**
1098
+ * Describes log levels
1099
+ */
1100
+ class SimpleLoggerLogLevels
1101
+ {
1102
+ const EMERGENCY = 'emergency';
1103
+ const ALERT = 'alert';
1104
+ const CRITICAL = 'critical';
1105
+ const ERROR = 'error';
1106
+ const WARNING = 'warning';
1107
+ const NOTICE = 'notice';
1108
+ const INFO = 'info';
1109
+ const DEBUG = 'debug';
1110
+ }
1111
+
loggers/SimpleMediaLogger.php ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs media uploads
5
+ */
6
+ class SimpleMediaLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = "SimpleMediaLogger";
10
+
11
+ /**
12
+ * Get array with information about this logger
13
+ *
14
+ * @return array
15
+ */
16
+ function getInfo() {
17
+
18
+ $arr_info = array(
19
+ "name" => "Media/Attachments Logger",
20
+ "description" => "Logs media uploads and edits",
21
+ "capability" => "edit_pages",
22
+ "messages" => array(
23
+ 'attachment_created' => __('Created {post_type} "{attachment_title}"', 'simple-history'),
24
+ 'attachment_updated' => __('Edited {post_type} "{attachment_title}"', 'simple-history'),
25
+ 'attachment_deleted' => __('Deleted {post_type} "{attachment_title}" ("{attachment_filename}")', 'simple-history')
26
+ ),
27
+ "labels" => array(
28
+ "search" => array(
29
+ "label" => _x("Media", "Media logger: search", "simple-history"),
30
+ "options" => array(
31
+ _x("Added media", "Media logger: search", "simple-history") => array(
32
+ "attachment_created"
33
+ ),
34
+ _x("Updated media", "Media logger: search", "simple-history") => array(
35
+ "attachment_updated"
36
+ ),
37
+ _x("Deleted media", "Media logger: search", "simple-history") => array(
38
+ "attachment_deleted"
39
+ ),
40
+ )
41
+ ) // end search array
42
+ ) // end labels
43
+ );
44
+
45
+ return $arr_info;
46
+
47
+ }
48
+
49
+ public function loaded() {
50
+
51
+ add_action("admin_init", array($this, "on_admin_init"));
52
+
53
+ }
54
+
55
+ function on_admin_init() {
56
+
57
+ add_action("add_attachment", array($this, "on_add_attachment"));
58
+ add_action("edit_attachment", array($this, "on_edit_attachment"));
59
+ add_action("delete_attachment", array($this, "on_delete_attachment"));
60
+
61
+ }
62
+
63
+ /**
64
+ * Modify plain output to inlcude link to post
65
+ */
66
+ public function getLogRowPlainTextOutput($row) {
67
+
68
+ $message = $row->message;
69
+ $context = $row->context;
70
+ $message_key = $context["_message_key"];
71
+
72
+ $attachment_id = $context["attachment_id"];
73
+ $attachment_post = get_post( $attachment_id );
74
+ $attachment_is_available = is_a($attachment_post, "WP_Post");
75
+
76
+ // Only link to attachment if it is still available
77
+ if ( $attachment_is_available ) {
78
+
79
+ if ( "attachment_updated" == $message_key ) {
80
+
81
+ $message = __('Edited {post_type} <a href="{edit_link}">"{attachment_title}"</a>', "simple-history");
82
+
83
+ } else if ( "attachment_created" == $message_key ) {
84
+
85
+ $message = __('Uploaded {post_type} <a href="{edit_link}">"{attachment_title}"</a>', "simple-history");
86
+
87
+ }
88
+
89
+ }
90
+
91
+ $context["post_type"] = esc_html( $context["post_type"] );
92
+ $context["attachment_filename"] = esc_html( $context["attachment_filename"] );
93
+ $context["edit_link"] = get_edit_post_link( $attachment_id );
94
+
95
+ return $this->interpolate($message, $context);
96
+
97
+ }
98
+
99
+ /**
100
+ * Get output for detailed log section
101
+ */
102
+ function getLogRowDetailsOutput($row) {
103
+
104
+ $context = $row->context;
105
+ $message_key = $context["_message_key"];
106
+ $output = "";
107
+
108
+ if ( "attachment_updated" == $message_key ) {
109
+
110
+ // Attachment is changed = don't show thumbs and all
111
+
112
+ } else if ( "attachment_deleted" == $message_key ) {
113
+
114
+ // Attachment is deleted = don't show thumbs and all
115
+
116
+ } else if ( "attachment_created" == $message_key ) {
117
+
118
+ // Attachment is created/uploaded = show details with image thumbnail
119
+
120
+ $attachment_id = $context["attachment_id"];
121
+ $filetype = wp_check_filetype( $context["attachment_filename"] );
122
+ $file_url = wp_get_attachment_url( $attachment_id );
123
+ $edit_link = get_edit_post_link( $attachment_id );
124
+ $message = "";
125
+ $full_src = false;
126
+
127
+ $is_image = wp_attachment_is_image( $attachment_id );
128
+ $is_video = strpos($filetype["type"], "video/") !== false;
129
+ $is_audio = strpos($filetype["type"], "audio/") !== false;
130
+
131
+ $full_image_width = null;
132
+ $full_image_height = null;
133
+
134
+ if ( $is_image ) {
135
+
136
+ $thumb_src = wp_get_attachment_image_src($attachment_id, array(350,500));
137
+ $full_src = wp_get_attachment_image_src($attachment_id, "full");
138
+ #sf_d($thumb_src, '$thumb_src');
139
+ #sf_d($full_src, '$full_src');
140
+
141
+ $full_image_width = $full_src[1];
142
+ $full_image_height = $full_src[2];
143
+
144
+ // is_image is also true for mime types that WP can't create thumbs for
145
+ // so we need to check that wp got an resized version
146
+ if ( $full_image_width && $full_image_height ) {
147
+
148
+ $context["full_image_width"] = $full_image_width;
149
+ $context["full_image_height"] = $full_image_width;
150
+ $context["attachment_thumb"] = sprintf('<div class="SimpleHistoryLogitemThumbnail"><img src="%1$s"></div>', $thumb_src[0] );
151
+
152
+ }
153
+
154
+ } else if ($is_audio) {
155
+
156
+ $content = sprintf('[audio src="%1$s"]', $file_url);
157
+ $context["attachment_thumb"] = do_shortcode( $content );
158
+
159
+ } else if ($is_video) {
160
+
161
+ $content = sprintf('[video src="%1$s"]', $file_url);
162
+ $context["attachment_thumb"] = do_shortcode( $content );
163
+
164
+ } else {
165
+
166
+ // use wordpress icon for other media types
167
+ $context["attachment_thumb"] = wp_get_attachment_image( $attachment_id, null, true );
168
+
169
+ }
170
+
171
+ $context["attachment_size_format"] = size_format( $row->context["attachment_filesize"] );
172
+ $context["attachment_filetype_extension"] = strtoupper( $filetype["ext"] );
173
+
174
+ if ( ! empty( $context["attachment_thumb"] ) ) {
175
+
176
+ if ($is_image) {
177
+ $message .= "<a href='".$edit_link."'>";
178
+ }
179
+
180
+ $message .= __('{attachment_thumb}', 'simple-history');
181
+
182
+ if ($is_image) {
183
+ $message .= "</a>";
184
+ }
185
+
186
+ }
187
+
188
+ $message .= "<p class='SimpleHistoryLogitem--logger-SimpleMediaLogger--attachment-meta'>";
189
+ $message .= "<span class='SimpleHistoryLogitem__inlineDivided'>" . __('{attachment_size_format}', "simple-history") . "</span> ";
190
+ $message .= "<span class='SimpleHistoryLogitem__inlineDivided'>" . __('{attachment_filetype_extension}', "simple-history") . "</span>";
191
+ if ($full_image_width && $full_image_height) {
192
+ $message .= " <span class='SimpleHistoryLogitem__inlineDivided'>" . __('{full_image_width} × {full_image_height}') . "</span>";
193
+ }
194
+ //$message .= " <span class='SimpleHistoryLogitem__inlineDivided'>" . sprintf( __('<a href="%1$s">Edit attachment</a>'), $edit_link ) . "</span>";
195
+ $message .= "</p>";
196
+
197
+ $output .= $this->interpolate($message, $context);
198
+
199
+ }
200
+
201
+ return $output;
202
+
203
+ }
204
+
205
+ /**
206
+ * Called when an attachment is added
207
+ */
208
+ function on_add_attachment($attachment_id) {
209
+
210
+ $attachment_post = get_post( $attachment_id );
211
+ $filename = esc_html( wp_basename( $attachment_post->guid ) );
212
+ $mime = get_post_mime_type( $attachment_post );
213
+ $file = get_attached_file( $attachment_id );
214
+ $file_size = false;
215
+
216
+ if ( file_exists( $file ) ) {
217
+ $file_size = filesize( $file );
218
+ }
219
+
220
+ $this->infoMessage(
221
+ 'attachment_created',
222
+ array(
223
+ "post_type" => get_post_type( $attachment_post ),
224
+ "attachment_id" => $attachment_id,
225
+ "attachment_title" => get_the_title( $attachment_post ),
226
+ "attachment_filename" => $filename,
227
+ "attachment_mime" => $mime,
228
+ "attachment_filesize" => $file_size
229
+ )
230
+ );
231
+
232
+ }
233
+
234
+ /**
235
+ * An attachmet is changed
236
+ * is this only being called if the title of the attachment is changed?!
237
+ *
238
+ * @param int $attachment_id
239
+ */
240
+ function on_edit_attachment($attachment_id) {
241
+
242
+ $attachment_post = get_post( $attachment_id );
243
+ $filename = esc_html( wp_basename( $attachment_post->guid ) );
244
+ $mime = get_post_mime_type( $attachment_post );
245
+ $file = get_attached_file( $attachment_id );
246
+
247
+ $this->infoMessage(
248
+ "attachment_updated",
249
+ array(
250
+ "post_type" => get_post_type( $attachment_post ),
251
+ "attachment_id" => $attachment_id,
252
+ "attachment_title" => get_the_title( $attachment_post ),
253
+ "attachment_filename" => $filename,
254
+ "attachment_mime" => $mime
255
+ )
256
+ );
257
+
258
+ }
259
+
260
+ /**
261
+ * Called when an attachment is deleted
262
+ */
263
+ function on_delete_attachment($attachment_id) {
264
+
265
+ $attachment_post = get_post( $attachment_id );
266
+ $filename = esc_html( wp_basename( $attachment_post->guid ) );
267
+ $mime = get_post_mime_type( $attachment_post );
268
+ $file = get_attached_file( $attachment_id );
269
+
270
+ $this->infoMessage(
271
+ "attachment_deleted",
272
+ array(
273
+ "post_type" => get_post_type( $attachment_post ),
274
+ "attachment_id" => $attachment_id,
275
+ "attachment_title" => get_the_title( $attachment_post ),
276
+ "attachment_filename" => $filename,
277
+ "attachment_mime" => $mime
278
+ )
279
+ );
280
+
281
+ }
282
+
283
+ }
loggers/SimpleMenuLogger.php ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs WordPress menu edits
5
+ */
6
+ class SimpleMenuLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ /**
12
+ * Get array with information about this logger
13
+ *
14
+ * @return array
15
+ */
16
+ function getInfo() {
17
+
18
+ $arr_info = array(
19
+ "name" => "Menu Logger",
20
+ "description" => "Logs menu edits",
21
+ "capability" => "edit_theme_options",
22
+ "messages" => array(
23
+ 'created_menu' => __('Created menu "{menu_name}"', "simple-history"),
24
+ 'edited_menu' => __('Edited menu "{menu_name}"', "simple-history"),
25
+ 'deleted_menu' => __('Deleted menu "{menu_name}"', "simple-history"),
26
+ 'edited_menu_item' => __('Edited a menu item', "simple-history"),
27
+ 'edited_menu_locations' => __('Updated menu locations', "simple-history"),
28
+ ),
29
+ "labels" => array(
30
+ "search" => array(
31
+ "label" => _x("Menus", "Menu logger: search", "simple-history"),
32
+ "options" => array(
33
+ _x("Created menus", "Menu updates logger: search", "simple-history") => array(
34
+ "created_menu"
35
+ ),
36
+ _x("Edited menus", "Menu updates logger: search", "simple-history") => array(
37
+ "edited_menu",
38
+ "edited_menu_item",
39
+ "edited_menu_locations"
40
+ ),
41
+ _x("Deleted menus", "Menu updates logger: search", "simple-history") => array(
42
+ "deleted_menu"
43
+ )
44
+ )
45
+ ) // end search array
46
+ ) // end labels
47
+ );
48
+
49
+ return $arr_info;
50
+
51
+ }
52
+
53
+ function loaded() {
54
+
55
+ /*
56
+ * Fires after a navigation menu has been successfully deleted.
57
+ *
58
+ * @since 3.0.0
59
+ *
60
+ * @param int $term_id ID of the deleted menu.
61
+ do_action( 'wp_delete_nav_menu', $menu->term_id );
62
+ */
63
+ //add_action("wp_delete_nav_menu", array($this, "on_wp_delete_nav_menu"), 10, 1 );
64
+ add_action("load-nav-menus.php", array($this, "on_load_nav_menus_page_detect_delete"));
65
+
66
+ /*
67
+ * Fires after a navigation menu is successfully created.
68
+ *
69
+ * @since 3.0.0
70
+ *
71
+ * @param int $term_id ID of the new menu.
72
+ * @param array $menu_data An array of menu data.
73
+ do_action( 'wp_create_nav_menu', $_menu['term_id'], $menu_data );
74
+ */
75
+ add_action("wp_create_nav_menu", array($this, "on_wp_create_nav_menu"), 10, 2 );
76
+
77
+
78
+ /*
79
+ * Fires after a navigation menu item has been updated.
80
+ *
81
+ * @since 3.0.0
82
+ *
83
+ * @see wp_update_nav_menu_items()
84
+ *
85
+ * @param int $menu_id ID of the updated menu.
86
+ * @param int $menu_item_db_id ID of the updated menu item.
87
+ * @param array $args An array of arguments used to update a menu item.
88
+ do_action( 'wp_update_nav_menu_item', $menu_id, $menu_item_db_id, $args );
89
+ */
90
+
91
+ // This is fired when adding nav items in the editor, not at save, so not
92
+ // good to log because user might not end up saving the changes
93
+ // add_action("wp_update_nav_menu_item", array($this, "on_wp_update_nav_menu_item"), 10, 3 );
94
+
95
+ // Fired before "wp_update_nav_menu" below, to remember menu layput before it's updated
96
+ // so we can't detect changes
97
+ add_action("load-nav-menus.php", array($this, "on_load_nav_menus_page_detect_update"));
98
+
99
+ /*
100
+ * Fires after a navigation menu has been successfully updated.
101
+ *
102
+ * @since 3.0.0
103
+ *
104
+ * @param int $menu_id ID of the updated menu.
105
+ * @param array $menu_data An array of menu data.
106
+ do_action( 'wp_update_nav_menu', $menu_id, $menu_data );
107
+ */
108
+ //add_action("wp_update_nav_menu", array($this, "on_wp_update_nav_menu"), 10, 2 );
109
+
110
+ // Detect meny location change in "manage locations"
111
+ add_action("load-nav-menus.php", array($this, "on_load_nav_menus_page_detect_locations_update"));
112
+ }
113
+
114
+ /**
115
+ * Can't use action "wp_delete_nav_menu" beacuse
116
+ * it's fired after menu is deleted, so we don't have the name in this action
117
+ */
118
+ function on_load_nav_menus_page_detect_delete() {
119
+
120
+ /*
121
+ http://playground-root.ep/wp-admin/nav-menus.php?menu=22&action=delete&0=http%3A%2F%2Fplayground-root.ep%2Fwp-admin%2F&_wpnonce=f52e8a31ba
122
+ $_REQUEST:
123
+ Array
124
+ (
125
+ [menu] => 22
126
+ [action] => delete
127
+ [0] => http://playground-root.ep/wp-admin/
128
+ [_wpnonce] => f52e8a31ba
129
+ )
130
+ */
131
+
132
+ // Check that needed vars are set
133
+ if ( ! isset( $_REQUEST["menu"], $_REQUEST["action"] ) ) {
134
+ return;
135
+ }
136
+
137
+ if ( "delete" !== $_REQUEST["action"]) {
138
+ return;
139
+ }
140
+
141
+ $menu_id = $_REQUEST["menu"];
142
+ if ( ! is_nav_menu( $menu_id) ) {
143
+ return;
144
+ }
145
+
146
+ $menu = wp_get_nav_menu_object( $menu_id );
147
+
148
+
149
+ $this->infoMessage(
150
+ "deleted_menu",
151
+ array(
152
+ "menu_term_id" => $menu_id,
153
+ "menu_name" => $menu->name,
154
+ )
155
+ );
156
+
157
+ }
158
+
159
+ /**
160
+ * Fired after menu is deleted, so we don't have the name in this action
161
+ * So that's why we can't use this only
162
+ */
163
+ /*
164
+ function on_wp_delete_nav_menu($menu_term_id) {
165
+
166
+ $this->infoMessage(
167
+ "deleted_menu",
168
+ array(
169
+ "menu_term_id" => $menu_term_id,
170
+ "menu" => print_r($menu, true),
171
+ "request" => print_r($_REQUEST, true),
172
+ )
173
+ );
174
+
175
+ }
176
+ */
177
+
178
+ function on_wp_create_nav_menu($term_id, $menu_data) {
179
+
180
+ $menu = wp_get_nav_menu_object( $term_id );
181
+
182
+ if ( ! $menu ) {
183
+ return;
184
+ }
185
+
186
+ $this->infoMessage(
187
+ "created_menu",
188
+ array(
189
+ "term_id" => $term_id,
190
+ "menu_name" => $menu->name
191
+ )
192
+ );
193
+
194
+ }
195
+
196
+ /*
197
+ function on_wp_update_nav_menu_item($menu_id, $menu_item_db_id, $args) {
198
+
199
+ $this->infoMessage(
200
+ "edited_menu_item",
201
+ array(
202
+ "menu_id" => $menu_id,
203
+ "menu_item_db_id" => $menu_item_db_id,
204
+ "args" => $this->simpleHistory->json_encode($args),
205
+ "request" => $this->simpleHistory->json_encode($_REQUEST)
206
+ )
207
+ );
208
+
209
+ }
210
+ */
211
+
212
+ /**
213
+ * Detect menu being saved
214
+ */
215
+ function on_load_nav_menus_page_detect_update() {
216
+
217
+ /*
218
+ This is the data to be saved
219
+ $_REQUEST:
220
+ Array
221
+ (
222
+ [action] => update
223
+ [menu] => 25
224
+ [menu-name] => Main menu edit
225
+ [menu-item-title] => Array
226
+ (
227
+ [25243] => My new page edited
228
+ [25244] => My new page
229
+ [25245] => This is my new page. How does it look in the logs? <h1>Hej!</h1>
230
+ [25264] => This page have revisions
231
+ [25265] => Lorem ipsum dolor sit amet
232
+ )
233
+ [menu-locations] => Array
234
+ (
235
+ [primary] => 25
236
+ )
237
+ )
238
+ */
239
+
240
+ // Check that needed vars are set
241
+ if ( ! isset( $_REQUEST["menu"], $_REQUEST["action"], $_REQUEST["menu-name"] ) ) {
242
+ return;
243
+ }
244
+
245
+ // Only go on for update action
246
+ if ( "update" !== $_REQUEST["action"]) {
247
+ return;
248
+ }
249
+
250
+ // Make sure we got the id of a menu
251
+ $menu_id = $_REQUEST["menu"];
252
+ if ( ! is_nav_menu( $menu_id) ) {
253
+ return;
254
+ }
255
+
256
+ // Get saved menu. May be empty if this is the first time we save the menu
257
+ $arr_prev_menu_items = wp_get_nav_menu_items( $menu_id );
258
+
259
+ // Compare new items to be saved with old version
260
+ $old_ids = wp_list_pluck( $arr_prev_menu_items, "db_id" );
261
+ $new_ids = array_values( isset( $_POST["menu-item-db-id"] ) ? $_POST["menu-item-db-id"] : array() );
262
+
263
+ // Get ids of added and removed post ids
264
+ $arr_removed = array_diff($old_ids, $new_ids);
265
+ $arr_added = array_diff($new_ids, $old_ids);
266
+
267
+ // Get old version location
268
+ // $prev_menu = wp_get_nav_menu_object( $menu_id );
269
+ // $locations = get_registered_nav_menus();
270
+ // $menu_locations = get_nav_menu_locations();
271
+
272
+ $this->infoMessage(
273
+ "edited_menu",
274
+ array(
275
+ "menu_id" => $menu_id,
276
+ "menu_name" => $_POST["menu-name"],
277
+ "menu_items_added" => sizeof($arr_added),
278
+ "menu_items_removed" => sizeof($arr_removed),
279
+ //"request" => $this->simpleHistory->json_encode($_REQUEST)
280
+ )
281
+ );
282
+
283
+ }
284
+
285
+ /**
286
+ * This seems to get called twice
287
+ * one time with menu_data, a second without
288
+ */
289
+ /*
290
+ function on_wp_update_nav_menu($menu_id, $menu_data = array()) {
291
+
292
+ if (empty($menu_data)) {
293
+ return;
294
+ }
295
+
296
+ $this->infoMessage(
297
+ "edited_menu",
298
+ array(
299
+ "menu_id" => $menu_id,
300
+ "menu_name" => $menu_data["menu-name"],
301
+ "menu_data" => $this->simpleHistory->json_encode($menu_data),
302
+ "request" => $this->simpleHistory->json_encode($_REQUEST)
303
+ )
304
+ );
305
+
306
+ }
307
+ */
308
+
309
+ /**
310
+ * Get detailed output
311
+ */
312
+ function getLogRowDetailsOutput($row) {
313
+
314
+ $context = $row->context;
315
+ $message_key = $context["_message_key"];
316
+ $output = "";
317
+
318
+ if ( "edited_menu" == $message_key ) {
319
+
320
+ if ( ! empty( $context["menu_items_added"] ) || ! empty( $context["menu_items_removed"] ) ) {
321
+
322
+ $output .= "<p>";
323
+
324
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
325
+ $output .= sprintf(
326
+ _nx( '%1$s menu item added', '%1$s menu items added', $context["menu_items_added"], "menu logger", "simple-history" ),
327
+ esc_attr( $context["menu_items_added"] )
328
+ );
329
+ $output .= '</span> ';
330
+
331
+ $output .= '<span class="SimpleHistoryLogitem__inlineDivided">';
332
+ $output .= sprintf(
333
+ _nx( '%1$s menu item removed', '%1$s menu items removed', $context["menu_items_removed"], "menu logger", "simple-history" ),
334
+ esc_attr( $context["menu_items_removed"] )
335
+ );
336
+ $output .= '</span> ';
337
+
338
+ $output .= "</p>";
339
+
340
+ }
341
+
342
+
343
+ }
344
+
345
+ return $output;
346
+
347
+ }
348
+
349
+ /**
350
+ * Log updates to theme menu locations
351
+ */
352
+ function on_load_nav_menus_page_detect_locations_update() {
353
+
354
+ // Check that needed vars are set
355
+ if ( ! isset( $_REQUEST["menu"], $_REQUEST["action"] ) ) {
356
+ return;
357
+ }
358
+
359
+ if ( "locations" !== $_REQUEST["action"]) {
360
+ return;
361
+ }
362
+
363
+ /*
364
+ Array
365
+ (
366
+ [menu-locations] => Array
367
+ (
368
+ [primary] => 25
369
+ )
370
+ )
371
+ */
372
+ $menu_locations = $_POST["menu-locations"];
373
+
374
+ $this->infoMessage(
375
+ "edited_menu_locations",
376
+ array(
377
+ "menu_locations" => $this->simpleHistory->json_encode($menu_locations)
378
+ )
379
+ );
380
+
381
+ }
382
+ }
loggers/SimpleOptionsLogger.php ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+
5
+ <form> is posted to options.php
6
+
7
+ If $_GET['action'] == 'update' we are saving settings sent from a settings page
8
+
9
+ update_option( $option, $value );
10
+ set_transient('settings_errors', get_settings_errors(), 30);
11
+
12
+
13
+ * Fires immediately before an option value is updated.
14
+ * @param string $option Name of the option to update.
15
+ * @param mixed $old_value The old option value.
16
+ * @param mixed $value The new option value.
17
+ do_action( 'update_option', $option, $old_value, $value );
18
+
19
+
20
+ * Fires after the value of an option has been successfully updated.
21
+ *
22
+ * @since 2.9.0
23
+ *
24
+ * @param string $option Name of the updated option.
25
+ * @param mixed $old_value The old option value.
26
+ * @param mixed $value The new option value.
27
+ do_action( 'updated_option', $option, $old_value, $value );
28
+
29
+ options white list.
30
+ $whitelist_options = apply_filters( 'whitelist_options', $whitelist_options );
31
+ Array
32
+ (
33
+ [general] => Array
34
+ (
35
+ [0] => blogname
36
+ [1] => blogdescription
37
+ [2] => gmt_offset
38
+ [3] => date_format
39
+ [4] => time_format
40
+ [5] => start_of_week
41
+ [6] => timezone_string
42
+ [7] => siteurl
43
+ [8] => home
44
+ [9] => admin_email
45
+ [10] => users_can_register
46
+ [11] => default_role
47
+ )
48
+
49
+ [discussion] => Array
50
+ (
51
+ [0] => default_pingback_flag
52
+ [1] => default_ping_status
53
+ [2] => default_comment_status
54
+ [3] => comments_notify
55
+ [4] => moderation_notify
56
+ [5] => comment_moderation
57
+ [6] => require_name_email
58
+ [7] => comment_whitelist
59
+ [8] => comment_max_links
60
+ [9] => moderation_keys
61
+ [10] => blacklist_keys
62
+ [11] => show_avatars
63
+ [12] => avatar_rating
64
+ [13] => avatar_default
65
+ [14] => close_comments_for_old_posts
66
+ [15] => close_comments_days_old
67
+ [16] => thread_comments
68
+ [17] => thread_comments_depth
69
+ [18] => page_comments
70
+ [19] => comments_per_page
71
+ [20] => default_comments_page
72
+ [21] => comment_order
73
+ [22] => comment_registration
74
+ )
75
+
76
+ [media] => Array
77
+ (
78
+ [0] => thumbnail_size_w
79
+ [1] => thumbnail_size_h
80
+ [2] => thumbnail_crop
81
+ [3] => medium_size_w
82
+ [4] => medium_size_h
83
+ [5] => large_size_w
84
+ [6] => large_size_h
85
+ [7] => image_default_size
86
+ [8] => image_default_align
87
+ [9] => image_default_link_type
88
+ [10] => uploads_use_yearmonth_folders
89
+ )
90
+
91
+ [reading] => Array
92
+ (
93
+ [0] => posts_per_page
94
+ [1] => posts_per_rss
95
+ [2] => rss_use_excerpt
96
+ [3] => show_on_front
97
+ [4] => page_on_front
98
+ [5] => page_for_posts
99
+ [6] => blog_public
100
+ )
101
+
102
+ [writing] => Array
103
+ (
104
+ [0] => use_smilies
105
+ [1] => default_category
106
+ [2] => default_email_category
107
+ [3] => use_balanceTags
108
+ [4] => default_link_category
109
+ [5] => default_post_format
110
+ [6] => mailserver_url
111
+ [7] => mailserver_port
112
+ [8] => mailserver_login
113
+ [9] => mailserver_pass
114
+ [10] => ping_sites
115
+ )
116
+
117
+ */
118
+
119
+ /**
120
+ * Logs changes to wordpress options
121
+ */
122
+ class SimpleOptionsLogger extends SimpleLogger
123
+ {
124
+
125
+ public $slug = __CLASS__;
126
+
127
+ /**
128
+ * Get array with information about this logger
129
+ *
130
+ * @return array
131
+ */
132
+ function getInfo() {
133
+
134
+ $arr_info = array(
135
+ "name" => "Options Logger",
136
+ "description" => "Logs updates to WordPress settings",
137
+ "capability" => "manage_options",
138
+ "messages" => array(
139
+ //'option_updated' => __('Updated option "{option}" on settings page "{option_page}"', "simple-history"),
140
+ 'option_updated' => __('Updated option "{option}"', "simple-history"),
141
+ /*
142
+
143
+ Updated option "default_comment_status" on settings page "discussion"
144
+ Edited option "default_comment_status" on settings page "discussion"
145
+ Modified option "default_comment_status" on settings page "discussion"
146
+
147
+ Edited settings page "discussion" and the "default_comment_status" options
148
+
149
+ */
150
+ ),
151
+ "labels" => array(
152
+ "search" => array(
153
+ "label" => _x("Options", "Options logger: search", "simple-history"),
154
+ "options" => array(
155
+ _x("Changed options", "Options logger: search", "simple-history") => array(
156
+ "option_updated"
157
+ ),
158
+ )
159
+ ) // end search array
160
+ ) // end labels
161
+ );
162
+
163
+ return $arr_info;
164
+
165
+ }
166
+
167
+ function loaded() {
168
+
169
+ add_action( 'updated_option', array($this, "on_updated_option"), 10, 3 );
170
+
171
+ }
172
+
173
+ function on_updated_option($option, $old_value, $new_value) {
174
+
175
+ if (empty( $_SERVER["REQUEST_URI"] )) {
176
+ return;
177
+ }
178
+
179
+ $arr_option_pages = array(
180
+ 0 => "options.php",
181
+ 1 => "options-permalink.php"
182
+ );
183
+
184
+ // We only want to log options being added via pages in $arr_option_pages
185
+ if ( ! in_array( basename( $_SERVER["REQUEST_URI"] ), $arr_option_pages ) || basename( dirname( $_SERVER["REQUEST_URI"] ) ) !== "wp-admin" ) {
186
+ return;
187
+ }
188
+
189
+ // Also only if "option_page" is set to one of these "built in" ones
190
+ // We don't wanna start loging things from other plugins, like EDD
191
+ $option_page = isset( $_REQUEST["option_page"] ) ? $_REQUEST["option_page"] : ""; // general | discussion | ...
192
+
193
+ $arr_valid_option_pages = array(
194
+ 'general',
195
+ 'discussion',
196
+ 'media',
197
+ 'reading',
198
+ 'writing',
199
+ );
200
+
201
+ if ( $option_page && ! in_array($option_page, $arr_valid_option_pages) ) {
202
+ return;
203
+ }
204
+
205
+ $this->debugMessage( "option_updated", array(
206
+ "option" => $option,
207
+ "old_value" => $old_value,
208
+ "new_value" => $new_value,
209
+ "REQUEST_URI" => $_SERVER["REQUEST_URI"],
210
+ "referer" => wp_get_referer(),
211
+ "option_page" => $option_page,
212
+ "$_REQUEST" => print_r($_REQUEST, true),
213
+ ) );
214
+
215
+
216
+ }
217
+
218
+ /**
219
+ * Get detailed output
220
+ */
221
+ function getLogRowDetailsOutput($row) {
222
+
223
+ $context = $row->context;
224
+ $message_key = $context["_message_key"];
225
+ $output = "";
226
+
227
+ if ( "option_updated" == $message_key ) {
228
+
229
+ //$message = 'Old value was {old_value} and new value is {new_value}';
230
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
231
+
232
+ // Output old and new values
233
+ if ( $context["new_value"] || $context["old_value"] ) {
234
+
235
+ $output .= sprintf(
236
+ '
237
+ <tr>
238
+ <td>%1$s</td>
239
+ <td>%2$s</td>
240
+ </tr>
241
+ ',
242
+ __("New value", "simple-history"),
243
+ esc_html( $context["new_value"] )
244
+ );
245
+
246
+ $output .= sprintf(
247
+ '
248
+ <tr>
249
+ <td>%1$s</td>
250
+ <td>%2$s</td>
251
+ </tr>
252
+ ',
253
+ __("Old value", "simple-history"),
254
+ esc_html( $context["old_value"] )
255
+ );
256
+ }
257
+
258
+ // If key option_page this was saved from regular settings pages
259
+ if ( ! empty( $context["option_page"] ) ) {
260
+
261
+ $output .= sprintf(
262
+ '
263
+ <tr>
264
+ <td>%1$s</td>
265
+ <td><a href="%3$s">%2$s</a></td>
266
+ </tr>
267
+ ',
268
+ __("Settings page", "simple-history"),
269
+ esc_html( $context["option_page"] ),
270
+ admin_url("options-{$context["option_page"]}.php")
271
+ );
272
+
273
+ }
274
+
275
+ // If option = permalink_structure then we did it from permalink page
276
+ if ( ! empty( $context["option"] ) && ( "permalink_structure" == $context["option"] || "tag_base" == $context["option"] || "category_base" == $context["option"] ) ) {
277
+
278
+ $output .= sprintf(
279
+ '
280
+ <tr>
281
+ <td>%1$s</td>
282
+ <td><a href="%3$s">%2$s</a></td>
283
+ </tr>
284
+ ',
285
+ __("Settings page", "simple-history"),
286
+ "permalink",
287
+ admin_url("options-permalink.php")
288
+ );
289
+
290
+ }
291
+
292
+ $output .= "</table>";
293
+
294
+ }
295
+
296
+ return $output;
297
+
298
+ }
299
+ }
loggers/SimplePluginLogger.php ADDED
@@ -0,0 +1,967 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+
5
+
6
+ # logga installs/updates av plugins som är silent
7
+
8
+ // Innan update körs en av dessa
9
+ $current = get_site_option( 'active_sitewide_plugins', array() );
10
+ $current = get_option( 'active_plugins', array() );
11
+
12
+ // efter update körs en av dessa
13
+ update_site_option( 'active_sitewide_plugins', $current );
14
+ update_option('active_plugins', $current);
15
+
16
+ // så: jämför om arrayen har ändrats, och om den har det = ny plugin aktiverats
17
+
18
+
19
+
20
+
21
+ # extra stuff
22
+ vid aktivering/installation av plugin: spara resultat från
23
+ get_plugin_files($plugin)
24
+ så vid ev intrång/skadlig kod uppladdad så kan man analysera lite
25
+
26
+ */
27
+
28
+
29
+ /**
30
+ * Logs plugins installs and updates
31
+ */
32
+ class SimplePluginLogger extends SimpleLogger
33
+ {
34
+
35
+ // The logger slug. Defaulting to the class name is nice and logical I think
36
+ public $slug = __CLASS__;
37
+
38
+ /**
39
+ * Get array with information about this logger
40
+ *
41
+ * @return array
42
+ */
43
+ function getInfo() {
44
+
45
+ $arr_info = array(
46
+ "name" => "Plugin Logger",
47
+ "description" => "Logs plugin installs, uninstalls and updates",
48
+ "capability" => "activate_plugins", // install_plugins, activate_plugins,
49
+ "messages" => array(
50
+
51
+ 'plugin_activated' => _x(
52
+ 'Activated plugin "{plugin_name}"',
53
+ 'Plugin was non-silently activated by a user',
54
+ 'simple-history'
55
+ ),
56
+
57
+ 'plugin_deactivated' => _x(
58
+ 'Deactivated plugin "{plugin_name}"',
59
+ 'Plugin was non-silently deactivated by a user',
60
+ 'simple-history'
61
+ ),
62
+
63
+ 'plugin_installed' => _x(
64
+ 'Installed plugin "{plugin_name}"',
65
+ 'Plugin was installed',
66
+ 'simple-history'
67
+ ),
68
+
69
+ 'plugin_installed_failed' => _x(
70
+ 'Failed to install plugin "{plugin_name}"',
71
+ 'Plugin failed to install',
72
+ 'simple-history'
73
+ ),
74
+
75
+ 'plugin_updated' => _x(
76
+ 'Updated plugin "{plugin_name}" to version {plugin_version} from {plugin_prev_version}',
77
+ 'Plugin was updated',
78
+ 'simple-history'
79
+ ),
80
+
81
+ 'plugin_update_failed' => _x(
82
+ 'Updated plugin "{plugin_name}"',
83
+ 'Plugin update failed',
84
+ 'simple-history'
85
+ ),
86
+
87
+ 'plugin_file_edited' => _x(
88
+ 'Edited plugin file "{plugin_edited_file}"',
89
+ 'Plugin file edited',
90
+ 'simple-history'
91
+ ),
92
+
93
+ 'plugin_deleted' => _x(
94
+ 'Deleted plugin "{plugin_name}"',
95
+ 'Plugin files was deleted',
96
+ 'simple-history'
97
+ ),
98
+
99
+ // bulk versions
100
+ 'plugin_bulk_updated' => _x(
101
+ 'Updated plugin "{plugin_name}" from {plugin_prev_version} to {plugin_version}',
102
+ 'Plugin was updated in bulk',
103
+ 'simple-history'
104
+ ),
105
+ ), // messages
106
+ "labels" => array(
107
+ "search" => array(
108
+ "label" => _x("Plugins", "Plugin logger: search", "simple-history"),
109
+ "options" => array(
110
+ _x("Activated plugins", "Plugin logger: search", "simple-history") => array(
111
+ 'plugin_activated'
112
+ ),
113
+ _x("Deactivated plugins", "Plugin logger: search", "simple-history") => array(
114
+ 'plugin_deactivated'
115
+ ),
116
+ _x("Installed plugins", "Plugin logger: search", "simple-history") => array(
117
+ 'plugin_installed'
118
+ ),
119
+ _x("Failed plugin installs", "Plugin logger: search", "simple-history") => array(
120
+ 'plugin_installed_failed'
121
+ ),
122
+ _x("Updated plugins", "Plugin logger: search", "simple-history") => array(
123
+ 'plugin_updated',
124
+ 'plugin_bulk_updated'
125
+ ),
126
+ _x("Failed plugin updates", "Plugin logger: search", "simple-history") => array(
127
+ 'plugin_update_failed'
128
+ ),
129
+ _x("Edited plugin files", "Plugin logger: search", "simple-history") => array(
130
+ 'plugin_file_edited'
131
+ ),
132
+ _x("Deleted plugins", "Plugin logger: search", "simple-history") => array(
133
+ 'plugin_deleted'
134
+ ),
135
+ )
136
+ ) // search array
137
+ ) // labels
138
+ );
139
+
140
+ return $arr_info;
141
+
142
+ }
143
+
144
+ public function loaded() {
145
+
146
+ #sf_d(get_plugins(), 'get_plugins()');
147
+
148
+ //do_action( 'current_screen', $current_screen );
149
+ // The first hook where current screen is available
150
+ add_action( 'current_screen', array( $this, "save_versions_before_update" ) );
151
+ add_action( 'delete_site_transient_update_plugins', array( $this, "remove_saved_versions" ) );
152
+
153
+ // Fires after a plugin has been activated.
154
+ // If a plugin is silently activated (such as during an update),
155
+ // this hook does not fire.
156
+ add_action( 'activated_plugin', array( $this, "on_activated_plugin" ), 10, 2 );
157
+
158
+ // Fires after a plugin is deactivated.
159
+ // If a plugin is silently deactivated (such as during an update),
160
+ // this hook does not fire.
161
+ add_action( 'deactivated_plugin', array( $this, "on_deactivated_plugin" ), 10, 2 );
162
+
163
+
164
+ // Fires after the upgrades has done it's thing
165
+ // Check hook extra for upgrader initiator
166
+ //add_action( 'upgrader_post_install', array( $this, "on_upgrader_post_install" ), 10, 3 );
167
+ add_action( 'upgrader_process_complete', array( $this, "on_upgrader_process_complete" ), 10, 2 );
168
+
169
+ // Dirty check for things that we can't catch using filters or actions
170
+ add_action( 'admin_init', array( $this, "check_filterless_things" ) );
171
+
172
+ // Detect files removed
173
+ add_action( 'setted_transient', array( $this, 'on_setted_transient_for_remove_files' ), 10, 2 );
174
+
175
+ /*
176
+ do_action( 'automatic_updates_complete', $this->update_results );
177
+ * Fires after all automatic updates have run.
178
+ *
179
+ * @since 3.8.0
180
+ *
181
+ * @param array $update_results The results of all attempted updates.
182
+ */
183
+
184
+ }
185
+
186
+ /**
187
+ * Detect plugin being deleted
188
+ * When WP is done deleting a plugin it sets a transient called plugins_delete_result:
189
+ * set_transient('plugins_delete_result_' . $user_ID, $delete_result);
190
+ *
191
+ * We detect when that transient is set and then we have all info needed to log the plugin delete
192
+ *
193
+ */
194
+ public function on_setted_transient_for_remove_files($transient, $value) {
195
+
196
+ if ( ! $user_id = get_current_user_id() ) {
197
+ return;
198
+ }
199
+
200
+ $transient_name = '_transient_plugins_delete_result_' . $user_id;
201
+ if ( $transient_name !== $transient ) {
202
+ return;
203
+ }
204
+
205
+ // We found the transient we were looking for
206
+ if (
207
+ isset( $_POST["action"] )
208
+ && "delete-selected" == $_POST["action"]
209
+ && isset( $_POST["checked"] )
210
+ && is_array( $_POST["checked"] )
211
+ ) {
212
+
213
+ /*
214
+ [checked] => Array
215
+ (
216
+ [0] => the-events-calendar/the-events-calendar.php
217
+ )
218
+ */
219
+
220
+ $plugins_deleted = $_POST["checked"];
221
+ $plugins_before_update = json_decode( get_option( $this->slug . "_plugin_info_before_update", false ), true );
222
+
223
+ foreach ($plugins_deleted as $plugin) {
224
+
225
+ $context = array(
226
+ "plugin" => $plugin
227
+ );
228
+
229
+ if ( is_array( $plugins_before_update ) && isset( $plugins_before_update[ $plugin ] ) ) {
230
+ $context["plugin_name"] = $plugins_before_update[ $plugin ]["Name"];
231
+ $context["plugin_title"] = $plugins_before_update[ $plugin ]["Title"];
232
+ $context["plugin_description"] = $plugins_before_update[ $plugin ]["Description"];
233
+ $context["plugin_author"] = $plugins_before_update[ $plugin ]["Author"];
234
+ $context["plugin_version"] = $plugins_before_update[ $plugin ]["Version"];
235
+ $context["plugin_url"] = $plugins_before_update[ $plugin ]["PluginURI"];
236
+ }
237
+
238
+ $this->infoMessage(
239
+ "plugin_deleted",
240
+ $context
241
+ );
242
+
243
+ }
244
+
245
+ }
246
+
247
+ $this->remove_saved_versions();
248
+
249
+ }
250
+
251
+ /**
252
+ * Save all plugin information before a plugin is updated or removed.
253
+ * This way we can know both the old (pre updated/removed) and the current version of the plugin
254
+ */
255
+ public function save_versions_before_update() {
256
+
257
+ $current_screen = get_current_screen();
258
+ $request_uri = $_SERVER["SCRIPT_NAME"];
259
+
260
+ // Only add option on pages where needed
261
+ $do_store = false;
262
+
263
+ if (
264
+ SimpleHistory::ends_with( $request_uri, "/wp-admin/update.php" )
265
+ && isset( $current_screen->base )
266
+ && "update" == $current_screen->base
267
+ ) {
268
+
269
+ // Plugin update screen
270
+ $do_store = true;
271
+
272
+ } else if (
273
+ SimpleHistory::ends_with( $request_uri, "/wp-admin/plugins.php" )
274
+ && isset( $current_screen->base )
275
+ && "plugins" == $current_screen->base
276
+ && ( isset( $_POST["action"] ) && "delete-selected" == $_POST["action"] )
277
+ ) {
278
+
279
+ // Plugin delete screen, during delete
280
+ $do_store = true;
281
+
282
+ }
283
+
284
+ if ( $do_store ) {
285
+ update_option( $this->slug . "_plugin_info_before_update", SimpleHistory::json_encode( get_plugins() ) );
286
+ }
287
+
288
+ }
289
+
290
+ /**
291
+ * when plugin updates are done wp_clean_plugins_cache() is called,
292
+ * which in it's turn run:
293
+ * delete_site_transient( 'update_plugins' );
294
+ * do_action( 'delete_site_transient_' . $transient, $transient );
295
+ * delete_site_transient_update_plugins
296
+ */
297
+ public function remove_saved_versions() {
298
+
299
+ delete_option( $this->slug . "_plugin_info_before_update" );
300
+
301
+ }
302
+
303
+ function check_filterless_things() {
304
+
305
+ // Var is string with length 113: /wp-admin/plugin-editor.php?file=my-plugin%2Fviews%2Fplugin-file.php
306
+ $referer = wp_get_referer();
307
+
308
+ // contains key "path" with value like "/wp-admin/plugin-editor.php"
309
+ $referer_info = parse_url($referer);
310
+
311
+ if ( "/wp-admin/plugin-editor.php" === $referer_info["path"] ) {
312
+
313
+ // We are in plugin editor
314
+ // Check for plugin edit saved
315
+ if ( isset( $_POST["newcontent"] ) && isset( $_POST["action"] ) && "update" == $_POST["action"] && isset( $_POST["file"] ) && ! empty( $_POST["file"] ) ) {
316
+
317
+ // A file was edited
318
+ $file = $_POST["file"];
319
+
320
+ // $plugins = get_plugins();
321
+ // http://codex.wordpress.org/Function_Reference/wp_text_diff
322
+
323
+ // Generate a diff of changes
324
+ if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) ) {
325
+ require( ABSPATH . WPINC . '/wp-diff.php' );
326
+ }
327
+
328
+ $original_file_contents = file_get_contents( WP_PLUGIN_DIR . "/" . $file );
329
+ $new_file_contents = wp_unslash( $_POST["newcontent"] );
330
+
331
+ $left_lines = explode("\n", $original_file_contents);
332
+ $right_lines = explode("\n", $new_file_contents);
333
+ $text_diff = new Text_Diff($left_lines, $right_lines);
334
+
335
+ $num_added_lines = $text_diff->countAddedLines();
336
+ $num_removed_lines = $text_diff->countDeletedLines();
337
+
338
+ // Generate a diff in classic diff format
339
+ $renderer = new Text_Diff_Renderer();
340
+ $diff = $renderer->render($text_diff);
341
+
342
+ $this->infoMessage(
343
+ 'plugin_file_edited',
344
+ array(
345
+ "plugin_edited_file" => $file,
346
+ "plugin_edit_diff" => $diff,
347
+ "plugin_edit_num_added_lines" => $num_added_lines,
348
+ "plugin_edit_num_removed_lines" => $num_removed_lines,
349
+ )
350
+ );
351
+
352
+ $did_log = true;
353
+
354
+ }
355
+
356
+ }
357
+
358
+
359
+ }
360
+
361
+ /**
362
+ * Called when a single plugin is updated or installed
363
+ * (not bulk)
364
+ */
365
+ function on_upgrader_process_complete( $plugin_upgrader_instance, $arr_data ) {
366
+
367
+ /*
368
+
369
+ # WordPress core update
370
+
371
+ $arr_data:
372
+ Array
373
+ (
374
+ [action] => update
375
+ [type] => core
376
+ )
377
+
378
+
379
+ # Plugin install
380
+
381
+ $arr_data:
382
+ Array
383
+ (
384
+ [type] => plugin
385
+ [action] => install
386
+ )
387
+
388
+
389
+ # Plugin update
390
+
391
+ $arr_data:
392
+ Array
393
+ (
394
+ [type] => plugin
395
+ [action] => install
396
+ )
397
+
398
+ # Bulk actions
399
+
400
+ array(
401
+ 'action' => 'update',
402
+ 'type' => 'plugin',
403
+ 'bulk' => true,
404
+ 'plugins' => $plugins,
405
+ )
406
+
407
+ */
408
+
409
+ // To keep track of if something was logged, so wen can output debug info only
410
+ // only if we did not log anything
411
+ $did_log = false;
412
+
413
+ if ( isset( $arr_data["type"] ) && "plugin" == $arr_data["type"] ) {
414
+
415
+ // Single plugin install
416
+ if ( isset( $arr_data["action"] ) && "install" == $arr_data["action"] && ! $plugin_upgrader_instance->bulk ) {
417
+
418
+ // Upgrader contains current info
419
+ $context = array(
420
+ "plugin_name" => $plugin_upgrader_instance->skin->api->name,
421
+ "plugin_slug" => $plugin_upgrader_instance->skin->api->slug,
422
+ "plugin_version" => $plugin_upgrader_instance->skin->api->version,
423
+ "plugin_author" => $plugin_upgrader_instance->skin->api->author,
424
+ "plugin_last_updated" => $plugin_upgrader_instance->skin->api->last_updated,
425
+ "plugin_requires" => $plugin_upgrader_instance->skin->api->requires,
426
+ "plugin_tested" => $plugin_upgrader_instance->skin->api->tested,
427
+ "plugin_rating" => $plugin_upgrader_instance->skin->api->rating,
428
+ "plugin_num_ratings" => $plugin_upgrader_instance->skin->api->num_ratings,
429
+ "plugin_downloaded" => $plugin_upgrader_instance->skin->api->downloaded,
430
+ "plugin_added" => $plugin_upgrader_instance->skin->api->added,
431
+ "plugin_source_files" => $this->simpleHistory->json_encode( $plugin_upgrader_instance->result["source_files"] ),
432
+ //"upgrader_skin_api" => $this->simpleHistory->json_encode( $plugin_upgrader_instance->skin->api )
433
+ );
434
+
435
+ if ( is_a( $plugin_upgrader_instance->skin->result, "WP_Error" ) ) {
436
+
437
+ // Add errors
438
+ // Errors is in original wp admin language
439
+ $context["error_messages"] = $this->simpleHistory->json_encode( $plugin_upgrader_instance->skin->result->errors );
440
+ $context["error_data"] = $this->simpleHistory->json_encode( $plugin_upgrader_instance->skin->result->error_data );
441
+
442
+ $this->infoMessage(
443
+ 'plugin_installed_failed',
444
+ $context
445
+ );
446
+
447
+ $did_log = true;
448
+
449
+ } else {
450
+
451
+ // Plugin was successfully installed
452
+ // Try to grab more info from the readme
453
+ // Would be nice to grab a screenshot, but that is difficult since they often are stored remotely
454
+ $plugin_destination = isset( $plugin_upgrader_instance->result["destination"] ) ? $plugin_upgrader_instance->result["destination"] : null;
455
+ if ($plugin_destination) {
456
+
457
+ $plugin_info = $plugin_upgrader_instance->plugin_info();
458
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_info );
459
+ $context["plugin_description"] = $plugin_data["Description"];
460
+ $context["plugin_url"] = $plugin_data["PluginURI"];
461
+
462
+ }
463
+
464
+ $this->infoMessage(
465
+ 'plugin_installed',
466
+ $context
467
+ );
468
+
469
+ $did_log = true;
470
+
471
+ }
472
+
473
+ } // install single
474
+
475
+ // Single plugin update
476
+ if ( isset( $arr_data["action"] ) && "update" == $arr_data["action"] && ! $plugin_upgrader_instance->bulk ) {
477
+
478
+ // No plugin info in instance, so get it ourself
479
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $arr_data["plugin"] );
480
+
481
+ // autoptimize/autoptimize.php
482
+
483
+ $plugin_slug = dirname( $arr_data["plugin"] );
484
+
485
+ /*
486
+ @TODO
487
+ efter update:
488
+ Undefined variable: update_plugins
489
+ */
490
+ $context = array(
491
+ "plugin_slug" => $plugin_slug,
492
+ "request" => $this->simpleHistory->json_encode( $_REQUEST ),
493
+ "update_plugins" => $this->simpleHistory->json_encode( $update_plugins ),
494
+ "plugin_name" => $plugin_data["Name"],
495
+ "plugin_title" => $plugin_data["Title"],
496
+ "plugin_description" => $plugin_data["Description"],
497
+ "plugin_author" => $plugin_data["Author"],
498
+ "plugin_version" => $plugin_data["Version"],
499
+ "plugin_url" => $plugin_data["PluginURI"],
500
+ "plugin_source_files" => $this->simpleHistory->json_encode( $plugin_upgrader_instance->result["source_files"] )
501
+ );
502
+
503
+ // update status for plugins are in response
504
+ // plugin folder + index file = key
505
+ // use this transient to get url and package
506
+ $update_plugins = get_site_transient( 'update_plugins' );
507
+ if ( $update_plugins && isset( $update_plugins->response[ $arr_data["plugin"] ] ) ) {
508
+
509
+ /*
510
+ $update_plugins[plugin_path/slug]:
511
+ {
512
+ "id": "8986",
513
+ "slug": "autoptimize",
514
+ "plugin": "autoptimize/autoptimize.php",
515
+ "new_version": "1.9.1",
516
+ "url": "https://wordpress.org/plugins/autoptimize/",
517
+ "package": "https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip"
518
+ }
519
+ */
520
+
521
+ $plugin_update_info = $update_plugins->response[ $arr_data["plugin"] ];
522
+
523
+ // autoptimize/autoptimize.php
524
+ if ( isset( $plugin_update_info->plugin ) ) {
525
+ $context["plugin_update_info_plugin"] = $plugin_update_info->plugin;
526
+ }
527
+
528
+ // https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip
529
+ if ( isset( $plugin_update_info->package ) ) {
530
+ $context["plugin_update_info_package"] = $plugin_update_info->package;
531
+ }
532
+
533
+ }
534
+
535
+ // To get old version we use our option
536
+ $plugins_before_update = json_decode( get_option( $this->slug . "_plugin_info_before_update", false ), true );
537
+ if ( is_array( $plugins_before_update ) && isset( $plugins_before_update[ $arr_data["plugin"] ] ) ) {
538
+
539
+ $context["plugin_prev_version"] = $plugins_before_update[ $arr_data["plugin"] ]["Version"];
540
+
541
+ }
542
+
543
+ if ( is_a( $plugin_upgrader_instance->skin->result, "WP_Error" ) ) {
544
+
545
+ // Add errors
546
+ // Errors is in original wp admin language
547
+ $context["error_messages"] = json_encode( $plugin_upgrader_instance->skin->result->errors );
548
+ $context["error_data"] = json_encode( $plugin_upgrader_instance->skin->result->error_data );
549
+
550
+ $this->infoMessage(
551
+ 'plugin_update_failed',
552
+ $context
553
+ );
554
+
555
+ $did_log = true;
556
+
557
+ } else {
558
+
559
+ $this->infoMessage(
560
+ 'plugin_updated',
561
+ $context
562
+ );
563
+
564
+ #echo "on_upgrader_process_complete";
565
+ #sf_d( $plugin_upgrader_instance, '$plugin_upgrader_instance' );
566
+ #sf_d( $arr_data, '$arr_data' );
567
+
568
+ $did_log = true;
569
+
570
+ }
571
+
572
+ } // update single
573
+
574
+
575
+ /**
576
+ * For bulk updates $arr_data looks like:
577
+ * Array
578
+ * (
579
+ * [action] => update
580
+ * [type] => plugin
581
+ * [bulk] => 1
582
+ * [plugins] => Array
583
+ * (
584
+ * [0] => plugin-folder-1/plugin-index.php
585
+ * [1] => my-plugin-folder/my-plugin.php
586
+ * )
587
+ * )
588
+ */
589
+ if ( isset( $arr_data["bulk"] ) && $arr_data["bulk"] && isset( $arr_data["action"] ) && "update" == $arr_data["action"] ) {
590
+
591
+ $plugins_updated = isset( $arr_data["plugins"] ) ? (array) $arr_data["plugins"] : array();
592
+
593
+ foreach ($plugins_updated as $plugin_name) {
594
+
595
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name );
596
+
597
+ $plugin_slug = dirname( $plugin_name );
598
+
599
+ $context = array(
600
+ "plugin_slug" => $plugin_slug,
601
+ "plugin_name" => $plugin_data["Name"],
602
+ "plugin_title" => $plugin_data["Title"],
603
+ "plugin_description" => $plugin_data["Description"],
604
+ "plugin_author" => $plugin_data["Author"],
605
+ "plugin_version" => $plugin_data["Version"],
606
+ "plugin_url" => $plugin_data["PluginURI"]
607
+ );
608
+
609
+ // get url and package
610
+ $update_plugins = get_site_transient( 'update_plugins' );
611
+ if ( $update_plugins && isset( $update_plugins->response[ $plugin_name ] ) ) {
612
+
613
+ /*
614
+ $update_plugins[plugin_path/slug]:
615
+ {
616
+ "id": "8986",
617
+ "slug": "autoptimize",
618
+ "plugin": "autoptimize/autoptimize.php",
619
+ "new_version": "1.9.1",
620
+ "url": "https://wordpress.org/plugins/autoptimize/",
621
+ "package": "https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip"
622
+ }
623
+ */
624
+
625
+ $plugin_update_info = $update_plugins->response[ $plugin_name ];
626
+
627
+ // autoptimize/autoptimize.php
628
+ if ( isset( $plugin_update_info->plugin ) ) {
629
+ $context["plugin_update_info_plugin"] = $plugin_update_info->plugin;
630
+ }
631
+
632
+ // https://downloads.wordpress.org/plugin/autoptimize.1.9.1.zip
633
+ if ( isset( $plugin_update_info->package ) ) {
634
+ $context["plugin_update_info_package"] = $plugin_update_info->package;
635
+ }
636
+
637
+ }
638
+
639
+ // To get old version we use our option
640
+ // @TODO: this does not always work, why?
641
+ $plugins_before_update = json_decode( get_option( $this->slug . "_plugin_info_before_update", false ), true );
642
+ if ( is_array( $plugins_before_update ) && isset( $plugins_before_update[ $plugin_name ] ) ) {
643
+
644
+ $context["plugin_prev_version"] = $plugins_before_update[ $plugin_name ]["Version"];
645
+
646
+ }
647
+
648
+ $this->infoMessage(
649
+ 'plugin_bulk_updated',
650
+ $context
651
+ );
652
+
653
+ }
654
+
655
+ } // bulk update
656
+
657
+
658
+ } // if plugin
659
+
660
+ if ( ! $did_log ) {
661
+ #echo "on_upgrader_process_complete";
662
+ #sf_d( $plugin_upgrader_instance, '$plugin_upgrader_instance' );
663
+ #sf_d( $arr_data, '$arr_data' );
664
+ #exit;
665
+ }
666
+
667
+ }
668
+
669
+ /*
670
+ * Called from filter 'upgrader_post_install'.
671
+ *
672
+ * Used to log bulk plugin installs and updates
673
+ *
674
+ * Filter docs:
675
+ *
676
+ * Filter the install response after the installation has finished.
677
+ *
678
+ * @param bool $response Install response.
679
+ * @param array $hook_extra Extra arguments passed to hooked filters.
680
+ * @param array $result Installation result data.
681
+ */
682
+ public function on_upgrader_post_install( $response, $hook_extra, $result ) {
683
+
684
+ #echo "on_upgrader_post_install";
685
+ /*
686
+
687
+ # Plugin update:
688
+ $hook_extra
689
+ Array
690
+ (
691
+ [plugin] => plugin-folder/plugin-name.php
692
+ [type] => plugin
693
+ [action] => update
694
+ )
695
+
696
+ # Plugin install, i.e. download/install, but not activation:
697
+ $hook_extra:
698
+ Array
699
+ (
700
+ [type] => plugin
701
+ [action] => install
702
+ )
703
+
704
+ */
705
+
706
+ if ( isset( $hook_extra["action"] ) && $hook_extra["action"] == "install" && isset( $hook_extra["type"] ) && $hook_extra["type"] == "plugin" ) {
707
+
708
+ // It's a plugin install
709
+ #error_log("plugin install");
710
+
711
+
712
+ } else if ( isset( $hook_extra["action"] ) && $hook_extra["action"] == "update" && isset( $hook_extra["type"] ) && $hook_extra["type"] == "plugin" ) {
713
+
714
+ // It's a plugin upgrade
715
+ #echo "plugin update!";
716
+ //error_log("plugin update");
717
+
718
+ } else {
719
+
720
+ //error_log("other");
721
+
722
+ }
723
+
724
+ #sf_d($response, '$response');
725
+ #sf_d($hook_extra, '$hook_extra');
726
+ #sf_d($result, '$result');
727
+ #exit;
728
+
729
+ return $response;
730
+
731
+ }
732
+
733
+ /*
734
+
735
+ * Filter the list of action links available following bulk plugin updates.
736
+ *
737
+ * @since 3.0.0
738
+ *
739
+ * @param array $update_actions Array of plugin action links.
740
+ * @param array $plugin_info Array of information for the last-updated plugin.
741
+
742
+ $update_actions = apply_filters( 'update_bulk_plugins_complete_actions', $update_actions, $this->plugin_info );
743
+
744
+ */
745
+
746
+ /*
747
+
748
+
749
+ *
750
+ * Fires when the bulk upgrader process is complete.
751
+ *
752
+ * @since 3.6.0
753
+ *
754
+ * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
755
+ * be a Theme_Upgrader or Core_Upgrade instance.
756
+ * @param array $data {
757
+ * Array of bulk item update data.
758
+ *
759
+ * @type string $action Type of action. Default 'update'.
760
+ * @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'.
761
+ * @type bool $bulk Whether the update process is a bulk update. Default true.
762
+ * @type array $packages Array of plugin, theme, or core packages to update.
763
+ * }
764
+ *
765
+ do_action( 'upgrader_process_complete', $this, array(
766
+ 'action' => 'update',
767
+ 'type' => 'plugin',
768
+ 'bulk' => true,
769
+ 'plugins' => $plugins,
770
+ ) );
771
+
772
+
773
+ do_action( 'upgrader_process_complete', $this, array( 'action' => 'update', 'type' => 'core' ) );
774
+ */
775
+
776
+ /**
777
+ * Plugin is activated
778
+ * plugin_name is like admin-menu-tree-page-view/index.php
779
+ */
780
+ function on_activated_plugin($plugin_name, $network_wide) {
781
+
782
+ /*
783
+ Plugin data returned array contains the following:
784
+ 'Name' - Name of the plugin, must be unique.
785
+ 'Title' - Title of the plugin and the link to the plugin's web site.
786
+ 'Description' - Description of what the plugin does and/or notes from the author.
787
+ 'Author' - The author's name
788
+ 'AuthorURI' - The authors web site address.
789
+ 'Version' - The plugin version number.
790
+ 'PluginURI' - Plugin web site address.
791
+ 'TextDomain' - Plugin's text domain for localization.
792
+ 'DomainPath' - Plugin's relative directory path to .mo files.
793
+ 'Network' - Boolean. Whether the plugin can only be activated network wide.
794
+ */
795
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name );
796
+
797
+ $plugin_slug = dirname( $plugin_name );
798
+
799
+ $context = array(
800
+ "plugin_name" => $plugin_data["Name"],
801
+ "plugin_slug" => $plugin_slug,
802
+ "plugin_title" => $plugin_data["Title"],
803
+ "plugin_description" => $plugin_data["Description"],
804
+ "plugin_author" => $plugin_data["Author"],
805
+ "plugin_version" => $plugin_data["Version"],
806
+ "plugin_url" => $plugin_data["PluginURI"],
807
+ );
808
+
809
+ $this->infoMessage( 'plugin_activated', $context );
810
+
811
+ }
812
+
813
+ /**
814
+ * Plugin is deactivated
815
+ * plugin_name is like admin-menu-tree-page-view/index.php
816
+ */
817
+ function on_deactivated_plugin($plugin_name) {
818
+
819
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_name );
820
+ $plugin_slug = dirname( $plugin_name );
821
+
822
+ $context = array(
823
+ "plugin_name" => $plugin_data["Name"],
824
+ "plugin_slug" => $plugin_slug,
825
+ "plugin_title" => $plugin_data["Title"],
826
+ "plugin_description" => $plugin_data["Description"],
827
+ "plugin_author" => $plugin_data["Author"],
828
+ "plugin_version" => $plugin_data["Version"],
829
+ "plugin_url" => $plugin_data["PluginURI"],
830
+ );
831
+
832
+ $this->infoMessage( 'plugin_deactivated', $context );
833
+
834
+ }
835
+
836
+
837
+ /**
838
+ * Get output for detailed log section
839
+ */
840
+ function getLogRowDetailsOutput($row) {
841
+
842
+ $context = $row->context;
843
+ $message_key = $context["_message_key"];
844
+ $output = "";
845
+
846
+ // When a plugin is installed we show a bit more information
847
+ // We do it only on install because we don't want to clutter to log,
848
+ // and when something is installed the description is most useul for other
849
+ // admins on the site
850
+ if ( "plugin_installed" === $message_key ) {
851
+
852
+ if ( isset($context["plugin_description"]) ) {
853
+
854
+ // Description includes a link to author, remove that, i.e. all text after and including <cite>
855
+ $plugin_description = $context["plugin_description"];
856
+ $cite_pos = mb_strpos($plugin_description, "<cite>");
857
+ if ($cite_pos) {
858
+ $plugin_description = mb_strcut( $plugin_description, 0, $cite_pos );
859
+ }
860
+
861
+ // Keys to show
862
+ $arr_plugin_keys = array(
863
+ "plugin_version" => _x("Version", "plugin logger - detailed output version", "simple-history"),
864
+ "plugin_description" => "Description",
865
+ "plugin_author" => _x("Author", "plugin logger - detailed output author", "simple-history"),
866
+ "plugin_url" => _x("URL", "plugin logger - detailed output url", "simple-history"),
867
+ "plugin_requires" => _x("Requires", "plugin logger - detailed output author", "simple-history"),
868
+ "plugin_tested" => _x("Compatible up to", "plugin logger - detailed output compatible", "simple-history"),
869
+ "plugin_downloaded" => _x("Downloads", "plugin logger - detailed output downloaded", "simple-history"),
870
+ // also available: plugin_rating, plugin_num_ratings
871
+ );
872
+
873
+ $arr_plugin_keys = apply_filters("simple_history/plugin_logger/row_details_plugin_info_keys", $arr_plugin_keys);
874
+
875
+ // Start output of plugin meta data table
876
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
877
+
878
+ foreach ( $arr_plugin_keys as $key => $desc ) {
879
+
880
+ switch ($key) {
881
+
882
+ case "plugin_downloaded":
883
+ $desc_output = esc_attr( number_format_i18n( (int) $context[ $key ] ) );
884
+ break;
885
+
886
+ // author is already formatted
887
+ case "plugin_author":
888
+ $desc_output = $context[ $key ];
889
+ break;
890
+
891
+ // URL needs a link
892
+ case "plugin_url":
893
+ $desc_output = sprintf('<a href="%1$s">%1$s</a>', esc_attr( $context["plugin_url"] ));
894
+ break;
895
+
896
+ case "plugin_description":
897
+ $desc_output = $plugin_description;
898
+ break;
899
+
900
+ default;
901
+ $desc_output = esc_html( $context[ $key ] );
902
+ break;
903
+ }
904
+
905
+ $output .= sprintf(
906
+ '
907
+ <tr>
908
+ <td>%1$s</td>
909
+ <td>%2$s</td>
910
+ </tr>
911
+ ',
912
+ esc_html($desc),
913
+ $desc_output
914
+ );
915
+
916
+ }
917
+
918
+ $plugin_slug = ! empty($context["plugin_slug"]) ? $context["plugin_slug"] : "";
919
+ if ( $plugin_slug ) {
920
+
921
+ $output .= sprintf(
922
+ '
923
+ <tr>
924
+ <td></td>
925
+ <td><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></td>
926
+ </tr>
927
+ ',
928
+ admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=&amp;TB_iframe=true&amp;width=640&amp;height=550" ),
929
+ esc_html_x("View plugin info", "plugin logger: plugin info thickbox title view all info", "simple-history")
930
+ );
931
+
932
+ }
933
+
934
+ $output .= "</table>";
935
+
936
+ }
937
+
938
+ } elseif ( "plugin_bulk_updated" === $message_key || "plugin_updated" === $message_key || "plugin_activated" === $message_key || "plugin_deactivated" === $message_key ) {
939
+
940
+ $plugin_slug = !empty($context["plugin_slug"]) ? $context["plugin_slug"] : "";
941
+
942
+ if ($plugin_slug) {
943
+
944
+ $link_title = esc_html_x("View plugin info", "plugin logger: plugin info thickbox title", "simple-history");
945
+ $url = admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=&amp;TB_iframe=true&amp;width=640&amp;height=550" );
946
+
947
+ if ( "plugin_updated" == $message_key || "plugin_bulk_updated" == $message_key ) {
948
+ $link_title = esc_html_x("View changelog", "plugin logger: plugin info thickbox title", "simple-history");
949
+ $url = admin_url( "plugin-install.php?tab=plugin-information&amp;plugin={$plugin_slug}&amp;section=changelog&amp;TB_iframe=true&amp;width=772&amp;height=550" );
950
+ }
951
+
952
+ $output .= sprintf(
953
+ '<p><a title="%2$s" class="thickbox" href="%1$s">%2$s</a></p>',
954
+ $url,
955
+ $link_title
956
+ );
957
+
958
+ }
959
+
960
+ } // if plugin_updated
961
+
962
+ return $output;
963
+
964
+ }
965
+
966
+
967
+ }
loggers/SimplePostLogger.php ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs changes to posts and pages, including custom post types
5
+ */
6
+ class SimplePostLogger extends SimpleLogger
7
+ {
8
+
9
+ // The logger slug. Defaulting to the class name is nice and logical I think
10
+ public $slug = __CLASS__;
11
+
12
+ public function loaded() {
13
+
14
+ add_action("admin_init", array($this, "on_admin_init"));
15
+
16
+ }
17
+
18
+ /**
19
+ * Get array with information about this logger
20
+ *
21
+ * @return array
22
+ */
23
+ function getInfo() {
24
+
25
+ $arr_info = array(
26
+ "name" => "Post Logger",
27
+ "description" => "Logs the creation and modification of posts and pages",
28
+ "capability" => "edit_pages",
29
+ "messages" => array(
30
+ 'post_created' => __('Created {post_type} "{post_title}"', 'simple-history'),
31
+ 'post_updated' => __('Updated {post_type} "{post_title}"', 'simple-history'),
32
+ 'post_restored' => __('Restored {post_type} "{post_title}" from trash', 'simple-history'),
33
+ 'post_deleted' => __('Deleted {post_type} "{post_title}"', 'simple-history'),
34
+ 'post_trashed' => __('Moved {post_type} "{post_title}" to the trash', 'simple-history')
35
+ ),
36
+ "labels" => array(
37
+ "search" => array(
38
+ "label" => _x("Posts & Pages", "Post logger: search", "simple-history"),
39
+ "options" => array(
40
+ _x("Posts created", "Post logger: search", "simple-history") => array(
41
+ "post_created"
42
+ ),
43
+ _x("Posts updated", "Post logger: search", "simple-history") => array(
44
+ "post_updated"
45
+ ),
46
+ _x("Posts trashed", "Post logger: search", "simple-history") => array(
47
+ "post_trashed"
48
+ ),
49
+ _x("Posts deleted", "Post logger: search", "simple-history") => array(
50
+ "post_deleted"
51
+ ),
52
+ _x("Posts restored", "Post logger: search", "simple-history") => array(
53
+ "post_restored"
54
+ ),
55
+ )
56
+ ) // end search array
57
+ ) // end labels
58
+
59
+ );
60
+
61
+ return $arr_info;
62
+
63
+ }
64
+
65
+ function on_admin_init() {
66
+
67
+ add_action("transition_post_status", array($this, "on_transition_post_status"), 10, 3);
68
+ add_action("delete_post", array($this, "on_delete_post"));
69
+ add_action("untrash_post", array($this, "on_untrash_post"));
70
+
71
+ }
72
+
73
+ /**
74
+ * Called when a post is restored from the trash
75
+ */
76
+ function on_untrash_post($post_id) {
77
+
78
+ $post = get_post($post_id);
79
+
80
+ $this->info(
81
+ $this->messages["post_restored"],
82
+ array(
83
+ "post_id" => $post_id,
84
+ "post_type" => get_post_type($post),
85
+ "post_title" => get_the_title($post)
86
+ )
87
+ );
88
+
89
+ }
90
+
91
+ /**
92
+ * Called when a post is deleted from the trash
93
+ */
94
+ function on_delete_post($post_id) {
95
+
96
+ $post = get_post($post_id);
97
+
98
+ if ( wp_is_post_revision($post_id) ) {
99
+ return;
100
+ }
101
+
102
+ if ( $post->post_status === "auto-draft" || $post->post_status === "inherit" ) {
103
+ return;
104
+ }
105
+
106
+ if ( "nav_menu_item" == get_post_type( $post ) ) {
107
+ return;
108
+ }
109
+
110
+ $this->infoMessage(
111
+ "post_deleted",
112
+ array(
113
+ "post_id" => $post_id,
114
+ "post_type" => get_post_type($post),
115
+ "post_title" => get_the_title($post)
116
+ )
117
+ );
118
+
119
+ }
120
+
121
+
122
+ /**
123
+ * Fired when a post has changed status
124
+ */
125
+ function on_transition_post_status($new_status, $old_status, $post) {
126
+
127
+ // Don't log revisions
128
+ if ( wp_is_post_revision( $post ) ) {
129
+ return;
130
+ }
131
+
132
+ // Don't log nav_menu_updates
133
+ /*
134
+ $post_types = get_post_types();
135
+ Array
136
+ (
137
+ [post] => post
138
+ [page] => page
139
+ [attachment] => attachment
140
+ [revision] => revision
141
+ [nav_menu_item] => nav_menu_item
142
+ [texts] => texts
143
+ [products] => products
144
+ [book] => book
145
+ )
146
+ */
147
+ if ( "nav_menu_item" == get_post_type( $post ) ) {
148
+ return;
149
+ }
150
+
151
+ /*
152
+ From new to auto-draft <- ignore
153
+ From new to inherit <- ignore
154
+ From auto-draft to draft <- page/post created
155
+ From draft to draft
156
+ From draft to pending
157
+ From pending to publish
158
+ From pending to trash
159
+ From something to publish = post published
160
+ if not from & to = same, then user has changed something
161
+ */
162
+
163
+ $context = array(
164
+ "post_id" => $post->ID,
165
+ "post_type" => get_post_type($post),
166
+ "post_title" => get_the_title($post),
167
+ "post_new_status" => $new_status,
168
+ "post_old_status" => $old_status
169
+ );
170
+
171
+ if ($old_status == "auto-draft" && ($new_status != "auto-draft" && $new_status != "inherit")) {
172
+
173
+ // Post created
174
+ $this->infoMessage( "post_created", $context );
175
+
176
+ } elseif ($new_status == "auto-draft" || ($old_status == "new" && $new_status == "inherit")) {
177
+
178
+ // Post was automagically saved by WordPress
179
+ return;
180
+
181
+ } elseif ($new_status == "trash") {
182
+
183
+ // Post trashed
184
+ $this->infoMessage( "post_trashed", $context );
185
+
186
+ } else {
187
+
188
+ // Post updated
189
+ $this->infoMessage( "post_updated", $context );
190
+
191
+ }
192
+
193
+ }
194
+
195
+ /**
196
+ * Modify plain output to inlcude link to post
197
+ */
198
+ public function getLogRowPlainTextOutput($row) {
199
+
200
+ $context = $row->context;
201
+ $post_id = $context["post_id"];
202
+
203
+ // Default to original log message
204
+ $message = $row->message;
205
+
206
+ // Check if post still is available
207
+ // It wil return a WP_Post Object if post still is in system
208
+ // If post is deleted from trash (not just moved there), then null is returned
209
+ $post = get_post( $post_id );
210
+ $post_is_available = is_a($post, "WP_Post");
211
+
212
+ #sf_d($post_is_available, '$post_is_available');
213
+ #sf_d($message_key, '$message_key');
214
+
215
+ $message_key = isset($context["_message_key"]) ? $context["_message_key"] : null;
216
+
217
+ // Try to get singular name
218
+ $post_type_obj = get_post_type_object( $context["post_type"] );
219
+ if ( ! is_null( $post_type_obj ) ) {
220
+
221
+ if ( ! empty ($post_type_obj->labels->singular_name) ) {
222
+ $context["post_type"] = strtolower( $post_type_obj->labels->singular_name );
223
+ }
224
+
225
+ }
226
+
227
+ // If post is not available any longer then we can't link to it, so keep plain message then
228
+ if ( $post_is_available ) {
229
+
230
+ if ( "post_updated" == $message_key ) {
231
+
232
+ $message = __('Updated {post_type} <a href="{edit_link}">"{post_title}"</a>', "simple-history");
233
+
234
+ } else if ( "post_deleted" == $message_key ) {
235
+
236
+ $message = __('Deleted {post_type} "{post_title}"');
237
+
238
+ } else if ( "post_created" == $message_key ) {
239
+
240
+ $message = __('Created {post_type} <a href="{edit_link}">"{post_title}"</a>', "simple-history");
241
+
242
+ } else if ( "post_trashed" == $message_key ) {
243
+
244
+ // while in trash we can still get actions to delete or restore if we follow the edit link
245
+ $message = __('Moved {post_type} <a href="{edit_link}">"{post_title}"</a> to the trash', "simple-history");
246
+
247
+ }
248
+
249
+ } // post still available
250
+
251
+ $context["post_type"] = esc_html( $context["post_type"] );
252
+ $context["post_title"] = esc_html( $context["post_title"] );
253
+ $context["edit_link"] = get_edit_post_link( $post_id );
254
+
255
+ return $this->interpolate($message, $context);
256
+
257
+ }
258
+
259
+ }
loggers/SimpleThemeLogger.php ADDED
@@ -0,0 +1,940 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs WordPress theme edits
5
+ */
6
+ class SimpleThemeLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ // When swithing themes, this will contain info about the theme we are switching from
12
+ private $prev_theme_data;
13
+
14
+ /**
15
+ * Get array with information about this logger
16
+ *
17
+ * @return array
18
+ */
19
+ function getInfo() {
20
+
21
+ $arr_info = array(
22
+ "name" => "Theme Logger",
23
+ "description" => "Logs theme edits",
24
+ "capability" => "edit_theme_options",
25
+ "messages" => array(
26
+ 'theme_switched' => __('Switched theme to "{theme_name}" from "{prev_theme_name}"', "simple-history"),
27
+ 'appearance_customized' => __('Customized theme appearance "{setting_id}"', "simple-history"),
28
+ 'widget_removed' => __('Removed widget "{widget_id_base}" from sidebar "{sidebar_id}"', "simple-history"),
29
+ 'widget_added' => __('Added widget "{widget_id_base}" to sidebar "{sidebar_id}"', "simple-history"),
30
+ 'widget_order_changed' => __('Changed widget order "{widget_id_base}" in sidebar "{sidebar_id}"', "simple-history"),
31
+ 'widget_edited' => __('Changed widget "{widget_id_base}" in sidebar "{sidebar_id}"', "simple-history"),
32
+ "custom_background_changed" => __("Changed settings for the theme custom background", "simple_history")
33
+ ),
34
+ "labels" => array(
35
+ "search" => array(
36
+ "label" => _x("Themes & Widgets", "Theme logger: search", "simple-history"),
37
+ "options" => array(
38
+ _x("Switched themes", "Theme logger: search", "simple-history") => array(
39
+ "theme_switched"
40
+ ),
41
+ _x("Changed appearance of themes", "Theme logger: search", "simple-history") => array(
42
+ "appearance_customized"
43
+ ),
44
+ _x("Added widgets", "Theme logger: search", "simple-history") => array(
45
+ "widget_added"
46
+ ),
47
+ _x("Removed widgets", "Theme logger: search", "simple-history") => array(
48
+ "widget_removed"
49
+ ),
50
+ _x("Changed widgets order", "Theme logger: search", "simple-history") => array(
51
+ "widget_order_changed"
52
+ ),
53
+ _x("Edited widgets", "Theme logger: search", "simple-history") => array(
54
+ "widget_edited"
55
+ ),
56
+ _x("Background of themes changed", "Theme logger: search", "simple-history") => array(
57
+ "custom_background_changed"
58
+ ),
59
+ )
60
+ ) // end search array
61
+ ) // end labels
62
+
63
+ );
64
+
65
+ return $arr_info;
66
+
67
+ }
68
+
69
+ function loaded() {
70
+
71
+ /**
72
+ * Fires after the theme is switched.
73
+ * @param string $new_name Name of the new theme.
74
+ * @param WP_Theme $new_theme WP_Theme instance of the new theme.
75
+ */
76
+ add_action( 'switch_theme', array( $this, "on_switch_theme" ), 10, 2 );
77
+ add_action( 'load-themes.php', array( $this, "on_page_load_themes" ) );
78
+
79
+ add_action("customize_save", array( $this, "on_action_customize_save" ));
80
+
81
+ add_action("sidebar_admin_setup", array( $this, "on_action_sidebar_admin_setup__detect_widget_delete") );
82
+ add_action("sidebar_admin_setup", array( $this, "on_action_sidebar_admin_setup__detect_widget_add") );
83
+ add_action("wp_ajax_widgets-order", array( $this, "on_action_sidebar_admin_setup__detect_widget_order_change"), 1 );
84
+ //add_action("sidebar_admin_setup", array( $this, "on_action_sidebar_admin_setup__detect_widget_edit") );
85
+
86
+ add_filter( 'widget_update_callback', array( $this, "on_widget_update_callback" ), 10, 4 );
87
+
88
+ add_action( "load-appearance_page_custom-background", array( $this, "on_page_load_custom_background" ) );
89
+
90
+
91
+ }
92
+
93
+ function on_page_load_custom_background() {
94
+
95
+ if ( empty( $_POST ) ) {
96
+ return;
97
+ }
98
+
99
+ $arr_valid_post_keys = array(
100
+ "reset-background" => 1,
101
+ "remove-background" => 1,
102
+ "background-repeat" => 1,
103
+ "background-position-x" => 1,
104
+ "background-attachment" => 1,
105
+ "background-color" => 1
106
+ );
107
+
108
+ $valid_post_key_exists = array_intersect_key($arr_valid_post_keys, $_POST);
109
+
110
+ if ( ! empty( $valid_post_key_exists) ) {
111
+
112
+ $context = array();
113
+ // $context["POST"] = $this->simpleHistory->json_encode( $_POST );
114
+
115
+ $this->infoMessage(
116
+ "custom_background_changed",
117
+ $context
118
+ );
119
+
120
+ }
121
+
122
+
123
+
124
+ }
125
+
126
+ /*
127
+ WP_Customize_Manager $this WP_Customize_Manager instance.
128
+ */
129
+ function on_action_customize_save($customize_manager) {
130
+
131
+ /*
132
+ - Loop through all sections
133
+ - And then through all controls in section
134
+ - Foreach control get it's setting
135
+ - Get each settings prev value
136
+ - Get each settings new value
137
+ - Store changed values
138
+
139
+ */
140
+
141
+ /*
142
+ The following sections are built-in:
143
+ ------------------------------------
144
+ title_tagline - Site Title & Tagline
145
+ colors - Colors
146
+ header_image - Header Image
147
+ background_image - Background Image
148
+ nav - Navigation
149
+ static_front_page - Static Front Page
150
+ */
151
+ /*
152
+ Array
153
+ (
154
+ [wp_customize] => on
155
+ [theme] => make
156
+ [customized] => {\"widget_pages[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_calendar[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_calendar[6]\":{\"encoded_serialized_instance\":\"YToxOntzOjU6InRpdGxlIjtzOjE3OiJTZWUgd2hhdCBoYXBwZW5zISI7fQ==\",\"title\":\"See what happens!\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"ca37c1913982fa69bce33f77cef871cd\"},\"widget_calendar[7]\":{\"encoded_serialized_instance\":\"YToxOntzOjU6InRpdGxlIjtzOjg6IkthbGVuZGVyIjt9\",\"title\":\"Kalender\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"c602d6d891e3f7addb11ca21ac142b49\"},\"widget_archives[4]\":{\"encoded_serialized_instance\":\"YTozOntzOjU6InRpdGxlIjtzOjA6IiI7czo1OiJjb3VudCI7aTowO3M6ODoiZHJvcGRvd24iO2k6MDt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"3480afa3934342872c740122c4988ab5\"},\"widget_archives[6]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_meta[2]\":{\"encoded_serialized_instance\":\"YToxOntzOjU6InRpdGxlIjtzOjA6IiI7fQ==\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"b518e607928dcfc07867f25e07a3a875\"},\"widget_search[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_text[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_categories[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_recent-posts[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_recent-comments[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_rss[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_tag_cloud[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_nav_menu[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_caldera_forms_widget[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_edd_cart_widget[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_edd_categories_tags_widget[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_edd_categories_tags_widget[2]\":{\"encoded_serialized_instance\":\"YTo0OntzOjU6InRpdGxlIjtzOjA6IiI7czo4OiJ0YXhvbm9teSI7czoxNzoiZG93bmxvYWRfY2F0ZWdvcnkiO3M6NToiY291bnQiO3M6MDoiIjtzOjEwOiJoaWRlX2VtcHR5IjtzOjA6IiI7fQ==\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"afb767ddd896180593a758ba3228a6a4\"},\"widget_edd_product_details[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"widget_icl_lang_sel_widget[1]\":{\"encoded_serialized_instance\":\"YTowOnt9\",\"title\":\"\",\"is_widget_customizer_js_value\":true,\"instance_hash_key\":\"1eb11012ea65a32e655e37f998225608\"},\"sidebars_widgets[wp_inactive_widgets]\":[\"calendar-6\",\"calendar-7\",\"archives-4\",\"archives-6\",\"edd_categories_tags_widget-2\"],\"sidebars_widgets[sidebar-left]\":[\"meta-2\"],\"sidebars_widgets[sidebar-right]\":[],\"sidebars_widgets[footer-1]\":[],\"sidebars_widgets[footer-2]\":[],\"sidebars_widgets[footer-3]\":[],\"sidebars_widgets[footer-4]\":[],\"blogname\":\"My test sitexxxxx\",\"blogdescription\":\"hej\",\"header_textcolor\":false,\"background_color\":\"#30d132\",\"header_image\":false,\"header_image_data\":\"\",\"background_image\":\"http://playground-root.ep/assets/uploads/2014/09/small-image.gif\",\"background_image_thumb\":\"\",\"background_repeat\":\"repeat-y\",\"background_position_x\":\"right\",\"background_attachment\":\"scroll\",\"nav_menu_locations[primary]\":\"31\",\"nav_menu_locations[social]\":0,\"nav_menu_locations[header-bar]\":\"32\",\"show_on_front\":\"page\",\"page_on_front\":\"24851\",\"page_for_posts\":\"25253\",\"logo-regular\":\"\",\"logo-retina\":\"\",\"logo-favicon\":\"\",\"logo-apple-touch\":\"\",\"social-facebook\":\"\",\"social-twitter\":\"\",\"social-google-plus-square\":\"\",\"social-linkedin\":\"\",\"social-instagram\":\"\",\"social-flickr\":\"\",\"social-youtube\":\"\",\"social-vimeo-square\":\"\",\"social-pinterest\":\"\",\"social-email\":\"\",\"social-hide-rss\":0,\"social-custom-rss\":\"\",\"font-subset\":\"latin\",\"font-family-site-title\":\"Dawning of a New Day\",\"font-size-site-title\":32,\"font-family-site-tagline\":\"Open Sans\",\"font-size-site-tagline\":12,\"font-family-nav\":\"Open Sans\",\"font-size-nav\":14,\"font-family-subnav\":\"Open Sans\",\"font-size-subnav\":13,\"font-subnav-mobile\":1,\"font-family-widget\":\"Open Sans\",\"font-size-widget\":13,\"font-family-h1\":\"monospace\",\"font-size-h1\":50,\"font-family-h2\":\"monospace\",\"font-size-h2\":37,\"font-family-h3\":\"monospace\",\"font-size-h3\":26,\"font-family-h4\":\"monospace\",\"font-size-h4\":26,\"font-family-h5\":\"monospace\",\"font-size-h5\":18,\"font-family-h6\":\"monospace\",\"font-size-h6\":15,\"font-family-body\":\"Open Sans\",\"font-size-body\":17,\"color-primary\":\"#2fce6f\",\"color-secondary\":\"#35c904\",\"color-text\":\"#969696\",\"color-detail\":\"#b9bcbf\",\"main-background-color\":\"#ffffff\",\"header-bar-background-color\":\"#171717\",\"header-bar-text-color\":\"#16dd66\",\"header-bar-border-color\":\"#171717\",\"header-background-color\":\"#ffffff\",\"header-text-color\":\"#171717\",\"color-site-title\":\"#1a6aba\",\"footer-background-color\":\"#eaecee\",\"footer-text-color\":\"#464849\",\"footer-border-color\":\"#b9bcbf\",\"header-background-image\":\"\",\"header-background-repeat\":\"no-repeat\",\"header-background-position\":\"center\",\"header-background-size\":\"cover\",\"header-layout\":1,\"header-branding-position\":\"left\",\"header-bar-content-layout\":\"flipped\",\"header-text\":\"text\",\"header-show-social\":0,\"header-show-search\":1,\"general-layout\":\"boxed\",\"general-sticky-label\":\"sticky name\",\"main-content-link-underline\":0,\"layout-blog-hide-header\":0,\"layout-blog-hide-footer\":0,\"layout-blog-sidebar-left\":0,\"layout-blog-sidebar-right\":1,\"layout-blog-featured-images\":\"post-header\",\"layout-blog-featured-images-alignment\":\"center\",\"layout-blog-post-date\":\"absolute\",\"layout-blog-post-date-location\":\"top\",\"layout-blog-post-author\":\"avatar\",\"layout-blog-post-author-location\":\"post-footer\",\"layout-blog-auto-excerpt\":0,\"layout-blog-show-categories\":1,\"layout-blog-show-tags\":1,\"layout-blog-comment-count\":\"none\",\"layout-blog-comment-count-location\":\"before-content\",\"layout-archive-hide-header\":0,\"layout-archive-hide-footer\":0,\"layout-archive-sidebar-left\":0,\"layout-archive-sidebar-right\":1,\"layout-archive-featured-images\":\"post-header\",\"layout-archive-featured-images-alignment\":\"center\",\"layout-archive-post-date\":\"absolute\",\"layout-archive-post-date-location\":\"top\",\"layout-archive-post-author\":\"avatar\",\"layout-archive-post-author-location\":\"post-footer\",\"layout-archive-auto-excerpt\":0,\"layout-archive-show-categories\":1,\"layout-archive-show-tags\":1,\"layout-archive-comment-count\":\"none\",\"layout-archive-comment-count-location\":\"before-content\",\"layout-search-hide-header\":0,\"layout-search-hide-footer\":0,\"layout-search-sidebar-left\":true,\"layout-search-sidebar-right\":1,\"layout-search-featured-images\":\"thumbnail\",\"layout-search-featured-images-alignment\":\"center\",\"layout-search-post-date\":\"absolute\",\"layout-search-post-date-location\":\"top\",\"layout-search-post-author\":\"name\",\"layout-search-post-author-location\":\"post-footer\",\"layout-search-auto-excerpt\":1,\"layout-search-show-categories\":1,\"layout-search-show-tags\":1,\"layout-search-comment-count\":\"none\",\"layout-search-comment-count-location\":\"before-content\",\"layout-post-hide-header\":0,\"layout-post-hide-footer\":0,\"layout-post-sidebar-left\":0,\"layout-post-sidebar-right\":0,\"layout-post-featured-images\":\"post-header\",\"layout-post-featured-images-alignment\":\"center\",\"layout-post-post-date\":\"absolute\",\"layout-post-post-date-location\":\"top\",\"layout-post-post-author\":\"name\",\"layout-post-post-author-location\":\"post-footer\",\"layout-post-show-categories\":0,\"layout-post-show-tags\":0,\"layout-post-comment-count\":\"none\",\"layout-post-comment-count-location\":\"before-content\",\"layout-page-hide-header\":0,\"layout-page-hide-footer\":0,\"layout-page-sidebar-left\":0,\"layout-page-sidebar-right\":0,\"layout-page-hide-title\":1,\"layout-page-featured-images\":\"none\",\"layout-page-featured-images-alignment\":\"center\",\"layout-page-post-date\":\"none\",\"layout-page-post-date-location\":\"top\",\"layout-page-post-author\":\"none\",\"layout-page-post-author-location\":\"post-footer\",\"layout-page-comment-count\":\"none\",\"layout-page-comment-count-location\":\"before-content\",\"footer-background-image\":\"\",\"footer-background-repeat\":\"no-repeat\",\"footer-background-position\":\"center\",\"footer-background-size\":\"cover\",\"footer-widget-areas\":3,\"footer-layout\":1,\"footer-text\":\"\",\"footer-show-social\":1,\"background_size\":\"auto\",\"main-background-image\":\"\",\"main-background-repeat\":\"repeat\",\"main-background-position\":\"left\",\"main-background-size\":\"auto\",\"navigation-mobile-label\":\"Menuxx\",\"hide-site-title\":0,\"hide-tagline\":0}
157
+ [nonce] => e983bc7d41
158
+ [action] => customize_save
159
+ )
160
+ */
161
+
162
+ /*
163
+ keys in customized = settings id
164
+ */
165
+ #print_r($_REQUEST);
166
+
167
+ // Needed to get sections and controls in sorted order
168
+ $customize_manager->prepare_controls();
169
+
170
+ $settings = $customize_manager->settings();
171
+ $sections = $customize_manager->sections();
172
+ $controls = $customize_manager->controls();
173
+
174
+ $customized = json_decode( wp_unslash( $_REQUEST["customized"] ) );
175
+
176
+ foreach ($customized as $setting_id => $posted_values) {
177
+
178
+ foreach ($settings as $one_setting) {
179
+
180
+ if ($one_setting->id == $setting_id) {
181
+
182
+ // sf_d("MATCH");
183
+ $old_value = $one_setting->value();
184
+ $new_value = $one_setting->post_value();
185
+
186
+ if ($old_value != $new_value) {
187
+
188
+ $context = array(
189
+ "setting_id" => $one_setting->id,
190
+ "setting_old_value" => $old_value,
191
+ "setting_new_value" => $new_value,
192
+ #"control_id" => $section_control->id,
193
+ #"control_label" => $section_control->label,
194
+ #"control_type" => $section_control->type,
195
+ #"section_id" => $section->id,
196
+ #"section_title" => $section->title,
197
+ );
198
+
199
+ // value is changed
200
+ // find which control it belongs to
201
+ #foreach ($sections as $section) {
202
+ foreach ($controls as $one_control) {
203
+
204
+ foreach ($one_control->settings as $section_control_setting) {
205
+
206
+ if ( $section_control_setting->id == $setting_id) {
207
+
208
+ #echo "\n" . $one_control->id;
209
+ #echo "\n" . $one_control->label;
210
+ #echo "\n" . $one_control->type;
211
+ $context["control_id"] = $one_control->id;
212
+ $context["control_label"] = $one_control->label;
213
+ $context["control_type"] = $one_control->type;
214
+
215
+ }
216
+
217
+ }
218
+
219
+ }
220
+ #}
221
+
222
+ $this->infoMessage(
223
+ "appearance_customized",
224
+ $context
225
+ );
226
+
227
+ }
228
+
229
+ }
230
+
231
+ }
232
+
233
+ }
234
+
235
+ return;
236
+ #print_r( json_decode( $customized ) );
237
+ #exit;
238
+
239
+
240
+
241
+
242
+
243
+ // Set to true to echo some info about stuff
244
+ // that can be views in console when saving
245
+ $debug = 0;
246
+
247
+ $arr_changed_settings = array();
248
+ $arr_changed_settings_ids = array();
249
+
250
+
251
+
252
+ /*
253
+
254
+
255
+ foreach ($settings as $setting) {
256
+
257
+ #echo "\n\nsetting";
258
+ #sf_d( $setting->id );
259
+ #sf_d( $setting->value() );
260
+ #sf_d( $setting->post_value() );
261
+
262
+ // Get control for this settings
263
+ foreach ($sections as $section) {
264
+ foreach ($section->controls as $control) {
265
+ foreach ($control->settings as $one_setting) {
266
+ sf_d( $one_setting->id );
267
+ }
268
+ }
269
+ }
270
+
271
+
272
+ }
273
+ return;
274
+ */
275
+ foreach ($sections as $section ) {
276
+ #echo "Section: " . $section->id . " (".$section->title.")";
277
+ // Id is unique slug
278
+ // Can't use title because that's translated
279
+ if ($debug) {
280
+
281
+ echo "\n-------\n";
282
+ echo "Section: " . $section->id . " (".$section->title.")";
283
+
284
+ }
285
+
286
+ $section_controls = $section->controls;
287
+ foreach ($section_controls as $section_control) {
288
+
289
+ /*
290
+ if ( ! $section_control->check_capabilities() ) {
291
+ echo "\n\n\nno access to control";
292
+ } else {
293
+ echo "\n have access to control";
294
+ }
295
+ */
296
+
297
+ // Settings is always array, but mostly with just one setting in it it seems like
298
+ $section_control_settings = $section_control->settings;
299
+
300
+ if ($debug) {
301
+ echo "\n\nControl ";
302
+ echo $section_control->id . " (". $section_control->label . ")";
303
+ //echo "\ncontrol setting: " . $section_control->setting;
304
+ // echo "\nSettings:";
305
+ }
306
+
307
+ /*
308
+ Control ttfmake_stylekit-info ()
309
+ har underlig setting
310
+ setting = blogname = uppdateras som en tok!
311
+ */
312
+ /*if ( $section_control->id == "ttfmake_stylekit-info" ) {
313
+ print_r( sizeof($section_control_settings) );
314
+ echo "\nid: " . $section_control_settings[0]->id;
315
+ echo "\ntitle: " . $section_control_settings[0]->title;
316
+ exit;
317
+ }*/
318
+
319
+ foreach ( $section_control_settings as $one_setting ) {
320
+
321
+ if ($debug) {
322
+ // setting id is supposed to be unique, but some themes
323
+ // seems to register "blogname" for example
324
+ // which messes things up...
325
+ // solution:
326
+ // order sections so built-in sections always are added first
327
+ // and then only store a seting id once (the first time = for the built in setting)
328
+ // hm.. nope, does not work. in this case theme "make" is using blogname in their own control
329
+ echo "\nsetting id " . $one_setting->id;
330
+ }
331
+
332
+ $old_value = $one_setting->value();
333
+ $new_value = $one_setting->post_value();
334
+
335
+ // If old and new value is different then we log
336
+ if ($old_value != $new_value && ! in_array($one_setting_id, $arr_changed_settings_ids) ) {
337
+ #if ( $old_value != $new_value ) {
338
+
339
+ if ($debug) {
340
+ echo "\nSetting with id ";
341
+ echo $one_setting->id;
342
+ echo " changed";
343
+ echo "\nold value $old_value";
344
+ echo "\nnew value $new_value";
345
+ }
346
+
347
+ $arr_changed_settings[] = array(
348
+ "setting_id" => $one_setting->id,
349
+ "setting_old_value" => $old_value,
350
+ "setting_new_value" => $new_value,
351
+ "control_id" => $section_control->id,
352
+ "control_label" => $section_control->label,
353
+ "control_type" => $section_control->type,
354
+ "section_id" => $section->id,
355
+ "section_title" => $section->title,
356
+ );
357
+
358
+ $arr_changed_settings_ids[] = $one_setting->id;
359
+
360
+ } // if settings changed
361
+
362
+ } // foreach setting
363
+
364
+ } // foreach control
365
+
366
+ } // foreach section
367
+
368
+ /*
369
+ arr_changed_settingsArray
370
+ (
371
+ [0] => Array
372
+ (
373
+ [setting_id] => background_color
374
+ [setting_old_value] => 31d68e
375
+ [setting_new_value] => 2f37ce
376
+ [control_id] => background_color
377
+ [control_label] => Background Color
378
+ [control_type] => color
379
+ [section_id] => background_image
380
+ [section_title] => Background
381
+ )
382
+
383
+ [1] => Array
384
+ (
385
+ [setting_id] => font-header
386
+ [setting_old_value] => sans-serif
387
+ [setting_new_value] => monospace
388
+ [control_id] => ttfmake_font-header
389
+ [control_label] => Headers
390
+ [control_type] => select
391
+ [section_id] => ttfmake_font
392
+ [section_title] => Fonts
393
+ )
394
+
395
+ )
396
+ */
397
+ if ($arr_changed_settings) {
398
+
399
+ if ($debug) {
400
+ echo "\narr_changed_settings";
401
+ print_r( $arr_changed_settings );
402
+ }
403
+
404
+ // Store each changed settings as one log entry
405
+ // We could store all in just one, but then we would get
406
+ // problems when outputing formatted output (too many things to show at once)
407
+ // or when gathering stats
408
+ foreach ( $arr_changed_settings as $one_changed_setting ) {
409
+
410
+ $this->infoMessage(
411
+ "appearance_customized",
412
+ $one_changed_setting
413
+ );
414
+
415
+ }
416
+
417
+ }
418
+
419
+ }
420
+
421
+ /**
422
+ * When a new theme is about to get switched to
423
+ * we save info about the old one
424
+ */
425
+ function on_page_load_themes() {
426
+
427
+ //sf_d($_REQUEST, "request");exit;
428
+ /*
429
+ request:
430
+ Array
431
+ (
432
+ [action] => activate
433
+ [stylesheet] => wp-theme-bonny-starter
434
+ [_wpnonce] => 31b033ba59
435
+ )
436
+ */
437
+
438
+ if ( ! isset($_GET["action"]) || $_GET["action"] != "activate") {
439
+ return;
440
+ }
441
+
442
+ // Get current theme / the theme we are switching from
443
+ $current_theme = wp_get_theme();
444
+ /*
445
+ $current_theme:
446
+ WP_Theme Object
447
+ (
448
+ [theme_root:WP_Theme:private] => /Users/bonny/Documents/Sites/playground-root/assets/themes
449
+ [headers:WP_Theme:private] => Array
450
+ (
451
+ [Name] => Twenty Eleven
452
+ [ThemeURI] => http://wordpress.org/themes/twentyeleven
453
+ [Description] => The 2011 theme for WordPress is sophisticated, lightweight, and adaptable. Make it yours with a custom menu, header image, and background -- then go further with available theme options for light or dark color scheme, custom link colors, and three layout choices. Twenty Eleven comes equipped with a Showcase page template that transforms your front page into a showcase to show off your best content, widget support galore (sidebar, three footer areas, and a Showcase page widget area), and a custom "Ephemera" widget to display your Aside, Link, Quote, or Status posts. Included are styles for print and for the admin editor, support for featured images (as custom header images on posts and pages and as large images on featured "sticky" posts), and special styles for six different post formats.
454
+ [Author] => the WordPress team
455
+ [AuthorURI] => http://wordpress.org/
456
+ [Version] => 1.8
457
+ [Template] =>
458
+ [Status] =>
459
+ [Tags] => dark, light, white, black, gray, one-column, two-columns, left-sidebar, right-sidebar, fixed-layout, responsive-layout, custom-background, custom-colors, custom-header, custom-menu, editor-style, featured-image-header, featured-images, flexible-header, full-width-template, microformats, post-formats, rtl-language-support, sticky-post, theme-options, translation-ready
460
+ [TextDomain] => twentyeleven
461
+ [DomainPath] =>
462
+ )
463
+
464
+ [headers_sanitized:WP_Theme:private] =>
465
+ [name_translated:WP_Theme:private] =>
466
+ [errors:WP_Theme:private] =>
467
+ [stylesheet:WP_Theme:private] => twentyeleven
468
+ [template:WP_Theme:private] => twentyeleven
469
+ [parent:WP_Theme:private] =>
470
+ [theme_root_uri:WP_Theme:private] =>
471
+ [textdomain_loaded:WP_Theme:private] =>
472
+ [cache_hash:WP_Theme:private] => 797bf456e43982f41d5477883a6815da
473
+ )
474
+
475
+ */
476
+ if ( ! is_a($current_theme, "WP_Theme") ) {
477
+ return;
478
+ }
479
+ #sf_d($current_theme);
480
+
481
+ $this->prev_theme_data = array(
482
+ "name" => $current_theme->name,
483
+ "version" => $current_theme->version
484
+ );
485
+
486
+ }
487
+
488
+ function on_switch_theme($new_name, $new_theme) {
489
+
490
+ $prev_theme_data = $this->prev_theme_data;
491
+
492
+ $this->infoMessage(
493
+ "theme_switched",
494
+ array(
495
+ "theme_name" => $new_name,
496
+ "theme_version" => $new_theme->version,
497
+ "prev_theme_name" => $prev_theme_data["name"],
498
+ "prev_theme_version" => $prev_theme_data["version"]
499
+ )
500
+ );
501
+
502
+ }
503
+
504
+ function getLogRowDetailsOutput($row) {
505
+
506
+ $context = $row->context;
507
+ $message_key = $context["_message_key"];
508
+ $output = "";
509
+
510
+ // Theme customizer
511
+ if ( "appearance_customized" == $message_key ) {
512
+
513
+ #if ( ! class_exists("WP_Customize_Manager") ) {
514
+ # require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
515
+ # $wp_customize = new WP_Customize_Manager;
516
+ #}
517
+
518
+ //$output .= "<pre>" . print_r($context, true);
519
+ if ( isset( $context["setting_old_value"] ) && isset( $context["setting_new_value"] ) ) {
520
+
521
+ $output .= "<table class='SimpleHistoryLogitem__keyValueTable'>";
522
+
523
+ // Output section, if saved
524
+ if ( ! empty( $context["section_id"] ) ) {
525
+ $output .= sprintf(
526
+ '
527
+ <tr>
528
+ <td>%1$s</td>
529
+ <td>%2$s</td>
530
+ </tr>
531
+ ',
532
+ __("Section", "simple-history"),
533
+ esc_html( $context["section_id"] )
534
+ );
535
+ }
536
+
537
+ // Don't output prev and new value if none exist
538
+ if ( empty( $context["setting_old_value"] ) && empty( $context["setting_new_value"] ) ) {
539
+
540
+ // empty, so skip
541
+
542
+ } else {
543
+
544
+ // if control is color let's be fancy and output as color
545
+ $control_type = isset( $context["control_type"] ) ? $context["control_type"] : "";
546
+ $str_old_value_prepend = "";
547
+ $str_new_value_prepend = "";
548
+
549
+ if ("color" == $control_type) {
550
+
551
+ $str_old_value_prepend .= sprintf(
552
+ '<span style="background-color: #%1$s; width: 1em; display: inline-block;">&nbsp;</span> ',
553
+ esc_attr( ltrim( $context["setting_old_value"], " #" ) )
554
+ );
555
+
556
+ $str_new_value_prepend .= sprintf(
557
+ '<span style="background-color: #%1$s; width: 1em; display: inline-block;">&nbsp;</span> ',
558
+ esc_attr( ltrim( $context["setting_new_value"], "#" ) )
559
+ );
560
+
561
+ }
562
+
563
+ $output .= sprintf(
564
+ '
565
+ <tr>
566
+ <td>%1$s</td>
567
+ <td>%3$s%2$s</td>
568
+ </tr>
569
+ ',
570
+ __("New value", "simple-history"),
571
+ esc_html( $context["setting_new_value"] ),
572
+ $str_new_value_prepend
573
+ );
574
+
575
+ $output .= sprintf(
576
+ '
577
+ <tr>
578
+ <td>%1$s</td>
579
+ <td>%3$s%2$s</td>
580
+ </tr>
581
+ ',
582
+ __("Old value", "simple-history"),
583
+ esc_html( $context["setting_old_value"] ),
584
+ $str_old_value_prepend
585
+ );
586
+
587
+
588
+ }
589
+
590
+ $output .= "</table>";
591
+
592
+ }
593
+
594
+
595
+ }
596
+
597
+ return $output;
598
+
599
+ }
600
+
601
+ /**
602
+ * Add widget name and sidebar name to output
603
+ */
604
+ function getLogRowPlainTextOutput($row) {
605
+
606
+ $context = $row->context;
607
+ $message_key = $context["_message_key"];
608
+ $output = "";
609
+
610
+ // Widget changed or added or removed
611
+ // Simple replace widget_id_base and sidebar_id with widget name and sidebar name
612
+ if ( in_array($message_key, array("widget_added", "widget_edited", "widget_removed") ) ) {
613
+
614
+ $widget = $this->getWidgetByIdBase( $context["widget_id_base"] );
615
+ $sidebar = $this->getSidebarById( $context["sidebar_id"] );
616
+
617
+ if ( $widget && $sidebar ) {
618
+
619
+ $message = $this->interpolate( $row->message, array(
620
+ "widget_id_base" => $widget->name,
621
+ "sidebar_id" => $sidebar["name"],
622
+ ) );
623
+
624
+ $output .= $message;
625
+
626
+ }
627
+
628
+
629
+ }
630
+
631
+ // Fallback to default/parent output
632
+ if ( ! $output ) {
633
+
634
+ $output .= parent::getLogRowPlainTextOutput($row);
635
+
636
+ }
637
+
638
+ return $output;
639
+
640
+ }
641
+
642
+ /*
643
+ function on_action_sidebar_admin_setup__detect_widget_edit() {
644
+
645
+ if ( isset( $_REQUEST["action"] ) && ( $_REQUEST["action"] == "save-widget" ) && isset( $_POST["sidebar"] ) && isset( $_POST["id_base"] ) ) {
646
+
647
+ $widget_id_base = $_POST["id_base"];
648
+
649
+ // a key with widget-{$widget_id_base} exists if we are saving
650
+ if ( ! isset( $_POST["widget-{$widget_id_base}"] ) ) {
651
+ return;
652
+ }
653
+
654
+ $context = array();
655
+
656
+ $widget_save_data = $_POST["widget-{$widget_id_base}"];
657
+ $context["widget_save_data"] = $this->simpleHistory->json_encode( $widget_save_data );
658
+
659
+ // Add widget info
660
+ $context["widget_id_base"] = $widget_id_base;
661
+ $widget = $this->getWidgetByIdBase( $widget_id_base );
662
+ if ($widget) {
663
+ $context["widget_name_translated"] = $widget->name;
664
+ }
665
+
666
+ // Add sidebar info
667
+ $sidebar_id = $_POST["sidebar"];
668
+ $context["sidebar_id"] = $sidebar_id;
669
+ $sidebar = $this->getSidebarById( $sidebar_id );
670
+ if ($sidebar) {
671
+ $context["sidebar_name_translated"] = $sidebar["name"];
672
+ }
673
+
674
+ $this->infoMessage(
675
+ "widget_edited",
676
+ $context
677
+ );
678
+
679
+ }
680
+
681
+ }
682
+ */
683
+
684
+ /**
685
+ * A widget is changed, i.e. new values are saved
686
+ * @TODO: first time a widget is added it seems to call this and we get double edit logs that are confusing
687
+ */
688
+ function on_widget_update_callback($instance, $new_instance, $old_instance, $widget_instance) {
689
+
690
+ #sf_d($instance);
691
+ /*
692
+ Array
693
+ (
694
+ [title] => Custom menu I am abc
695
+ [nav_menu] => 0
696
+ )
697
+
698
+ */
699
+
700
+ #sf_d($new_instance);
701
+ /*
702
+ Custom Menu
703
+ Array
704
+ (
705
+ [title] => Custom menu I am abc
706
+ [nav_menu] => 0
707
+ )
708
+ */
709
+
710
+ #sf_d($old_instance);
711
+ /*
712
+ Array
713
+ (
714
+ [title] => Custom menu I am
715
+ [nav_menu] => 0
716
+ )
717
+ */
718
+
719
+ #sf_d($widget_instance);
720
+ /*
721
+ WP_Nav_Menu_Widget Object
722
+ (
723
+ [id_base] => nav_menu
724
+ [name] => Custom Menu
725
+ [widget_options] => Array
726
+ (
727
+ [classname] => widget_nav_menu
728
+ [description] => Add a custom menu to your sidebar.
729
+ )
730
+
731
+ [control_options] => Array
732
+ (
733
+ [id_base] => nav_menu
734
+ )
735
+
736
+ [number] => 2
737
+ [id] => nav_menu-2
738
+ [updated] =>
739
+ [option_name] => widget_nav_menu
740
+ )
741
+ */
742
+
743
+ // If old_instance is empty then this widget has just been added
744
+ // and we log that as "Added" not "Edited"
745
+ if ( empty( $old_instance ) ) {
746
+ return $instance;
747
+ }
748
+
749
+ $widget_id_base = $widget_instance->id_base;
750
+
751
+ $context = array();
752
+
753
+ // Add widget info
754
+ $context["widget_id_base"] = $widget_id_base;
755
+ $widget = $this->getWidgetByIdBase( $widget_id_base );
756
+ if ($widget) {
757
+ $context["widget_name_translated"] = $widget->name;
758
+ }
759
+
760
+ // Add sidebar info
761
+ $sidebar_id = $_POST["sidebar"];
762
+ $context["sidebar_id"] = $sidebar_id;
763
+ $sidebar = $this->getSidebarById( $sidebar_id );
764
+ if ($sidebar) {
765
+ $context["sidebar_name_translated"] = $sidebar["name"];
766
+ }
767
+
768
+ // Calculate changes
769
+ $context["old_instance"] = $this->simpleHistory->json_encode( $old_instance );
770
+ $context["new_instance"] = $this->simpleHistory->json_encode( $new_instance );
771
+
772
+ $this->infoMessage(
773
+ "widget_edited",
774
+ $context
775
+ );
776
+
777
+
778
+ return $instance;
779
+
780
+ }
781
+
782
+ /**
783
+ * Change Widgets order
784
+ * action=widgets-order is also called after deleting a widget
785
+ * to many log entries with changed, just confusing.
786
+ * need to rethink this
787
+ */
788
+ function on_action_sidebar_admin_setup__detect_widget_order_change() {
789
+
790
+ /*
791
+ if ( isset( $_REQUEST["action"] ) && ( $_REQUEST["action"] == "widgets-order" ) ) {
792
+
793
+ $context = array();
794
+
795
+ // Get old order
796
+ $sidebars = isset( $GLOBALS['wp_registered_sidebars'] ) ? $GLOBALS['wp_registered_sidebars'] : false;
797
+ if ($sidebars) {
798
+ $context["sidebars_from"] = $this->simpleHistory->json_encode( $sidebars );
799
+ }
800
+
801
+ $new_sidebars = $_POST["sidebars"];
802
+ $context["sidebars_to"] = $this->simpleHistory->json_encode( $new_sidebars );
803
+
804
+ $widget_factory = isset( $GLOBALS["wp_widget_factory"] ) ? $GLOBALS["wp_widget_factory"] : false;
805
+ $context["widgets_from"] = $this->simpleHistory->json_encode( $widget_factory->widgets );
806
+
807
+ //$wp_registered_widgets, $wp_registered_sidebars, $sidebars_widgets;
808
+ $sidebars_widgets = isset( $GLOBALS["sidebars_widgets"] ) ? $GLOBALS["sidebars_widgets"] : false;
809
+ $context["sidebars_widgets"] = $this->simpleHistory->json_encode( $sidebars_widgets );
810
+
811
+ $this->infoMessage(
812
+ "widget_order_changed",
813
+ $context
814
+ );
815
+
816
+ }
817
+ */
818
+
819
+ }
820
+
821
+ /**
822
+ * Widget added
823
+ */
824
+ function on_action_sidebar_admin_setup__detect_widget_add() {
825
+
826
+ if ( isset( $_POST["add_new"] ) && ! empty( $_POST["add_new"] ) && isset( $_POST["sidebar"] ) && isset( $_POST["id_base"] ) ) {
827
+
828
+ // Add widget info
829
+ $widget_id_base = $_POST["id_base"];
830
+ $context["widget_id_base"] = $widget_id_base;
831
+ $widget = $this->getWidgetByIdBase( $widget_id_base );
832
+ if ($widget) {
833
+ $context["widget_name_translated"] = $widget->name;
834
+ }
835
+
836
+ // Add sidebar info
837
+ $sidebar_id = $_POST["sidebar"];
838
+ $context["sidebar_id"] = $sidebar_id;
839
+ $sidebar = $this->getSidebarById( $sidebar_id );
840
+ if ($sidebar) {
841
+ $context["sidebar_name_translated"] = $sidebar["name"];
842
+ }
843
+
844
+ $this->infoMessage(
845
+ "widget_added",
846
+ $context
847
+ );
848
+
849
+ }
850
+
851
+ }
852
+ /*
853
+ * widget deleted
854
+ */
855
+ function on_action_sidebar_admin_setup__detect_widget_delete() {
856
+
857
+ // Widget was deleted
858
+ if ( isset( $_POST["delete_widget"] ) ) {
859
+
860
+ $context = array();
861
+
862
+ // Add widget info
863
+ $widget_id_base = $_POST["id_base"];
864
+ $context["widget_id_base"] = $widget_id_base;
865
+ $widget = $this->getWidgetByIdBase( $widget_id_base );
866
+ if ($widget) {
867
+ $context["widget_name_translated"] = $widget->name;
868
+ }
869
+
870
+ // Add sidebar info
871
+ $sidebar_id = $_POST["sidebar"];
872
+ $context["sidebar_id"] = $sidebar_id;
873
+ $sidebar = $this->getSidebarById( $sidebar_id );
874
+ if ($sidebar) {
875
+ $context["sidebar_name_translated"] = $sidebar["name"];
876
+ }
877
+
878
+ $this->infoMessage(
879
+ "widget_removed",
880
+ $context
881
+ );
882
+
883
+ }
884
+
885
+ }
886
+
887
+ /**
888
+ * Get a sidebar by id
889
+ *
890
+ * @param string $sidebar_id
891
+ * @return sidebar info or false on failure
892
+ */
893
+ function getSidebarById($sidebar_id) {
894
+
895
+ $sidebars = isset( $GLOBALS['wp_registered_sidebars'] ) ? $GLOBALS['wp_registered_sidebars'] : false;
896
+
897
+ if ( ! $sidebars ) {
898
+ return false;
899
+ }
900
+
901
+ // Add sidebar info
902
+ if ( isset( $sidebars[ $sidebar_id ] ) ) {
903
+
904
+ return $sidebars[ $sidebar_id ];
905
+
906
+ }
907
+
908
+ return false;
909
+
910
+ }
911
+
912
+ /**
913
+ * Get an widget by id's id_base
914
+ *
915
+ * @param string $id_base
916
+ * @return wp_widget object or false on failure
917
+ */
918
+ function getWidgetByIdBase($widget_id_base) {
919
+
920
+ $widget_factory = isset( $GLOBALS["wp_widget_factory"] ) ? $GLOBALS["wp_widget_factory"] : false;
921
+
922
+ if ( ! $widget_factory ) {
923
+ return false;
924
+ }
925
+
926
+ foreach ($widget_factory->widgets as $one_widget) {
927
+
928
+ if ( $one_widget->id_base == $widget_id_base ) {
929
+
930
+ return $one_widget;
931
+
932
+ }
933
+
934
+ }
935
+
936
+ return false;
937
+
938
+ }
939
+
940
+ }
loggers/SimpleUserLogger.php ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Logs changes to user logins (and logouts)
5
+ */
6
+ class SimpleUserLogger extends SimpleLogger
7
+ {
8
+
9
+ public $slug = __CLASS__;
10
+
11
+ /**
12
+ * Get array with information about this logger
13
+ *
14
+ * @return array
15
+ */
16
+ function getInfo() {
17
+
18
+ $arr_info = array(
19
+ "name" => "User Logger",
20
+ "description" => "Logs user logins, logouts, and failed logins",
21
+ "capability" => "edit_users",
22
+ "messages" => array(
23
+ 'user_login_failed' => __('Failed to login to account with username "{login_user_login}" because an incorrect password was entered', "simple-history"),
24
+ 'user_unknown_login_failed' => __('Failed to login with username "{failed_login_username}" because no user with that username exist', "simple-history"),
25
+ 'user_logged_in' => __('Logged in', "simple-history"),
26
+ 'user_unknown_logged_in' => __("Unknown user logged in", "simple-history"),
27
+ 'user_logged_out' => __("Logged out", "simple-history"),
28
+ 'user_updated_profile' => __("Edited the profile for user {edited_user_login} ({edited_user_email})", "simple-history"),
29
+ 'user_created' => __("Created user {created_user_login} ({created_user_email}) with role {created_user_role}", "simple-history"),
30
+ 'user_deleted' => __("Deleted user {deleted_user_login} ({deleted_user_email})", "simple-history"),
31
+ ),
32
+
33
+ "labels" => array(
34
+ "search" => array(
35
+ "label" => _x("Users", "User logger: search", "simple-history"),
36
+ "label_all" => _x("All user activity", "User logger: search", "simple-history"),
37
+ "options" => array(
38
+ _x("Successful user logins", "User logger: search", "simple-history") => array(
39
+ "user_logged_in",
40
+ "user_unknown_logged_in"
41
+ ),
42
+ _x("Failed user logins", "User logger: search", "simple-history") => array(
43
+ 'user_login_failed',
44
+ 'user_unknown_login_failed'
45
+ ),
46
+ _x('User logouts', 'User logger: search', 'simple-history') => array(
47
+ "user_logged_out"
48
+ ),
49
+ _x('Created users', 'User logger: search', 'simple-history') => array(
50
+ "user_created"
51
+ ),
52
+ _x("User profile updates", "User logger: search", "simple-history") => array(
53
+ "user_updated_profile"
54
+ ),
55
+ _x('User deletions', 'User logger: search', 'simple-history') => array(
56
+ "user_deleted"
57
+ )
58
+
59
+ )
60
+ ) // end search
61
+
62
+ ) // end labels
63
+
64
+ );
65
+ #sf_d($arr_info);exit;
66
+ return $arr_info;
67
+
68
+ }
69
+
70
+ /**
71
+ * Add actions and filters when logger is loaded by Simple History
72
+ */
73
+ public function loaded() {
74
+
75
+ // Plain logins and logouts
76
+ add_action("wp_login", array($this, "on_wp_login" ), 10, 3 );
77
+ add_action("wp_logout", array($this, "on_wp_logout" ) );
78
+
79
+ // Failed login attempt to username that exists
80
+ add_action("wp_authenticate_user", array($this, "on_wp_authenticate_user"), 10, 2);
81
+
82
+ // Failed to login to user that did not exist (perhaps brute force)
83
+ add_filter( 'authenticate', array($this, "on_authenticate"), 10, 3);
84
+
85
+ // User is changed
86
+ add_action("profile_update", array($this, "on_profile_update"), 10, 2);
87
+
88
+ // User is created
89
+ add_action("user_register", array($this, "on_user_register"), 10, 2);
90
+
91
+ // user is deleted
92
+ add_action( 'delete_user', array($this, "on_delete_user"), 10, 2 );
93
+
94
+ }
95
+
96
+ /**
97
+ * Fires before a user is deleted from the database.
98
+ *
99
+ * @param int $user_id ID of the deleted user.
100
+ * @param int|null $reassign ID of the user to reassign posts and links to.
101
+ * Default null, for no reassignment.
102
+ */
103
+ public function on_delete_user($user_id, $reassign) {
104
+
105
+ $wp_user_to_delete = get_userdata( $user_id );
106
+
107
+ // wp_user->roles (array) - the roles the user is part of.
108
+ $role = null;
109
+ if ( is_array( $wp_user_to_delete->roles ) && ! empty( $wp_user_to_delete->roles[0] ) ) {
110
+ $role = $wp_user_to_delete->roles[0];
111
+ }
112
+
113
+ $context = array(
114
+ "deleted_user_id" => $wp_user_to_delete->ID,
115
+ "deleted_user_email" => $wp_user_to_delete->user_email,
116
+ "deleted_user_login" => $wp_user_to_delete->user_login,
117
+ "deleted_user_role" => $role,
118
+ "reassign_user_id" => $reassign,
119
+ "server_http_user_agent" => $_SERVER["HTTP_USER_AGENT"],
120
+ );
121
+
122
+ // Let's log this as a little bit more significant that just "message"
123
+ $this->noticeMessage("user_deleted", $context);
124
+
125
+ }
126
+
127
+ /**
128
+ * Modify row output
129
+ */
130
+ public function getLogRowPlainTextOutput($row) {
131
+
132
+ $context = $row->context;
133
+
134
+ $output = parent::getLogRowPlainTextOutput($row);
135
+ $current_user_id = get_current_user_id();
136
+
137
+ if ( "user_updated_profile" == $context["_message_key"]) {
138
+
139
+ $wp_user = get_user_by( "id", $context["edited_user_id"] );
140
+
141
+ // If edited_user_id and _user_id is the same then a user edited their own profile
142
+ // Note: it's not the same thing as the currently logged in user (but.. it can be!)
143
+ if ( $context["edited_user_id"] === $context["_user_id"] ) {
144
+
145
+ if ($wp_user) {
146
+
147
+ $context["edit_profile_link"] = get_edit_user_link( $wp_user->ID );
148
+
149
+ // User still exist, so link to their profile
150
+ if ( $current_user_id === $context["_user_id"] ) {
151
+
152
+ // User that is viewing the log is the same as the edited user
153
+ $msg = __('Edited <a href="{edit_profile_link}">your profile</a>', "simple-history");
154
+
155
+ } else {
156
+
157
+ $msg = __('Edited <a href="{edit_profile_link}">their profile</a>', "simple-history");
158
+
159
+ }
160
+
161
+ $output = $this->interpolate($msg, $context);
162
+
163
+ } else {
164
+
165
+ // User does not exist any longer
166
+ $output = __("Edited your profile", "simple-history");
167
+
168
+ }
169
+
170
+ } else {
171
+
172
+ // User edited another users profile
173
+ if ($wp_user) {
174
+
175
+ // Edited user still exist, so link to their profile
176
+ $context["edit_profile_link"] = get_edit_user_link($wp_user->ID);
177
+ $msg = __('Edited the profile for user <a href="{edit_profile_link}">{edited_user_login} ({edited_user_email})</a>', "simple-history");
178
+ $output = $this->interpolate( $msg, $context );
179
+
180
+ } else {
181
+
182
+ // Edited user does not exist any longer
183
+
184
+ }
185
+
186
+ }
187
+
188
+ } // if user_updated_profile
189
+
190
+ return $output;
191
+ }
192
+
193
+
194
+ /**
195
+ * Log failed login attempt to username that exists
196
+ *
197
+ * @param object $user user object that was tried to gain access to
198
+ * @param string password used
199
+ */
200
+ function on_wp_authenticate_user($user, $password) {
201
+
202
+ // Only log failed attempts
203
+ if ( ! wp_check_password($password, $user->user_pass, $user->ID) ) {
204
+
205
+ // Overwrite some vars that Simple History set automagically
206
+ $context = array(
207
+ "_initiator" => SimpleLoggerLogInitiators::WEB_USER,
208
+ "_user_id" => null,
209
+ "_user_login" => null,
210
+ "_user_email" => null,
211
+ "login_user_id" => $user->ID,
212
+ "login_user_email" => $user->user_email,
213
+ "login_user_login" => $user->user_login,
214
+ "server_http_user_agent" => $_SERVER["HTTP_USER_AGENT"],
215
+ "_occasionsID" => __CLASS__ . '/' . __FUNCTION__ . "/failed_user_login/userid:{$user->ID}"
216
+ );
217
+
218
+ /**
219
+ * Maybe store password too
220
+ * Default is to not do this because of privacy and security
221
+ *
222
+ * @since 2.0
223
+ *
224
+ * @param bool $log_password
225
+ */
226
+ $log_password = false;
227
+ $log_password = apply_filters("simple_history/comments_logger/log_failed_password", $log_password);
228
+ if ($log_password) {
229
+ $context["login_user_password"] = $password;
230
+ }
231
+
232
+ $this->warningMessage("user_login_failed", $context);
233
+
234
+ }
235
+
236
+ return $user;
237
+
238
+ }
239
+
240
+ /**
241
+ * User logs in
242
+ *
243
+ * @param string $user_login
244
+ * @param object $user
245
+ */
246
+ function on_wp_login($user_login, $user) {
247
+
248
+ $context = array();
249
+
250
+ if ( $user->ID ) {
251
+
252
+ $context = array(
253
+ "user_id" => $user->ID,
254
+ "user_email" => $user->user_email,
255
+ "user_login" => $user->user_login
256
+ );
257
+
258
+ // Override some data that is usually set automagically by Simple History
259
+ // Because wp_get_current_user() does not return any data yet at this point
260
+ $context["_initiator"] = SimpleLoggerLogInitiators::WP_USER;
261
+ $context["_user_id"] = $user->ID;
262
+ $context["_user_login"] = $user->user_login;
263
+ $context["_user_email"] = $user->user_email;
264
+ $context["server_http_user_agent"] = $_SERVER["HTTP_USER_AGENT"];
265
+
266
+ $this->infoMessage("user_logged_in", $context);
267
+
268
+ } else {
269
+
270
+ // when does this happen?
271
+ $this->warningMessage("user_unknown_logged_in", $context );
272
+
273
+
274
+ }
275
+
276
+ }
277
+
278
+ /**
279
+ * User logs out
280
+ * http://codex.wordpress.org/Plugin_API/Action_Reference/wp_logout
281
+ */
282
+ function on_wp_logout() {
283
+
284
+ $this->infoMessage("user_logged_out");
285
+
286
+ }
287
+
288
+ /**
289
+ * User is edited
290
+ */
291
+ function on_profile_update($user_id) {
292
+
293
+ if ( ! $user_id || ! is_numeric($user_id))
294
+ return;
295
+
296
+ $wp_user_edited = get_userdata( $user_id );
297
+
298
+ $context = array(
299
+ "edited_user_id" => $wp_user_edited->ID,
300
+ "edited_user_email" => $wp_user_edited->user_email,
301
+ "edited_user_login" => $wp_user_edited->user_login,
302
+ "server_http_user_agent" => $_SERVER["HTTP_USER_AGENT"],
303
+ );
304
+
305
+ $this->infoMessage("user_updated_profile", $context);
306
+
307
+ }
308
+
309
+ /**
310
+ * User is created
311
+ */
312
+ function on_user_register($user_id) {
313
+
314
+ if ( ! $user_id || ! is_numeric($user_id))
315
+ return;
316
+
317
+ $wp_user_added = get_userdata( $user_id );
318
+
319
+ // wp_user->roles (array) - the roles the user is part of.
320
+ $role = null;
321
+ if ( is_array( $wp_user_added->roles ) && ! empty( $wp_user_added->roles[0] ) ) {
322
+ $role = $wp_user_added->roles[0];
323
+ }
324
+
325
+ $context = array(
326
+ "created_user_id" => $wp_user_added->ID,
327
+ "created_user_email" => $wp_user_added->user_email,
328
+ "created_user_login" => $wp_user_added->user_login,
329
+ "created_user_role" => $role,
330
+ "server_http_user_agent" => $_SERVER["HTTP_USER_AGENT"],
331
+ );
332
+
333
+ $this->infoMessage("user_created", $context);
334
+
335
+ }
336
+
337
+ /**
338
+ * Attempt to login to user that does not exist
339
+ */
340
+ function on_authenticate($user, $username, $password) {
341
+
342
+ // Don't log empty usernames
343
+ if ( ! trim($username)) {
344
+ return $user;
345
+ }
346
+
347
+ // If username is not a user in the system then this
348
+ // is consideraded a failed login attempt
349
+ $wp_user = get_user_by( "login", $username );
350
+
351
+ if (false === $wp_user) {
352
+
353
+ $context = array(
354
+ "_initiator" => SimpleLoggerLogInitiators::WEB_USER,
355
+ "failed_login_username" => $username,
356
+ "server_http_user_agent" => $_SERVER["HTTP_USER_AGENT"],
357
+ // count all failed logins to unknown users as the same occasions,
358
+ // to prevent log being flooded with login/hack attempts
359
+ "_occasionsID" => __CLASS__ . '/' . __FUNCTION__
360
+ );
361
+
362
+ /**
363
+ * Maybe store password too
364
+ * Default is to not do this because of privacy and security
365
+ *
366
+ * @since 2.0
367
+ *
368
+ * @param bool $log_password
369
+ */
370
+ $log_password = false;
371
+ $log_password = apply_filters("simple_history/comments_logger/log_not_existing_user_password", $log_password);
372
+ if ($log_password) {
373
+ $context["failed_login_password"] = $password;
374
+ }
375
+
376
+ $this->warningMessage("user_unknown_login_failed", $context);
377
+
378
+ }
379
+
380
+ return $user;
381
+
382
+
383
+ }
384
+
385
+ }
package.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "SimpleHistory",
3
+ "version": "0.0.1",
4
+ "description": "WordPress log plugin",
5
+ "author": "Pär Thernström",
6
+ "license": "GPL",
7
+ "devDependencies": {
8
+ "grunt": "~0.4.5",
9
+ "grunt-pot": "^0.2.0",
10
+ "grunt-wp-i18n": "^0.4.9",
11
+ "load-grunt-tasks": "~1.0.0",
12
+ "time-grunt": "^1.0.0"
13
+ }
14
+ }
readme.txt CHANGED
@@ -1,10 +1,10 @@
1
  === Simple History ===
2
- Contributors: eskapism, MarsApril, Offereins
3
  Donate link: http://eskapism.se/sida/donate/
4
- Tags: history, log, changes, changelog, audit, trail, stream, pages, attachments, users, cms, dashboard, admin, syslog, activity
5
- Requires at least: 3.8.0
6
- Tested up to: 4.0
7
- Stable tag: 1.3.11
8
 
9
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
10
 
@@ -32,23 +32,16 @@ info about added, updated or removed users
32
  see when a user login & logut
33
  * **Failed user logins**<br>
34
  see when someone has tried to log in, but failed. The log will then include ip address of the possible hacker.
35
- * **bbPress**<br>
36
- view changes to forums and topics and view user changes
37
- * **Gravity Forms**<br>
38
- see who created, edited or deleted a form, field, or entry
39
 
40
- With it's easy to use Extender system, developers can add their own settings and items to track.
41
 
42
- There is also a **RSS feed of changes** available, so you can keep track of the changes made
43
- via your favorite RSS reader on your phone, on your iPad, or on your computer.
44
-
45
- It’s a plugin that is good to have on websites where several people are
46
  involved in editing the content.
47
 
48
  #### Example scenarios
49
 
50
  Keep track of what other people are doing:
51
- _"Has someone done anything today? Ah, Sarah uploaded
52
  the new press release and created an article for it. Great! Now I don't have to do that."_
53
 
54
  Or for debug purposes:
@@ -60,139 +53,73 @@ that must be it."_
60
  See the plugin in action with this short screencast:
61
  [youtube http://www.youtube.com/watch?v=4cu4kooJBzs]
62
 
63
- #### Add your own events to simple history
64
-
65
- If you are a plugin developer and would like to add your own things/events to Simple History
66
- you can do that by calling the function simple_history_add like this:
67
- `<?php
68
-
69
- # Check that function exists before trying to use it
70
- # Just in case someone disabled the history plugin or similar
71
- if (function_exists("simple_history_add")) {
72
-
73
- # Log that an email has been sent
74
- simple_history_add(array(
75
- "object_type" => "Email",
76
- "object_name" => "Hi there",
77
- "action" => "was sent"
78
- ));
79
-
80
- # Will show “Plugin your_plugin_name Edited” in the history log
81
- simple_history_add("action=edited&object_type=plugin&object_name=your_plugin_name");
82
-
83
- # Will show the history item "Starship USS Enterprise repaired"
84
- simple_history_add("action=repaired&object_type=Starship&object_name=USS Enterprise");
85
-
86
- # Log with some extra details about the email
87
- simple_history_add(array(
88
- "object_type" => "Email",
89
- "object_name" => "Hi there",
90
- "action" => "was sent",
91
- "description" => "The database query to generate the email took .3 seconds. This is email number 4 that is sent to this user"
92
- ));
93
-
94
- ?>
95
- `
96
-
97
- #### Add support for your own custom events
98
-
99
- There's also a simple class that you can extend to add support for custom history items. It's super simple to use! Take a look at `class.simple-history-extend.php` to get started, and then extending Simple_History_Extend and fill the required methods with their events and log messages.
100
 
101
- #### Never clear the history
102
-
103
- By default the items in the history log is cleared automatically afer 60 days.
104
- You can override this behaviour by using a filter, like this:
105
 
106
  `
107
  <?php
108
- // Never clear the database
109
- add_action("simple_history_allow_db_purge", function($bool) {
110
- return false;
111
- });
112
- ?>
113
- `
114
 
115
- #### Filters
116
 
117
- Available filters if you want to modify any behavior
 
118
 
119
- * simple_history_rss_item_title
120
- * simple_history_view_history_capability
121
- * simple_history_show_settings_page
122
- * simple_history_rss_item_description
123
- * simple_history_rss_item_title
124
- * simple_history_show_on_dashboard
125
- * simple_history_show_as_page
126
- * simple_history_allow_db_purge
127
- * simple_history_db_purge_days_interval
128
 
 
 
 
129
 
130
  #### Translations/Languages
131
 
132
- This plugin is available in the following languages:
133
 
134
- * English
135
- * German
136
- * Simplified Chinese
137
- * Swedish
138
- * French
139
- * Arabic
140
-
141
- Lots of thanks to the translators!
142
 
143
  #### Contribute at GitHub
 
144
  Development of this plugin takes place at GitHub. Please join in with feature requests, bug reports, or even pull requests!
145
  https://github.com/bonny/WordPress-Simple-History
146
 
147
- #### Donation and more plugins
 
148
  * If you like this plugin don't forget to [donate to support further development](http://eskapism.se/sida/donate/).
149
  * More [WordPress CMS plugins](http://wordpress.org/extend/plugins/profile/eskapism) by the same author.
150
 
151
- == Installation ==
152
-
153
- 1. Upload the folder "simple-history" to "/wp-content/plugins/"
154
- 1. Activate the plugin through the "Plugins" menu in WordPress
155
- 1. Done!
156
-
157
- Now Simple History will be visible in a submenu under the dashboard main menu. You can also show it directly on the dashboard by modified Simple History's settings page.
158
-
159
- == Feedback ==
160
-
161
- Like the plugin? Dislike it? Got bugs or feature request?
162
- Great! Contact me at par.thernstrom@gmail.com or at https://twitter.com/eskapism and hopefully
163
- I can do something about it.
164
-
165
 
166
  == Screenshots ==
167
 
168
- 1. Simple History showing som recent changes to my posts, users and attachments. Also showing several failed login attempts to one of my users.
 
 
169
 
170
- 2. Simple History settings. Choose to show the plugin on your dashboard, or as a separately page. Or both. Or none, since you can choose
171
- to only use the secret RSS feed to keep track of the changes on you web site/WordPress installation.
172
 
173
- 3. The RSS feed with changes, as shown in Firefox.
174
 
175
 
176
  == Changelog ==
177
 
178
- = 1.3.11 =
179
- - Don't use deprecated function get_commentdata(). Fixes https://wordpress.org/support/topic/get_commentdata-function-is-deprecated.
180
- - Don't use mysql_query() directly. Fixes https://wordpress.org/support/topic/deprecated-mysql-warning.
181
- - 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!
182
-
183
- = 1.3.10 =
184
- - Fix: correct usage of "its"
185
- - Fix: removed serif font in log. Fixes https://wordpress.org/support/topic/two-irritations-and-pleas-for-change.
186
 
187
- = 1.3.9 =
188
- - Fixed strict standards warning
189
- - Tested on WordPress 4.0
190
 
191
- = 1.3.8 =
192
- - 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.
193
-
194
- = 1.3.7 =
195
- - Added filter for rss feed: `simple_history/rss_feed_args`. Fixes http://wordpress.org/support/topic/more-rss-feed-items.
 
 
 
 
 
 
196
 
197
  = 1.3.6 =
198
  - Added Polish translation
@@ -396,5 +323,3 @@ by admin (John Doe), just now
396
 
397
  = 0.1 =
398
  * First public version. It works!
399
-
400
-
1
  === Simple History ===
2
+ Contributors: eskapism
3
  Donate link: http://eskapism.se/sida/donate/
4
+ Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, cms, dashboard, admin, syslog, feed, activity, stream
5
+ Requires at least: 3.6.0
6
+ Tested up to: 4.0.1
7
+ Stable tag: 2.0
8
 
9
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
10
 
32
  see when a user login & logut
33
  * **Failed user logins**<br>
34
  see when someone has tried to log in, but failed. The log will then include ip address of the possible hacker.
 
 
 
 
35
 
36
+ There is also a **RSS feed of changes** available, so you can keep track of the changes made via your favorite RSS reader on your phone, on your iPad, or on your computer.
37
 
38
+ It’s a plugin that is good to have on websites where several people are
 
 
 
39
  involved in editing the content.
40
 
41
  #### Example scenarios
42
 
43
  Keep track of what other people are doing:
44
+ _"Has someone done anything today? Ah, Sarah uploaded
45
  the new press release and created an article for it. Great! Now I don't have to do that."_
46
 
47
  Or for debug purposes:
53
  See the plugin in action with this short screencast:
54
  [youtube http://www.youtube.com/watch?v=4cu4kooJBzs]
55
 
56
+ #### API so you can add your own events to Simple History
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ If you are a theme or plugin developer and would like to add your own things/events to Simple History you can do that by calling the function `simple_history_add()` like this:
 
 
 
59
 
60
  `
61
  <?php
 
 
 
 
 
 
62
 
63
+ if ( function_exists("SimpleLogger") ) {
64
 
65
+ // Most basic example: just add some information to the log
66
+ SimpleLogger()->info("This is a message sent to the log");
67
 
68
+ // A bit more advanced: log events with different severities
69
+ SimpleLogger()->info("User admin edited page 'About our company'");
70
+ SimpleLogger()->warning("User 'Jessie' deleted user 'Kim'");
71
+ SimpleLogger()->debug("Ok, cron job is running!");
 
 
 
 
 
72
 
73
+ }
74
+ ?>
75
+ `
76
 
77
  #### Translations/Languages
78
 
79
+ I'm looking for translations of Simple History!
80
 
81
+ Check out the [localization](https://developer.wordpress.org/plugins/internationalization/localization/) part of the Plugin Handbook for info on how to translate plugins. When you're done with your translation email it to me at par.thernstrom@gmail.com, or [add a pull request](https://github.com/bonny/WordPress-Simple-History/tree/v2).
 
 
 
 
 
 
 
82
 
83
  #### Contribute at GitHub
84
+
85
  Development of this plugin takes place at GitHub. Please join in with feature requests, bug reports, or even pull requests!
86
  https://github.com/bonny/WordPress-Simple-History
87
 
88
+ #### Donation & more plugins
89
+
90
  * If you like this plugin don't forget to [donate to support further development](http://eskapism.se/sida/donate/).
91
  * More [WordPress CMS plugins](http://wordpress.org/extend/plugins/profile/eskapism) by the same author.
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
  == Screenshots ==
95
 
96
+ 1. The log view + it also shows the filter function in use - the log only shows event that
97
+ are of type post and pages and media (i.e. images & other uploads), and only events
98
+ initiated by a specific user.
99
 
100
+ 2. Events with different severity Simple History uses the log levels specified in the PHP PSR-3 standard.
 
101
 
102
+ 3. Events have context with extra details - Each logged event can include useful rich formatted extra information. For example: a plugin install can contain author info and a the url to the plugin, and an uploaded image can contain a thumbnail of the image.
103
 
104
 
105
  == Changelog ==
106
 
107
+ = 2.0 (november 2014) =
 
 
 
 
 
 
 
108
 
109
+ Major update - Simple History is now better and nicer than ever before! :)
110
+ I've spend hundreds of hours making this update, so if you use it and like it please [donate to keep my spirit up](http://eskapism.se/sida/donate/) or [give it a nice review](https://wordpress.org/support/view/plugin-reviews/simple-history).
 
111
 
112
+ - Code cleanup and modularization
113
+ - Support for log contexts
114
+ - Kinda PSR-3-compatible :)
115
+ - Can handle larger logs (doesn't load whole log into memory any more)
116
+ - Use nonces at more places
117
+ - More filters and hooks to make it easier to customize
118
+ - Better looking! well, at least I think so ;)
119
+ - Much better logging system to make it much easier to create new loggers and to translate logs into different languages
120
+ - Features as plugins: more things are moved into modules/its own file
121
+ - Users see different logs depending on their capability, for example an administrator will see what plugins have been installed, but an editor will not see any plugin related logs
122
+ - Much much more.
123
 
124
  = 1.3.6 =
125
  - Added Polish translation
323
 
324
  = 0.1 =
325
  * First public version. It works!
 
 
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file
screenshot-3.png CHANGED
Binary file
scripts.js DELETED
@@ -1,315 +0,0 @@
1
-
2
- /**
3
- * Object for Simple History
4
- */
5
- var simple_history = (function($) {
6
-
7
- var elms = {};
8
-
9
- function init() {
10
-
11
- // Only add JS things if Simple History exists on page
12
- if (! $("div.simple-history-ol-wrapper").length) {
13
- return;
14
- }
15
-
16
- // setup elements
17
- elms.wrap = $(".simple-history-wrap");
18
- elms.ol_wrapper = elms.wrap.find(".simple-history-ol-wrapper");
19
-
20
- // so wrapper does not collapse when loading new items
21
- elms.ol_wrapper.css("max-height", elms.ol_wrapper.height() );
22
-
23
- addListeners();
24
-
25
- elms.wrap.addClass("simple-history-is-ready simple-history-has-items");
26
-
27
- }
28
-
29
- function make_wrapper_expandable() {
30
- elms.ol_wrapper.css("max-height", "1000px");
31
- }
32
-
33
- /**
34
- * Reload history, starting at page 1
35
- */
36
- function reload(e) {
37
-
38
- e.preventDefault();
39
- jQuery(".simple-history-filter input[type='button']").trigger("click", [ {} ]);
40
-
41
- }
42
-
43
- function keyboardNav(e) {
44
-
45
- var link_to_click = null;
46
-
47
- if (e.keyCode == 37) {
48
- link_to_click = ".prev-page";
49
- } else if (e.keyCode == 39) {
50
- link_to_click = ".next-page";
51
- }
52
-
53
- if (link_to_click) {
54
- $(".simple-history-tablenav").find(link_to_click).trigger("click");
55
- }
56
-
57
- }
58
-
59
- /**
60
- * Add listeners to enable keyboard navigation and to show/hide things
61
- */
62
- function addListeners() {
63
-
64
- /*
65
- Character codes:
66
- 37 - left
67
- 38 - up
68
- 39 - right
69
- 40 - down
70
- */
71
-
72
- // Reload history when clicking reload-button
73
- $(document).on("click", ".simple-fields-reload", reload);
74
-
75
- // Enable keyboard navigation if we are on Simple Historys own page
76
- if ( $(".dashboard_page_simple_history_page").length ) {
77
-
78
- $(document).keydown(keyboardNav);
79
-
80
- }
81
-
82
- // show occasions
83
- $(document).on("click", "a.simple-history-occasion-show", function(e) {
84
-
85
- $(this).closest("li").find("ul.simple-history-occasions").toggle();
86
-
87
- make_wrapper_expandable();
88
-
89
- e.preventDefault();
90
-
91
- });
92
-
93
- // show details for main entry
94
- $(document).on("click", ".simple-history-item-description-toggler", function(e) {
95
- e.preventDefault();
96
- var self = $(this);
97
- make_wrapper_expandable();
98
- self.closest("li").toggleClass("simple-history-item-description-wrap-is-open");
99
- });
100
-
101
- // show details for occasions
102
- $(document).on("click", ".simple-history-occasions-details-toggle", function(e) {
103
- e.preventDefault();
104
- var self = $(this);
105
- make_wrapper_expandable();
106
- self.closest("li").toggleClass("simple-history-occasions-one-description-is-open");
107
- });
108
-
109
-
110
- } // function
111
-
112
- /**
113
- * Get currently selected filters
114
- * @return object with type, subtype, user_id
115
- */
116
- function get_selected_filters() {
117
-
118
- var obj = {
119
- type: $("select.simple-history-filter-type option:selected").data("simple-history-filter-type"),
120
- subtype: $("select.simple-history-filter-type option:selected").data("simple-history-filter-subtype"),
121
- user_id: $("select.simple-history-filter-user option:selected").data("simple-history-filter-user-id")
122
- };
123
-
124
- return obj;
125
-
126
- }
127
-
128
- return {
129
- "init": init,
130
- "get_selected_filters": get_selected_filters
131
- };
132
-
133
- })(jQuery);
134
-
135
- jQuery(function() {
136
- simple_history.init();
137
- });
138
-
139
-
140
- // the current page
141
- var simple_history_current_page = 0,
142
- simple_history_jqXHR = null;
143
-
144
- // search on enter
145
- jQuery(document).on("keyup", ".simple-history-filter-search input[type='text'], .simple-history-tablenav .current-page", function(e) {
146
- var $target = jQuery(e.target);
147
- // Key is enter
148
- var extraParams = {};
149
- if (e.keyCode == 13) {
150
- if ($target.hasClass("current-page")) {
151
- // not search, but go to page
152
- extraParams.enterType = "goToPage";
153
- } else {
154
- extraParams.enterType = "search";
155
- }
156
- jQuery(".simple-history-filter input[type='button']").trigger("click", [ extraParams ]);
157
- }
158
- });
159
-
160
- /**
161
- * Load page with history items when click on seach on when selecting someting in the dropdowns
162
- */
163
- jQuery(document).on("click change", "select.simple-history-filter, .simple-history-filter a, .simple-history-filter input[type='button'], .simple-history-tablenav a", function(e, extraParams) {
164
-
165
- var $t = jQuery(this),
166
- $ol = jQuery("ol.simple-history"),
167
- $wrapper = jQuery(".simple-history-ol-wrapper"),
168
- num_added = $ol.find("> li").length,
169
- search = jQuery("p.simple-history-filter-search input[type='text']").val(),
170
- $target = jQuery(e.target),
171
- $target_link = $target.closest("a"),
172
- $tablenav = jQuery("div.simple-history-tablenav"),
173
- $current_page = $tablenav.find(".current-page"),
174
- $total_pages = $tablenav.find(".total-pages"),
175
- $next_page = $tablenav.find(".next-page"),
176
- $prev_page = $tablenav.find(".prev-page"),
177
- $first_page = $tablenav.find(".first-page"),
178
- $last_page = $tablenav.find(".last-page"),
179
- $displaying_num = $tablenav.find(".displaying-num"),
180
- filters = simple_history.get_selected_filters(),
181
- $simple_history_wrap = jQuery(".simple-history-wrap");
182
-
183
- e.preventDefault();
184
-
185
- // If event is of type click and called form dropdown then don't continue (only go on when dropdown is changed)
186
- if ( "click" === e.type && "SELECT" === e.target.nodeName ) return;
187
-
188
- // if target is a child of simple-history-tablenav then this is a click in pagination
189
- if ($t.closest("div.simple-history-tablenav").length > 0) {
190
-
191
- var prev_current_page = simple_history_current_page;
192
-
193
- if ($target_link.hasClass("disabled")) {
194
- return;
195
- } else if ($target_link.hasClass("first-page")) {
196
- simple_history_current_page = 0;
197
- } else if ($target_link.hasClass("last-page")) {
198
- simple_history_current_page = parseInt($total_pages.text(), 10) - 1;
199
- } else if ($target_link.hasClass("prev-page")) {
200
- simple_history_current_page = simple_history_current_page - 1;
201
- } else if ($target_link.hasClass("next-page")) {
202
- simple_history_current_page = simple_history_current_page + 1;
203
- }
204
-
205
- // Don't go before page 0 or after total pages. Could happend if you navigated quickly with keyboard.
206
- if ( simple_history_current_page < 0 || simple_history_current_page >= $total_pages.text() ) {
207
- simple_history_current_page = prev_current_page;
208
- return;
209
- }
210
-
211
- } else {
212
-
213
- num_added = 0;
214
-
215
- if (extraParams && extraParams.enterType && extraParams.enterType == "goToPage") {
216
- // pressed enter on go to page-input
217
- simple_history_current_page = parseInt($current_page.val(), 10)-1; // -1 because we add one later on. feels kinda wierd, I know.
218
- if (isNaN(simple_history_current_page)) {
219
- simple_history_current_page = 0;
220
- }
221
- } else {
222
- // click on filter link, let's load from the beginning
223
- simple_history_current_page = 0;
224
-
225
- }
226
- }
227
-
228
- $simple_history_wrap.addClass("simple-history-is-loading simple-history-has-items");
229
-
230
- // update current page
231
- $current_page.val(simple_history_current_page+1);
232
-
233
- var data = {
234
- "action": "simple_history_ajax",
235
- "type": filters.type,
236
- "subtype" : filters.subtype,
237
- "user_id": filters.user_id,
238
- "search": search,
239
- "num_added": num_added,
240
- "page": simple_history_current_page
241
- };
242
-
243
- // If a previous ajax call is ongoing: cancel it
244
- if (simple_history_jqXHR) {
245
- simple_history_jqXHR.abort();
246
- }
247
-
248
- simple_history_jqXHR = jQuery.post(ajaxurl, data, function(data, textStatus, XMLHttpRequest){
249
-
250
- // If no more can be loaded show message about that
251
- if (data.error == "noMoreItems") {
252
-
253
- jQuery(".simple-history-ol-wrapper").height("auto");
254
- $simple_history_wrap.removeClass("simple-history-has-items simple-history-is-loading");
255
- $simple_history_wrap.addClass("simple-history-no-items-found");
256
-
257
- $displaying_num.html(0);
258
- $total_pages.text(1);
259
-
260
- } else {
261
-
262
- // Items found, add and show
263
- $simple_history_wrap.removeClass("simple-history-is-loading simple-history-no-items-found");
264
-
265
- // update number of existing items and total pages
266
- $displaying_num.html(data.filtered_items_total_count_string);
267
- $total_pages.text(data.filtered_items_total_pages);
268
-
269
- $ol.html(data.items_li);
270
-
271
- // set wrapper to the height required to show items
272
- //$wrapper.height( $ol.height() );
273
- $wrapper.css( "max-height", $ol.height() );
274
- $simple_history_wrap.removeClass("simple-history-is-loading");
275
-
276
- }
277
-
278
- // enable/disable next/prev-links
279
-
280
- // if we are getting the last page, set next+last to disabled
281
- if ($total_pages.text() == simple_history_current_page+1) {
282
- $last_page.addClass("disabled");
283
- $next_page.addClass("disabled");
284
- }
285
-
286
- // active next + last if there are more than one pages
287
- if ($total_pages.text() > 1 && $total_pages.text() != simple_history_current_page+1) {
288
- $last_page.removeClass("disabled");
289
- $next_page.removeClass("disabled");
290
- }
291
-
292
- // if we are past page 1 then active prev + first
293
- if (simple_history_current_page > 0) {
294
- $prev_page.removeClass("disabled");
295
- $first_page.removeClass("disabled");
296
- }
297
-
298
- // if we are at first then disable first + prev
299
- if (simple_history_current_page === 0) {
300
- $prev_page.addClass("disabled");
301
- $first_page.addClass("disabled");
302
- }
303
-
304
- $wrapper.removeClass("simple-history-is-loading");
305
-
306
- });
307
-
308
- });
309
-
310
- jQuery("ol.simple-history .when").live("mouseover", function() {
311
- jQuery(this).closest("li").find(".when_detail").fadeIn("fast");
312
- });
313
- jQuery("ol.simple-history .when").live("mouseout", function() {
314
- jQuery(this).closest("li").find(".when_detail").fadeOut("fast");
315
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/class.simple-history-extend.php DELETED
@@ -1,303 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Simple History Extend Class
5
- *
6
- * Use this class to build modules upon.
7
- *
8
- * @package Simple History Extender
9
- * @subpackage Main
10
- */
11
-
12
- // Exit if accessed directly
13
- if ( !defined( 'ABSPATH' ) ) exit;
14
-
15
- if ( !class_exists( 'Simple_History_Extend' ) ) :
16
-
17
- /**
18
- * Plugin class
19
- */
20
- class Simple_History_Extend {
21
-
22
- var $id;
23
- var $title;
24
- var $plugin;
25
- var $description;
26
-
27
- var $supports; // Events supported
28
- var $lacks; // Events not supported
29
-
30
- var $events;
31
-
32
- var $modules;
33
-
34
- /**
35
- * Build this module
36
- *
37
- * @param array $args Class arguments. Requires
38
- * array(
39
- * 'id' => The module id
40
- * 'title' => The module title
41
- * 'plugin' => The plugin basename or False if not a plugin
42
- * 'description' => Description of the module
43
- * 'tabs' => The contents of the contextual help tab. Can
44
- * contain 'supports' array and 'lacks' array
45
- * );
46
- * @return void
47
- */
48
- function __construct( $args ){
49
- $this->setup_globals( $args );
50
- $this->setup_actions();
51
- }
52
-
53
- /**
54
- * Define local class variables
55
- *
56
- * @param array $args Class arguments
57
- * @return void
58
- */
59
- function setup_globals( $args ){
60
-
61
- // Set module params
62
- $this->id = $args['id'];
63
- $this->title = $args['title'];
64
- $this->plugin = isset( $args['plugin'] ) ? $args['plugin'] : false;
65
- $this->description = isset( $args['description'] )
66
- ? $args['description']
67
- : ( $this->plugin
68
- ? sprintf( __('Log events for the %s plugin.', 'sh-extender'), $this->title )
69
- : sprintf( __('Log events for %s.', 'sh-extender'), $this->title )
70
- );
71
-
72
- // Set module tab contents
73
- $this->supports = isset( $args['tabs']['supports'] ) ? $args['tabs']['supports'] : $args['tabs'];
74
- $this->lacks = isset( $args['tabs']['lacks'] ) ? $args['tabs']['lacks'] : array();
75
-
76
- // Set module events
77
- $this->events = $this->setup_events();
78
- }
79
-
80
- /**
81
- * Return generic events for setup in $this->events
82
- *
83
- * @return array $events
84
- */
85
- public function setup_events(){
86
-
87
- // Setup default events
88
- $events = array(
89
- 'new' => __('created', 'sh-extender'),
90
- 'edit' => __('edited', 'sh-extender'),
91
- 'delete' => __('deleted', 'sh-extender'),
92
- 'spam' => __('marked as spam', 'sh-extender'),
93
- 'unspam' => __('unmarked as spam', 'sh-extender'),
94
- 'trash' => __('trashed', 'sh-extender'),
95
- 'untrash' => __('untrashed', 'sh-extender'),
96
- 'submit' => __('submitted', 'sh-extender')
97
- );
98
-
99
- return wp_parse_args( $this->add_events(), $events );
100
- }
101
-
102
- /**
103
- * Register action and filter hooks
104
- *
105
- * @return void
106
- */
107
- public function setup_actions(){
108
-
109
- // Bail out if module plugin is not active
110
- if ( $this->plugin && !is_plugin_active( $this->plugin ) )
111
- return;
112
-
113
- // Register settings field and help tab
114
- add_action( 'admin_init', array( $this, 'settings_field' ) );
115
- add_filter( 'sh_extender_add_help_tabs', array( $this, 'add_help_tab' ) );
116
-
117
- // Do we really need to load this every time we create a module?
118
- $modules = get_option( 'sh_extender_modules' );
119
-
120
- // Bail out if module is not active
121
- if ( !isset( $modules[$this->id] ) || !$modules[$this->id]['active'] )
122
- return;
123
-
124
- // Register custom log actions
125
- $this->add_actions();
126
- }
127
-
128
- /** Override this function in child class to add custom events to $this->events */
129
- function add_events(){
130
- return array();
131
- }
132
-
133
- /** Override this function in child class to add log actions */
134
- function add_actions(){
135
- }
136
-
137
- /**
138
- * Adds the module help tab to the contextual admin help
139
- *
140
- * @param array $tabs The tabs already added
141
- * @return array $tabs The tabs with module tab added
142
- */
143
- function add_help_tab( $tabs ){
144
-
145
- // This module has no tab
146
- if ( empty( $this->supports ) )
147
- return $tabs;
148
-
149
- // Build content string starting with supporting events
150
- $content = '<p><strong>'. sprintf( __('The %s module logs the following events:', 'sh-extender'), $this->title ) .'</strong></p><p>';
151
-
152
- // Create supports list
153
- $content .= '<ul>';
154
-
155
- // Create supports list items
156
- foreach ( $this->supports as $item )
157
- $content .= '<li>'. $item .'</li>';
158
-
159
- // Close supports list
160
- $content .= '</ul>';
161
-
162
- // Add non-supported events if there are any
163
- if ( !empty( $this->lacks ) ){
164
-
165
- $content .= '</p><p><strong>'. sprintf( __('The %s module does not support the following events:', 'sh-extender'), $this->title ) .'</strong></p><p>';
166
-
167
- // Create lacks list
168
- $content .='<ul>';
169
-
170
- // Create lacks list items
171
- foreach ( $this->lacks as $item )
172
- $content .= '<li>'. $item .'</li>';
173
-
174
- // Close lacks list
175
- $content .='</ul>';
176
- }
177
-
178
- // Close content string
179
- $content .= '</p>';
180
-
181
- // Add module tab to the tabs
182
- $tabs[] = array(
183
- 'id' => $this->id,
184
- 'title' => $this->title,
185
- 'content' => $content
186
- );
187
-
188
- return $tabs;
189
- }
190
-
191
- /**
192
- * Register settings field for this module
193
- *
194
- * @return void
195
- */
196
- public function settings_field(){
197
- global $simple_history_extender;
198
-
199
- add_settings_field(
200
- $simple_history_extender->modules_name .'['. $this->id .']',
201
- $this->title,
202
- array( $this, 'module_field' ),
203
- $simple_history_extender->page,
204
- $simple_history_extender->modules_section
205
- );
206
- }
207
-
208
- /**
209
- * Output settings field for this module
210
- *
211
- * @return void
212
- */
213
- public function module_field(){
214
- global $simple_history_extender;
215
-
216
- echo '<label><input type="checkbox" name="'. $simple_history_extender->modules_name .'['. $this->id .'][active]" value="1" '. checked( isset( $simple_history_extender->modules[$this->id] ) ? $simple_history_extender->modules[$this->id]['active'] : false, true, false ) .' /> ';
217
- echo '<span class="description">'. $this->description .'</span></label>';
218
- }
219
-
220
- /** Helpers ******************************************************/
221
-
222
- /**
223
- * Add a custom event to the Simple History plugin
224
- *
225
- * Call this function in all modules with $this->extend()
226
- *
227
- * @param array $r The event arguments. Shortened for convenience:
228
- * - action > action
229
- * - object_type > type
230
- * - object_subtype > subtype
231
- * - object_name > name
232
- * - object_id > id
233
- * - user_id > user_id
234
- * @uses simple_history_extend() To add an event to Simple History
235
- * @return void
236
- */
237
- function extend( $r ){
238
- $args = array(
239
- 'action' => isset( $r['action'] ) ? $r['action'] : __('updated'),
240
- 'object_type' => isset( $r['type'] ) ? $r['type'] : null,
241
- 'object_subtype' => isset( $r['subtype'] ) ? $r['subtype'] : null,
242
- 'object_name' => isset( $r['name'] ) ? $r['name'] : null,
243
- 'object_id' => isset( $r['id'] ) ? $r['id'] : null,
244
- 'user_id' => isset( $r['user_id'] ) ? $r['user_id'] : null
245
- );
246
-
247
- // Do the magic
248
- simple_history_add( $args );
249
- }
250
-
251
- static function extendStatic( $r ){
252
- $args = array(
253
- 'action' => isset( $r['action'] ) ? $r['action'] : __('updated'),
254
- 'object_type' => isset( $r['type'] ) ? $r['type'] : null,
255
- 'object_subtype' => isset( $r['subtype'] ) ? $r['subtype'] : null,
256
- 'object_name' => isset( $r['name'] ) ? $r['name'] : null,
257
- 'object_id' => isset( $r['id'] ) ? $r['id'] : null,
258
- 'user_id' => isset( $r['user_id'] ) ? $r['user_id'] : null
259
- );
260
-
261
- // Do the magic
262
- simple_history_add( $args );
263
- }
264
-
265
- /**
266
- * Extend Simple History shortcut for User type
267
- *
268
- * @param int $user_id User ID
269
- * @param string $action The logged action
270
- * @return void
271
- */
272
- function extend_user( $user_id, $action ){
273
- $user = get_userdata( $user_id );
274
-
275
- $this->extend( array(
276
- 'action' => $action,
277
- 'type' => __('User'),
278
- 'name' => apply_filters( 'she_extend_user_name', $user->user_login ),
279
- 'id' => $user_id
280
- ) );
281
- }
282
-
283
- /**
284
- * Extend Simple History shortcut for Post type
285
- *
286
- * @param int $post_id Post ID
287
- * @param string $action The logged action
288
- * @return void
289
- */
290
- function extend_post( $post_id, $action ){
291
- $post_type = get_post_type( $post_id );
292
- $cpt = get_post_type_object( $post_type );
293
-
294
- $this->extend( array(
295
- 'action' => $action,
296
- 'type' => $cpt->labels->singular_name,
297
- 'name' => get_the_title( $post_id ), // Previous or changed title?
298
- 'id' => $post_id
299
- ) );
300
- }
301
- }
302
-
303
- endif; // class_exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/languages/sh-extender-de_DE.mo DELETED
Binary file
simple-history-extender/languages/sh-extender-de_DE.po DELETED
@@ -1,413 +0,0 @@
1
- msgid ""
2
- msgstr ""
3
- "Project-Id-Version: Simple History Extender\n"
4
- "POT-Creation-Date: 2012-11-26 12:31+0100\n"
5
- "PO-Revision-Date: 2013-05-18 08:36+0100\n"
6
- "Last-Translator: Ralph Stenzel <ralph@klein-aber-fein.de>\n"
7
- "Language-Team: Ralph Stenzel <ralph@klein-aber-fein.de>\n"
8
- "MIME-Version: 1.0\n"
9
- "Content-Type: text/plain; charset=UTF-8\n"
10
- "Content-Transfer-Encoding: 8bit\n"
11
- "X-Generator: Poedit 1.5.4\n"
12
- "X-Poedit-KeywordsList: _;gettext;gettext_noop;__;_e\n"
13
- "X-Poedit-Basepath: .\n"
14
- "X-Poedit-SourceCharset: UTF-8\n"
15
- "Language: de_DE\n"
16
- "X-Poedit-SearchPath-0: ..\n"
17
-
18
- #: ../simple-history-extender.php:161
19
- msgid "Settings"
20
- msgstr "Einstellungen"
21
-
22
- #: ../simple-history-extender.php:183
23
- #, php-format
24
- msgid ""
25
- "The Simple History Extender plugin was deactivated because the Simple "
26
- "History plugin was not found installed or active.<br/><br/><a href=\"%s"
27
- "\">Return</a>."
28
- msgstr ""
29
- "Das Simple History Extender-Plugin wurde deaktiviert, weil das Simple "
30
- "History-Plugin nicht gefunden wurde oder nicht aktiv war.<br/><br/><a href="
31
- "\"%s\">Return</a>."
32
-
33
- #: ../simple-history-extender.php:184
34
- #, php-format
35
- msgid ""
36
- "The Simple History Extender plugin was deactivated.<br/><br/><a href=\"%s"
37
- "\">Return</a>."
38
- msgstr ""
39
- "Das Simple History Extender-Plugin wurde deaktiviert.<br/><br/><a href=\"%s"
40
- "\">Return</a>."
41
-
42
- #: ../simple-history-extender.php:213
43
- msgid "Simple History Extender Modules"
44
- msgstr "Simple History Erweiterungs-Module"
45
-
46
- #: ../simple-history-extender.php:223
47
- msgid ""
48
- "Activate or deactivate the events you want to log. Read the Help tab if you "
49
- "want to know which actions are supported and which aren't."
50
- msgstr ""
51
- "Aktivieren oder deaktivieren Sie die Ereignisse, welche protokolliert werden "
52
- "sollen. Lesen Sie die Hilfe, wenn Sie wissen wollen, welche Aktionen "
53
- "unterstützt werden und welche nicht."
54
-
55
- #: ../simple-history-extender.php:255
56
- msgid "activated"
57
- msgstr "aktiviert"
58
-
59
- #: ../simple-history-extender.php:255
60
- msgid "deactivated"
61
- msgstr "deaktiviert"
62
-
63
- #: ../simple-history-extender.php:256
64
- msgid "Simple History Extender Module"
65
- msgstr "Simple History Erweiterungs-Modul"
66
-
67
- #: ../class.sh-extend.php:68
68
- #, php-format
69
- msgid "Log events for the %s plugin."
70
- msgstr "Protokolliere Ereignisse für das %s-Plugin."
71
-
72
- #: ../class.sh-extend.php:69
73
- #, php-format
74
- msgid "Log events for %s."
75
- msgstr "Prorotokolliere Ereignisse für %s."
76
-
77
- #: ../class.sh-extend.php:89
78
- msgid "created"
79
- msgstr "erstellt"
80
-
81
- #: ../class.sh-extend.php:90
82
- msgid "edited"
83
- msgstr "geändert"
84
-
85
- #: ../class.sh-extend.php:91
86
- msgid "deleted"
87
- msgstr "gelöscht"
88
-
89
- #: ../class.sh-extend.php:92
90
- msgid "marked as spam"
91
- msgstr "als Spam gekennzeichnet"
92
-
93
- #: ../class.sh-extend.php:93
94
- msgid "unmarked as spam"
95
- msgstr "Spam-Kennzeichnung entfernt"
96
-
97
- #: ../class.sh-extend.php:94
98
- msgid "trashed"
99
- msgstr "gelöscht"
100
-
101
- #: ../class.sh-extend.php:95
102
- msgid "untrashed"
103
- msgstr "wiederhergestellt"
104
-
105
- #: ../class.sh-extend.php:96
106
- msgid "submitted"
107
- msgstr "abgeschickt"
108
-
109
- #: ../class.sh-extend.php:150
110
- #, php-format
111
- msgid "The %s module logs the following events:"
112
- msgstr "Das %s-Modul protokolliert die folgenden Ereignisse:"
113
-
114
- #: ../class.sh-extend.php:157
115
- #, php-format
116
- msgid "The %s module does not support the following events:"
117
- msgstr "Das %s-Modul unterstützt die folgenden Ereignisse nicht:"
118
-
119
- #: ../class.sh-extend.php:226
120
- msgid "updated"
121
- msgstr "aktualisiert"
122
-
123
- #: ../modules/gravityforms.php:28
124
- msgid "Gravity Forms"
125
- msgstr "Gravity Forms"
126
-
127
- #: ../modules/gravityforms.php:32
128
- msgid "Creating, editing and deleting a form."
129
- msgstr "Ein Formular erstellen, ändern und löschen."
130
-
131
- #: ../modules/gravityforms.php:33
132
- msgid "Deleting a field from an existing form."
133
- msgstr "Ein Feld eines bereits existierenden Formulars löschen."
134
-
135
- #: ../modules/gravityforms.php:34
136
- msgid "Submitting, editing and deleting an entry."
137
- msgstr "Einen Eintrag absenden, ändern oder löschen."
138
-
139
- #: ../modules/gravityforms.php:35
140
- msgid "Changing the status of an entry, including read/unread and star/unstar."
141
- msgstr ""
142
- "Den Status eines Eintrags ändern, einschließlich gelesen/ungelesen und "
143
- "markieren/unmarkieren."
144
-
145
- #: ../modules/gravityforms.php:38
146
- msgid "Duplicating a form."
147
- msgstr "Dupliziere ein Formular."
148
-
149
- #: ../modules/gravityforms.php:39
150
- msgid "Setting a form to active/inactive."
151
- msgstr "Ein Formular auf aktiv/inaktiv setzend."
152
-
153
- #: ../modules/gravityforms.php:48
154
- msgid "starred"
155
- msgstr "markiert"
156
-
157
- #: ../modules/gravityforms.php:49
158
- msgid "unstarred"
159
- msgstr "unmarkiert"
160
-
161
- #: ../modules/gravityforms.php:50
162
- msgid "marked as read"
163
- msgstr "als gelesen gekennzeichnet"
164
-
165
- #: ../modules/gravityforms.php:51
166
- msgid "marked as unread"
167
- msgstr "als ungelesen gekennzeichnet"
168
-
169
- #: ../modules/gravityforms.php:110
170
- #, php-format
171
- msgid "from %s"
172
- msgstr "von %s"
173
-
174
- #: ../modules/gravityforms.php:112
175
- msgid "from unknown"
176
- msgstr "von unbekannt"
177
-
178
- #: ../modules/gravityforms.php:120
179
- msgid "Form"
180
- msgstr "Formular"
181
-
182
- #: ../modules/gravityforms.php:129
183
- msgid "Form entry"
184
- msgstr "Formular-Eintrag"
185
-
186
- #: ../modules/gravityforms.php:150
187
- msgid "without entries deleted"
188
- msgstr "ohne gelöschte Einträge"
189
-
190
- #: ../modules/gravityforms.php:151
191
- #, php-format
192
- msgid "with %d entries deleted"
193
- msgstr "mit %d gelöschten Einträgen"
194
-
195
- #: ../modules/gravityforms.php:160
196
- #, php-format
197
- msgid "field %s deleted"
198
- msgstr "Feld %s gelöscht"
199
-
200
- #: ../modules/gravityforms.php:201
201
- msgid "restored"
202
- msgstr "wiederhergestellt"
203
-
204
- #: ../modules/gravityforms.php:206
205
- msgid "changed status"
206
- msgstr "änderte den Status"
207
-
208
- #: ../modules/widgets.php:25
209
- msgid "Widgets"
210
- msgstr "Widgets"
211
-
212
- #: ../modules/widgets.php:27
213
- msgid "Log events for the Widgets section of your WP install."
214
- msgstr ""
215
- "Protokolliere Ereignisse für die Widgets-Abteilung Ihrer WordPress-"
216
- "Installation."
217
-
218
- #: ../modules/widgets.php:30
219
- msgid "Adding, updating and deleting widgets in/from a sidebar."
220
- msgstr ""
221
- "Widgets zu/aus einer Seitenleiste hinzufügen, aktualisieren oder löschen."
222
-
223
- #: ../modules/widgets.php:33
224
- msgid "Moving widgets between sidebars."
225
- msgstr "Widgets zwischen Seitenleisten verschiebend."
226
-
227
- #: ../modules/widgets.php:34
228
- msgid "Setting a widget to active/inactive."
229
- msgstr "Ein Widget auf aktiv/inaktiv setzend."
230
-
231
- #: ../modules/widgets.php:86
232
- #, php-format
233
- msgid "removed from sidebar %s"
234
- msgstr "entfernt von Seitenleiste %s"
235
-
236
- #: ../modules/widgets.php:88
237
- #, php-format
238
- msgid "updated in sidebar %s"
239
- msgstr "aktualisiert in Seitenleiste %s"
240
-
241
- #: ../modules/widgets.php:90
242
- #, php-format
243
- msgid "added to sidebar %s"
244
- msgstr "hinzugefügt zur Seitenleiste %s"
245
-
246
- #: ../modules/widgets.php:95
247
- msgid "Widget"
248
- msgstr "Widget"
249
-
250
- #: ../modules/bbpress.php:28
251
- msgid "BBPress"
252
- msgstr "BBPress"
253
-
254
- #: ../modules/bbpress.php:32
255
- msgid "Creating, editing and deleting a forum, topic, reply."
256
- msgstr "Ein Forum, ein Thema oder eine Antwort erstellen, ändern oder löschen."
257
-
258
- #: ../modules/bbpress.php:33
259
- msgid "Setting the type of a forum to category or forum."
260
- msgstr "Den Typ eines Forums auf Kategorie oder auf Forum setzen."
261
-
262
- #: ../modules/bbpress.php:34
263
- msgid "Setting the status of a forum, topic to open or closed."
264
- msgstr ""
265
- "Den Status eines Forums oder eines Themas auf offen oder auf geschlossen "
266
- "setzen."
267
-
268
- #: ../modules/bbpress.php:35
269
- msgid "Setting the forum visibility to public, private or hidden."
270
- msgstr ""
271
- "Die Sichtbarkeit eines Forums auf öffentlich, privat oder versteckt setzen."
272
-
273
- #: ../modules/bbpress.php:36
274
- msgid "Trashing and untrashing a forum, topic, reply."
275
- msgstr "Ein Forum, ein Thema oder eine Antwort löschen oder wiederherstellen."
276
-
277
- #: ../modules/bbpress.php:37
278
- msgid "Marking and unmarking a topic, reply as spam."
279
- msgstr "Ein Thema oder eine Antwort als Spam markieren oder entmarkieren."
280
-
281
- #: ../modules/bbpress.php:38
282
- msgid "Marking and unmarking a topic as sticky."
283
- msgstr "Ein Thema als sticky markieren oder entmarkieren."
284
-
285
- #: ../modules/bbpress.php:39
286
- msgid "Merging and splitting a topic."
287
- msgstr "Themen zusammenführen oder splitten."
288
-
289
- #: ../modules/bbpress.php:40
290
- msgid "Updating, merging and deleting a topic tag."
291
- msgstr "Eine Themen-Kennzeichnung aktualisieren, zusammenführen oder löschen."
292
-
293
- #: ../modules/bbpress.php:41
294
- msgid "A user (un)favoriting and (un)subscribing to a topic."
295
- msgstr "Ein Benutzer, der ein Thema (un)favorisiert oder abonniert/abbestellt."
296
-
297
- #: ../modules/bbpress.php:42
298
- msgid "A user saving his/her profile."
299
- msgstr "Ein(e) das eigene Profil speichernde Benutzer(in)."
300
-
301
- #: ../modules/bbpress.php:53
302
- msgid "closed"
303
- msgstr "geschlossen"
304
-
305
- #: ../modules/bbpress.php:54
306
- msgid "opened"
307
- msgstr "geöffnet"
308
-
309
- #: ../modules/bbpress.php:55
310
- msgid "marked as sticky"
311
- msgstr "als sticky markiert"
312
-
313
- #: ../modules/bbpress.php:56
314
- msgid "marked as super sticky"
315
- msgstr "als super sticky markiert"
316
-
317
- #: ../modules/bbpress.php:57
318
- msgid "unmarked as sticky"
319
- msgstr "Sticky-Markierung entfernt"
320
-
321
- #: ../modules/bbpress.php:58
322
- msgid "set to category type"
323
- msgstr "auf Typ Kategorie gesetzt"
324
-
325
- #: ../modules/bbpress.php:59
326
- msgid "set to forum type"
327
- msgstr "auf Typ Forum gesetzt"
328
-
329
- #: ../modules/bbpress.php:60
330
- msgid "set to public"
331
- msgstr "auf öffentlich gesetzt"
332
-
333
- #: ../modules/bbpress.php:61
334
- msgid "set to private"
335
- msgstr "auf privat gesetzt"
336
-
337
- #: ../modules/bbpress.php:62
338
- msgid "set to hidden"
339
- msgstr "auf versteckt gesetzt"
340
-
341
- #: ../modules/bbpress.php:63
342
- #, php-format
343
- msgid "in forum %s merged into %s"
344
- msgstr "im Forum %s vereinigt mit %s"
345
-
346
- #: ../modules/bbpress.php:64
347
- #, php-format
348
- msgid "in forum %s split from reply %s by %s into %s in forum %s"
349
- msgstr "im Forum %s abgeteilt von Antwort %s durch %s in %s im Forum %s"
350
-
351
- #: ../modules/bbpress.php:131
352
- msgid "Forum"
353
- msgstr "Forum"
354
-
355
- #: ../modules/bbpress.php:141
356
- msgid "Topic"
357
- msgstr "Thema"
358
-
359
- #: ../modules/bbpress.php:150
360
- msgid "Topic Tag"
361
- msgstr "Themen-Kennzeichen"
362
-
363
- #: ../modules/bbpress.php:161
364
- #, php-format
365
- msgid "by %s"
366
- msgstr "von %s"
367
-
368
- #: ../modules/bbpress.php:162
369
- msgid "Reply"
370
- msgstr "Antwort"
371
-
372
- #: ../modules/bbpress.php:173
373
- msgid "User"
374
- msgstr "Benutzer"
375
-
376
- #: ../modules/bbpress.php:185
377
- #, php-format
378
- msgid "as child of %s"
379
- msgstr "als Kind von %s"
380
-
381
- #: ../modules/bbpress.php:246 ../modules/bbpress.php:256
382
- #, php-format
383
- msgid "in forum %s"
384
- msgstr "im Forum %s"
385
-
386
- #: ../modules/bbpress.php:369
387
- msgid "favorited"
388
- msgstr "als Favorit gekennzeichnet"
389
-
390
- #: ../modules/bbpress.php:373
391
- msgid "unfavorited"
392
- msgstr "Favoriten-Kennzeichnung entfernt"
393
-
394
- #: ../modules/bbpress.php:377
395
- msgid "subscribed"
396
- msgstr "abonniert"
397
-
398
- #: ../modules/bbpress.php:381
399
- msgid "unsubscribed"
400
- msgstr "abbestellt"
401
-
402
- #: ../modules/bbpress.php:385
403
- msgid "profile updated"
404
- msgstr "Profil aktualisiert"
405
-
406
- #: ../modules/bbpress.php:389
407
- msgid "registered"
408
- msgstr "registriert"
409
-
410
- #: ../modules/bbpress.php:397
411
- #, php-format
412
- msgid "changed forum role to %s"
413
- msgstr "Forum-Rolle gewechselt zu %s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/languages/sh-extender-nl_NL.mo DELETED
Binary file
simple-history-extender/languages/sh-extender-nl_NL.po DELETED
@@ -1,415 +0,0 @@
1
- msgid ""
2
- msgstr ""
3
- "Project-Id-Version: Simple History Extender\n"
4
- "POT-Creation-Date: 2012-11-27 00:32+0100\n"
5
- "PO-Revision-Date: 2012-11-27 00:33+0100\n"
6
- "Last-Translator: \n"
7
- "Language-Team: \n"
8
- "Language: Dutch\n"
9
- "MIME-Version: 1.0\n"
10
- "Content-Type: text/plain; charset=UTF-8\n"
11
- "Content-Transfer-Encoding: 8bit\n"
12
- "X-Generator: Poedit 1.5.4\n"
13
- "X-Poedit-KeywordsList: _;gettext;gettext_noop;__;_e\n"
14
- "X-Poedit-Basepath: .\n"
15
- "X-Poedit-SourceCharset: UTF-8\n"
16
- "X-Poedit-SearchPath-0: ..\n"
17
-
18
- #: ../simple-history-extender.php:161
19
- msgid "Settings"
20
- msgstr ""
21
-
22
- #: ../simple-history-extender.php:183
23
- #, php-format
24
- msgid ""
25
- "The Simple History Extender plugin was deactivated because the Simple "
26
- "History plugin was not found installed or active.<br/><br/><a href=\"%s"
27
- "\">Return</a>."
28
- msgstr ""
29
- "De Simple History Extender plugin is gedeactiveerd omdat de Simple History "
30
- "plugin niet gevonden is of niet actief is.<br/><br/><a href=\"%s\">Ga terug</"
31
- "a>."
32
-
33
- #: ../simple-history-extender.php:184
34
- #, php-format
35
- msgid ""
36
- "The Simple History Extender plugin was deactivated.<br/><br/><a href=\"%s"
37
- "\">Return</a>."
38
- msgstr ""
39
- "De Simple History Extender plugin is gedeactiveerd.<br/><br/><a href=\"%s"
40
- "\">Ga terug</a>."
41
-
42
- #: ../simple-history-extender.php:213
43
- msgid "Simple History Extender Modules"
44
- msgstr ""
45
-
46
- #: ../simple-history-extender.php:223
47
- msgid ""
48
- "Activate or deactivate the events you want to log. Read the Help tab if you "
49
- "want to know which actions are supported and which aren't."
50
- msgstr ""
51
- "Activeer of deactiveer de momenten die je wilt registreren. Lees de Help tab "
52
- "als je wilt weten welke acties worden geregistreerd en welke niet."
53
-
54
- #: ../simple-history-extender.php:255
55
- msgid "activated"
56
- msgstr "geactiveerd"
57
-
58
- #: ../simple-history-extender.php:255
59
- msgid "deactivated"
60
- msgstr "gedeactiveerd"
61
-
62
- #: ../simple-history-extender.php:256
63
- msgid "Simple History Extender Module"
64
- msgstr ""
65
-
66
- #: ../class.sh-extend.php:68
67
- #, php-format
68
- msgid "Log events for the %s plugin."
69
- msgstr "Registreer momenten voor de %s plugin."
70
-
71
- #: ../class.sh-extend.php:69
72
- #, php-format
73
- msgid "Log events for %s."
74
- msgstr "Registreer momenten voor %s."
75
-
76
- #: ../class.sh-extend.php:89
77
- msgid "created"
78
- msgstr "aangemaakt"
79
-
80
- #: ../class.sh-extend.php:90
81
- msgid "edited"
82
- msgstr "bewerkt"
83
-
84
- #: ../class.sh-extend.php:91
85
- msgid "deleted"
86
- msgstr "verwijderd"
87
-
88
- #: ../class.sh-extend.php:92
89
- msgid "marked as spam"
90
- msgstr "gemarkeerd als spam"
91
-
92
- #: ../class.sh-extend.php:93
93
- msgid "unmarked as spam"
94
- msgstr "gemarkeerd als geen spam"
95
-
96
- #: ../class.sh-extend.php:94
97
- msgid "trashed"
98
- msgstr "in de prullenbak gegooid"
99
-
100
- #: ../class.sh-extend.php:95
101
- msgid "untrashed"
102
- msgstr "uit de prullenbak gehaald"
103
-
104
- #: ../class.sh-extend.php:96
105
- msgid "submitted"
106
- msgstr "verzonden"
107
-
108
- #: ../class.sh-extend.php:150
109
- #, php-format
110
- msgid "The %s module logs the following events:"
111
- msgstr "De %s module registreert de volgende acties:"
112
-
113
- #: ../class.sh-extend.php:165
114
- #, php-format
115
- msgid "The %s module does not support the following events:"
116
- msgstr "De %s module registreert niet deze acties:"
117
-
118
- #: ../class.sh-extend.php:239
119
- msgid "updated"
120
- msgstr "bijgewerkt"
121
-
122
- #: ../modules/gravityforms.php:28
123
- msgid "Gravity Forms"
124
- msgstr ""
125
-
126
- #: ../modules/gravityforms.php:32
127
- msgid "Creating, editing and deleting a form."
128
- msgstr "Aanmaken, bewerken en verwijderen van een formulier."
129
-
130
- #: ../modules/gravityforms.php:33
131
- msgid "Deleting a field from an existing form."
132
- msgstr "Verwijderen van een dataveld van een bestaand formulier."
133
-
134
- #: ../modules/gravityforms.php:34
135
- msgid "Submitting, editing and deleting an entry."
136
- msgstr "Verzenden, bewerken en verwijderen van een ingevuld formulier."
137
-
138
- #: ../modules/gravityforms.php:35
139
- msgid "Changing the status of an entry, including read/unread and star/unstar."
140
- msgstr ""
141
- "Wijzigen van de status van een reactie, inclusief markeren van lezen/"
142
- "ongelezen en met ster/zonder ster."
143
-
144
- #: ../modules/gravityforms.php:38
145
- msgid "Duplicating a form."
146
- msgstr "Dupliceren van een formulier."
147
-
148
- #: ../modules/gravityforms.php:39
149
- msgid "Setting a form to active/inactive."
150
- msgstr "Markeren van een formulier als actief/inactief."
151
-
152
- #: ../modules/gravityforms.php:48
153
- msgid "starred"
154
- msgstr "gemarkeerd met een ster"
155
-
156
- #: ../modules/gravityforms.php:49
157
- msgid "unstarred"
158
- msgstr "gemarkeerde ster verwijderd"
159
-
160
- #: ../modules/gravityforms.php:50
161
- msgid "marked as read"
162
- msgstr "gemarkeerd als gelezen"
163
-
164
- #: ../modules/gravityforms.php:51
165
- msgid "marked as unread"
166
- msgstr "gemarkeerd als gelezen"
167
-
168
- #: ../modules/gravityforms.php:110
169
- #, php-format
170
- msgid "from %s"
171
- msgstr "van %s"
172
-
173
- #: ../modules/gravityforms.php:112
174
- msgid "from unknown"
175
- msgstr "van onbekend"
176
-
177
- #: ../modules/gravityforms.php:120
178
- msgid "Form"
179
- msgstr "Formulier"
180
-
181
- #: ../modules/gravityforms.php:129
182
- msgid "Form entry"
183
- msgstr "Ingevuld formulier"
184
-
185
- #: ../modules/gravityforms.php:150
186
- msgid "without entries deleted"
187
- msgstr "zonder reacties verwijderd"
188
-
189
- #: ../modules/gravityforms.php:151
190
- #, php-format
191
- msgid "with %d entries deleted"
192
- msgstr "met %d reacties verwijderd"
193
-
194
- #: ../modules/gravityforms.php:160
195
- #, php-format
196
- msgid "field %s deleted"
197
- msgstr "veld %s verwijderd"
198
-
199
- #: ../modules/gravityforms.php:201
200
- msgid "restored"
201
- msgstr "hersteld"
202
-
203
- #: ../modules/gravityforms.php:206
204
- msgid "changed status"
205
- msgstr "status gewijzigd"
206
-
207
- #: ../modules/widgets.php:25
208
- msgid "Widgets"
209
- msgstr ""
210
-
211
- #: ../modules/widgets.php:27
212
- msgid "Log events for the Widgets section of your WP install."
213
- msgstr "Registreer momenten voor het Widget onderdeel van je WP installatie."
214
-
215
- #: ../modules/widgets.php:30
216
- msgid "Adding, updating and deleting widgets in/from a sidebar."
217
- msgstr "Toevoegen, bijwerken en verwijderen van widgets in/uit een sidebar."
218
-
219
- #: ../modules/widgets.php:33
220
- msgid "Moving widgets between sidebars."
221
- msgstr "Verplaatsen van widgets tussen sidebars."
222
-
223
- #: ../modules/widgets.php:34
224
- msgid "Setting a widget to active/inactive."
225
- msgstr "Markeren van een widget als actief/inactief."
226
-
227
- #: ../modules/widgets.php:86
228
- #, php-format
229
- msgid "removed from sidebar %s"
230
- msgstr "verwijderd van sidebar %s"
231
-
232
- #: ../modules/widgets.php:88
233
- #, php-format
234
- msgid "updated in sidebar %s"
235
- msgstr "bijgwerkt in sidebar %s"
236
-
237
- #: ../modules/widgets.php:90
238
- #, php-format
239
- msgid "added to sidebar %s"
240
- msgstr "toegevoegd aan sidebar %s"
241
-
242
- #: ../modules/widgets.php:95
243
- msgid "Widget"
244
- msgstr ""
245
-
246
- #: ../modules/bbpress.php:28
247
- msgid "BBPress"
248
- msgstr ""
249
-
250
- #: ../modules/bbpress.php:32
251
- msgid "Creating, editing and deleting a forum, topic, reply."
252
- msgstr "Aanmaken, wijzigen en verwijderen van een forum, onderwerp of reactie."
253
-
254
- #: ../modules/bbpress.php:33
255
- msgid "Setting the type of a forum to category or forum."
256
- msgstr "Markeren van een forum als type categorie of type forum."
257
-
258
- #: ../modules/bbpress.php:34
259
- msgid "Setting the status of a forum, topic to open or closed."
260
- msgstr ""
261
- "Markeren van de status van een forum of onderwerp als open of gesloten."
262
-
263
- #: ../modules/bbpress.php:35
264
- msgid "Setting the forum visibility to public, private or hidden."
265
- msgstr "Markeren van een forum als publiek of privé zichtbaar of verborgen."
266
-
267
- #: ../modules/bbpress.php:36
268
- msgid "Trashing and untrashing a forum, topic, reply."
269
- msgstr ""
270
- "Verplaatsen van een forum, onderwerp of reactie in en uit de prullenbak."
271
-
272
- #: ../modules/bbpress.php:37
273
- msgid "Marking and unmarking a topic, reply as spam."
274
- msgstr "Markeren van een onderwerp of reactie als spam of geen spam."
275
-
276
- #: ../modules/bbpress.php:38
277
- msgid "Marking and unmarking a topic as sticky."
278
- msgstr "Markeren van een onderwerp als sticky of niet sticky."
279
-
280
- #: ../modules/bbpress.php:39
281
- msgid "Merging and splitting a topic."
282
- msgstr "Samenvoegen en splitsen van een ondewerp."
283
-
284
- #: ../modules/bbpress.php:40
285
- msgid "Updating, merging and deleting a topic tag."
286
- msgstr "Bijwerken, samenvoegen en verwijderen van een onderwerptag."
287
-
288
- #: ../modules/bbpress.php:41
289
- msgid "A user (un)favoriting and (un)subscribing to a topic."
290
- msgstr ""
291
- "Een gebruiker markeert een onderwerp als favoriet of abonneert zich op een "
292
- "onderwerp of maakt dit ongedaan."
293
-
294
- #: ../modules/bbpress.php:42
295
- msgid "A user saving his/her profile."
296
- msgstr "Een gebruiker bewerkt zijn/haar profiel."
297
-
298
- #: ../modules/bbpress.php:53
299
- msgid "closed"
300
- msgstr "gesloten"
301
-
302
- #: ../modules/bbpress.php:54
303
- msgid "opened"
304
- msgstr "geopend"
305
-
306
- #: ../modules/bbpress.php:55
307
- msgid "marked as sticky"
308
- msgstr "gemarkeerd als sticky"
309
-
310
- #: ../modules/bbpress.php:56
311
- msgid "marked as super sticky"
312
- msgstr "gemarkeerd als super sticky"
313
-
314
- #: ../modules/bbpress.php:57
315
- msgid "unmarked as sticky"
316
- msgstr "gemarkeerd als niet sticky"
317
-
318
- #: ../modules/bbpress.php:58
319
- msgid "set to category type"
320
- msgstr "gewijzigd naar category type"
321
-
322
- #: ../modules/bbpress.php:59
323
- msgid "set to forum type"
324
- msgstr "gewijzigd naar forum type"
325
-
326
- #: ../modules/bbpress.php:60
327
- msgid "set to public"
328
- msgstr "publiek zichtbaar"
329
-
330
- #: ../modules/bbpress.php:61
331
- msgid "set to private"
332
- msgstr "privé zichtbaar"
333
-
334
- #: ../modules/bbpress.php:62
335
- msgid "set to hidden"
336
- msgstr "verborgen"
337
-
338
- #: ../modules/bbpress.php:63
339
- #, php-format
340
- msgid "in forum %s merged into %s"
341
- msgstr "in forum %s samengevoegd in onderwerp %s"
342
-
343
- #: ../modules/bbpress.php:64
344
- #, php-format
345
- msgid "in forum %s split from reply %s by %s into %s in forum %s"
346
- msgstr ""
347
- "in forum %s gesplitst vanaf reactie %s door %s in onderwerp %s in forum %s"
348
-
349
- #: ../modules/bbpress.php:131
350
- msgid "Forum"
351
- msgstr ""
352
-
353
- #: ../modules/bbpress.php:141
354
- msgid "Topic"
355
- msgstr ""
356
-
357
- #: ../modules/bbpress.php:150
358
- msgid "Topic Tag"
359
- msgstr ""
360
-
361
- #: ../modules/bbpress.php:161
362
- #, php-format
363
- msgid "by %s"
364
- msgstr "door %s"
365
-
366
- #: ../modules/bbpress.php:162
367
- msgid "Reply"
368
- msgstr ""
369
-
370
- #: ../modules/bbpress.php:173
371
- msgid "User"
372
- msgstr ""
373
-
374
- #: ../modules/bbpress.php:185
375
- #, php-format
376
- msgid "as child of %s"
377
- msgstr "als subforum van %s"
378
-
379
- #: ../modules/bbpress.php:246 ../modules/bbpress.php:256
380
- #, php-format
381
- msgid "in forum %s"
382
- msgstr "in forum %s"
383
-
384
- #: ../modules/bbpress.php:369
385
- msgid "favorited"
386
- msgstr "gemarkeerd als favoriet"
387
-
388
- #: ../modules/bbpress.php:373
389
- msgid "unfavorited"
390
- msgstr "niet meer favoriet gemarkeerd"
391
-
392
- #: ../modules/bbpress.php:377
393
- msgid "subscribed"
394
- msgstr "geabonneerd"
395
-
396
- #: ../modules/bbpress.php:381
397
- msgid "unsubscribed"
398
- msgstr "abonnement ongedaan gemaakt"
399
-
400
- #: ../modules/bbpress.php:385
401
- msgid "profile updated"
402
- msgstr "profiel bijgewerkt"
403
-
404
- #: ../modules/bbpress.php:389
405
- msgid "registered"
406
- msgstr "geregistreerd"
407
-
408
- #: ../modules/bbpress.php:397
409
- #, php-format
410
- msgid "changed forum role to %s"
411
- msgstr "forum rol gewijzigd naar %s"
412
-
413
- #: ../modules/bbpress.php:397
414
- msgid "none"
415
- msgstr "geen"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/languages/sh-extender.pot DELETED
@@ -1,396 +0,0 @@
1
- msgid ""
2
- msgstr ""
3
- "Project-Id-Version: Simple History Extender\n"
4
- "POT-Creation-Date: 2012-11-26 12:31+0100\n"
5
- "PO-Revision-Date: 2012-11-26 12:32+0100\n"
6
- "Last-Translator: \n"
7
- "Language-Team: \n"
8
- "MIME-Version: 1.0\n"
9
- "Content-Type: text/plain; charset=UTF-8\n"
10
- "Content-Transfer-Encoding: 8bit\n"
11
- "X-Generator: Poedit 1.5.4\n"
12
- "X-Poedit-KeywordsList: _;gettext;gettext_noop;__;_e\n"
13
- "X-Poedit-Basepath: .\n"
14
- "X-Poedit-SourceCharset: UTF-8\n"
15
- "X-Poedit-SearchPath-0: ..\n"
16
-
17
- #: ../simple-history-extender.php:161
18
- msgid "Settings"
19
- msgstr ""
20
-
21
- #: ../simple-history-extender.php:183
22
- #, php-format
23
- msgid ""
24
- "The Simple History Extender plugin was deactivated because the Simple "
25
- "History plugin was not found installed or active.<br/><br/><a href=\"%s"
26
- "\">Return</a>."
27
- msgstr ""
28
-
29
- #: ../simple-history-extender.php:184
30
- #, php-format
31
- msgid ""
32
- "The Simple History Extender plugin was deactivated.<br/><br/><a href=\"%s"
33
- "\">Return</a>."
34
- msgstr ""
35
-
36
- #: ../simple-history-extender.php:213
37
- msgid "Simple History Extender Modules"
38
- msgstr ""
39
-
40
- #: ../simple-history-extender.php:223
41
- msgid ""
42
- "Activate or deactivate the events you want to log. Read the Help tab if you "
43
- "want to know which actions are supported and which aren't."
44
- msgstr ""
45
-
46
- #: ../simple-history-extender.php:255
47
- msgid "activated"
48
- msgstr ""
49
-
50
- #: ../simple-history-extender.php:255
51
- msgid "deactivated"
52
- msgstr ""
53
-
54
- #: ../simple-history-extender.php:256
55
- msgid "Simple History Extender Module"
56
- msgstr ""
57
-
58
- #: ../class.sh-extend.php:68
59
- #, php-format
60
- msgid "Log events for the %s plugin."
61
- msgstr ""
62
-
63
- #: ../class.sh-extend.php:69
64
- #, php-format
65
- msgid "Log events for %s."
66
- msgstr ""
67
-
68
- #: ../class.sh-extend.php:89
69
- msgid "created"
70
- msgstr ""
71
-
72
- #: ../class.sh-extend.php:90
73
- msgid "edited"
74
- msgstr ""
75
-
76
- #: ../class.sh-extend.php:91
77
- msgid "deleted"
78
- msgstr ""
79
-
80
- #: ../class.sh-extend.php:92
81
- msgid "marked as spam"
82
- msgstr ""
83
-
84
- #: ../class.sh-extend.php:93
85
- msgid "unmarked as spam"
86
- msgstr ""
87
-
88
- #: ../class.sh-extend.php:94
89
- msgid "trashed"
90
- msgstr ""
91
-
92
- #: ../class.sh-extend.php:95
93
- msgid "untrashed"
94
- msgstr ""
95
-
96
- #: ../class.sh-extend.php:96
97
- msgid "submitted"
98
- msgstr ""
99
-
100
- #: ../class.sh-extend.php:150
101
- #, php-format
102
- msgid "The %s module logs the following events:"
103
- msgstr ""
104
-
105
- #: ../class.sh-extend.php:157
106
- #, php-format
107
- msgid "The %s module does not support the following events:"
108
- msgstr ""
109
-
110
- #: ../class.sh-extend.php:226
111
- msgid "updated"
112
- msgstr ""
113
-
114
- #: ../modules/gravityforms.php:28
115
- msgid "Gravity Forms"
116
- msgstr ""
117
-
118
- #: ../modules/gravityforms.php:32
119
- msgid "Creating, editing and deleting a form."
120
- msgstr ""
121
-
122
- #: ../modules/gravityforms.php:33
123
- msgid "Deleting a field from an existing form."
124
- msgstr ""
125
-
126
- #: ../modules/gravityforms.php:34
127
- msgid "Submitting, editing and deleting an entry."
128
- msgstr ""
129
-
130
- #: ../modules/gravityforms.php:35
131
- msgid "Changing the status of an entry, including read/unread and star/unstar."
132
- msgstr ""
133
-
134
- #: ../modules/gravityforms.php:38
135
- msgid "Duplicating a form."
136
- msgstr ""
137
-
138
- #: ../modules/gravityforms.php:39
139
- msgid "Setting a form to active/inactive."
140
- msgstr ""
141
-
142
- #: ../modules/gravityforms.php:48
143
- msgid "starred"
144
- msgstr ""
145
-
146
- #: ../modules/gravityforms.php:49
147
- msgid "unstarred"
148
- msgstr ""
149
-
150
- #: ../modules/gravityforms.php:50
151
- msgid "marked as read"
152
- msgstr ""
153
-
154
- #: ../modules/gravityforms.php:51
155
- msgid "marked as unread"
156
- msgstr ""
157
-
158
- #: ../modules/gravityforms.php:110
159
- #, php-format
160
- msgid "from %s"
161
- msgstr ""
162
-
163
- #: ../modules/gravityforms.php:112
164
- msgid "from unknown"
165
- msgstr ""
166
-
167
- #: ../modules/gravityforms.php:120
168
- msgid "Form"
169
- msgstr ""
170
-
171
- #: ../modules/gravityforms.php:129
172
- msgid "Form entry"
173
- msgstr ""
174
-
175
- #: ../modules/gravityforms.php:150
176
- msgid "without entries deleted"
177
- msgstr ""
178
-
179
- #: ../modules/gravityforms.php:151
180
- #, php-format
181
- msgid "with %d entries deleted"
182
- msgstr ""
183
-
184
- #: ../modules/gravityforms.php:160
185
- #, php-format
186
- msgid "field %s deleted"
187
- msgstr ""
188
-
189
- #: ../modules/gravityforms.php:201
190
- msgid "restored"
191
- msgstr ""
192
-
193
- #: ../modules/gravityforms.php:206
194
- msgid "changed status"
195
- msgstr ""
196
-
197
- #: ../modules/widgets.php:25
198
- msgid "Widgets"
199
- msgstr ""
200
-
201
- #: ../modules/widgets.php:27
202
- msgid "Log events for the Widgets section of your WP install."
203
- msgstr ""
204
-
205
- #: ../modules/widgets.php:30
206
- msgid "Adding, updating and deleting widgets in/from a sidebar."
207
- msgstr ""
208
-
209
- #: ../modules/widgets.php:33
210
- msgid "Moving widgets between sidebars."
211
- msgstr ""
212
-
213
- #: ../modules/widgets.php:34
214
- msgid "Setting a widget to active/inactive."
215
- msgstr ""
216
-
217
- #: ../modules/widgets.php:86
218
- #, php-format
219
- msgid "removed from sidebar %s"
220
- msgstr ""
221
-
222
- #: ../modules/widgets.php:88
223
- #, php-format
224
- msgid "updated in sidebar %s"
225
- msgstr ""
226
-
227
- #: ../modules/widgets.php:90
228
- #, php-format
229
- msgid "added to sidebar %s"
230
- msgstr ""
231
-
232
- #: ../modules/widgets.php:95
233
- msgid "Widget"
234
- msgstr ""
235
-
236
- #: ../modules/bbpress.php:28
237
- msgid "BBPress"
238
- msgstr ""
239
-
240
- #: ../modules/bbpress.php:32
241
- msgid "Creating, editing and deleting a forum, topic, reply."
242
- msgstr ""
243
-
244
- #: ../modules/bbpress.php:33
245
- msgid "Setting the type of a forum to category or forum."
246
- msgstr ""
247
-
248
- #: ../modules/bbpress.php:34
249
- msgid "Setting the status of a forum, topic to open or closed."
250
- msgstr ""
251
-
252
- #: ../modules/bbpress.php:35
253
- msgid "Setting the forum visibility to public, private or hidden."
254
- msgstr ""
255
-
256
- #: ../modules/bbpress.php:36
257
- msgid "Trashing and untrashing a forum, topic, reply."
258
- msgstr ""
259
-
260
- #: ../modules/bbpress.php:37
261
- msgid "Marking and unmarking a topic, reply as spam."
262
- msgstr ""
263
-
264
- #: ../modules/bbpress.php:38
265
- msgid "Marking and unmarking a topic as sticky."
266
- msgstr ""
267
-
268
- #: ../modules/bbpress.php:39
269
- msgid "Merging and splitting a topic."
270
- msgstr ""
271
-
272
- #: ../modules/bbpress.php:40
273
- msgid "Updating, merging and deleting a topic tag."
274
- msgstr ""
275
-
276
- #: ../modules/bbpress.php:41
277
- msgid "A user (un)favoriting and (un)subscribing to a topic."
278
- msgstr ""
279
-
280
- #: ../modules/bbpress.php:42
281
- msgid "A user saving his/her profile."
282
- msgstr ""
283
-
284
- #: ../modules/bbpress.php:53
285
- msgid "closed"
286
- msgstr ""
287
-
288
- #: ../modules/bbpress.php:54
289
- msgid "opened"
290
- msgstr ""
291
-
292
- #: ../modules/bbpress.php:55
293
- msgid "marked as sticky"
294
- msgstr ""
295
-
296
- #: ../modules/bbpress.php:56
297
- msgid "marked as super sticky"
298
- msgstr ""
299
-
300
- #: ../modules/bbpress.php:57
301
- msgid "unmarked as sticky"
302
- msgstr ""
303
-
304
- #: ../modules/bbpress.php:58
305
- msgid "set to category type"
306
- msgstr ""
307
-
308
- #: ../modules/bbpress.php:59
309
- msgid "set to forum type"
310
- msgstr ""
311
-
312
- #: ../modules/bbpress.php:60
313
- msgid "set to public"
314
- msgstr ""
315
-
316
- #: ../modules/bbpress.php:61
317
- msgid "set to private"
318
- msgstr ""
319
-
320
- #: ../modules/bbpress.php:62
321
- msgid "set to hidden"
322
- msgstr ""
323
-
324
- #: ../modules/bbpress.php:63
325
- #, php-format
326
- msgid "in forum %s merged into %s"
327
- msgstr ""
328
-
329
- #: ../modules/bbpress.php:64
330
- #, php-format
331
- msgid "in forum %s split from reply %s by %s into %s in forum %s"
332
- msgstr ""
333
-
334
- #: ../modules/bbpress.php:131
335
- msgid "Forum"
336
- msgstr ""
337
-
338
- #: ../modules/bbpress.php:141
339
- msgid "Topic"
340
- msgstr ""
341
-
342
- #: ../modules/bbpress.php:150
343
- msgid "Topic Tag"
344
- msgstr ""
345
-
346
- #: ../modules/bbpress.php:161
347
- #, php-format
348
- msgid "by %s"
349
- msgstr ""
350
-
351
- #: ../modules/bbpress.php:162
352
- msgid "Reply"
353
- msgstr ""
354
-
355
- #: ../modules/bbpress.php:173
356
- msgid "User"
357
- msgstr ""
358
-
359
- #: ../modules/bbpress.php:185
360
- #, php-format
361
- msgid "as child of %s"
362
- msgstr ""
363
-
364
- #: ../modules/bbpress.php:246 ../modules/bbpress.php:256
365
- #, php-format
366
- msgid "in forum %s"
367
- msgstr ""
368
-
369
- #: ../modules/bbpress.php:369
370
- msgid "favorited"
371
- msgstr ""
372
-
373
- #: ../modules/bbpress.php:373
374
- msgid "unfavorited"
375
- msgstr ""
376
-
377
- #: ../modules/bbpress.php:377
378
- msgid "subscribed"
379
- msgstr ""
380
-
381
- #: ../modules/bbpress.php:381
382
- msgid "unsubscribed"
383
- msgstr ""
384
-
385
- #: ../modules/bbpress.php:385
386
- msgid "profile updated"
387
- msgstr ""
388
-
389
- #: ../modules/bbpress.php:389
390
- msgid "registered"
391
- msgstr ""
392
-
393
- #: ../modules/bbpress.php:397
394
- #, php-format
395
- msgid "changed forum role to %s"
396
- msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/modules/bbpress.php DELETED
@@ -1,400 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Simple History Extender BBPress Class
5
- *
6
- * Extend Simple History for BBPress events
7
- * Version 2.2
8
- *
9
- * @since 0.0.2
10
- *
11
- * @package Simple History Extender
12
- * @subpackage Modules
13
- */
14
-
15
- // Exit if accessed directly
16
- if ( !defined( 'ABSPATH' ) ) exit;
17
-
18
- if ( !class_exists( 'Simple_History_Extend_BBPress' ) ) :
19
-
20
- /**
21
- * Plugin class
22
- */
23
- class Simple_History_Extend_BBPress extends Simple_History_Extend {
24
-
25
- function __construct(){
26
- parent::__construct( array(
27
- 'id' => 'bbpress',
28
- 'title' => __('BBPress', 'sh-extender'),
29
- 'plugin' => 'bbpress/bbpress.php',
30
- 'tabs' => array(
31
- 'supports' => array(
32
- __('Creating, editing and deleting a forum, topic, reply.', 'sh-extender'),
33
- __('Setting the type of a forum to category or forum.', 'sh-extender'),
34
- __('Setting the status of a forum, topic to open or closed.', 'sh-extender'),
35
- __('Setting the forum visibility to public, private or hidden.', 'sh-extender'),
36
- __('Trashing and untrashing a forum, topic, reply.', 'sh-extender'),
37
- __('Marking and unmarking a topic, reply as spam.', 'sh-extender'),
38
- __('Marking and unmarking a topic as sticky.', 'sh-extender'),
39
- __('Merging and splitting a topic.', 'sh-extender'),
40
- __('Updating, merging and deleting a topic tag.', 'sh-extender'),
41
- __('A user (un)favoriting and (un)subscribing to a topic.', 'sh-extender'),
42
- __('A user saving his/her profile.', 'sh-extender')
43
- )
44
- )
45
- )
46
- );
47
- }
48
-
49
- function add_events(){
50
-
51
- // Add custom bbPress events text
52
- $events = array(
53
- 'close' => __('closed', 'sh-extender'),
54
- 'open' => __('opened', 'sh-extender'),
55
- 'stick' => __('marked as sticky', 'sh-extender'),
56
- 'super-stick' => __('marked as super sticky', 'sh-extender'),
57
- 'unstick' => __('unmarked as sticky', 'sh-extender'),
58
- 'categorize' => __('set to category type', 'sh-extender'),
59
- 'normalize' => __('set to forum type', 'sh-extender'),
60
- 'publicize' => __('set to public', 'sh-extender'),
61
- 'privatize' => __('set to private', 'sh-extender'),
62
- 'hide' => __('set to hidden', 'sh-extender'),
63
- 'merge' => __('in forum %s merged into %s', 'sh-extender'),
64
- 'split' => __('in forum %s split from reply %s by %s into %s in forum %s', 'sh-extender')
65
- );
66
-
67
- return $events;
68
- }
69
-
70
- function add_actions(){
71
-
72
- // Forum
73
- add_action( 'bbp_new_forum', array( $this, 'new_forum' ) ); // Covered by Simple History
74
- add_action( 'bbp_edit_forum', array( $this, 'edit_forum' ) ); // ..
75
- add_action( 'bbp_closed_forum', array( $this, 'closed_forum' ) );
76
- add_action( 'bbp_opened_forum', array( $this, 'opened_forum' ) );
77
- add_action( 'bbp_categorized_forum', array( $this, 'categorized_forum' ) );
78
- add_action( 'bbp_normalized_forum', array( $this, 'normalized_forum' ) );
79
- add_action( 'bbp_publicized_forum', array( $this, 'publicized_forum' ) );
80
- add_action( 'bbp_privatized_forum', array( $this, 'privatized_forum' ) );
81
- add_action( 'bbp_hid_forum', array( $this, 'hid_forum' ) );
82
- add_action( 'bbp_deleted_forum', array( $this, 'deleted_forum' ) ); // ..
83
- add_action( 'bbp_trashed_forum', array( $this, 'trashed_forum' ) ); // ..
84
- add_action( 'bbp_untrashed_forum', array( $this, 'untrashed_forum' ) ); // ..
85
-
86
- // Topic
87
- add_action( 'bbp_new_topic', array( $this, 'new_topic' ), 10, 4 ); // Covered by Simple History
88
- add_action( 'bbp_edit_topic', array( $this, 'edit_topic' ), 10, 5 ); // ..
89
- add_action( 'bbp_merged_topic', array( $this, 'merged_topic' ), 10, 3 );
90
- add_action( 'bbp_post_split_topic', array( $this, 'post_split_topic' ), 10, 3 );
91
- add_action( 'bbp_closed_topic', array( $this, 'closed_topic' ) );
92
- add_action( 'bbp_opened_topic', array( $this, 'opened_topic' ) );
93
- add_action( 'bbp_spammed_topic', array( $this, 'spammed_topic' ) );
94
- add_action( 'bbp_unspammed_topic', array( $this, 'unspammed_topic' ) );
95
- add_action( 'bbp_sticked_topic', array( $this, 'sticked_topic' ), 10, 3 );
96
- add_action( 'bbp_unsticked_topic', array( $this, 'unsticked_topic' ), 10, 2 );
97
- add_action( 'bbp_deleted_topic', array( $this, 'deleted_topic' ) ); // ..
98
- add_action( 'bbp_trashed_topic', array( $this, 'trashed_topic' ) ); // ..
99
- add_action( 'bbp_untrashed_topic', array( $this, 'untrashed_topic' ) ); // ..
100
-
101
- // Topic Tag
102
- add_action( 'bbp_update_topic_tag', array( $this, 'update_topic_tag' ), 10, 4 );
103
- add_action( 'bbp_merge_topic_tag', array( $this, 'merge_topic_tag' ), 10, 3 );
104
- add_action( 'bbp_delete_topic_tag', array( $this, 'delete_topic_tag' ), 10, 2 );
105
-
106
- // Reply
107
- add_action( 'bbp_new_reply', array( $this, 'new_reply' ), 10, 5 ); // Covered by Simple History
108
- add_action( 'bbp_edit_reply', array( $this, 'edit_reply' ), 10, 6 ); // ..
109
- add_action( 'bbp_spammed_reply', array( $this, 'spammed_reply' ) );
110
- add_action( 'bbp_unspammed_reply', array( $this, 'unspammed_reply' ) );
111
- add_action( 'bbp_deleted_reply', array( $this, 'deleted_reply' ) ); // ..
112
- add_action( 'bbp_trashed_reply', array( $this, 'trashed_reply' ) ); // ..
113
- add_action( 'bbp_untrashed_reply', array( $this, 'untrashed_reply' ) ); // ..
114
-
115
- // User
116
- add_action( 'bbp_add_user_favorite', array( $this, 'add_user_favorite' ), 10, 2 );
117
- add_action( 'bbp_remove_user_favorite', array( $this, 'remove_user_favorite' ), 10, 2 );
118
- add_action( 'bbp_add_user_subscription', array( $this, 'add_user_subscription' ), 10, 2 );
119
- add_action( 'bbp_remove_user_subscription', array( $this, 'remove_user_subscription' ), 10, 2 );
120
- // add_action( 'bbp_profile_update', array( $this, 'profile_update' ), 10, 2 ); // Covered by Simple History
121
- // add_action( 'bbp_user_register', array( $this, 'user_register' ) ); // ..
122
- add_filter( 'bbp_set_user_role', array( $this, 'set_user_role' ), 10, 3 );
123
-
124
- }
125
-
126
- /** Helpers ******************************************************/
127
-
128
- function extend_forum( $forum_id, $action ){
129
- $this->extend( array(
130
- 'action' => $action,
131
- 'type' => __('Forum', 'bbpress'),
132
- 'name' => bbp_get_forum_title( $forum_id ),
133
- 'id' => $forum_id
134
- ) );
135
- }
136
-
137
- // @todo Author can be anonymous
138
- function extend_topic( $topic_id, $action, $user_id = null ){
139
- $this->extend( array(
140
- 'action' => $action,
141
- 'type' => __('Topic', 'bbpress'),
142
- 'name' => bbp_get_topic_title( $topic_id ),
143
- 'id' => $topic_id
144
- ) );
145
- }
146
-
147
- function extend_topic_tag( $tag_id, $action, $tag ){
148
- $this->extend( array(
149
- 'action' => $action,
150
- 'type' => __('Topic Tag', 'bbpress'),
151
- 'name' => bbp_get_topic_tag_name( $tag ),
152
- 'id' => $tag_id
153
- ) );
154
- }
155
-
156
- // @todo Author can be anonymous
157
- function extend_reply( $reply_id, $action, $user_id ){
158
- $user = get_userdata( $user_id );
159
-
160
- $this->extend( array(
161
- 'action' => sprintf( __('by %s', 'sh-extender'), $user->user_login ) .' '. $action,
162
- 'type' => __('Reply', 'bbpress'),
163
- 'name' => bbp_get_reply_title( $reply_id ),
164
- 'id' => $reply_id
165
- ) );
166
- }
167
-
168
- /** Forum ********************************************************/
169
-
170
- public function new_forum( $forum_args ){
171
- if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
172
- return;
173
-
174
- $child = 0 != $forum_args['post_parent'] ? ' '. sprintf( __('as child of %s'), bbp_get_forum_title( $forum_args['post_parent'] ) ) : '';
175
- $this->extend_forum( $forum_args['forum_id'], $this->events['new'] . $child );
176
- }
177
-
178
- public function edit_forum( $forum_args ){
179
- if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
180
- return;
181
-
182
- $this->extend_forum( $forum_args['forum_id'], $this->events['edit'] );
183
- }
184
-
185
- public function closed_forum( $forum_id ){
186
- $this->extend_forum( $forum_id, $this->events['close'] );
187
- }
188
-
189
- public function opened_forum( $forum_id ){
190
- $this->extend_forum( $forum_id, $this->events['open'] );
191
- }
192
-
193
- public function categorized_forum( $forum_id ){
194
- $this->extend_forum( $forum_id, $this->events['categorize'] );
195
- }
196
-
197
- public function normalized_forum( $forum_id ){
198
- $this->extend_forum( $forum_id, $this->events['normalize'] );
199
- }
200
-
201
- public function publicized_forum( $forum_id ){
202
- if ( bbp_get_forum_visibility( $forum_id ) == 'public' )
203
- $this->extend_forum( $forum_id, $this->events['publicize'] );
204
- }
205
-
206
- public function privatized_forum( $forum_id ){
207
- if ( bbp_get_forum_visibility( $forum_id ) == 'private' )
208
- $this->extend_forum( $forum_id, $this->events['privatize'] );
209
- }
210
-
211
- public function hid_forum( $forum_id ){
212
- $this->extend_forum( $forum_id, $this->events['hide'] );
213
- }
214
-
215
- public function deleted_forum( $forum_id ){
216
- $this->extend_forum( $forum_id, $this->events['delete'] );
217
- }
218
-
219
- public function trashed_forum( $forum_id ){
220
- $this->extend_forum( $forum_id, $this->events['trash'] );
221
- }
222
-
223
- public function untrashed_forum( $forum_id ){
224
- $this->extend_forum( $forum_id, $this->events['untrash'] );
225
- }
226
-
227
- /** Topic ********************************************************/
228
-
229
- public function new_topic( $topic_id, $forum_id, $anonymous_data, $topic_author ){
230
- if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
231
- return;
232
-
233
- $this->extend_topic(
234
- $topic_id,
235
- $this->events['new'] .' '. sprintf( __('in forum %s', 'sh-extender'), bbp_get_forum_title( $forum_id ) )
236
- );
237
- }
238
-
239
- public function edit_topic( $topic_id, $forum_id, $anonymous_data, $topic_author, $is_edit ){
240
- if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
241
- return;
242
-
243
- $this->extend_topic(
244
- $topic_id,
245
- $this->events['edit'] .' '. sprintf( __('in forum %s', 'sh-extender'), bbp_get_forum_title( $forum_id ) )
246
- );
247
- }
248
-
249
- public function merged_topic( $destination_topic_id, $source_topic_id, $source_parent_id ){
250
- $this->extend_topic(
251
- $source_topic_id,
252
- sprintf( $this->events['merge'], bbp_get_forum_title( $source_parent_id ), bbp_get_topic_title( $destination_topic_id ) )
253
- );
254
- }
255
-
256
- public function post_split_topic( $from_reply_id, $source_topic_id, $destination_topic_id ){
257
- $this->extend_topic(
258
- $source_topic_id,
259
- sprintf( $this->events['split'], bbp_get_reply_title( $from_reply_id ), bbp_get_topic_title( $destination_topic_id ) )
260
- );
261
- }
262
-
263
- public function closed_topic( $topic_id ){
264
- $this->extend_topic( $topic_id, $this->events['close'] );
265
- }
266
-
267
- public function opened_topic( $topic_id ){
268
- $this->extend_topic( $topic_id, $this->events['open'] );
269
- }
270
-
271
- public function spammed_topic( $topic_id ){
272
- $this->extend_topic( $topic_id, $this->events['spam'] );
273
- }
274
-
275
- public function unspammed_topic( $topic_id ){
276
- $this->extend_topic( $topic_id, $this->events['unspam'] );
277
- }
278
-
279
- public function sticked_topic( $topic_id, $super, $success ){
280
- if ( $success ){
281
- if ( $super )
282
- $this->extend_topic( $topic_id, $this->events['super-stick'] );
283
- else
284
- $this->extend_topic( $topic_id, $this->events['stick'] );
285
- }
286
- }
287
-
288
- public function unsticked_topic( $topic_id, $success ){
289
- if ( $success )
290
- $this->extend_topic( $topic_id, $this->events['unstick'] );
291
- }
292
-
293
- public function deleted_topic( $topic_id ){
294
- $this->extend_topic( $topic_id, $this->events['delete'] );
295
- }
296
-
297
- public function trashed_topic( $topic_id ){
298
- $this->extend_topic( $topic_id, $this->events['trash'] );
299
- }
300
-
301
- public function untrashed_topic( $topic_id ){
302
- $this->extend_topic( $topic_id, $this->events['untrash'] );
303
- }
304
-
305
- /** Topic Tags ***************************************************/
306
-
307
- public function update_topic_tag( $tag_id, $tag, $name, $slug ){
308
- $this->extend_topic_tag( $tag_id, $this->events['edit'], $tag );
309
- }
310
-
311
- public function merge_topic_tag( $tag_id, $to_tag, $tag ){
312
- $this->extend_topic_tag( $tag_id, sprintf( $this->events['merge'], bbp_get_topic_tag_name( $to_tag ) ), $tag );
313
- }
314
-
315
- public function delete_topic_tag( $tag_id, $tag ){
316
- $this->extend_topic_tag( $tag_id, $this->events['delete'], $tag );
317
- }
318
-
319
- /** Reply ********************************************************/
320
-
321
- public function new_reply( $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author ){
322
- if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
323
- return;
324
-
325
- $this->extend_reply( $reply_id, $this->events['new'], $reply_author );
326
- }
327
-
328
- public function edit_reply( $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author, $is_edit ){
329
- if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
330
- return;
331
-
332
- $this->extend_reply( $reply_id, $this->events['edit'], $reply_author );
333
- }
334
-
335
- public function spammed_reply( $reply_id ){
336
- $this->extend_reply( $reply_id, $this->events['spam'], $reply_author );
337
- }
338
-
339
- public function unspammed_reply( $reply_id ){
340
- $this->extend_reply( $reply_id, $this->events['unspam'], $reply_author );
341
- }
342
-
343
- public function deleted_reply( $reply_id ){
344
- $this->extend_reply( $reply_id, $this->events['delete'], $reply_author );
345
- }
346
-
347
- public function trashed_reply( $reply_id ){
348
- $this->extend_reply( $reply_id, $this->events['trash'], $reply_author );
349
- }
350
-
351
- public function untrashed_reply( $reply_id ){
352
- $this->extend_reply( $reply_id, $this->events['untrash'], $reply_author );
353
- }
354
-
355
- /** User *********************************************************/
356
-
357
- public function add_user_favorite( $user_id, $topic_id ){
358
- $this->extend_topic( $topic_id, __('favorited', 'sh-extender') );
359
- }
360
-
361
- public function remove_user_favorite( $user_id, $topic_id ){
362
- $this->extend_topic( $topic_id, __('unfavorited', 'sh-extender') );
363
- }
364
-
365
- public function add_user_subscription( $user_id, $topic_id ){
366
- $this->extend_topic( $topic_id, __('subscribed', 'sh-extender') );
367
- }
368
-
369
- public function remove_user_subscription( $user_id, $topic_id ){
370
- $this->extend_topic( $topic_id, __('unsubscribed', 'sh-extender') );
371
- }
372
-
373
- public function profile_update( $user_id, $old_user_data ){
374
- $this->extend_user( $user_id, __('profile updated', 'sh-extender') );
375
- }
376
-
377
- public function user_register( $user_id ){
378
- $this->extend_user( $user_id, __('registered', 'sh-extender') );
379
- }
380
-
381
- /**
382
- * @todo Removing ones role does somehow not trigger bbp_set_user_role action
383
- * while it actually has to.
384
- */
385
- public function set_user_role( $new_role, $user_id, $user ){
386
-
387
- // Only log if a new role was actually assigned
388
- if ( false !== $new_role ){
389
- $bbp_roles = bbp_get_dynamic_roles();
390
- $this->extend_user( $user_id, sprintf( __('changed forum role to %s', 'sh-extender'), !empty( $new_role ) ? translate_user_role( $bbp_roles[$new_role]['name'] ) : __('none') ) );
391
- }
392
-
393
- return $new_role;
394
- }
395
-
396
- }
397
-
398
- new Simple_History_Extend_BBPress();
399
-
400
- endif; // class_exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/modules/gravityforms.php DELETED
@@ -1,225 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Simple History Extender Gravity Forms Class
5
- *
6
- * Extend Simple History for Gravity Forms events
7
- * Version 1.6.9
8
- *
9
- * @since 0.0.1
10
- *
11
- * @package Simple History Extender
12
- * @subpackage Modules
13
- */
14
-
15
- // Exit if accessed directly
16
- if ( !defined( 'ABSPATH' ) ) exit;
17
-
18
- if ( !class_exists( 'Simple_History_Extend_GravityForms' ) ) :
19
-
20
- /**
21
- * Plugin class
22
- */
23
- class Simple_History_Extend_GravityForms extends Simple_History_Extend {
24
-
25
- function __construct(){
26
- parent::__construct( array(
27
- 'id' => 'gravityforms',
28
- 'title' => __('Gravity Forms', 'sh-extender'),
29
- 'plugin' => 'gravityforms/gravityforms.php',
30
- 'tabs' => array(
31
- 'supports' => array(
32
- __('Creating, editing and deleting a form.', 'sh-extender'),
33
- __('Deleting a field from an existing form.', 'sh-extender'),
34
- __('Submitting, editing and deleting an entry.', 'sh-extender'),
35
- __('Changing the status of an entry, including read/unread and star/unstar.', 'sh-extender')
36
- ),
37
- 'lacks' => array(
38
- __('Duplicating a form.', 'sh-extender'),
39
- __('Setting a form to active/inactive.', 'sh-extender')
40
- )
41
- )
42
- )
43
- );
44
- }
45
-
46
- function add_events(){
47
- $events = array(
48
- 'star' => __('starred', 'sh-extender'),
49
- 'unstar' => __('unstarred', 'sh-extender'),
50
- 'read' => __('marked as read', 'sh-extender'),
51
- 'unread' => __('marked as unread', 'sh-extender'),
52
- );
53
-
54
- return $events;
55
- }
56
-
57
- function add_actions(){
58
-
59
- // Form & Fields create/update/delete
60
- add_action( 'gform_after_save_form', array( $this, 'after_save_form' ), 10, 2 );
61
- add_action( 'gform_before_delete_form', array( $this, 'before_delete_form' ), 10, 1 );
62
- add_action( 'gform_before_delete_field', array( $this, 'before_delete_field' ), 10, 2 );
63
-
64
- /**
65
- * NOTE: Setting form active/inactive not loggable without
66
- * proper hook at RGFormsModel::update_form_active()
67
- */
68
-
69
- // Entries create/update/delete
70
- add_action( 'gform_after_submission', array( $this, 'after_submission' ), 10, 2 );
71
- add_action( 'gform_after_update_entry', array( $this, 'after_update_entry' ), 10, 2 );
72
- add_action( 'gform_delete_lead', array( $this, 'delete_entry' ), 10, 1 );
73
- add_action( 'gform_update_status', array( $this, 'update_status' ), 10, 3 );
74
- add_action( 'gform_update_is_starred', array( $this, 'update_is_starred' ), 10, 3 );
75
- add_action( 'gform_update_is_read', array( $this, 'update_is_read' ), 10, 3 );
76
- }
77
-
78
- /** Helpers ******************************************************/
79
-
80
- function get_form( $id ){
81
- return RGFormsModel::get_form_meta( $id );
82
- }
83
-
84
- function get_entry( $id ){
85
- return RGFormsModel::get_lead( $id );
86
- }
87
-
88
- function get_field( $form_id, $id ){
89
- $form = $this->get_form( $form_id );
90
- return RGFormsModel::get_field( $form, $id );
91
- }
92
-
93
- function form_title( $form_id ){
94
- $form = $this->get_form( $form_id );
95
- return $form['title'];
96
- }
97
-
98
- function entry_form_title( $entry_id ){
99
- $entry = $this->get_entry( $entry_id );
100
- $form = $this->get_form( $entry['form_id'] );
101
- return $form['title'];
102
- }
103
-
104
- function created_by( $entry_id, $trailing_space = true ){
105
- $entry = $this->get_entry( $entry_id );
106
- $user_id = $entry['created_by'];
107
-
108
- if ( !is_null( $user_id ) ){
109
- $user = get_userdata( $user_id );
110
- $from = sprintf( __('from %s', 'sh-extender'), $user->user_login );
111
- } else
112
- $from = __('from unknown', 'sh-extender');
113
-
114
- return $from . ( $trailing_space ? ' ' : '' );
115
- }
116
-
117
- function extend_form( $form_id, $action ){
118
- $this->extend( array(
119
- 'action' => $action,
120
- 'type' => __('Form', 'sh-extender'),
121
- 'name' => $this->form_title( $form_id ),
122
- 'id' => $form_id
123
- ) );
124
- }
125
-
126
- function extend_entry( $entry_id, $action, $created_by = true ){
127
- $this->extend( array(
128
- 'action' => $created_by ? $this->created_by( $entry_id ) . $action : $action,
129
- 'type' => __('Form entry', 'sh-extender'),
130
- 'name' => $this->entry_form_title( $entry_id ),
131
- 'id' => $entry_id
132
- ) );
133
- }
134
-
135
- /** Form & Fields create/update/delete ***************************/
136
-
137
- /**
138
- * @todo Get it working for creating form duplicate
139
- */
140
- function after_save_form( $form, $is_new ){
141
- $this->extend_form( $form['id'], $is_new ? $this->events['new'] : $this->events['edit'] );
142
- }
143
-
144
- function before_delete_form( $form_id ){
145
- $entries = RGFormsModel::get_lead_count( $form_id, '' );
146
-
147
- $this->extend_form(
148
- $form_id,
149
- 0 == $entries
150
- ? __('without entries deleted', 'sh-extender')
151
- : sprintf( __('with %d entries deleted', 'sh-extender'), $entries )
152
- );
153
- }
154
-
155
- function before_delete_field( $form_id, $field_id ){
156
- $field = $this->get_field( $form_id, $field_id );
157
-
158
- $this->extend_form(
159
- $form_id,
160
- sprintf( __('field %s deleted', 'sh-extender'), $field['label'] .' (ID: '. $field_id .')' )
161
- );
162
- }
163
-
164
- /** Entries create/update/delete *********************************/
165
-
166
- function after_submission( $entry, $form ){
167
- $this->extend_entry( $entry['id'], $this->events['submit'], false );
168
- }
169
-
170
- function after_update_entry( $form, $entry_id ){
171
- $this->extend_entry( $entry_id, $this->events['edit'] );
172
- }
173
-
174
- function delete_entry( $entry_id ){
175
- $this->extend_entry( $entry_id, $this->events['delete'] );
176
- }
177
-
178
- function update_status( $entry_id, $new_value, $old_value ){
179
- if ( $old_value !== $new_value ){
180
-
181
- switch ( $new_value ){
182
- case 'spam':
183
- $action = $this->events['spam'];
184
- break;
185
-
186
- case 'trash':
187
- $action = $this->events['trash'];
188
- break;
189
-
190
- case 'active':
191
- switch ( $old_value ){
192
- case 'trash' :
193
- $action = $this->events['untrash'];
194
- break;
195
-
196
- case 'spam' :
197
- $action = $this->events['unspam'];
198
- break;
199
-
200
- default :
201
- $action = __('restored', 'sh-extender');
202
- }
203
- break;
204
-
205
- default:
206
- $action = __('changed status', 'sh-extender');
207
- }
208
-
209
- $this->extend_entry( $entry_id, $action );
210
- }
211
- }
212
-
213
- function update_is_starred( $entry_id, $new_value, $old_value ){
214
- $this->extend_entry( $entry_id, 1 == $new_value ? $this->events['star'] : $this->events['unstar'] );
215
- }
216
-
217
- function update_is_read( $entry_id, $new_value, $old_value ){
218
- $this->extend_entry( $entry_id, 1 == $new_value ? $this->events['read'] : $this->events['unread'] );
219
- }
220
-
221
- }
222
-
223
- new Simple_History_Extend_GravityForms();
224
-
225
- endif; // class_exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/modules/widgets.php DELETED
@@ -1,104 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Simple History Extender Widgets Class
5
- *
6
- * @since 0.0.1
7
- *
8
- * @package Simple History Extender
9
- * @subpackage Modules
10
- */
11
-
12
- // Exit if accessed directly
13
- if ( !defined( 'ABSPATH' ) ) exit;
14
-
15
- if ( !class_exists( 'Simple_History_Extend_Widgets' ) ) :
16
-
17
- /**
18
- * Plugin class
19
- */
20
- class Simple_History_Extend_Widgets extends Simple_History_Extend {
21
-
22
- function __construct(){
23
- parent::__construct( array(
24
- 'id' => 'widgets',
25
- 'title' => __('Widgets', 'sh-extender'),
26
- 'plugin' => false,
27
- 'description' => __('Log events for the Widgets section of your WP install.', 'sh-extender'),
28
- 'tabs' => array(
29
- 'supports' => array(
30
- __('Adding, updating and deleting widgets in/from a sidebar.', 'sh-extender'),
31
- ),
32
- 'lacks' => array(
33
- __('Moving widgets between sidebars.', 'sh-extender'),
34
- __('Setting a widget to active/inactive.', 'sh-extender')
35
- )
36
- )
37
- )
38
- );
39
- }
40
-
41
- function add_actions(){
42
-
43
- // Widget create/update/remove
44
- add_action( 'sidebar_admin_setup', array( $this, 'widgets_setup' ), 10 );
45
-
46
- /**
47
- * NOTE: Need to find hook for activate/deactivate widgets
48
- */
49
- }
50
-
51
- /** Widgets ******************************************************/
52
-
53
- /**
54
- * Log event where widget is added, updated or removed in/from sidebar
55
- *
56
- * @return void
57
- */
58
- public function widgets_setup(){
59
- global $wp_registered_widgets, $wp_registered_sidebars;
60
-
61
- // We need all these variables
62
- if ( !isset( $_POST['id_base'] ) && !isset( $_POST['widget-id'] ) && !isset( $_POST['sidebar'] ) )
63
- return;
64
-
65
- /**
66
- * Get the number of the main widget reference in the widget list.
67
- * It's allways one smaller (-1) than our current new widget instance.
68
- */
69
- $number = absint( substr( $_POST['widget-id'], strlen( $_POST['id_base'] ) + 1 ) ) - 1;
70
- $title = false;
71
-
72
- // Find the widget name from the list of available widgets
73
- foreach ( $wp_registered_widgets as $widget ){
74
- if ( $_POST['id_base'] .'-'. $number == $widget['id'] ){
75
- $title = esc_html( strip_tags( $widget['name'] ) );
76
- break;
77
- }
78
- }
79
-
80
- // Fetch widget name from previous save
81
- if ( !$title )
82
- $title = esc_html( strip_tags( $wp_registered_widgets[$_POST['widget-id']]['name'] ) );
83
-
84
- // Define the action
85
- if ( isset( $_POST['delete_widget'] ) )
86
- $action = __('removed from sidebar %s', 'sh-extender');
87
- elseif ( isset( $wp_registered_widgets[$_POST['widget-id']] ) )
88
- $action = __('updated in sidebar %s', 'sh-extender');
89
- else
90
- $action = __('added to sidebar %s', 'sh-extender');
91
-
92
- // Extend SH
93
- $this->extend( array(
94
- 'action' => sprintf( $action, $wp_registered_sidebars[$_POST['sidebar']]['name'] ),
95
- 'type' => __('Widget'),
96
- 'name' => $title,
97
- 'id' => $_POST['widget-id']
98
- ) );
99
- }
100
- }
101
-
102
- new Simple_History_Extend_Widgets();
103
-
104
- endif; // class_exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
simple-history-extender/simple-history-extender.php DELETED
@@ -1,303 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Plugin Name: Simple History Extender
5
- * Description: Extend the Simple History plugin with more events to log.
6
- * Version: 0.0.3
7
- * Author: Laurens Offereins
8
- * Author URI: http://www.offereinspictures.nl
9
- */
10
-
11
- // Exit if accessed directly
12
- if ( !defined( 'ABSPATH' ) ) exit;
13
-
14
- if ( !class_exists( 'SimpleHistoryExtender' ) ) :
15
-
16
- /**
17
- * Plugin class
18
- */
19
- class SimpleHistoryExtender {
20
-
21
- public $file;
22
- public $basename;
23
- public $sh_plugin;
24
- public $domain;
25
- public $page;
26
- public $sh_pagenow;
27
- public $opt_group;
28
- public $plugin_dir;
29
-
30
- public $modules_dir;
31
- public $modules_name;
32
- public $modules_section;
33
- public $modules;
34
-
35
- /**
36
- * Build this class
37
- */
38
- public function __construct(){
39
- $this->setup_globals();
40
- $this->setup_actions();
41
- }
42
-
43
- /**
44
- * Define local class variables
45
- *
46
- * @return void
47
- */
48
- public function setup_globals(){
49
- $this->file = __FILE__;
50
- $this->basename = plugin_basename( $this->file );
51
- $this->sh_plugin = 'simple-history/index.php';
52
- $this->domain = 'sh-extender';
53
- $this->page = 'simple_history_settings_menu_slug';
54
- $this->sh_pagenow = 'settings_page_'. $this->page;
55
- $this->opt_group = 'simple_history_settings_group';
56
- $this->plugin_dir = plugin_dir_path( $this->file );
57
-
58
- $this->modules_dir = $this->plugin_dir .'modules/';
59
- $this->modules_name = 'sh_extender_modules';
60
- $this->modules_section = 'sh-extender-modules';
61
- $this->modules = get_option( $this->modules_name );
62
- }
63
-
64
- /**
65
- * Register action and filter hooks
66
- *
67
- * @return void
68
- */
69
- public function setup_actions(){
70
-
71
- // Plugin
72
- add_action( 'activate_'. $this->basename, array( $this, 'on_activate' ) );
73
- add_action( 'deactivate_'. $this->basename, array( $this, 'on_deactivate' ) );
74
- add_action( 'uninstall_'. $this->basename, array( $this, 'on_uninstall' ) );
75
- add_action( 'plugins_loaded', array( $this, 'textdomain' ) );
76
- add_filter( 'plugin_action_links', array( $this, 'action_links' ), 10, 2 );
77
- add_action( 'deactivate_'. $this->sh_plugin, array( $this, 'on_deactivate_sh' ) );
78
-
79
- // Admin
80
- add_action( 'init', array( $this, 'load_modules' ) );
81
- add_action( 'admin_init', array( $this, 'register_settings' ) );
82
- add_action( 'load-'. $this->sh_pagenow, array( $this, 'add_help_tabs' ) );
83
- }
84
-
85
- /** Plugin *******************************************************/
86
-
87
- /**
88
- * Act on plugin activation
89
- *
90
- * @return void
91
- */
92
- public function on_activate(){
93
-
94
- // Cancel activation if Simple History is not active
95
- if ( !is_plugin_active( $this->sh_plugin ) ){
96
- $this->deactivate_plugin( true );
97
- }
98
- }
99
-
100
- /**
101
- * Act on plugin deactivation
102
- *
103
- * @return void
104
- */
105
- public function on_deactivate(){
106
- // Do stuff
107
- }
108
-
109
- /**
110
- * Act on plugin uninstallation
111
- *
112
- * @return void
113
- */
114
- public function on_uninstall(){
115
-
116
- // Remove option from DB
117
- delete_option( $this->modules_name );
118
- }
119
-
120
- /**
121
- * Load the translation files
122
- *
123
- * @return void
124
- */
125
- public function textdomain(){
126
- load_plugin_textdomain( $this->domain, false, dirname( $this->basename ) . '/languages/' );
127
- }
128
-
129
- /**
130
- * Add plugin action links to the plugin row actions
131
- *
132
- * @param array $links The current plugin row actions
133
- * @param string $file The plugin file basename
134
- */
135
- public function action_links( $links, $file ) {
136
-
137
- // Create settings link for this plugin only
138
- if ( $this->basename == $file )
139
- $links[] = '<a href="' . add_query_arg( 'page', $this->page, 'options-general.php' ) . '">'. __('Settings') .'</a>';
140
-
141
- return $links;
142
- }
143
-
144
- /**
145
- * Deactivate this plugin and maybe display error message
146
- *
147
- * @param boolean $die Whether this function should execute wp_die()
148
- * @param integer $message The message ID of the message to display
149
- * @uses deactivate_plugins()
150
- * @return void
151
- */
152
- public function deactivate_plugin( $die = false, $message = 0 ){
153
-
154
- // Deactivate this plugin
155
- deactivate_plugins( $this->basename );
156
-
157
- // Redirect user and present die message
158
- if ( $die ){
159
-
160
- // Default messages
161
- $messages = array(
162
- 0 => __('The Simple History Extender plugin was deactivated because the Simple History plugin was not found installed or active.', 'sh-extender'),
163
- 1 => __('The Simple History Extender plugin was deactivated.', 'sh-extender')
164
- );
165
-
166
- wp_die( sprintf(
167
- '<p>'. $messages[$message] .'</p><p><a href="%s">'. __('Return') .'</a></p>',
168
- // Remove previous messages
169
- remove_query_arg( array( 'activate', 'deactivate', 'error' ), wp_get_referer() )
170
- ) );
171
- exit;
172
- }
173
- }
174
-
175
- /**
176
- * Do plugin deactivation on Simple History deactivation
177
- *
178
- * Deactivates this plugin silently without feedback
179
- *
180
- * @link http://wordpress.stackexchange.com/questions/27850/deactivate-plugin-upon-deactivation-of-another-plugin/56924#56924
181
- */
182
- public function on_deactivate_sh(){
183
- if ( is_plugin_active( $this->basename ) )
184
- add_action( 'update_option_active_plugins', array( $this, 'deactivate_plugin' ) );
185
- }
186
-
187
- /** Admin ********************************************************/
188
-
189
- /**
190
- * Load the module files
191
- *
192
- * Require files later then 'plugins_loaded' action for
193
- * the module translation strings to be processed.
194
- *
195
- * @uses do_action() To call 'she_load_modules' for custom modules
196
- *
197
- * @return void
198
- */
199
- public function load_modules(){
200
-
201
- // Make is_plugin_active() available
202
- include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
203
-
204
- // Load Extend class which the modules depend upon
205
- require( $this->plugin_dir . 'class.simple-history-extend.php' );
206
-
207
- // Load modules from directory
208
- foreach ( scandir( $this->modules_dir ) as $file ){
209
- if ( 'php' == pathinfo( $file, PATHINFO_EXTENSION ) && file_exists( $this->modules_dir . $file ) )
210
- require( $this->modules_dir . $file );
211
- }
212
-
213
- // Hook for loading custom modules
214
- do_action( 'she_load_modules' );
215
- }
216
-
217
- /**
218
- * Add module settings to the Simple History admin page
219
- *
220
- * Creates an extra settings section for our modules and registers
221
- * all modules in one option in the DB.
222
- *
223
- * @return void
224
- */
225
- public function register_settings(){
226
- add_settings_section( $this->modules_section, __('Simple History Extender Modules', 'sh-extender'), array( $this, 'modules_settings_intro' ), $this->page );
227
- register_setting( $this->opt_group, $this->modules_name, array( $this, 'modules_settings_sanitize' ) );
228
- }
229
-
230
- /**
231
- * Output settings section information text
232
- *
233
- * @return void
234
- */
235
- public function modules_settings_intro(){
236
- echo '<p>'. __( 'Activate or deactivate the events you want to log. Read the Help tab if you want to know which actions are supported and which aren\'t.', 'sh-extender') .'</p>';
237
- }
238
-
239
- /**
240
- * Sanitize input values before saving settings to DB
241
- *
242
- * Additionally logs which modules are (de)activated.
243
- *
244
- * @param array $input The input values
245
- * @return array $retval The sanitized values
246
- */
247
- public function modules_settings_sanitize( $input ){
248
- global $wp_settings_fields;
249
-
250
- $old = get_option( $this->modules_name, array() );
251
- $retval = array();
252
-
253
- // Sanitize input
254
- if ( ! is_array($input) ) $input = array();
255
- foreach ( $input as $module => $args )
256
- $retval[$module]['active'] = isset( $args['active'] ) ? true : false;
257
-
258
- // Log module (de)activation
259
- foreach ( $wp_settings_fields[$this->page][$this->modules_section] as $id => $field ){
260
-
261
- // Strip module name from {settings_field_name[module_name]}
262
- $module = substr( $id, strpos( $id, '[' ) + 1, -1 );
263
-
264
- // Make sure not set modules are set to false
265
- if ( !isset( $retval[$module] ) )
266
- $retval[$module]['active'] = false;
267
-
268
- // Only log on change
269
- if ( ( !isset( $old[$module] ) && $retval[$module]['active'] )
270
- || ( isset( $old[$module] ) && $old[$module]['active'] !== $retval[$module]['active'] )
271
- ){
272
-
273
- Simple_History_Extend::extendStatic( array(
274
- 'action' => $retval[$module]['active'] ? __('activated', 'sh-extender') : __('deactivated', 'sh-extender'),
275
- 'type' => __('Simple History Extender Module', 'sh-extender'),
276
- 'name' => $field['title'],
277
- 'id' => $module
278
- ) );
279
- }
280
- }
281
-
282
- return $retval;
283
- }
284
-
285
- /**
286
- * Add module help tabs to the admin page contextual help
287
- *
288
- * @uses apply_filters() To call sh_extender_add_help_tabs where
289
- * modules can add their unique help tab
290
- * @uses WP_Screen::add_help_tab()
291
- */
292
- public function add_help_tabs(){
293
- $tabs = apply_filters( 'sh_extender_add_help_tabs', array() );
294
-
295
- // Loop over all tabs and add them to the screen
296
- foreach ( $tabs as $tab )
297
- get_current_screen()->add_help_tab( $tab );
298
- }
299
- }
300
-
301
- $GLOBALS['simple_history_extender'] = new SimpleHistoryExtender();
302
-
303
- endif; // class_exists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
styles.css DELETED
@@ -1,419 +0,0 @@
1
-
2
-
3
- .simple-history-ol-wrapper {
4
- margin-top: .75em;
5
- }
6
-
7
- .simple-history-filter {
8
- margin-bottom: .5em;
9
- margin-top: .5em;
10
- margin-right: 1em;
11
- display: inline-block;
12
- }
13
-
14
- ol.simple-history {
15
- list-style-type: none;
16
- padding: 0;
17
- margin: 0;
18
- border-top: 1px solid #DFDFDF;
19
- }
20
- #simple_history_dashboard_widget ol.simple-history {
21
- margin: 0 -10px;
22
- }
23
- ol.simple-history a {
24
- text-decoration: none;
25
- }
26
- ol.simple-history > li {
27
- margin: 0;
28
- padding: 10px 10px;
29
- position: relative;
30
- line-height: 1;
31
- border-top: 1px solid white;
32
- border-bottom: 1px solid #DFDFDF;
33
- background-color: #FCFCFC;
34
- min-height: 32px;
35
- /* min-height: 50px; */
36
- padding-left: 52px;
37
- }
38
- ol.simple-history > li:nth-child(odd) {
39
- background-color: #F9F9F9;
40
- }
41
- ol.simple-history .first {
42
- font-size: 13px;
43
- /*font-family: Georgia,"Times New Roman","Bitstream Charter",Times,serif;*/
44
- margin: 0 0 0.5em;
45
- }
46
-
47
- /* reverse background color order on wiget page so the light one is topmost */
48
- #simple_history_dashboard_widget ol.simple-history > li {
49
- background-color: #F9F9F9;
50
- }
51
- #simple_history_dashboard_widget ol.simple-history > li:nth-child(odd) {
52
- background-color: #FCFCFC;
53
- }
54
-
55
- ol.simple-history .second {
56
- color: #666;
57
- margin-top: .5em;
58
- }
59
-
60
- ol.simple-history .third {
61
- margin-top: .5em;
62
- }
63
- /*ol.simple-history .third ul {
64
- margin-top: .5em;
65
- }
66
- */
67
- .simple-history-who-avatar {
68
- position: absolute;
69
- top: 10px;
70
- left: 10px;
71
- }
72
-
73
- ol.simple-history .when {
74
- color: #999;
75
- cursor: help;
76
- border-bottom: 1px dotted #bbb;
77
- }
78
- ol.simple-history .when_detail {
79
- display: none;
80
- position: absolute;
81
- top: .4em;
82
- left: 5.5em;
83
- padding: .75ex;
84
- background-color: #fffdb5;
85
- border: 1px solid #eee;
86
- color: black;
87
- z-index: 1;
88
- box-shadow: 3px 3px 10px -3px rgba(0,0, 0, .25);
89
- }
90
-
91
-
92
- li.simple-history-has-attachment-thumnbail {
93
- /*min-height: 50px;*/
94
- }
95
- ol.simple-history .simple-history-has-attachment-thumnbail .first,
96
- ol.simple-history .simple-history-has-attachment-thumnbail .second,
97
- ol.simple-history .simple-history-has-attachment-thumnbail .third
98
- {
99
- /*margin-left: 60px;*/
100
- }
101
-
102
-
103
- .simple-history-object-image {
104
- margin: 1em 0 0 0;
105
- overflow: hidden;
106
- }
107
- .simple-history-object-image img {
108
- max-width: 80px;
109
- max-height: 60px;
110
- }
111
-
112
- .simple-history-attachment-thumbnail {
113
- float: left;
114
- margin-right: 1em;
115
- /*min-width: 60px;*/
116
- }
117
-
118
- .simple-history-attachment-meta {
119
- float: left;
120
- color: #666;
121
- }
122
-
123
- .simple-history-attachment-meta p {
124
- margin: 0 0 .5em 0;
125
- }
126
-
127
- .simple-history-item-description-wrap {
128
- margin: 0.5em 0;
129
- }
130
-
131
-
132
- .simple-history-item-description-toggler {
133
- border-left: 1px solid #bbb;
134
- padding-left: .75em;
135
- margin-left: .75em;
136
- line-height: 1;
137
- }
138
-
139
- .simple-history-item-description-toggler:before {
140
- /*content: "+ ";*/
141
- }
142
-
143
- .simple-history-action-description,
144
- .simple-history-occasions-one-action-description {
145
- margin: .75em 0 .75em .5ex;
146
- line-height: 1.4;
147
- display: none;
148
- background-color: #eee;
149
- padding: .5em;
150
- }
151
-
152
- .simple-history-item-description-wrap-is-open .simple-history-item-description-toggler:before {
153
- /*content: "- ";*/
154
- }
155
-
156
- .simple-history-item-description-wrap-is-open .simple-history-action-description{
157
- display: block;
158
- }
159
-
160
- .simple-history-occasions-details-toggle {
161
- margin-left: .75em;
162
- padding-left: .75em;
163
- border-left: 1px solid #bbb;
164
- }
165
-
166
-
167
- .simple-history-occasions-one-description-is-open .simple-history-occasions-one-action-description {
168
- display: block;
169
- margin-left: 2em;
170
- }
171
-
172
-
173
- .simple-history-occasions-one-action-description {
174
- display: none;
175
- }
176
-
177
- .simple-history-occasions-details-toggle
178
-
179
- .simple-history-title {
180
- font-weight: bold;
181
- }
182
-
183
- .simple-history-discrete {
184
- font-size: 11px;
185
- color: #999;
186
- }
187
-
188
- .simple-history-added-by-ajax {
189
- border-top: 1px solid #ddd;
190
- }
191
-
192
- ul.simple-history-occasions {
193
- margin: 1em 0 0 1em;
194
- padding: 0;
195
- list-style-type: none;
196
- color: #999;
197
- }
198
- ul.simple-history-occasions li {
199
- margin: .5em 0;
200
- padding: 0;
201
- line-height: 1.5;
202
- }
203
-
204
- .simple-history-rss-feed-dashboard {
205
- display: none;
206
- }
207
-
208
- #simple_history_dashboard_widget .simple-history-rss-feed-dashboard {
209
- position: absolute;
210
- bottom: 6px;
211
- right: 5px;
212
- margin: 0;
213
- display: block;
214
- }
215
-
216
- .simple-history-rss-feed-dashboard a,
217
- .simple-history-rss-feed-page span {
218
- background: transparent url(./img/ui-icons_888888_256x240.png) no-repeat -17px -176px;
219
- display: block;
220
- width: 16px;
221
- height: 16px;
222
- text-indent: -99999px;
223
- direction: ltr;
224
- }
225
-
226
- #simple_history_dashboard_widget .simple-history-rss-feed-page {
227
- display: none;
228
- }
229
-
230
- .dashboard_page_simple_history_page .wrap {
231
- position: relative;
232
- }
233
- .simple-history-rss-feed-page {
234
- display: block;
235
- position: absolute;
236
- right: 0;
237
- bottom: -3px;
238
- }
239
-
240
- .rtl .simple-history-rss-feed-page {
241
- right: auto;
242
- left: 0;
243
- }
244
-
245
- .simple-history-rss-feed-page span {
246
- float: left;
247
- }
248
-
249
- .simple-history-settings-page-updated {
250
- font-weight: bold;
251
- margin: 5px 0 15px;
252
- background-color: #FFFFE0;
253
- border: 1px solid #E6DB55;
254
- }
255
- .simple-history-settings-page-updated p {
256
- margin: 0.5em !important;
257
- }
258
- .simple-history-filter-search input[type=button] {
259
- cursor: pointer;
260
- }
261
-
262
- .simple-history-tablenav {
263
- visibility: hidden;
264
- }
265
- .simple-history-has-items .simple-history-tablenav {
266
- visibility: visible;
267
- }
268
-
269
- .simple-history-no-more-items {
270
- padding: .5em;
271
- background-color: #FFFFE0;
272
- border: 1px solid #E6DB55;
273
- font-weight: bold;
274
- display: none;
275
- }
276
-
277
- .simple-history-no-items-found .simple-history-no-more-items {
278
- display: block;
279
- }
280
-
281
- .simple-history-no-items-found .simple-history-ol-wrapper {
282
- display: none;
283
- }
284
-
285
- .simple-history-tablenav .tablenav-pages {
286
- float: none;
287
- }
288
-
289
- .simple-history-tablenav .displaying-num {
290
- display: inline-block;
291
- }
292
-
293
-
294
- .simple-history-tablenav .tablenav-pages a {
295
- background: #eee;
296
- display: inline-block;
297
- }
298
-
299
- .simple-history-tablenav .tablenav-pages a:hover {
300
- background-color: #e4e4e4;
301
- }
302
-
303
- .simple-history-tablenav .tablenav-pages a span {
304
- background: transparent url(./img/ui-icons_888888_256x240.png) no-repeat -240px -48px;
305
- display: block;
306
- text-indent: -30px;
307
- line-height: 1;
308
- overflow: hidden;
309
- padding: 2px 9px;
310
- }
311
-
312
- .simple-history-tablenav .tablenav-pages a.disabled {
313
- opacity: .5;
314
- }
315
-
316
- .simple-history-tablenav .tablenav-pages .prev-page span {
317
- background-position: -96px -16px;
318
- }
319
-
320
- .simple-history-tablenav .tablenav-pages .next-page span {
321
- background-position: -32px -16px;
322
- }
323
-
324
- .simple-history-tablenav .tablenav-pages .last-page span {
325
- background-position: -208px -48px;
326
- }
327
-
328
-
329
- .simple-history-loading {
330
- position: absolute;
331
- top: 55%;
332
- left: 10%;
333
- opacity: 0;
334
- }
335
- #simple_history_dashboard_widget .simple-history-loading {
336
- left: 40%;
337
- }
338
- .simple-history-loading img {
339
- vertical-align: text-bottom;
340
- }
341
-
342
- .simple-history-is-loading {
343
-
344
- }
345
-
346
- /* show loading indicator after a short while */
347
- .simple-history-is-loading .simple-history-loading {
348
- transition: all .25s ease-out;
349
- transition-delay: .75s;
350
- opacity: .75;
351
- }
352
-
353
- .simple-history-ol-wrapper {
354
- overflow: hidden;
355
- }
356
-
357
- .simple-history-ol-wrapper {
358
- /*overflow: visible;*/
359
- }
360
-
361
- .simple-history-wrap,
362
- .simple-history-ol-wrapper,
363
- ol.simple-history,
364
- .simple-history-tablenav {
365
- transition: all .15s ease-out;
366
- }
367
-
368
- .simple-history-ol-wrapper {
369
- xtransition: max-height .15s ease-out;
370
- }
371
-
372
- .simple-history-is-loading ol.simple-history {
373
- opacity: 0;
374
- }
375
- .simple-history-is-loading .simple-history-tablenav {
376
- opacity: .75;
377
- }
378
-
379
- .simple-history-wrap {
380
- visibility: hidden;
381
- }
382
-
383
- .simple-history-is-ready {
384
- visibility: visible;
385
- }
386
-
387
- .simple-fields-reload {
388
- -webkit-appearance: none;
389
- background: #f3f3f3;
390
- background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#fff), to(#f3f3f3));
391
- background-image: -webkit-linear-gradient(#fff, #f3f3f3);
392
- background-image: -moz-linear-gradient(#fff, #f3f3f3);
393
- background-image: -o-linear-gradient(#fff, #f3f3f3);
394
- background-image: linear-gradient(#fff, #f3f3f3);
395
- border: 1px solid #bbb;
396
- -webkit-border-radius: 3px;
397
- border-radius: 3px;
398
- -webkit-box-shadow: inset 0 1px 0 #fff;
399
- box-shadow: inset 0 1px 0 #fff;
400
- display: inline-block;
401
- padding: 5px 6px;
402
- text-indent: -9999px;
403
- margin-right: 1em;
404
- vertical-align: middle;
405
- }
406
- .simple-fields-reload:hover {
407
- background-color: #e4e4e4;
408
- border-color: #999;
409
- }
410
-
411
- .simple-fields-reload span {
412
- background: transparent url(./img/ui-icons_888888_256x240.png) no-repeat -64px -81px;
413
- width: 16px;
414
- height: 16px;
415
- display: block;
416
- text-indent: -9999px;
417
- direction: ltr;
418
- border: 0;
419
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/settings-general.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <form method="post" action="options.php">
2
+
3
+ <?php
4
+ // Prints out all settings sections added to a particular settings page
5
+ do_settings_sections(SimpleHistory::SETTINGS_MENU_SLUG);
6
+ ?>
7
+
8
+ <?php
9
+ // Output nonce, action, and option_page fields
10
+ settings_fields("simple_history_settings_group");
11
+ ?>
12
+
13
+ <?php submit_button(); ?>
14
+
15
+ </form>
templates/settings-log.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $pager_size = $this->get_pager_size();
4
+
5
+ /**
6
+ * Filter the pager size setting for the settings page
7
+ *
8
+ * @since 2.0
9
+ *
10
+ * @param int $pager_size
11
+ */
12
+ $pager_size = apply_filters("simple_history/settings_page_pager_size", $pager_size);
13
+
14
+ ?>
15
+ <div class="SimpleHistoryGui"
16
+ data-pager-size='<?php echo $pager_size ?>'
17
+ ></div>
18
+ <?php
19
+
20
+ global $wpdb;
21
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
22
+
templates/settings-statsForGeeks.php ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+
3
+ jQuery(function($) {
4
+
5
+ var $button = $(".js-SimpleHistoryShowsStatsForGeeks");
6
+ var $wrapper = $(".SimpleHistory__statsForGeeksInner");
7
+
8
+ $button.on("click", function() {
9
+ $wrapper.toggle();
10
+ });
11
+
12
+
13
+ });
14
+
15
+ </script>
16
+ <?php
17
+
18
+ defined('ABSPATH') OR exit;
19
+
20
+ echo "<hr>";
21
+ echo "<p class='hide-if-no-js'><button class='button js-SimpleHistoryShowsStatsForGeeks'>Show stats for geeks</button></p>";
22
+
23
+ ?>
24
+
25
+ <div class="SimpleHistory__statsForGeeksInner hide-if-js">
26
+ <?php
27
+
28
+ echo "<h4>Rows count</h4>";
29
+ $logQuery = new SimpleHistoryLogQuery();
30
+ $rows = $logQuery->query(array(
31
+ "posts_per_page" => 1,
32
+ //"date_from" => strtotime("-$period_days days")
33
+ ));
34
+
35
+ // This is the number of rows with occasions taken into consideration
36
+ $total_accassions_rows_count = $rows["total_row_count"];
37
+
38
+ // Total number of log rows
39
+ // Not caring about occasions, this number = all occasions
40
+ $total_num_rows = $wpdb->get_var("select count(*) FROM {$table_name}");
41
+ echo "<ul>";
42
+ echo "<li>Total $total_num_rows log rows in db.</li>";
43
+ echo "<li>Total $total_accassions_rows_count rows, when grouped by occasion id.</li>";
44
+ echo "</ul>";
45
+
46
+ echo "<h4>Clear history interval</h4>";
47
+ echo "<p>" . $this->sh->get_clear_history_interval() . "</p>";
48
+
49
+ $sql_table_size = sprintf('
50
+ SELECT table_name AS "table_name",
51
+ round(((data_length + index_length) / 1024 / 1024), 2) "size_in_mb"
52
+ FROM information_schema.TABLES
53
+ WHERE table_schema = "%1$s"
54
+ AND table_name IN ("%2$s", "%3$s");
55
+ ',
56
+ DB_NAME, // 1
57
+ $table_name, // 2
58
+ $table_name_contexts
59
+ );
60
+
61
+ $table_size_result = $wpdb->get_results($sql_table_size);
62
+
63
+
64
+ echo "<h4>Database size</h4>";
65
+
66
+ echo "<table class='widefat'>";
67
+ echo "
68
+ <thead>
69
+ <tr>
70
+ <th>Table name</th>
71
+ <th>Table size (MB)</th>
72
+ </tr>
73
+ </thead>
74
+ ";
75
+
76
+ $loopnum = 0;
77
+ foreach ($table_size_result as $one_table) {
78
+
79
+ printf('<tr class="%3$s">
80
+ <td>%1$s</td>
81
+ <td>%2$s</td>
82
+ </tr>',
83
+ $one_table->table_name,
84
+ $one_table->size_in_mb,
85
+ $loopnum % 2 ? " alt " : ""
86
+ );
87
+
88
+ $loopnum++;
89
+ }
90
+
91
+ echo "</table>";
92
+
93
+ // @TODO: this does actually only show all loggers that have logged rows,
94
+ // not all loggers!
95
+ echo "<h4>Loggers</h4>";
96
+
97
+ echo "<p>All instantiated loggers.</p>";
98
+
99
+ echo "<table class='widefat' cellpadding=2>";
100
+ echo "
101
+ <thead>
102
+ <tr>
103
+ <th>Slug</th>
104
+ <th>Name</th>
105
+ <th>Description</th>
106
+ <th>Capability</th>
107
+ <th>Rows count</th>
108
+ </tr>
109
+ </thead>
110
+ ";
111
+
112
+ $arr_logger_slugs = array();
113
+ foreach ( $this->sh->getInstantiatedLoggers() as $oneLogger ) {
114
+ $arr_logger_slugs[] = $oneLogger["instance"]->slug;
115
+ }
116
+
117
+ $sql_logger_counts = sprintf('
118
+ SELECT logger, count(id) as count
119
+ FROM %1$s
120
+ WHERE logger IN ("%2$s")
121
+ GROUP BY logger
122
+ ORDER BY count DESC
123
+ ', $table_name, join($arr_logger_slugs, '","'));
124
+
125
+ $logger_rows_count = $wpdb->get_results( $sql_logger_counts, OBJECT_K );
126
+
127
+ $loopnum = 0;
128
+ // foreach ( $logger_rows_count as $one_logger_count ) {
129
+ foreach ( $arr_logger_slugs as $one_logger_slug ) {
130
+
131
+ $logger = $this->sh->getInstantiatedLoggerBySlug( $one_logger_slug );
132
+
133
+ if ( ! $logger ) {
134
+ continue;
135
+ }
136
+
137
+ if ( isset( $logger_rows_count[ $one_logger_slug ] ) ) {
138
+ $one_logger_count = $logger_rows_count[ $one_logger_slug ];
139
+ } else {
140
+ // logger was not is sql result, so fake result
141
+ $one_logger_count = new stdclass;
142
+ $one_logger_count->count = 0;
143
+ }
144
+
145
+ $logger_info = $logger->getInfo();
146
+
147
+ printf(
148
+ '
149
+ <tr class="%6$s">
150
+ <td>%2$s</td>
151
+ <td>%3$s</td>
152
+ <td>%4$s</td>
153
+ <td>%5$s</td>
154
+ <td>%1$s</td>
155
+ </tr>
156
+ ',
157
+ $one_logger_count->count,
158
+ $one_logger_slug,
159
+ esc_html( $logger_info["name"]),
160
+ esc_html( $logger_info["description"]),
161
+ esc_html( $logger_info["capability"]),
162
+ $loopnum % 2 ? " alt " : "" // 6
163
+ );
164
+
165
+ $loopnum++;
166
+
167
+ }
168
+ echo "</table>";
169
+
170
+ ?>
171
+ </div><!-- // stats for geeks inner -->
templates/settings-statsInitiators.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Stats based by initiator
3
+
4
+ // Stats på level (notice, warning, debug, etc.)
5
+ $sql = sprintf('
6
+ SELECT
7
+ initiator,
8
+ count(initiator) as count
9
+ FROM %1$s
10
+ GROUP BY initiator
11
+ ORDER BY count DESC
12
+ ', $table_name
13
+ );
14
+
15
+ $level_counts = $wpdb->get_results($sql);
16
+
17
+ echo "<h3>Initiators</h3>";
18
+ echo "<table>";
19
+ echo "<tr>
20
+ <th>Initiator</th>
21
+ <th>Count</th>
22
+ </tr>";
23
+
24
+ foreach ( $level_counts as $row ) {
25
+
26
+ if ( empty($row->initiator) ) {
27
+ continue;
28
+ }
29
+
30
+ printf('
31
+ <tr>
32
+ <td>%1$s</td>
33
+ <td>%2$s</td>
34
+ </tr>
35
+ ',
36
+ $row->initiator,
37
+ $row->count
38
+ );
39
+
40
+ }
41
+
42
+ echo "</table>";
templates/settings-statsIntro.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Number of rows the last n days
4
+ function get_num_rows_last_n_days($period_days) {
5
+
6
+ global $wpdb;
7
+
8
+ $sql = sprintf(
9
+ 'select count(*) FROM %1$s WHERE UNIX_TIMESTAMP(date) >= %2$d',
10
+ $wpdb->prefix . SimpleHistory::DBTABLE,
11
+ strtotime("-$period_days days")
12
+ );
13
+
14
+ return $wpdb->get_var($sql);
15
+
16
+ }
17
+
18
+ printf(
19
+ __('<b>%1$s rows</b> have been logged the last <b>%2$s days</b>', "simple-history"),
20
+ get_num_rows_last_n_days($period_days),
21
+ $period_days
22
+ );
23
+
templates/settings-statsLogLevels.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Stats på level (notice, warning, debug, etc.)
4
+ echo "<h3>" . __("Log levels", "simple-history") . "</h3>";
5
+
6
+ echo "<p>" . __("Number of rows logged for each log level.", "simple-history") . "</p>";
7
+
8
+ /*
9
+ echo "<table>";
10
+ echo "<tr>
11
+ <th>Log level</th>
12
+ <th>Count</th>
13
+ </tr>";
14
+ */
15
+ $sql = sprintf('
16
+ SELECT
17
+ level,
18
+ count(level) as count
19
+ FROM %1$s
20
+ WHERE UNIX_TIMESTAMP(date) >= %2$d
21
+ GROUP BY level
22
+ ORDER BY count DESC
23
+ ',
24
+ $table_name, // 1
25
+ strtotime("-$period_days days") // 2
26
+ );
27
+
28
+
29
+ $level_counts = $wpdb->get_results($sql);
30
+
31
+ $arr_chart_data = array();
32
+ $arr_chart_labels = array();
33
+ $str_js_google_chart_data = '["Log level", "Count"], ';
34
+
35
+ foreach ( $level_counts as $row ) {
36
+
37
+ if ( empty($row->level) ) {
38
+ continue;
39
+ }
40
+
41
+ /*
42
+ printf('
43
+ <tr>
44
+ <td>%1$s</td>
45
+ <td>%2$s</td>
46
+ </tr>
47
+ ',
48
+ $row->level,
49
+ $row->count
50
+ );
51
+ */
52
+
53
+ $arr_chart_data[] = $row->count;
54
+ $arr_chart_labels[] = $row->level;
55
+
56
+ $str_js_google_chart_data .= sprintf(
57
+ '["%1$s", %2$d], ',
58
+ $row->level,
59
+ $row->count
60
+ );
61
+
62
+ }
63
+
64
+ $str_js_google_chart_data = rtrim($str_js_google_chart_data, ", ");
65
+
66
+ echo "</table>";
67
+
68
+ echo "<div class='SimpleHistoryChart__logLevelsPie'></div>";
69
+
70
+ ?>
71
+ <script>
72
+
73
+ /**
74
+ * Bar chart with log levels
75
+ */
76
+ /*
77
+ jQuery(function($) {
78
+
79
+ var data = {
80
+ labels: ["<?php echo implode('", "', $arr_chart_labels) ?>"],
81
+ series: [
82
+ [<?php echo implode(",", $arr_chart_data) ?>]
83
+ ]
84
+ };
85
+
86
+ var options = {
87
+ };
88
+
89
+ Chartist.Bar(".SimpleHistoryChart__logLevels", data, options);
90
+
91
+ });
92
+ */
93
+
94
+ jQuery(function($) {
95
+ var data = google.visualization.arrayToDataTable([
96
+ <?php echo $str_js_google_chart_data ?>
97
+ ]);
98
+
99
+ var options = {
100
+ xtitle: 'My Daily Activities',
101
+ backgroundColor: "transparent",
102
+ is3D: true,
103
+ legend: {
104
+ xposition: 'top',
105
+ alignment: 'center'
106
+ }
107
+ };
108
+
109
+ var chart = new google.visualization.PieChart( $(".SimpleHistoryChart__logLevelsPie").get(0) );
110
+
111
+ chart.draw(data, options);
112
+ });
113
+
114
+ </script>
templates/settings-statsLoggers.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ echo "<h4 class=''>";
4
+ echo __("Loggers", "simple-history");
5
+ echo "</h4>";
6
+
7
+ #echo '<div class="SimpleHistoryChart__loggersPie"></div>';
8
+ echo '<div class="SimpleHistoryChart__loggersPieGoogleChart"></div>';
9
+ #echo '<div class="SimpleHistoryChart__loggersGoogleBarChart"></div>';
10
+
11
+ $arr_logger_slugs = array();
12
+ foreach ( $this->sh->getInstantiatedLoggers() as $oneLogger ) {
13
+ $arr_logger_slugs[] = $oneLogger["instance"]->slug;
14
+ }
15
+
16
+ $sql_logger_counts = sprintf('
17
+ SELECT logger, count(id) as count
18
+ FROM %1$s
19
+ WHERE
20
+ logger IN ("%2$s")
21
+ AND UNIX_TIMESTAMP(date) >= %3$d
22
+ GROUP BY logger
23
+ ORDER BY count DESC
24
+ ',
25
+ $table_name, // 1
26
+ join($arr_logger_slugs, '","'), // 2
27
+ strtotime("-$period_days days")
28
+ );
29
+
30
+ $logger_rows_count = $wpdb->get_results( $sql_logger_counts );
31
+ #sf_d($logger_rows_count);
32
+ $str_js_chart_labels = "";
33
+ $str_js_chart_data = "";
34
+ $str_js_chart_data_chartist = "";
35
+ $str_js_google_chart_data = "['Logger name', 'Logged rows'],";
36
+ $i = 0;
37
+
38
+ //shuffle($arr_colors);
39
+ $max_loggers_in_chart = sizeof( $arr_colors );
40
+
41
+ foreach ( $logger_rows_count as $one_logger_count ) {
42
+
43
+ $logger = $this->sh->getInstantiatedLoggerBySlug( $one_logger_count->logger );
44
+
45
+ if ( ! $logger) {
46
+ continue;
47
+ }
48
+
49
+ if ($i > $max_loggers_in_chart) {
50
+ break;
51
+ }
52
+
53
+ $logger_info = $logger->getInfo();
54
+
55
+ $str_js_chart_data .= sprintf(
56
+ '
57
+ {
58
+ value: %1$d,
59
+ color:"#%3$s",
60
+ label: "%2$s"
61
+ },',
62
+ $one_logger_count->count, // 1
63
+ $logger_info["name"], // 2
64
+ $arr_colors[$i] // 3
65
+ );
66
+
67
+ $str_js_chart_data_chartist .= sprintf(
68
+ '%1$d,',
69
+ $one_logger_count->count // 1
70
+ );
71
+
72
+ $str_js_chart_labels .= sprintf(
73
+ '"%1$s",',
74
+ $logger_info["name"]
75
+ );
76
+
77
+ $str_js_google_chart_data .= sprintf(
78
+ '["%1$s", %2$d], ',
79
+ $logger_info["name"], // 1
80
+ $one_logger_count->count // 2
81
+ );
82
+
83
+ $i++;
84
+
85
+ }
86
+ $str_js_chart_data = rtrim($str_js_chart_data, ",");
87
+ $str_js_chart_data_chartist = rtrim($str_js_chart_data_chartist, ",");
88
+ $str_js_chart_labels = rtrim($str_js_chart_labels, ",");
89
+ $str_js_google_chart_data = rtrim($str_js_google_chart_data, ",");
90
+
91
+ ?>
92
+ <script>
93
+
94
+ /**
95
+ * Pie chart with loggers distribution
96
+ */
97
+ jQuery(function($) {
98
+
99
+ /*
100
+ var data = {
101
+ series: [<?php echo $str_js_chart_data_chartist ?>],
102
+ labels: [<?php echo $str_js_chart_labels ?>]
103
+ };
104
+
105
+ var options = {};
106
+
107
+ Chartist.Pie(".SimpleHistoryChart__loggersPie", data, options);
108
+ */
109
+
110
+ var data = google.visualization.arrayToDataTable([
111
+ <?php echo $str_js_google_chart_data ?>
112
+ ]);
113
+
114
+ var options = {
115
+ xtitle: 'My Daily Activities',
116
+ backgroundColor: "transparent",
117
+ is3D: true,
118
+ legend: {
119
+ xposition: 'top',
120
+ alignment: 'center'
121
+ }
122
+
123
+ };
124
+
125
+ var chart = new google.visualization.PieChart( $(".SimpleHistoryChart__loggersPieGoogleChart").get(0) );
126
+ chart.draw(data, options);
127
+
128
+ //var chart2 = new google.visualization.BarChart( $(".SimpleHistoryChart__loggersGoogleBarChart").get(0) );
129
+ //chart2.draw(data, options);
130
+
131
+ });
132
+
133
+ </script>
templates/settings-statsRowsPerDay.php ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ echo "<h4 class=''>";
4
+ echo __("Rows per day", "simple-history");
5
+ echo "</h4>";
6
+
7
+ $sql = sprintf(
8
+ '
9
+ SELECT
10
+ date_format(date, "%%Y-%%m-%%d") AS yearDate,
11
+ count(date) AS count
12
+ FROM
13
+ %1$s
14
+ WHERE UNIX_TIMESTAMP(date) >= %2$d
15
+ GROUP BY yearDate
16
+ ORDER BY yearDate ASC
17
+ ',
18
+ $wpdb->prefix . SimpleHistory::DBTABLE,
19
+ strtotime("-$period_days days")
20
+ );
21
+
22
+ $dates = $wpdb->get_results( $sql );
23
+
24
+ #echo '<div class="SimpleHistoryChart__rowsPerDay"></div>';
25
+ echo '<div class="SimpleHistoryChart__rowsPerDayGoogleChart"></div>';
26
+
27
+ // Loop from $period_start_date to $period_end_date
28
+ $interval = DateInterval::createFromDateString('1 day');
29
+ $period = new DatePeriod($period_start_date, $interval, $period_end_date->add( date_interval_create_from_date_string('1 days') ) );
30
+ $str_js_chart_labels = "";
31
+ $str_js_chart_data = "";
32
+ $str_js_google_chart_data = "";
33
+
34
+ foreach ( $period as $dt ) {
35
+
36
+ $datef = _x( 'M j', "stats: date in rows per day chart", "simple-history" );
37
+ $str_date = date_i18n( $datef, $dt->getTimestamp() );
38
+
39
+ $str_js_chart_labels .= sprintf(
40
+ '"%1$s",',
41
+ $str_date
42
+ );
43
+
44
+ // Get data for this day, if exist
45
+ // Day in object is in format '2014-09-07'
46
+ $day_data = wp_filter_object_list( $dates, array("yearDate" => $dt->format( "Y-m-d" )) );
47
+ $day_data_value = 0;
48
+ if ( $day_data ) {
49
+ $day_data_value = (int) current($day_data)->count;
50
+ }
51
+
52
+ $str_js_chart_data .= sprintf(
53
+ '%1$s,',
54
+ $day_data_value
55
+ );
56
+
57
+ $str_js_google_chart_data .= sprintf(
58
+ '["%2$s", %1$d], ',
59
+ $day_data_value, // 1
60
+ $str_date // 2
61
+ );
62
+
63
+ }
64
+
65
+ $str_js_chart_labels = rtrim($str_js_chart_labels, ",");
66
+ $str_js_chart_data = rtrim($str_js_chart_data, ",");
67
+ $str_js_google_chart_data = rtrim($str_js_google_chart_data, ",");
68
+
69
+ ?>
70
+
71
+ <script>
72
+
73
+ /**
74
+ * Bar chart with rows per day
75
+ */
76
+ jQuery(function($) {
77
+
78
+ /*
79
+ var data = {
80
+ // A labels array that can contain any sort of values
81
+ labels: [<?php echo $str_js_chart_labels ?>],
82
+ // Our series array that contains series objects or in this case series data arrays
83
+ series: [
84
+ [<?php echo $str_js_chart_data ?>]
85
+ ]
86
+ };
87
+
88
+ var options = {
89
+ // the name of the dates at bottom
90
+ axisX: {
91
+ // If the axis grid should be drawn or not
92
+ showGrid: false,
93
+ // Interpolation function that allows you to intercept the value from the axis label
94
+ labelInterpolationFnc: function(value, i) {
95
+
96
+ // If it's the last value then always show
97
+ if (i === data.series[0].length-1) {
98
+ return value;
99
+ }
100
+
101
+ // only return every n value
102
+ if ( i % 7 ) {
103
+ return "";
104
+ }
105
+
106
+ return value;
107
+ }
108
+ }
109
+ };
110
+
111
+ Chartist.Bar(".SimpleHistoryChart__rowsPerDay", data, options);
112
+ */
113
+
114
+ // Google Bar Chart
115
+
116
+ var data = google.visualization.arrayToDataTable([
117
+ ['Date', 'Number of rows'],
118
+ <?php echo $str_js_google_chart_data ?>
119
+ ]);
120
+
121
+ var options = {
122
+ xtitle: 'Company Performance',
123
+ xhAxis: {
124
+ title: 'Year',
125
+ titleTextStyle: {
126
+ color: 'red'
127
+ }
128
+ },
129
+ xlegend: { position: "none" },
130
+ backgroundColor: "transparent",
131
+ xchartArea: { left: 0, width: "80%" },
132
+ xchartArea2: {'width': '100%', 'xheight': '80%'},
133
+ xxlegend: {'position': 'bottom'},
134
+ legend: {
135
+ xposition: 'top',
136
+ alignment: 'center'
137
+ }
138
+
139
+
140
+ };
141
+
142
+ var chart = new google.visualization.LineChart( $(".SimpleHistoryChart__rowsPerDayGoogleChart").get(0) );
143
+
144
+ chart.draw(data, options);
145
+
146
+ });
147
+
148
+ </script>
templates/settings-statsUsers.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Output users
4
+ echo "<h3>" . __("Users", "simple-history") . "</h3>";
5
+
6
+ echo "<p>" . __("Number of logged items for the 5 users with most logged rows.", "simple-history") . "</p>";
7
+ echo "<p>" . __("Deleted users are also included.", "simple-history") . "</p>";
8
+
9
+ $sql_users = sprintf('
10
+ SELECT
11
+ DISTINCT value as user_id,
12
+ wp_users.*
13
+ FROM %1$s AS c
14
+ LEFT JOIN wp_users ON wp_users.id = c.value
15
+ WHERE c.key = "_user_id"
16
+ GROUP BY c.value
17
+ ',
18
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS // 1
19
+ );
20
+
21
+ $user_results = $wpdb->get_results($sql_users);
22
+ #sf_d($user_results);
23
+ #printf('<p>Total %1$s users found.</p>', sizeof( $user_results ));
24
+
25
+ echo "<table class='widefat' cellpadding=2>";
26
+ echo "<thead><tr>
27
+ <th></th>
28
+ <th>User ID</th>
29
+ <th>login</th>
30
+ <th>email</th>
31
+ <th>logged items</th>
32
+ <th>deleted</th>
33
+ </tr></thead>";
34
+
35
+ $arr_users = array();
36
+ foreach ($user_results as $one_user_result) {
37
+
38
+ $user_id = $one_user_result->user_id;
39
+ if ( empty( $user_id ) ) {
40
+ continue;
41
+ }
42
+
43
+ $str_deleted = empty($one_user_result->user_login) ? "yes" : "";
44
+
45
+ // get number of rows this user is responsible for
46
+ if ($user_id) {
47
+
48
+ $sql_user_count = sprintf('
49
+ SELECT count(VALUE) AS count
50
+ FROM %1$s AS c
51
+ INNER JOIN %2$s AS h ON h.id = c.history_id
52
+ WHERE c.key = "_user_id"
53
+ AND c.value = %3$s
54
+ AND UNIX_TIMESTAMP(h.date) >= %4$s
55
+ ',
56
+ $wpdb->prefix . SimpleHistory::DBTABLE_CONTEXTS, // 1
57
+ $wpdb->prefix . SimpleHistory::DBTABLE, // 2
58
+ $user_id, // 3
59
+ strtotime("-$period_days days") // 4
60
+ );
61
+
62
+ $user_rows_count = $wpdb->get_var( $sql_user_count );
63
+
64
+ }
65
+
66
+ $arr_users[] = array(
67
+ "user_id" => $user_id,
68
+ "user_login" => $one_user_result->user_login,
69
+ "user_email" => $one_user_result->user_email, // 3
70
+ "str_deleted" => $str_deleted,
71
+ "user_rows_count" => $user_rows_count
72
+ );
73
+
74
+ }
75
+
76
+ // order users by count
77
+ usort($arr_users, function($a, $b) {
78
+ return $a["user_rows_count"] < $b["user_rows_count"];
79
+ });
80
+
81
+ // only keep the top 10
82
+ $arr_users = array_slice($arr_users, 0, 5);
83
+
84
+ $loopnum = 0;
85
+ foreach ($arr_users as $one_user) {
86
+
87
+ printf('
88
+ <tr class="%6$s">
89
+ <td>%7$s</td>
90
+ <td>%1$s</td>
91
+ <td>%2$s</td>
92
+ <td>%3$s</td>
93
+ <td>%5$s</td>
94
+ <td>%4$s</td>
95
+ </tr>
96
+ ',
97
+ $one_user["user_id"],
98
+ $one_user["user_login"],
99
+ $one_user["user_email"], // 3
100
+ $one_user["str_deleted"],
101
+ $one_user["user_rows_count"],
102
+ $loopnum % 2 ? " alternate " : "", // 6
103
+ $this->sh->get_avatar( $one_user["user_email"], 38 ) // 7
104
+ );
105
+
106
+ $loopnum++;
107
+
108
+ }
109
+
110
+ echo "</table>";
111
+
templates/settings-style-example.php ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="SimpleHistoryGuiExample">
2
+
3
+ <ul class="SimpleHistoryLogitems">
4
+
5
+ <li data-row-id="665" data-occasions-count="0" data-occasions-id="8cdab45b0f40a0c9ffea63683e6edd8a" class="SimpleHistoryLogitem SimpleHistoryLogitem--loglevel-info SimpleHistoryLogitem--logger-SimpleMediaLogger SimpleHistoryLogitem--initiator-wp_user">
6
+
7
+ <div class="SimpleHistoryLogitem__firstcol">
8
+ <div class="SimpleHistoryLogitem__senderImage">
9
+ <img src="http://0.gravatar.com/avatar/eabcdc5ce4112ee4bceff4d7567d43a5?s=38&amp;d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D38&amp;r=G" class="avatar avatar-38 photo" height="38" width="38">
10
+ </div>
11
+ </div>
12
+
13
+ <div class="SimpleHistoryLogitem__secondcol">
14
+
15
+ <div class="SimpleHistoryLogitem__header">
16
+ <strong class="SimpleHistoryLogitem__inlineDivided">Jessie</strong>
17
+ <span class="SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__headerEmail">admin@example.com</span>
18
+ <span class="SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided"><a class="" href="http://playground-root.ep/wp-admin/index.php?page=simple_history_page#item/665"><time datetime="2014-08-11T21:08:44+00:00" title="2014-08-11T21:08:44+00:00" class="">1 min ago</time></a></span>
19
+ </div>
20
+
21
+ <div class="SimpleHistoryLogitem__text">
22
+ Short message descriping the thing that happened.
23
+ </div>
24
+
25
+ <div class="SimpleHistoryLogitem__details">
26
+
27
+ <p>More information about the event goes here. Add links, tables, text, lists, etc.</p>
28
+
29
+ <p>Some build in styles you can use:</p>
30
+
31
+ <p>
32
+ <a href="http://playground-root.ep/wp-admin/post.php?post=25097&amp;action=edit&amp;lang=en">
33
+ <div class="SimpleHistoryLogitemThumbnail">
34
+ <img src="http://placehold.it/250x250&text=Image">
35
+ </div>
36
+ </a>
37
+ </p>
38
+
39
+ <p>The <code>inlineDivided</code> class is used to group short pieces of information together, for example meta data:</p>
40
+
41
+ <p>
42
+ <span class="SimpleHistoryLogitem__inlineDivided">34 kB</span>
43
+ <span class="SimpleHistoryLogitem__inlineDivided">PNG</span>
44
+ <span class="SimpleHistoryLogitem__inlineDivided">420 × 420</span>
45
+ </p>
46
+
47
+ <p>
48
+ <span class="SimpleHistoryLogitem__inlineDivided"><em>Filesize</em> 34 kB</span>
49
+ <span class="SimpleHistoryLogitem__inlineDivided"><em>Format</em> PNG</span>
50
+ <span class="SimpleHistoryLogitem__inlineDivided"><em>Dimensions</em> 420 × 420</span>
51
+ </p>
52
+
53
+ <p>Tables can be used if you have more data to show, like the meta data for a plugin:</p>
54
+
55
+ <table class="SimpleHistoryLogitem__keyValueTable">
56
+ <tbody>
57
+ <tr>
58
+ <td>Author</td>
59
+ <td><a href="http://bbpress.org">The bbPress Community</a>
60
+ </td>
61
+ </tr>
62
+ <tr>
63
+ <td>URL</td>
64
+ <td><a href="http://bbpress.org">http://bbpress.org</a>
65
+ </td>
66
+ </tr>
67
+ <tr>
68
+ <td>Version</td>
69
+ <td>2.5.4</td>
70
+ </tr>
71
+ <tr>
72
+ <td>Updated</td>
73
+ <td>2014-07-15</td>
74
+ </tr>
75
+ <tr>
76
+ <td>Requires</td>
77
+ <td>3.6</td>
78
+ </tr>
79
+ <tr>
80
+ <td>Compatible up to</td>
81
+ <td>3.9.2</td>
82
+ </tr>
83
+ <tr>
84
+ <td>Downloads</td>
85
+ <td>1,392,515</td>
86
+ </tr>
87
+ </tbody>
88
+ </table>
89
+
90
+ <p>
91
+ <span class="SimpleHistoryLogitem__inlineDivided">
92
+ <em>Author:</em>
93
+ <a href="http://bbpress.org">The bbPress Community</a>
94
+ </span>
95
+
96
+ <span class="SimpleHistoryLogitem__inlineDivided">
97
+ <em>URL</em>
98
+ <a href="http://bbpress.org">http://bbpress.org</a>
99
+ </span>
100
+
101
+ <span class="SimpleHistoryLogitem__inlineDivided">
102
+ <em>Version:</em>
103
+ 2.5.4
104
+ </span>
105
+
106
+ <span class="SimpleHistoryLogitem__inlineDivided">
107
+ <em>Updated</em>
108
+ 2014-07-15
109
+ </span>
110
+ <span class="SimpleHistoryLogitem__inlineDivided">
111
+ <em>Requires</em>
112
+ 3.6
113
+ </span>
114
+ <span class="SimpleHistoryLogitem__inlineDivided">
115
+ <em>Compatible up to</em>
116
+ 3.9.2
117
+ </span>
118
+ <span class="SimpleHistoryLogitem__inlineDivided">
119
+ <em>Downloads</em>
120
+ 1,392,515
121
+ </span>
122
+ </p>
123
+
124
+ </div>
125
+
126
+ </div>
127
+ </li>
128
+
129
+ <?php
130
+ // All debug levels
131
+ $template = '
132
+ <li class="SimpleHistoryLogitem SimpleHistoryLogitem--loglevel-%1$s SimpleHistoryLogitem--logger-SimpleMediaLogger SimpleHistoryLogitem--initiator-wp_user">
133
+
134
+ <div class="SimpleHistoryLogitem__firstcol">
135
+ <div class="SimpleHistoryLogitem__senderImage">
136
+ <img src="http://0.gravatar.com/avatar/eabcdc5ce4112ee4bceff4d7567d43a5?s=38&amp;d=http%%3A%%2F%%2F0.gravatar.com%%2Favatar%%2Fad516503a11cd5ca435acc9bb6523536%%3Fs%%3D38&amp;r=G" class="avatar avatar-38 photo" height="38" width="38">
137
+ </div>
138
+ </div>
139
+
140
+ <div class="SimpleHistoryLogitem__secondcol">
141
+
142
+ <div class="SimpleHistoryLogitem__header">
143
+ <strong class="SimpleHistoryLogitem__inlineDivided">Jessie</strong>
144
+ <span class="SimpleHistoryLogitem__inlineDivided SimpleHistoryLogitem__headerEmail">admin@example.com</span>
145
+ <span class="SimpleHistoryLogitem__permalink SimpleHistoryLogitem__when SimpleHistoryLogitem__inlineDivided"><a class="" href="http://playground-root.ep/wp-admin/index.php?page=simple_history_page#item/665"><time datetime="2014-08-11T21:08:44+00:00" title="2014-08-11T21:08:44+00:00" class="">1 min ago</time></a></span>
146
+ </div>
147
+
148
+ <div class="SimpleHistoryLogitem__text">
149
+ %2$s
150
+ <span class="SimpleHistoryLogitem--logleveltag SimpleHistoryLogitem--logleveltag-%1$s">%1$s</span>
151
+ </div>
152
+
153
+ <!-- <div class="SimpleHistoryLogitem__details">
154
+
155
+ <p>Optional more information....</p>
156
+
157
+ </div> -->
158
+
159
+ </div>
160
+
161
+ </li>
162
+ ';
163
+
164
+ $arr_messages = array(
165
+ "emergency" => "Harddrive on VPS 1 has errors",
166
+ "alert" => "The WordPress installation on VPS 2 is running out of memory",
167
+ "critical" => "There is 21 security updates available for your site",
168
+ "error" => "A JavaScript error was detected on page <code>example.com/about-us/contact/</code>",
169
+ "warning" => "A user attempted to login to your site with username \"admin\"",
170
+ "notice" => "User Jessie logged in",
171
+ "info" => "Page \"about us\" was updated",
172
+ "debug" => "The variable <code>\$heyhey</code> had value <code>'abc123'</code> and the hash of the user values is <code>'1f3870be274f6c49b3e31a0c6728957f'</code>",
173
+ );
174
+
175
+ $refl = new ReflectionClass('SimpleLoggerLogLevels');
176
+ foreach ( $refl->getConstants() as $key => $val ) {
177
+
178
+ $msg = isset($arr_messages[$val]) ? $arr_messages[$val] : "This is a message with loglevel";
179
+ echo sprintf($template, $val, $msg);
180
+
181
+ }
182
+
183
+ ?>
184
+
185
+ </ul>
186
+ </div>
uninstall.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
  /**
3
  * File that is run during plugin uninstall (not just de-activate)
 
4
  */
5
 
6
  // If uninstall not called from WordPress exit
1
  <?php
2
  /**
3
  * File that is run during plugin uninstall (not just de-activate)
4
+ * @TODO: delete all tables in network if on multisite
5
  */
6
 
7
  // If uninstall not called from WordPress exit