MainWP Child Reports - Version 2.0

Version Description

  • 12-9-2019 =
  • Added: support for the Pro Reports extension
  • Updated: plugin functionality for better performance
Download this release

Release Info

Developer mainwp
Plugin Icon 128x128 MainWP Child Reports
Version 2.0
Comparing to
See all releases

Code changes from version 1.9.3 to 2.0

Files changed (103) hide show
  1. alerts/class-alert-trigger-action.php +193 -0
  2. alerts/class-alert-trigger-author.php +176 -0
  3. alerts/class-alert-trigger-context.php +225 -0
  4. alerts/class-alert-type-die.php +45 -0
  5. alerts/class-alert-type-email.php +205 -0
  6. alerts/class-alert-type-highlight.php +337 -0
  7. alerts/class-alert-type-ifttt.php +327 -0
  8. alerts/class-alert-type-menu-alert.php +187 -0
  9. alerts/class-alert-type-none.php +41 -0
  10. alerts/class-alert-type-slack.php +303 -0
  11. alerts/js/alert-type-highlight.js +89 -0
  12. classes/class-admin.php +1057 -0
  13. classes/class-alert-trigger.php +95 -0
  14. classes/class-alert-type.php +78 -0
  15. classes/class-alert.php +339 -0
  16. classes/class-alerts-list.php +400 -0
  17. classes/class-alerts.php +818 -0
  18. classes/class-author.php +285 -0
  19. classes/class-cli.php +234 -0
  20. classes/class-connector.php +307 -0
  21. classes/class-connectors.php +216 -0
  22. includes/date-interval.php → classes/class-date-interval.php +117 -136
  23. classes/class-db-driver-wpdb.php +168 -0
  24. classes/class-db-driver.php +53 -0
  25. classes/class-db.php +237 -0
  26. classes/class-export.php +241 -0
  27. classes/class-exporter.php +36 -0
  28. includes/filter-input.php → classes/class-filter-input.php +116 -120
  29. classes/class-form-generator.php +214 -0
  30. classes/class-install.php +515 -0
  31. classes/class-list-table.php +1077 -0
  32. classes/class-live-update.php +219 -0
  33. classes/class-log.php +339 -0
  34. classes/class-mainwp-child-report-helper.php +324 -0
  35. classes/class-network.php +526 -0
  36. classes/class-plugin.php +257 -0
  37. classes/class-preview-list-table.php +70 -0
  38. classes/class-query.php +301 -0
  39. classes/class-record.php +109 -0
  40. classes/class-settings.php +1153 -0
  41. classes/class-uninstall.php +229 -0
  42. classes/debug.log +42 -0
  43. connectors/backupbuddy.php +0 -41
  44. connectors/backupwordpress.php +0 -41
  45. connectors/backwpup.php +0 -76
  46. connectors/class-connector-acf.php +567 -0
  47. connectors/class-connector-bbpress.php +247 -0
  48. connectors/class-connector-blogs.php +376 -0
  49. connectors/class-connector-buddypress.php +868 -0
  50. connectors/class-connector-comments.php +649 -0
  51. connectors/class-connector-edd.php +540 -0
  52. connectors/class-connector-editor.php +328 -0
  53. connectors/class-connector-gravityforms.php +844 -0
  54. connectors/class-connector-installer.php +606 -0
  55. connectors/class-connector-jetpack.php +713 -0
  56. connectors/class-connector-mainwp-backups.php +143 -0
  57. connectors/class-connector-mainwp-maintenance.php +48 -0
  58. connectors/class-connector-mainwp-sucuri.php +102 -0
  59. connectors/class-connector-mainwp-wordfence.php +47 -0
  60. connectors/class-connector-media.php +242 -0
  61. connectors/class-connector-menus.php +248 -0
  62. connectors/class-connector-posts.php +412 -0
  63. connectors/class-connector-settings.php +848 -0
  64. connectors/class-connector-taxonomies.php +258 -0
  65. connectors/class-connector-user-switching.php +239 -0
  66. connectors/class-connector-users.php +381 -0
  67. connectors/class-connector-widgets.php +849 -0
  68. connectors/class-connector-woocommerce.php +841 -0
  69. connectors/class-connector-wordpress-seo.php +527 -0
  70. connectors/comments.php +0 -320
  71. connectors/editor.php +0 -140
  72. connectors/installer.php +0 -528
  73. connectors/maintenance.php +0 -41
  74. connectors/media.php +0 -153
  75. connectors/menus.php +0 -96
  76. connectors/posts.php +0 -257
  77. connectors/updraftplus.php +0 -41
  78. connectors/users.php +0 -172
  79. connectors/widgets.php +0 -596
  80. connectors/wordfence.php +0 -41
  81. connectors/wptimecapsule.php +0 -76
  82. exporters/class-exporter-csv.php +42 -0
  83. exporters/class-exporter-json.php +44 -0
  84. includes/admin.php +0 -560
  85. includes/class-wp-stream-author.php +0 -176
  86. includes/connector.php +0 -212
  87. includes/connectors.php +0 -169
  88. includes/context-query.php +0 -132
  89. includes/dashboard.php +0 -92
  90. includes/db-updates.php +120 -0
  91. includes/db.php +0 -191
  92. includes/feeds/atom.php +54 -0
  93. includes/feeds/json.php +7 -0
  94. includes/feeds/rss-2.0.php +65 -0
  95. includes/functions.php +139 -30
  96. includes/install.php +0 -376
  97. includes/lib/Carbon.php +2214 -0
  98. includes/list-table.php +0 -679
  99. includes/live-update.php +0 -141
  100. includes/log.php +0 -86
  101. includes/network.php +0 -370
  102. includes/query.php +0 -432
  103. includes/settings.php +0 -647
alerts/class-alert-trigger-action.php ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Trigger on an Action.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Trigger_Action
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Trigger_Action extends Alert_Trigger {
16
+
17
+ /**
18
+ * Unique identifier
19
+ *
20
+ * @var string
21
+ */
22
+ public $slug = 'action';
23
+
24
+ /**
25
+ * Meta key used in forms.
26
+ *
27
+ * @var string
28
+ */
29
+ public $meta_key = 'trigger_action';
30
+
31
+ /**
32
+ * Field key used in database
33
+ *
34
+ * @var string
35
+ */
36
+ public $field_key = 'wp_mainwp_stream_trigger_action';
37
+
38
+ /**
39
+ * Checks if a record matches the criteria from the trigger.
40
+ *
41
+ * @see Alert_Trigger::check_record().
42
+ *
43
+ * @param bool $success Status of previous checks.
44
+ * @param int $record_id Record ID.
45
+ * @param array $recordarr Record data.
46
+ * @param Alert $alert The Alert being worked on.
47
+ *
48
+ * @return bool False on failure, otherwise should return original value of $success.
49
+ */
50
+ public function check_record( $success, $record_id, $recordarr, $alert ) {
51
+ if ( ! empty( $alert->alert_meta['trigger_action'] ) && $recordarr['action'] !== $alert->alert_meta['trigger_action'] ) {
52
+ return false;
53
+ }
54
+
55
+ return $success;
56
+ }
57
+
58
+ /**
59
+ * Adds fields to the trigger form.
60
+ *
61
+ * @see Alert_Trigger::add_fields().
62
+ *
63
+ * @param Form_Generator $form The Form Object to add to.
64
+ * @param Alert $alert The Alert being worked on.
65
+ *
66
+ * @return void
67
+ */
68
+ public function add_fields( $form, $alert = array() ) {
69
+ $value = '';
70
+ if ( is_object( $alert ) && ! empty( $alert->alert_meta['trigger_action'] ) ) {
71
+ $value = $alert->alert_meta['trigger_action'];
72
+ }
73
+
74
+ $args = array(
75
+ 'name' => esc_attr( $this->field_key ),
76
+ 'value' => esc_attr( $value ),
77
+ 'options' => $this->get_values(),
78
+ 'classes' => 'wp_mainwp_stream_ajax_forward',
79
+ 'data' => array(
80
+ 'placeholder' => __( 'Any Action', 'mainwp-child-reports' ),
81
+ ),
82
+ );
83
+ $form->add_field( 'select2', $args );
84
+ }
85
+
86
+ /**
87
+ * Validate and save Alert object
88
+ *
89
+ * @see Alert_Trigger::save_fields().
90
+ *
91
+ * @param Alert $alert The Alert being worked on.
92
+ *
93
+ * @return void
94
+ */
95
+ public function save_fields( $alert ) {
96
+ $input = wp_mainwp_stream_filter_input( INPUT_POST, $this->field_key );
97
+ if ( array_key_exists( $input, $this->get_values( true ) ) ) {
98
+ $alert->alert_meta['trigger_action'] = $input;
99
+ } else {
100
+ $alert->alert_meta['trigger_action'] = '';
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Generate array of possible action values
106
+ *
107
+ * @param bool $flat If the array should be multidimensional.
108
+ *
109
+ * @return array
110
+ */
111
+ public function get_values( $flat = false ) {
112
+ $action_values = array();
113
+ foreach ( $this->get_terms_labels( 'action' ) as $action_id => $action_data ) {
114
+ if ( ! $flat ) {
115
+ $action_values[] = array(
116
+ 'id' => $action_id,
117
+ 'text' => $action_data,
118
+ );
119
+ } else {
120
+ $action_values[ $action_id ] = $action_data;
121
+ }
122
+ }
123
+
124
+ return $action_values;
125
+ }
126
+
127
+ /**
128
+ * Function will return all terms labels of given column
129
+ *
130
+ * @todo refactor Settings::get_terms_labels into general utility
131
+ *
132
+ * @param string $column string Name of the column.
133
+ *
134
+ * @return array
135
+ */
136
+ public function get_terms_labels( $column ) {
137
+ $return_labels = array();
138
+
139
+ if ( isset( $this->plugin->connectors->term_labels[ 'stream_' . $column ] ) ) {
140
+ if ( 'context' === $column && isset( $this->plugin->connectors->term_labels['stream_connector'] ) ) {
141
+ $connectors = $this->plugin->connectors->term_labels['stream_connector'];
142
+ $contexts = $this->plugin->connectors->term_labels['stream_context'];
143
+
144
+ foreach ( $connectors as $connector => $connector_label ) {
145
+ $return_labels[ $connector ]['label'] = $connector_label;
146
+ foreach ( $contexts as $context => $context_label ) {
147
+ if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context, $this->plugin->connectors->contexts[ $connector ] ) ) {
148
+ $return_labels[ $connector ]['children'][ $context ] = $context_label;
149
+ }
150
+ }
151
+ }
152
+ } else {
153
+ $return_labels = $this->plugin->connectors->term_labels[ 'stream_' . $column ];
154
+ }
155
+
156
+ ksort( $return_labels );
157
+ }
158
+
159
+ return $return_labels;
160
+ }
161
+
162
+ /**
163
+ * Returns the trigger's value for the given alert.
164
+ *
165
+ * @see Alert_Trigger::get_display_value().
166
+ *
167
+ * @param string $context The location this data will be displayed in.
168
+ * @param Alert $alert Alert being processed.
169
+ *
170
+ * @return string
171
+ */
172
+ public function get_display_value( $context = 'normal', $alert ) {
173
+ $action = ( ! empty( $alert->alert_meta['trigger_action'] ) ) ? $alert->alert_meta['trigger_action'] : null;
174
+
175
+ if ( 'post_title' === $context ) {
176
+ if ( empty( $action ) ) {
177
+ $action = __( 'performed any action on', 'mainwp-child-reports' );
178
+ }
179
+ } else {
180
+ if ( empty( $action ) ) {
181
+ $action = __( 'Any Action', 'mainwp-child-reports' );
182
+ } else {
183
+ $actions = $this->plugin->connectors->term_labels['stream_action'];
184
+ if ( ! empty( $actions[ $action ] ) ) {
185
+ $action = $actions[ $action ];
186
+ }
187
+ $action = ucfirst( $action );
188
+ }
189
+ }
190
+
191
+ return ucfirst( $action );
192
+ }
193
+ }
alerts/class-alert-trigger-author.php ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Trigger for an Author.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Trigger_Author
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Trigger_Author extends Alert_Trigger {
16
+
17
+ /**
18
+ * Unique identifier
19
+ *
20
+ * @var string
21
+ */
22
+ public $slug = 'author';
23
+
24
+ /**
25
+ * Field key used in database
26
+ *
27
+ * @var string
28
+ */
29
+ public $field_key = 'wp_mainwp_stream_trigger_author';
30
+
31
+ /**
32
+ * Checks if a record matches the criteria from the trigger.
33
+ *
34
+ * @see Alert_Trigger::check_record().
35
+ *
36
+ * @param bool $success Status of previous checks.
37
+ * @param int $record_id Record ID.
38
+ * @param array $recordarr Record data.
39
+ * @param Alert $alert The Alert being worked on.
40
+ *
41
+ * @return bool False on failure, otherwise should return original value of $success.
42
+ */
43
+ public function check_record( $success, $record_id, $recordarr, $alert ) {
44
+ if ( ! empty( $alert->alert_meta['trigger_author'] ) && intval( $alert->alert_meta['trigger_author'] ) !== intval( $recordarr['user_id'] ) ) {
45
+ return false;
46
+ }
47
+
48
+ return $success;
49
+ }
50
+
51
+ /**
52
+ * Adds fields to the trigger form.
53
+ *
54
+ * @see Alert_Trigger::add_fields().
55
+ *
56
+ * @param Form_Generator $form The Form Object to add to.
57
+ * @param Alert $alert The Alert being worked on.
58
+ *
59
+ * @return void
60
+ */
61
+ public function add_fields( $form, $alert = array() ) {
62
+ $value = '';
63
+ if ( is_object( $alert ) && ! empty( $alert->alert_meta['trigger_author'] ) ) {
64
+ $value = $alert->alert_meta['trigger_author'];
65
+ }
66
+
67
+ $args = array(
68
+ 'name' => esc_attr( $this->field_key ),
69
+ 'value' => esc_attr( $value ),
70
+ 'options' => $this->get_values(),
71
+ 'classes' => 'wp_mainwp_stream_ajax_forward',
72
+ 'data' => array(
73
+ 'placeholder' => __( 'Any Author', 'mainwp-child-reports' ),
74
+ ),
75
+ );
76
+ $form->add_field( 'select2', $args );
77
+ }
78
+
79
+ /**
80
+ * Generate array of possible action values
81
+ *
82
+ * @return array
83
+ */
84
+ public function get_values() {
85
+ $all_records = array();
86
+
87
+ $user_count = count_users();
88
+ $total_users = $user_count['total_users'];
89
+
90
+ if ( $total_users > $this->plugin->admin->preload_users_max ) {
91
+ return array();
92
+ }
93
+
94
+ $users = array_map(
95
+ function ( $user_id ) {
96
+ return new Author( $user_id );
97
+ },
98
+ get_users(
99
+ array(
100
+ 'fields' => 'ID',
101
+ )
102
+ )
103
+ );
104
+
105
+ if ( is_multisite() && is_super_admin() ) {
106
+ $super_admins = array_map(
107
+ function ( $login ) {
108
+ $user = get_user_by( 'login', $login );
109
+
110
+ return new Author( $user->ID );
111
+ },
112
+ get_super_admins()
113
+ );
114
+ $users = array_unique( array_merge( $users, $super_admins ) );
115
+ }
116
+
117
+ $user_meta = array(
118
+ 'is_wp_cli' => true,
119
+ );
120
+ $users[] = new Author( 0, $user_meta );
121
+
122
+ foreach ( $users as $user ) {
123
+ $all_records[] = array(
124
+ 'id' => $user->id,
125
+ 'value' => $user->id,
126
+ 'text' => $user->get_display_name(),
127
+ );
128
+ }
129
+
130
+ return $all_records;
131
+ }
132
+
133
+ /**
134
+ * Validate and save Alert object
135
+ *
136
+ * @see Alert_Trigger::save_fields().
137
+ *
138
+ * @param Alert $alert The Alert being worked on.
139
+ *
140
+ * @return void
141
+ */
142
+ public function save_fields( $alert ) {
143
+ $input = wp_mainwp_stream_filter_input( INPUT_POST, $this->field_key );
144
+ if ( array_key_exists( $input, $this->get_values( $alert ) ) ) {
145
+ $alert->alert_meta['trigger_author'] = $input;
146
+ } else {
147
+ $alert->alert_meta['trigger_author'] = '';
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Returns the trigger's value for the given alert.
153
+ *
154
+ * @see Alert_Trigger::get_display_value().
155
+ *
156
+ * @param string $context The location this data will be displayed in.
157
+ * @param Alert $alert Alert being processed.
158
+ *
159
+ * @return string
160
+ */
161
+ public function get_display_value( $context = 'normal', $alert ) {
162
+ $author = ( ! empty( $alert->alert_meta['trigger_author'] ) ) ? $alert->alert_meta['trigger_author'] : null;
163
+ if ( empty( $author ) ) {
164
+ $author = __( 'Any User', 'mainwp-child-reports' );
165
+ } elseif ( is_numeric( $author ) ) {
166
+ $author_data = get_userdata( $author );
167
+ if ( $author_data ) {
168
+ $author = $author_data->display_name;
169
+ } else {
170
+ $author = __( 'Unknown User', 'mainwp-child-reports' );
171
+ }
172
+ }
173
+
174
+ return ucfirst( $author );
175
+ }
176
+ }
alerts/class-alert-trigger-context.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Trigger on Context.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Trigger_Context
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Trigger_Context extends Alert_Trigger {
16
+
17
+ /**
18
+ * Unique identifier
19
+ *
20
+ * @var string
21
+ */
22
+ public $slug = 'context';
23
+
24
+ /**
25
+ * Field key used in database
26
+ *
27
+ * @var string
28
+ */
29
+ public $field_key = 'wp_mainwp_stream_trigger_context';
30
+
31
+ /**
32
+ * Checks if a record matches the criteria from the trigger.
33
+ *
34
+ * @see Alert_Trigger::check_record().
35
+ *
36
+ * @param bool $success Status of previous checks.
37
+ * @param int $record_id Record ID.
38
+ * @param array $recordarr Record data.
39
+ * @param Alert $alert The Alert being worked on.
40
+ *
41
+ * @return bool False on failure, otherwise should return original value of $success.
42
+ */
43
+ public function check_record( $success, $record_id, $recordarr, $alert ) {
44
+ if ( ! empty( $alert->alert_meta['trigger_connector'] ) && $recordarr['connector'] !== $alert->alert_meta['trigger_connector'] ) {
45
+ return false;
46
+ }
47
+ if ( ! empty( $alert->alert_meta['trigger_context'] ) && $recordarr['context'] !== $alert->alert_meta['trigger_context'] ) {
48
+ return false;
49
+ }
50
+
51
+ return $success;
52
+ }
53
+
54
+ /**
55
+ * Adds fields to the trigger form.
56
+ *
57
+ * @see Alert_Trigger::add_fields().
58
+ *
59
+ * @param Form_Generator $form The Form Object to add to.
60
+ * @param Alert $alert The Alert being worked on.
61
+ *
62
+ * @return void
63
+ */
64
+ public function add_fields( $form, $alert = array() ) {
65
+ $connector = '';
66
+ if ( is_object( $alert ) && ! empty( $alert->alert_meta['trigger_connector'] ) ) {
67
+ $connector = $alert->alert_meta['trigger_connector'];
68
+ }
69
+
70
+ $context = '';
71
+ if ( is_object( $alert ) && ! empty( $alert->alert_meta['trigger_context'] ) ) {
72
+ $context = $alert->alert_meta['trigger_context'];
73
+ }
74
+
75
+ // Context dropdown menu.
76
+ $context_values = array();
77
+
78
+ $form->add_field(
79
+ 'select2', array(
80
+ 'name' => 'wp_mainwp_stream_trigger_connector_or_context',
81
+ 'options' => $this->get_values(),
82
+ 'classes' => 'wp_mainwp_stream_ajax_forward connector_or_context',
83
+ 'data' => array(
84
+ 'placeholder' => __( 'Any Context', 'mainwp-child-reports' ),
85
+ ),
86
+ )
87
+ );
88
+
89
+ $form->add_field(
90
+ 'hidden', array(
91
+ 'name' => 'wp_mainwp_stream_trigger_connector',
92
+ 'value' => $connector,
93
+ 'classes' => 'connector wp_mainwp_stream_ajax_forward',
94
+ )
95
+ );
96
+
97
+ $form->add_field(
98
+ 'hidden', array(
99
+ 'name' => 'wp_mainwp_stream_trigger_context',
100
+ 'value' => $context,
101
+ 'classes' => 'context wp_mainwp_stream_ajax_forward',
102
+ )
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Validate and save Alert object
108
+ *
109
+ * @see Alert_Trigger::save_fields().
110
+ *
111
+ * @param Alert $alert The Alert being worked on.
112
+ *
113
+ * @return void
114
+ */
115
+ public function save_fields( $alert ) {
116
+ $alert->alert_meta['trigger_connector'] = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_connector' );
117
+ $alert->alert_meta['trigger_context'] = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_context' );
118
+ }
119
+
120
+ /**
121
+ * Generate array of possible action values
122
+ *
123
+ * @return array
124
+ */
125
+ public function get_values() {
126
+ $context_values = array();
127
+
128
+ foreach ( $this->get_terms_labels( 'context' ) as $context_id => $context_data ) {
129
+ if ( is_array( $context_data ) ) {
130
+ $child_values = array();
131
+ if ( isset( $context_data['children'] ) ) {
132
+ $child_values = array();
133
+ foreach ( $context_data['children'] as $child_id => $child_value ) {
134
+ $child_values[] = array(
135
+ 'value' => $context_id . '-' . $child_id,
136
+ 'id' => $context_id . '-' . $child_id,
137
+ 'text' => $child_value,
138
+ 'parent' => $context_id,
139
+ );
140
+ }
141
+ }
142
+ if ( isset( $context_data['label'] ) ) {
143
+ $context_values[] = array(
144
+ 'value' => $context_id,
145
+ 'id' => $context_id,
146
+ 'text' => $context_data['label'],
147
+ 'children' => $child_values,
148
+ );
149
+ }
150
+ } else {
151
+ $context_values[] = array(
152
+ 'value' => $context_id,
153
+ 'id' => $context_id,
154
+ 'text' => $context_data,
155
+ );
156
+ }
157
+ }
158
+
159
+ return $context_values;
160
+ }
161
+
162
+ /**
163
+ * Function will return all terms labels of given column
164
+ *
165
+ * @todo refactor Settings::get_terms_labels into general utility
166
+ *
167
+ * @param string $column string Name of the column.
168
+ *
169
+ * @return array
170
+ */
171
+ public function get_terms_labels( $column ) {
172
+ $return_labels = array();
173
+
174
+ if ( isset( $this->plugin->connectors->term_labels[ 'stream_' . $column ] ) ) {
175
+ if ( 'context' === $column && isset( $this->plugin->connectors->term_labels['stream_connector'] ) ) {
176
+ $connectors = $this->plugin->connectors->term_labels['stream_connector'];
177
+ $contexts = $this->plugin->connectors->term_labels['stream_context'];
178
+
179
+ foreach ( $connectors as $connector => $connector_label ) {
180
+ $return_labels[ $connector ]['label'] = $connector_label;
181
+ foreach ( $contexts as $context => $context_label ) {
182
+ if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context, $this->plugin->connectors->contexts[ $connector ] ) ) {
183
+ $return_labels[ $connector ]['children'][ $context ] = $context_label;
184
+ }
185
+ }
186
+ }
187
+ } else {
188
+ $return_labels = $this->plugin->connectors->term_labels[ 'stream_' . $column ];
189
+ }
190
+
191
+ ksort( $return_labels );
192
+ }
193
+
194
+ return $return_labels;
195
+ }
196
+
197
+ /**
198
+ * Returns the trigger's value for the given alert.
199
+ *
200
+ * @see Alert_Trigger::get_display_value().
201
+ *
202
+ * @param string $context The location this data will be displayed in.
203
+ * @param Alert $alert Alert being processed.
204
+ *
205
+ * @return string
206
+ */
207
+ public function get_display_value( $context = 'normal', $alert ) {
208
+ $context = ( ! empty( $alert->alert_meta['trigger_context'] ) ) ? $alert->alert_meta['trigger_context'] : null;
209
+ $connector = ( ! empty( $alert->alert_meta['trigger_connector'] ) ) ? $alert->alert_meta['trigger_connector'] : null;
210
+ if ( empty( $context ) && empty( $connector ) ) {
211
+ $context = __( 'Any Context', 'mainwp-child-reports' );
212
+ } else {
213
+ $term_labels = $this->get_terms_labels( 'context' );
214
+ if ( ! empty( $term_labels[ $connector ]['children'][ $context ] ) ) {
215
+ $context = $term_labels[ $connector ]['children'][ $context ];
216
+ } else {
217
+ if ( ! empty( $term_labels[ $connector ]['label'] ) ) {
218
+ $context = $term_labels[ $connector ]['label'];
219
+ }
220
+ }
221
+ }
222
+
223
+ return ucfirst( $context );
224
+ }
225
+ }
alerts/class-alert-type-die.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Used for debugging.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Type_Die
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Type_Die extends Alert_Type {
16
+
17
+ /**
18
+ * Alert type name
19
+ *
20
+ * @var string
21
+ */
22
+ public $name = 'Die Notifier';
23
+
24
+ /**
25
+ * Alert type slug
26
+ *
27
+ * @var string
28
+ */
29
+ public $slug = 'die';
30
+
31
+ /**
32
+ * Triggers a script exit when an alert is triggered. Debugging use only.
33
+ *
34
+ * @param int $record_id Record that triggered notification.
35
+ * @param array $recordarr Record details.
36
+ * @param array $options Alert options.
37
+ * @return void
38
+ */
39
+ public function alert( $record_id, $recordarr, $options ) {
40
+ echo '<pre>';
41
+ print_r( $recordarr ); // @codingStandardsIgnoreLine debug not loaded in production
42
+ echo '</pre>';
43
+ die( 'You have been notified!' );
44
+ }
45
+ }
alerts/class-alert-type-email.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email Alerts.
4
+ *
5
+ * Idea for future expansion: allow customization of email.
6
+ *
7
+ * @package WP_MainWP_Stream
8
+ */
9
+
10
+ namespace WP_MainWP_Stream;
11
+
12
+ /**
13
+ * Class Alert_Type_Email
14
+ *
15
+ * @package WP_MainWP_Stream
16
+ */
17
+ class Alert_Type_Email extends Alert_Type {
18
+
19
+ /**
20
+ * Alert type name
21
+ *
22
+ * @var string
23
+ */
24
+ public $name = 'Email';
25
+
26
+ /**
27
+ * Alert type slug
28
+ *
29
+ * @var string
30
+ */
31
+ public $slug = 'email';
32
+
33
+ /**
34
+ * Class Constructor
35
+ *
36
+ * @param Plugin $plugin Plugin object.
37
+ * @return void
38
+ */
39
+ public function __construct( $plugin ) {
40
+ parent::__construct( $plugin );
41
+ $this->plugin = $plugin;
42
+ if ( ! is_admin() ) {
43
+ return;
44
+ }
45
+ add_filter(
46
+ 'wp_mainwp_alerts_save_meta', array(
47
+ $this,
48
+ 'add_alert_meta',
49
+ ), 10, 2
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Sends an email to the given recipient.
55
+ *
56
+ * @param int $record_id Record that triggered notification.
57
+ * @param array $recordarr Record details.
58
+ * @param Alert $alert Alert options.
59
+ * @return void
60
+ */
61
+ public function alert( $record_id, $recordarr, $alert ) {
62
+ $options = wp_parse_args(
63
+ $alert->alert_meta, array(
64
+ 'email_recipient' => '',
65
+ 'email_subject' => '',
66
+ 'trigger_action' => '',
67
+ 'trigger_connector' => '',
68
+ 'trigger_context' => '',
69
+ )
70
+ );
71
+
72
+ if ( empty( $options['email_recipient'] ) && empty( $options['email_subject'] ) ) {
73
+ return;
74
+ }
75
+
76
+ // translators: Placeholder refers to the title of a site (e.g. "FooBar Website").
77
+ $message = sprintf( __( 'A Reports Alert was triggered on %s.', 'mainwp-child-reports' ), get_bloginfo( 'name' ) ) . "\n\n";
78
+
79
+ $user_id = $recordarr['user_id'];
80
+ $user = get_user_by( 'id', $user_id );
81
+
82
+ // translators: Placeholder refers to a username (e.g. "administrator").
83
+ $message .= sprintf( __( "User:\t%s", 'mainwp-child-reports' ), $user->user_login ) . "\n";
84
+
85
+ if ( ! empty( $alert->alert_meta['trigger_context'] ) ) {
86
+ $context = $this->plugin->alerts->alert_triggers['context']->get_display_value( 'list_table', $alert );
87
+
88
+ // translators: Placeholder refers to the context of the record (e.g. "Plugins").
89
+ $message .= sprintf( __( "Context:\t%s", 'mainwp-child-reports' ), $context ) . "\n";
90
+ }
91
+ if ( ! empty( $alert->alert_meta['trigger_action'] ) ) {
92
+ $action = $this->plugin->alerts->alert_triggers['action']->get_display_value( 'list_table', $alert );
93
+
94
+ // translators: Placeholder refers to the action of the record (e.g. "Installed").
95
+ $message .= sprintf( __( "Action:\t%s", 'mainwp-child-reports' ), $action ) . "\n";
96
+ }
97
+
98
+ $post = null;
99
+ if ( isset( $recordarr['object_id'] ) ) {
100
+ $post_id = $recordarr['object_id'];
101
+ $post = get_post( $post_id );
102
+ }
103
+ if ( is_object( $post ) && ! empty( $post ) ) {
104
+ $post_type = get_post_type_object( $post->post_type );
105
+
106
+ $message .= $post_type->labels->singular_name . ":\t" . $post->post_title . "\n\n";
107
+
108
+ $edit_post_link = get_edit_post_link( $post->ID, 'raw' );
109
+
110
+ // translators: Placeholder refers to the post type singular name (e.g. "Post").
111
+ $message .= sprintf( __( 'Edit %s', 'mainwp-child-reports' ), $post_type->labels->singular_name ) . "\n<$edit_post_link>\n";
112
+ }
113
+
114
+ $message .= "\n";
115
+
116
+ $edit_alert_link = admin_url( 'edit.php?post_type=wp_mainwp_alerts#post-' . $alert->ID );
117
+ $message .= __( 'Edit Alert', 'mainwp-child-reports' ) . "\n<$edit_alert_link>";
118
+
119
+ wp_mail( $options['email_recipient'], $options['email_subject'], $message );
120
+ }
121
+
122
+ /**
123
+ * Displays a settings form for the alert type
124
+ *
125
+ * @param Alert $alert Alert object for the currently displayed alert.
126
+ * @return void
127
+ */
128
+ public function display_fields( $alert ) {
129
+ $alert_meta = array();
130
+ if ( is_object( $alert ) ) {
131
+ $alert_meta = $alert->alert_meta;
132
+ }
133
+ $options = wp_parse_args(
134
+ $alert_meta, array(
135
+ 'email_recipient' => '',
136
+ 'email_subject' => '',
137
+ )
138
+ );
139
+
140
+ $form = new Form_Generator();
141
+ echo '<span class="wp_mainwp_stream_alert_type_description">' . esc_html__( 'Send a notification email to the recipient.', 'mainwp-child-reports' ) . '</span>';
142
+ echo '<label for="wp_mainwp_stream_email_recipient"><span class="title">' . esc_html__( 'Recipient', 'mainwp-child-reports' ) . '</span>';
143
+ echo '<span class="input-text-wrap">';
144
+ echo $form->render_field(
145
+ 'text', array(
146
+ 'name' => 'wp_mainwp_stream_email_recipient',
147
+ 'title' => esc_attr( __( 'Email Recipient', 'mainwp-child-reports' ) ),
148
+ 'value' => $options['email_recipient'],
149
+ )
150
+ ); // Xss ok.
151
+ echo '</span></label>';
152
+
153
+ echo '<label for="wp_mainwp_stream_email_subject"><span class="title">' . esc_html__( 'Subject', 'mainwp-child-reports' ) . '</span>';
154
+ echo '<span class="input-text-wrap">';
155
+ echo $form->render_field(
156
+ 'text', array(
157
+ 'name' => 'wp_mainwp_stream_email_subject',
158
+ 'title' => esc_attr( __( 'Email Subject', 'mainwp-child-reports' ) ),
159
+ 'value' => $options['email_subject'],
160
+ )
161
+ ); // Xss ok.
162
+ echo '</span></label>';
163
+ }
164
+
165
+ /**
166
+ * Validates and saves form settings for later use.
167
+ *
168
+ * @param Alert $alert Alert object for the currently displayed alert.
169
+ * @return void
170
+ */
171
+ public function save_fields( $alert ) {
172
+ check_admin_referer( 'save_alert', 'wp_mainwp_alerts_nonce' );
173
+
174
+ if ( isset( $_POST['wp_mainwp_stream_email_recipient'] ) ) {
175
+ $alert->alert_meta['email_recipient'] = sanitize_text_field( wp_unslash( $_POST['wp_mainwp_stream_email_recipient'] ) );
176
+ }
177
+
178
+ if ( isset( $_POST['wp_mainwp_stream_email_subject'] ) ) {
179
+ $alert->alert_meta['email_subject'] = sanitize_text_field( wp_unslash( $_POST['wp_mainwp_stream_email_subject'] ) );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Add alert meta if this is a highlight alert
185
+ *
186
+ * @param array $alert_meta The metadata to be inserted for this alert.
187
+ * @param string $alert_type The type of alert being added or updated.
188
+ *
189
+ * @return mixed
190
+ */
191
+ public function add_alert_meta( $alert_meta, $alert_type ) {
192
+ if ( $this->slug === $alert_type ) {
193
+ $email_recipient = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_email_recipient' );
194
+ if ( ! empty( $email_recipient ) ) {
195
+ $alert_meta['email_recipient'] = $email_recipient;
196
+ }
197
+ $email_subject = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_email_subject' );
198
+ if ( ! empty( $email_subject ) ) {
199
+ $alert_meta['email_subject'] = $email_subject;
200
+ }
201
+ }
202
+
203
+ return $alert_meta;
204
+ }
205
+ }
alerts/class-alert-type-highlight.php ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Highlight Alert type.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Type_Highlight
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Type_Highlight extends Alert_Type {
16
+
17
+ /**
18
+ * Main JS file script handle.
19
+ */
20
+ const SCRIPT_HANDLE = 'wp-mainwp-stream-alert-highlight-js';
21
+
22
+ /**
23
+ * Remove Highlight Ajax action label.
24
+ */
25
+ const REMOVE_ACTION = 'stream_remove_highlight';
26
+
27
+ /**
28
+ * Remove Action nonce name.
29
+ */
30
+ const REMOVE_ACTION_NONCE = 'stream-remove-highlight';
31
+
32
+ /**
33
+ * Alert type name
34
+ *
35
+ * @var string
36
+ */
37
+ public $name = 'Highlight';
38
+
39
+ /**
40
+ * Alert type slug
41
+ *
42
+ * @var string
43
+ */
44
+ public $slug = 'highlight';
45
+
46
+ /**
47
+ * The single Alert ID.
48
+ *
49
+ * @var int|string
50
+ */
51
+ public $single_alert_id;
52
+
53
+ /**
54
+ * The Plugin
55
+ *
56
+ * @var Plugin
57
+ */
58
+ public $plugin;
59
+
60
+ /**
61
+ * Class Constructor
62
+ *
63
+ * @param Plugin $plugin Plugin object.
64
+ * @return void
65
+ */
66
+ public function __construct( $plugin ) {
67
+ parent::__construct( $plugin );
68
+ $this->plugin = $plugin;
69
+ if ( ! is_admin() ) {
70
+ return;
71
+ }
72
+ add_filter(
73
+ 'wp_mainwp_stream_record_classes', array(
74
+ $this,
75
+ 'post_class',
76
+ ), 10, 2
77
+ );
78
+ add_action(
79
+ 'admin_enqueue_scripts', array(
80
+ $this,
81
+ 'enqueue_scripts',
82
+ )
83
+ );
84
+ add_action(
85
+ 'wp_ajax_' . self::REMOVE_ACTION, array(
86
+ $this,
87
+ 'ajax_remove_highlight',
88
+ )
89
+ );
90
+
91
+ if ( ! empty( $this->plugin->connectors->connectors ) && is_array( $this->plugin->connectors->connectors ) ) {
92
+ foreach ( $this->plugin->connectors->connectors as $connector ) {
93
+ add_filter(
94
+ 'wp_mainwp_stream_action_links_' . $connector->name, array(
95
+ $this,
96
+ 'action_link_remove_highlight',
97
+ ), 10, 2
98
+ );
99
+ }
100
+ }
101
+
102
+ add_filter(
103
+ 'wp_mainwp_alerts_save_meta', array(
104
+ $this,
105
+ 'add_alert_meta',
106
+ ), 10, 2
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Record that the Alert was triggered by a Record.
112
+ *
113
+ * In self::post_class() this value is checked so we can determine
114
+ * if the class should be added to the Record's display.
115
+ *
116
+ * @param int|string $record_id Record that triggered alert.
117
+ * @param array $recordarr Record details.
118
+ * @param object $alert Alert options.
119
+ * @return void
120
+ */
121
+ public function alert( $record_id, $recordarr, $alert ) {
122
+ $recordarr['ID'] = $record_id;
123
+ $this->single_alert_id = $alert->ID;
124
+ if ( ! empty( $alert->alert_meta['color'] ) ) {
125
+ $alert_meta = array(
126
+ 'highlight_color' => $alert->alert_meta['color'],
127
+ );
128
+ Alert::update_record_triggered_alerts( (object) $recordarr, $this->slug, $alert_meta );
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Displays a settings form for the alert type
134
+ *
135
+ * @param Alert $alert Alert object for the currently displayed alert.
136
+ * @return void
137
+ */
138
+ public function display_fields( $alert ) {
139
+ $alert_meta = array();
140
+ if ( is_object( $alert ) ) {
141
+ $alert_meta = $alert->alert_meta;
142
+ }
143
+ $options = wp_parse_args(
144
+ $alert_meta, array(
145
+ 'color' => 'yellow',
146
+ )
147
+ );
148
+
149
+ $form = new Form_Generator();
150
+ echo '<span class="wp_mainwp_stream_alert_type_description">' . esc_html__( 'Highlight this alert on the Reports records page.', 'mainwp-child-reports' ) . '</span>';
151
+ echo '<label for="wp_mainwp_stream_highlight_color"><span class="title">' . esc_html__( 'Color', 'mainwp-child-reports' ) . '</span>';
152
+ echo '<span class="input-text-wrap">';
153
+ echo $form->render_field(
154
+ 'select', array(
155
+ 'name' => 'wp_mainwp_stream_highlight_color',
156
+ 'title' => esc_attr( __( 'Highlight Color', 'mainwp-child-reports' ) ),
157
+ 'options' => $this->get_highlight_options(),
158
+ 'value' => $options['color'],
159
+ )
160
+ ); // Xss ok.
161
+ echo '</span></label>';
162
+ }
163
+
164
+ /**
165
+ * Lists available color options for alerts.
166
+ *
167
+ * @return array List of highlight color options.
168
+ */
169
+ public function get_highlight_options() {
170
+ return array(
171
+ 'yellow' => __( 'Yellow', 'mainwp-child-reports' ),
172
+ 'red' => __( 'Red', 'mainwp-child-reports' ),
173
+ 'green' => __( 'Green', 'mainwp-child-reports' ),
174
+ 'blue' => __( 'Blue', 'mainwp-child-reports' ),
175
+ );
176
+ }
177
+
178
+ /**
179
+ * Validates and saves form settings for later use.
180
+ *
181
+ * @param Alert $alert Alert object for the currently displayed alert.
182
+ * @return void
183
+ */
184
+ public function save_fields( $alert ) {
185
+ check_admin_referer( 'save_alert', 'wp_mainwp_alerts_nonce' );
186
+
187
+ if ( empty( $_POST['wp_mainwp_stream_highlight_color'] ) ) {
188
+ $alert->alert_meta['color'] = 'yellow';
189
+ }
190
+ $input_color = sanitize_text_field( wp_unslash( $_POST['wp_mainwp_stream_highlight_color'] ) );
191
+ if ( ! array_key_exists( $input_color, $this->get_highlight_options() ) ) {
192
+ $alert->alert_meta['color'] = 'yellow';
193
+ } else {
194
+ $alert->alert_meta['color'] = $input_color;
195
+ }
196
+
197
+ }
198
+
199
+ /**
200
+ * Apply highlight to records
201
+ *
202
+ * @param array $classes List of classes being applied to the post.
203
+ * @param object $record Record data.
204
+ * @return array New list of classes.
205
+ */
206
+ public function post_class( $classes, $record ) {
207
+ if ( ! empty( $record->meta['wp_mainwp_alerts_triggered']['highlight']['highlight_color'] ) ) {
208
+ $color = $record->meta['wp_mainwp_alerts_triggered']['highlight']['highlight_color'];
209
+ }
210
+
211
+ if ( empty( $color ) || ! is_string( $color ) ) {
212
+ return $classes;
213
+ }
214
+ $classes[] = 'alert-highlight highlight-' . esc_attr( $color ) . ' record-id-' . $record->ID;
215
+
216
+ return $classes;
217
+ }
218
+
219
+ /**
220
+ * Maybe add the "Remove Highlight" action link.
221
+ *
222
+ * This will appear on highlighted items on
223
+ * the Record List page.
224
+ *
225
+ * This is set to run for all Connectors
226
+ * in self::__construct().
227
+ *
228
+ * @filter wp_mainwp_stream_action_links_{ connector }
229
+ *
230
+ * @param array $actions Action links.
231
+ * @param object $record A record object.
232
+ *
233
+ * @return mixed
234
+ */
235
+ public function action_link_remove_highlight( $actions, $record ) {
236
+ $record = new Record( $record );
237
+ $alerts_triggered = $record->get_meta( Alerts::ALERTS_TRIGGERED_META_KEY, true );
238
+ if ( ! empty( $alerts_triggered[ $this->slug ] ) ) {
239
+ $actions[ __( 'Remove Highlight', 'mainwp-child-reports' ) ] = '#';
240
+ }
241
+
242
+ return $actions;
243
+ }
244
+
245
+ /**
246
+ * Ajax action to remove highlight.
247
+ *
248
+ * This is fired from the "Remove Highlight"
249
+ * action links on the Records list page.
250
+ *
251
+ * First, we validate and sanitize.
252
+ *
253
+ * Then, we get the Record meta and remove
254
+ * the "highlight" array from the
255
+ * "Alerts triggered" meta field.
256
+ *
257
+ * Finally, the meta field is updated.
258
+ *
259
+ * Note: this removes ALL highlights
260
+ * that were triggered by this record, not just
261
+ * those triggered on specific Alert (post) IDs.
262
+ *
263
+ * @action wp_ajax_stream_remove_highlight
264
+ */
265
+ public function ajax_remove_highlight() {
266
+ check_ajax_referer( self::REMOVE_ACTION_NONCE, 'security' );
267
+
268
+ $failure_message = __( 'Removing Highlight Failed', 'mainwp-child-reports' );
269
+
270
+ if ( empty( $_POST['recordId'] ) ) {
271
+ wp_send_json_error( $failure_message );
272
+ }
273
+
274
+ $record_id = sanitize_text_field( wp_unslash( $_POST['recordId'] ) );
275
+
276
+ if ( ! is_numeric( $record_id ) ) {
277
+ wp_send_json_error( $failure_message );
278
+ }
279
+ $record_obj = new \stdClass();
280
+ $record_obj->ID = $record_id;
281
+ $record = new Record( $record_obj );
282
+ $alerts_triggered = $record->get_meta( Alerts::ALERTS_TRIGGERED_META_KEY, true );
283
+ if ( isset( $alerts_triggered[ $this->slug ] ) ) {
284
+ unset( $alerts_triggered[ $this->slug ] );
285
+ }
286
+ $record->update_meta( Alerts::ALERTS_TRIGGERED_META_KEY, $alerts_triggered );
287
+ wp_send_json_success();
288
+ }
289
+
290
+ /**
291
+ * Enqueue Highlight-specific scripts.
292
+ *
293
+ * @param string $page WP admin page.
294
+ */
295
+ public function enqueue_scripts( $page ) {
296
+ if ( 'settings_page_mainwp-reports-page' === $page ) {
297
+ $min = wp_mainwp_stream_min_suffix();
298
+ wp_register_script( self::SCRIPT_HANDLE, $this->plugin->locations['url'] . 'alerts/js/alert-type-highlight.js', array( 'jquery' ) );
299
+
300
+ $exports = array(
301
+ 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
302
+ 'removeAction' => self::REMOVE_ACTION,
303
+ 'security' => wp_create_nonce( self::REMOVE_ACTION_NONCE ),
304
+ );
305
+
306
+ wp_scripts()->add_data(
307
+ self::SCRIPT_HANDLE,
308
+ 'data',
309
+ sprintf( 'var _streamAlertTypeHighlightExports = %s;', wp_json_encode( $exports ) )
310
+ );
311
+
312
+ wp_add_inline_script( self::SCRIPT_HANDLE, 'streamAlertTypeHighlight.init();', 'after' );
313
+ wp_enqueue_script( self::SCRIPT_HANDLE );
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Add alert meta if this is a highlight alert
319
+ *
320
+ * @param array $alert_meta The metadata to be inserted for this alert.
321
+ * @param string $alert_type The type of alert being added or updated.
322
+ *
323
+ * @return mixed
324
+ */
325
+ public function add_alert_meta( $alert_meta, $alert_type ) {
326
+ if ( $this->slug === $alert_type ) {
327
+ $color = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_highlight_color' );
328
+ if ( empty( $color ) ) {
329
+ $alert_meta['color'] = 'yellow';
330
+ } else {
331
+ $alert_meta['color'] = $color;
332
+ }
333
+ }
334
+
335
+ return $alert_meta;
336
+ }
337
+ }
alerts/class-alert-type-ifttt.php ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * If This Then That (IFTTT) Alert type.
4
+ *
5
+ * A user must create a IFTTT Maker account,
6
+ * and get their Maker Key, found here:
7
+ *
8
+ * @link https://ifttt.com/maker
9
+ * This will be used in the Stream Alert form.
10
+ *
11
+ * Next, they create a new recipe, using the
12
+ * "Maker" > "Receive a web request" trigger.
13
+ * @link https://ifttt.com/myrecipes/personal/new
14
+ *
15
+ * Here, they will enter a label for the event name.
16
+ * This will be also need to be entered into the Stream Alert form.
17
+ *
18
+ * Finally, the Maker Key and Event Name need to be entered into
19
+ * the Alert metabox.
20
+ *
21
+ * This class uses the above data to send at POST request to IFTTT,
22
+ * which then hooks into whichever service the user has determined
23
+ * in their Recipe.
24
+ *
25
+ * Notes:
26
+ *
27
+ * In their IFTTT recipe, the Task Content field "quicktags" are
28
+ * defined as such in the unfiltered state of this Alert Type:
29
+ *
30
+ * {{EventName}} - The name the user assigned to this event.
31
+ * {{Value1}} - The Record Summary
32
+ * {{Value2}} - The User that triggered the alert
33
+ * {{Value3}} - The date/time on which the alert was triggered.
34
+ *
35
+ * There are several filters in self::notify_ifttt() that
36
+ * allow for complete customization of these values.
37
+ *
38
+ * More info:
39
+ * @link https://ifttt.com/channels/maker/triggers/1636368624-receive-a-web-request
40
+ *
41
+ * @package WP_MainWP_Stream
42
+ */
43
+
44
+ namespace WP_MainWP_Stream;
45
+
46
+ /**
47
+ * Class Alert_Type_IFTTT
48
+ *
49
+ * @package WP_MainWP_Stream
50
+ */
51
+ class Alert_Type_IFTTT extends Alert_Type {
52
+
53
+ /**
54
+ * Alert type name
55
+ *
56
+ * @var string
57
+ */
58
+ public $name = 'IFTTT';
59
+
60
+ /**
61
+ * Alert type slug
62
+ *
63
+ * @var string
64
+ */
65
+ public $slug = 'ifttt';
66
+
67
+ /**
68
+ * Class Constructor
69
+ *
70
+ * @param Plugin $plugin Plugin object.
71
+ */
72
+ public function __construct( $plugin ) {
73
+ parent::__construct( $plugin );
74
+ $this->plugin = $plugin;
75
+ if ( ! is_admin() ) {
76
+ return;
77
+ }
78
+ add_filter(
79
+ 'wp_mainwp_alerts_save_meta', array(
80
+ $this,
81
+ 'add_alert_meta',
82
+ ), 10, 2
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Record that the Alert was triggered by a Record.
88
+ *
89
+ * In self::post_class() this value is checked so we can determine
90
+ * if the class should be added to the Record's display.
91
+ *
92
+ * @param int|string $record_id Record that triggered alert.
93
+ * @param array $recordarr Record details.
94
+ * @param object $alert Alert options.
95
+ * @return void
96
+ */
97
+ public function alert( $record_id, $recordarr, $alert ) {
98
+ $recordarr['ID'] = $record_id;
99
+ $this->notify_ifttt( $alert, $recordarr );
100
+ }
101
+
102
+ /**
103
+ * Displays a settings form for the alert type
104
+ *
105
+ * @param Alert $alert Alert object for the currently displayed alert.
106
+ * @return void
107
+ */
108
+ public function display_fields( $alert ) {
109
+ $alert_meta = array();
110
+ if ( is_object( $alert ) ) {
111
+ $alert_meta = $alert->alert_meta;
112
+ }
113
+ $options = wp_parse_args(
114
+ $alert_meta, array(
115
+ 'maker_key' => '',
116
+ 'event_name' => '',
117
+ )
118
+ );
119
+
120
+ $form = new Form_Generator();
121
+
122
+ echo '<span class="wp_mainwp_stream_alert_type_description">';
123
+ echo esc_html__( 'Trigger an IFTTT Maker recipe.', 'mainwp-child-reports' );
124
+ echo wp_kses_post( sprintf( ' (<a href="%1$s" target="_blank">%2$s</span></a>)', 'https://youtu.be/XFWtkHXv9h0', __( 'Tutorial', 'mainwp-child-reports' ) ) );
125
+ echo '</span>';
126
+ echo '<label for="wp_mainwp_stream_ifttt_maker_key"><span class="title">' . esc_html__( 'Maker Key', 'mainwp-child-reports' ) . '</span>';
127
+ echo '<span class="input-text-wrap">';
128
+ echo $form->render_field(
129
+ 'text', array(
130
+ 'name' => 'wp_mainwp_stream_ifttt_maker_key',
131
+ 'title' => esc_attr( __( 'Maker Key', 'mainwp-child-reports' ) ),
132
+ 'value' => $options['maker_key'],
133
+ )
134
+ ); // Xss ok.
135
+ echo '</span>';
136
+ printf(
137
+ '<span class="input-text-wrap"><a href="%1$s" target="_blank">%2$s %3$s</a></span>',
138
+ esc_url( 'https://ifttt.com/maker' ),
139
+ esc_html__( 'Open IFTTT Maker Channel', 'mainwp-child-reports' ),
140
+ '<span class="dashicons dashicons-external"></span>'
141
+ );
142
+ echo '</label>';
143
+
144
+ echo '<label for="wp_mainwp_stream_ifttt_event_name"><span class="title">' . esc_html__( 'Event Name', 'mainwp-child-reports' ) . '</span>';
145
+ echo '<span class="input-text-wrap">';
146
+ echo $form->render_field(
147
+ 'text', array(
148
+ 'name' => 'wp_mainwp_stream_ifttt_event_name',
149
+ 'title' => esc_attr( __( 'Event Name', 'mainwp-child-reports' ) ),
150
+ 'value' => $options['event_name'],
151
+ )
152
+ ); // Xss ok.
153
+ echo '</span>';
154
+ printf(
155
+ '<span class="input-text-wrap"><a href="%1$s" target="_blank">%2$s %3$s</a></span>',
156
+ esc_url( 'https://ifttt.com/myrecipes/personal' ),
157
+ esc_html__( 'Open IFTTT Recipes', 'mainwp-child-reports' ),
158
+ '<span class="dashicons dashicons-external"></span>'
159
+ );
160
+ echo '</label>';
161
+ }
162
+
163
+ /**
164
+ * Validates and saves form settings for later use.
165
+ *
166
+ * @param Alert $alert Alert object for the currently displayed alert.
167
+ * @return void
168
+ */
169
+ public function save_fields( $alert ) {
170
+ check_admin_referer( 'save_alert', 'wp_mainwp_alerts_nonce' );
171
+ $alert->alert_meta['maker_key'] = '';
172
+
173
+ if ( ! empty( $_POST['wp_mainwp_stream_ifttt_maker_key'] ) ) {
174
+ $alert->alert_meta['maker_key'] = sanitize_text_field( wp_unslash( $_POST['wp_mainwp_stream_ifttt_maker_key'] ) );
175
+ }
176
+ if ( ! empty( $_POST['wp_mainwp_stream_ifttt_event_name'] ) ) {
177
+ $alert->alert_meta['event_name'] = sanitize_text_field( wp_unslash( $_POST['wp_mainwp_stream_ifttt_event_name'] ) );
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Send a POST request to IFTTT.
183
+ *
184
+ * This method sends Record data to IFTTT for processing.
185
+ *
186
+ * There are several filters here (documented below) that
187
+ * allow for customization of the data sent.
188
+ *
189
+ * Note that IFTTT only allows for three specifically-named
190
+ * array keys of data. (also documented below)
191
+ *
192
+ * @param object $alert The Alert object.
193
+ * @param array $recordarr Array of Record data.
194
+ *
195
+ * @return bool
196
+ */
197
+ public function notify_ifttt( $alert, $recordarr ) {
198
+ if ( empty( $alert->alert_meta['maker_key'] ) || empty( $alert->alert_meta['event_name'] ) || empty( $recordarr ) ) {
199
+ return false;
200
+ }
201
+
202
+ $record_data = wp_parse_args(
203
+ $recordarr, array(
204
+ // translators: Placeholder refers to the Event Name of the Alert (e.g. "Update a post")
205
+ 'summary' => sprintf( __( 'The event %s was triggered' ), $alert->alert_meta['event_name'] ),
206
+ 'user_id' => get_current_user_id(),
207
+ 'created' => current_time( 'Y-m-d H:i:s' ),
208
+ // Blog's local time.
209
+ )
210
+ );
211
+
212
+ $user_id = $recordarr['user_id'];
213
+ $user = get_user_by( 'id', $user_id );
214
+
215
+ /**
216
+ * Filter User data field
217
+ *
218
+ * Defaults to 'user_login'.
219
+ *
220
+ * @param object $alert The Alert.
221
+ * @param array $recordarray The Record's data.
222
+ * @return string
223
+ */
224
+ $user_field = apply_filters( 'wp_mainwp_stream_alert_ifttt_user_data_value', 'user_login', $alert, $recordarr );
225
+ $user_value = ! empty( $user->$user_field ) ? $user->$user_field : $user->user_login;
226
+
227
+ $created = $recordarr['created'];
228
+ /**
229
+ * Filter the date format string
230
+ *
231
+ * Defaults to 'Y-m-d H:i:s'.
232
+ *
233
+ * @param object $alert The Alert.
234
+ * @param array $recordarray The Record's data.
235
+ * @return string
236
+ */
237
+ $date_format = apply_filters( 'wp_mainwp_stream_alert_ifttt_date_format', 'Y-m-d H:i:s', $alert, $recordarr );
238
+ $date = date( $date_format, strtotime( $created ) );
239
+
240
+ $url = 'https://maker.ifttt.com/trigger/' . $alert->alert_meta['event_name'] . '/with/key/' . $alert->alert_meta['maker_key'];
241
+
242
+ /*
243
+ * The array key labels for the request body are required by
244
+ * IFTTT; their names cannot be changed.
245
+ *
246
+ * The filter defaults are set up to send:
247
+ * value1 = Record Summary
248
+ *
249
+ * value2 = User login name
250
+ * (see the wp_mainwp_stream_alert_ifttt_user_data_value filter above)
251
+ *
252
+ * value3 = Record Date
253
+ * (see the wp_mainwp_stream_alert_ifttt_date_format filter above)
254
+ *
255
+ * The filters below allow complete customization of these data values.
256
+ */
257
+ $args = array(
258
+ 'headers' => array(
259
+ 'Content-Type' => 'application/json',
260
+ ),
261
+ 'body' => wp_json_encode(
262
+ array(
263
+ /**
264
+ * Filter the first IFTTT alert value
265
+ *
266
+ * @param string $summary The Record's summary.
267
+ * @param object $alert The Alert.
268
+ * @param array $recordarr Array of Record data.
269
+ * @return mixed
270
+ */
271
+ 'value1' => apply_filters( 'wp_mainwp_stream_alert_ifttt_value_one', $record_data['summary'], $alert, $recordarr ),
272
+
273
+ /**
274
+ * Filter the second IFTTT alert value
275
+ *
276
+ * @param string $user_value The user meta value requested above.
277
+ * @param int $user_id The user ID who fired the Alert.
278
+ * @param object $alert The Alert.
279
+ * @param array $recordarr Array of Record data.
280
+ * @return mixed
281
+ */
282
+ 'value2' => apply_filters( 'wp_mainwp_stream_alert_ifttt_value_two', $user_value, $user_id, $alert, $recordarr ),
283
+
284
+ /**
285
+ * Filter the third IFTTT alert value
286
+ *
287
+ * @param string $date The Record's date.
288
+ * @param object $alert The Alert.
289
+ * @param array $recordarr Array of Record data.
290
+ * @return mixed
291
+ */
292
+ 'value3' => apply_filters( 'wp_mainwp_stream_alert_ifttt_value_three', $date, $alert, $recordarr ),
293
+ )
294
+ ),
295
+ );
296
+
297
+ $response = wp_remote_post( $url, $args );
298
+ if ( ! is_array( $response ) ) {
299
+ return false;
300
+ }
301
+
302
+ return true;
303
+ }
304
+
305
+ /**
306
+ * Add alert meta if this is a highlight alert
307
+ *
308
+ * @param array $alert_meta The metadata to be inserted for this alert.
309
+ * @param string $alert_type The type of alert being added or updated.
310
+ *
311
+ * @return mixed
312
+ */
313
+ public function add_alert_meta( $alert_meta, $alert_type ) {
314
+ if ( $this->slug === $alert_type ) {
315
+ $maker_key = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_ifttt_maker_key' );
316
+ if ( ! empty( $maker_key ) ) {
317
+ $alert_meta['maker_key'] = $maker_key;
318
+ }
319
+ $event_name = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_ifttt_event_name' );
320
+ if ( ! empty( $event_name ) ) {
321
+ $alert_meta['event_name'] = $event_name;
322
+ }
323
+ }
324
+
325
+ return $alert_meta;
326
+ }
327
+ }
alerts/class-alert-type-menu-alert.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Menu Alert type.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Type_Menu_Alert
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Type_Menu_Alert extends Alert_Type {
16
+
17
+ /**
18
+ * Alert type name
19
+ *
20
+ * @var string
21
+ */
22
+ public $name = 'Create Menu Alert';
23
+
24
+ /**
25
+ * Alert type slug
26
+ *
27
+ * @var string
28
+ */
29
+ public $slug = 'menu-alert';
30
+
31
+ /**
32
+ * Class Constructor
33
+ *
34
+ * @param Plugin $plugin Plugin object.
35
+ * @return void
36
+ */
37
+ public function __construct( $plugin ) {
38
+ parent::__construct( $plugin );
39
+ add_action( 'admin_bar_menu', array( $this, 'menu_alert' ), 99 );
40
+ }
41
+
42
+ /**
43
+ * Notify user of triggered alert.
44
+ *
45
+ * @param int $record_id Record that triggered alert.
46
+ * @param array $recordarr Record details.
47
+ * @param array $options Alert options.
48
+ * @return void
49
+ */
50
+ public function alert( $record_id, $recordarr, $options ) {
51
+ $this->add_message( $recordarr['summary'] );
52
+ }
53
+
54
+ /**
55
+ * Displays a settings form for the alert type
56
+ *
57
+ * @param Alert $alert Alert object for the currently displayed alert.
58
+ * @return void
59
+ */
60
+ public function display_fields( $alert ) {
61
+ $alert_meta = array();
62
+ if ( is_object( $alert ) ) {
63
+ $alert_meta = $alert->alert_meta;
64
+ }
65
+ $options = wp_parse_args(
66
+ $alert_meta, array(
67
+ 'clear_immediate' => false,
68
+ )
69
+ );
70
+
71
+ $form = new Form_Generator();
72
+ $form->add_field(
73
+ 'checkbox', array(
74
+ 'name' => 'wp_mainwp_stream_menu_alert_clear_immediate',
75
+ 'text' => esc_attr( __( 'Clear alerts after seen.', 'mainwp-child-reports' ) ),
76
+ 'value' => $options['clear_immediate'],
77
+ 'title' => __( 'Menu Bar', 'mainwp-child-reports' ),
78
+ )
79
+ );
80
+
81
+ echo $form->render_fields(); // Xss ok.
82
+ }
83
+
84
+ /**
85
+ * Validates and saves form settings for later use.
86
+ *
87
+ * @param Alert $alert Alert object for the currently displayed alert.
88
+ * @return void
89
+ */
90
+ public function save_fields( $alert ) {
91
+ check_admin_referer( 'save_alert', 'wp_mainwp_alerts_nonce' );
92
+
93
+ $alert->alert_meta['clear_immediate'] = ! empty( $_POST['wp_mainwp_stream_menu_alert_clear_immediate'] );
94
+ }
95
+
96
+ /**
97
+ * Display and clear all unviewed menu alerts
98
+ *
99
+ * @param WP_Admin_Bar $wp_admin_bar Representation of the WP Admin Bar.
100
+ * @return bool True if notifications were displayed, false otherwise.
101
+ */
102
+ public function menu_alert( $wp_admin_bar ) {
103
+ $messages = $this->get_messages();
104
+ if ( ! $messages ) {
105
+ return false;
106
+ }
107
+
108
+ $wp_admin_bar->add_node(
109
+ array(
110
+ 'id' => 'wp_mainwp_stream_alert_notify',
111
+ 'parent' => false,
112
+ 'title' => __( 'New Reports Alert', 'mainwp-child-reports' ),
113
+ 'href' => '#',
114
+ 'meta' => array(
115
+ 'class' => 'opposite',
116
+ ),
117
+ )
118
+ );
119
+
120
+ foreach ( $messages as $key => $message ) {
121
+ $wp_admin_bar->add_node(
122
+ array(
123
+ 'id' => 'wp_mainwp_stream_alert_notify_' . $key,
124
+ 'parent' => 'wp_mainwp_stream_alert_notify',
125
+ 'title' => esc_html( $message ),
126
+ 'href' => '#',
127
+ 'meta' => array(
128
+ 'class' => 'opposite',
129
+ ),
130
+ )
131
+ );
132
+ }
133
+
134
+ $this->clear_messages();
135
+
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Get a list of all current alert messages for current user.
141
+ *
142
+ * @todo update this for VIP. (get_user_meta)
143
+ *
144
+ * @return array List of alert messages
145
+ */
146
+ public function get_messages() {
147
+ $current_user = wp_get_current_user();
148
+ $messages = get_user_meta( $current_user->ID, $this->get_key(), false );
149
+
150
+ return $messages;
151
+ }
152
+
153
+ /**
154
+ * Adds a new alert message for the current user.
155
+ *
156
+ * @todo update this for VIP. (add_user_meta)
157
+ *
158
+ * @param string $message Alert message to add.
159
+ * @return void
160
+ */
161
+ public function add_message( $message ) {
162
+ $current_user = wp_get_current_user();
163
+ add_user_meta( $current_user->ID, $this->get_key(), $message, false );
164
+ }
165
+
166
+ /**
167
+ * Clears all alert messages for the current user.
168
+ *
169
+ * @todo update this for VIP. (delete_user_meta)
170
+ *
171
+ * @param bool $global Whether to clear globally.
172
+ * @return void
173
+ */
174
+ public function clear_messages( $global = false ) {
175
+ $current_user = wp_get_current_user();
176
+ delete_user_meta( $current_user->ID, $this->get_key(), $global );
177
+ }
178
+
179
+ /**
180
+ * Returns meta key for pending alerts.
181
+ *
182
+ * @return string Meta key
183
+ */
184
+ public function get_key() {
185
+ return 'wp_mainwp_alerts_menu_pending';
186
+ }
187
+ }
alerts/class-alert-type-none.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * "Do nothing" Alert type.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Type_None
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Type_None extends Alert_Type {
16
+ /**
17
+ * Notifier name
18
+ *
19
+ * @var string
20
+ */
21
+ public $name = 'Do Nothing';
22
+
23
+ /**
24
+ * Notifier slug
25
+ *
26
+ * @var string
27
+ */
28
+ public $slug = 'none';
29
+
30
+ /**
31
+ * Does not notify user.
32
+ *
33
+ * @param int $record_id Record that triggered alert.
34
+ * @param array $recordarr Record details.
35
+ * @param array $options Alert options.
36
+ * @return void
37
+ */
38
+ public function alert( $record_id, $recordarr, $options ) {
39
+ // Do nothing.
40
+ }
41
+ }
alerts/class-alert-type-slack.php ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Slack Alerts.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Type_Slack
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert_Type_Slack extends Alert_Type {
16
+
17
+ /**
18
+ * Alert type name
19
+ *
20
+ * @var string
21
+ */
22
+ public $name = 'Slack';
23
+
24
+ /**
25
+ * Alert type slug
26
+ *
27
+ * @var string
28
+ */
29
+ public $slug = 'slack';
30
+
31
+ /**
32
+ * Class Constructor
33
+ *
34
+ * @param Plugin $plugin Plugin object.
35
+ */
36
+ public function __construct( $plugin ) {
37
+ parent::__construct( $plugin );
38
+ $this->plugin = $plugin;
39
+ if ( ! is_admin() ) {
40
+ return;
41
+ }
42
+ add_filter(
43
+ 'wp_mainwp_alerts_save_meta', array(
44
+ $this,
45
+ 'add_alert_meta',
46
+ ), 10, 2
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Sends an message to the Slack channel.
52
+ *
53
+ * @param int $record_id Record that triggered notification.
54
+ * @param array $recordarr Record details.
55
+ * @param Alert $alert Alert options.
56
+ * @return void
57
+ */
58
+ public function alert( $record_id, $recordarr, $alert ) {
59
+ $options = wp_parse_args(
60
+ $alert->alert_meta, array(
61
+ 'webhook' => '',
62
+ 'channel' => '',
63
+ 'username' => '',
64
+ 'icon' => '',
65
+ 'trigger_action' => '',
66
+ 'trigger_connector' => '',
67
+ 'trigger_context' => '',
68
+ )
69
+ );
70
+ if ( empty( $options['webhook'] ) ) {
71
+ return;
72
+ }
73
+ $user_id = (int) $recordarr['user_id'];
74
+ $user = get_userdata( $user_id );
75
+ $logo = wp_get_attachment_image_src( get_theme_mod( 'custom_logo' ), 'full' );
76
+ $context = $recordarr['context'];
77
+ $action = $recordarr['action'];
78
+
79
+ if ( ! empty( $alert->alert_meta['trigger_context'] ) ) {
80
+ $context = $this->plugin->alerts->alert_triggers['context']->get_display_value( 'list_table', $alert );
81
+ }
82
+ if ( ! empty( $alert->alert_meta['trigger_action'] ) ) {
83
+ $action = $this->plugin->alerts->alert_triggers['action']->get_display_value( 'list_table', $alert );
84
+ }
85
+
86
+ $fields = array(
87
+ array(
88
+ 'title' => 'IP Address',
89
+ 'value' => $recordarr['ip'],
90
+ 'short' => true,
91
+ ),
92
+ array(
93
+ 'title' => 'Connector',
94
+ 'value' => $recordarr['connector'],
95
+ 'short' => true,
96
+ ),
97
+ array(
98
+ 'title' => 'Context',
99
+ 'value' => $context,
100
+ 'short' => true,
101
+ ),
102
+ array(
103
+ 'title' => 'Action',
104
+ 'value' => $action,
105
+ 'short' => true,
106
+ ),
107
+ );
108
+
109
+ $post = null;
110
+ if ( isset( $recordarr['object_id'] ) ) {
111
+ $post_id = $recordarr['object_id'];
112
+ $post = get_post( $post_id );
113
+ }
114
+ if ( is_object( $post ) && ! empty( $post ) ) {
115
+ $post_type = get_post_type_object( $post->post_type );
116
+ $edit_post_link = get_edit_post_link( $post->ID, 'raw' );
117
+ array_push(
118
+ $fields, array(
119
+ 'title' => 'Edit ' . $post_type->labels->singular_name,
120
+ 'value' => "<$edit_post_link>",
121
+ 'short' => false,
122
+ )
123
+ );
124
+ }
125
+
126
+ $edit_alert_link = admin_url( 'edit.php?post_type=wp_mainwp_alerts#post-' . $alert->ID );
127
+ array_push(
128
+ $fields, array(
129
+ 'title' => 'Edit Alert',
130
+ 'value' => "<$edit_alert_link>",
131
+ 'short' => false,
132
+ )
133
+ );
134
+
135
+ $attachment = array(
136
+ 'author_icon' => get_avatar_url( $user_id, 16 ),
137
+ 'author_link' => admin_url( "admin.php?page=wp_stream&user_id=$user_id" ),
138
+ 'author_name' => trim( "$user->first_name $user->last_name" ),
139
+ 'fallback' => html_entity_decode( $recordarr['summary'] ),
140
+ 'fields' => $fields,
141
+ 'footer' => get_bloginfo( 'name' ),
142
+ 'footer_icon' => get_site_icon_url( 16, $logo[0], $recordarr['blog_id'] ),
143
+ 'title' => html_entity_decode( $recordarr['summary'] ),
144
+ 'ts' => strtotime( $recordarr['created'] ),
145
+ );
146
+ if ( array_key_exists( 'object_id', $recordarr ) ) {
147
+ $object_id = (int) $recordarr['object_id'];
148
+ $context = $recordarr['context'];
149
+ $attachment['title_link'] = admin_url( "admin.php?page=wp_stream&object_id=$object_id&context=$context" );
150
+ }
151
+ $data = array(
152
+ 'attachments' => array( $attachment ),
153
+ );
154
+ if ( ! empty( $options['channel'] ) ) {
155
+ $data['channel'] = $options['channel'];
156
+ }
157
+ if ( ! empty( $options['username'] ) ) {
158
+ $data['username'] = $options['username'];
159
+ }
160
+ if ( ! empty( $options['icon'] ) ) {
161
+ if ( substr( $options['icon'], 0, 1 ) === ':' ) {
162
+ $data['icon_emoji'] = $options['icon'];
163
+ } elseif ( substr( $options['icon'], 0, 4 ) === 'http' ) {
164
+ $data['icon_url'] = $options['icon'];
165
+ }
166
+ }
167
+ wp_remote_post(
168
+ $options['webhook'], array(
169
+ 'body' => wp_json_encode( $data ),
170
+ 'headers' => array( 'Content-Type' => 'application/json' ),
171
+ )
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Displays a settings form for the alert type
177
+ *
178
+ * @param Alert $alert Alert object for the currently displayed alert.
179
+ * @return void
180
+ */
181
+ public function display_fields( $alert ) {
182
+ $alert_meta = array();
183
+ if ( is_object( $alert ) ) {
184
+ $alert_meta = $alert->alert_meta;
185
+ }
186
+ $options = wp_parse_args(
187
+ $alert_meta, array(
188
+ 'webhook' => '',
189
+ 'channel' => '',
190
+ 'username' => '',
191
+ 'icon' => '',
192
+ )
193
+ );
194
+ $form = new Form_Generator();
195
+ echo '<span class="wp_mainwp_stream_alert_type_description">' . esc_html__( 'Send a rich message notification to Slack.', 'mainwp-child-reports' ) . '</span>';
196
+ echo '<label for="wp_mainwp_stream_slack_webhook"><span class="title">' . esc_html__( 'Webhook URL', 'mainwp-child-reports' ) . '</span>';
197
+ echo '<span class="input-text-wrap">';
198
+ echo $form->render_field(
199
+ 'text', array(
200
+ 'name' => 'wp_mainwp_stream_slack_webhook',
201
+ 'title' => esc_attr( __( 'Webhook URL', 'mainwp-child-reports' ) ),
202
+ 'value' => $options['webhook'],
203
+ )
204
+ ); // Xss ok.
205
+ echo '</span>';
206
+ echo '<span class="input-text-wrap">' . esc_html__( 'The webhook URL', 'mainwp-child-reports' ) . '</span>';
207
+ echo '</label>';
208
+ echo '<label for="wp_mainwp_stream_slack_channel"><span class="title">' . esc_html__( 'Channel', 'mainwp-child-reports' ) . '</span>';
209
+ echo '<span class="input-text-wrap">';
210
+ echo $form->render_field(
211
+ 'text', array(
212
+ 'name' => 'wp_mainwp_stream_slack_channel',
213
+ 'title' => esc_attr( __( 'Channel', 'mainwp-child-reports' ) ),
214
+ 'value' => $options['channel'],
215
+ )
216
+ ); // Xss ok.
217
+ echo '</span>';
218
+ echo '<span class="input-text-wrap">' . esc_html__( 'The channel to send to (optional)', 'mainwp-child-reports' ) . '</span>';
219
+ echo '</label>';
220
+ echo '<label for="wp_mainwp_stream_slack_username"><span class="title">' . esc_html__( 'Username', 'mainwp-child-reports' ) . '</span>';
221
+ echo '<span class="input-text-wrap">';
222
+ echo $form->render_field(
223
+ 'text', array(
224
+ 'name' => 'wp_mainwp_stream_slack_username',
225
+ 'title' => esc_attr( __( 'Username', 'mainwp-child-reports' ) ),
226
+ 'value' => $options['username'],
227
+ )
228
+ ); // Xss ok.
229
+ echo '</span>';
230
+ echo '<span class="input-text-wrap">' . esc_html__( 'The username to send as (optional)', 'mainwp-child-reports' ) . '</span>';
231
+ echo '</label>';
232
+ echo '<label for="wp_mainwp_stream_slack_icon"><span class="title">' . esc_html__( 'Icon', 'mainwp-child-reports' ) . '</span>';
233
+ echo '<span class="input-text-wrap">';
234
+ echo $form->render_field(
235
+ 'text', array(
236
+ 'name' => 'wp_mainwp_stream_slack_icon',
237
+ 'title' => esc_attr( __( 'Icon', 'mainwp-child-reports' ) ),
238
+ 'value' => $options['icon'],
239
+ )
240
+ ); // Xss ok.
241
+ echo '</span>';
242
+ echo '<span class="input-text-wrap">' . esc_html__( 'The URL or emoji (with colons!) to use as the icon (optional)', 'mainwp-child-reports' ) . '</span>';
243
+ echo '</label>';
244
+ }
245
+
246
+ /**
247
+ * Validates and saves form settings for later use.
248
+ *
249
+ * @param Alert $alert Alert object for the currently displayed alert.
250
+ * @return void
251
+ */
252
+ public function save_fields( $alert ) {
253
+ check_admin_referer( 'save_alert', 'wp_mainwp_alerts_nonce' );
254
+
255
+ $webhook = wp_mainwp_stream_filter_input( 'INPUT_POST', 'wp_mainwp_stream_slack_webhook', 'FILTER_VALIDATE_URL' );
256
+ if ( ! empty( $webhook ) ) {
257
+ $alert->alert_meta['webhook'] = $webhook;
258
+ }
259
+ $channel = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_channel' );
260
+ if ( ! empty( $channel ) ) {
261
+ $alert->alert_meta['channel'] = $channel;
262
+ }
263
+ $username = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_username' );
264
+ if ( ! empty( $username ) ) {
265
+ $alert->alert_meta['username'] = $username;
266
+ }
267
+ $icon = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_icon' );
268
+ if ( ! empty( $icon ) ) {
269
+ $alert->alert_meta['icon'] = $icon;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Add alert meta if this is a Slack alert
275
+ *
276
+ * @param array $alert_meta The metadata to be inserted for this alert.
277
+ * @param string $alert_type The type of alert being added or updated.
278
+ *
279
+ * @return mixed
280
+ */
281
+ public function add_alert_meta( $alert_meta, $alert_type ) {
282
+ if ( $this->slug === $alert_type ) {
283
+ $webhook = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_webhook' );
284
+ if ( ! empty( $webhook ) ) {
285
+ $alert_meta['webhook'] = $webhook;
286
+ }
287
+ $channel = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_channel' );
288
+ if ( ! empty( $channel ) ) {
289
+ $alert_meta['channel'] = $channel;
290
+ }
291
+ $username = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_username' );
292
+ if ( ! empty( $username ) ) {
293
+ $alert_meta['username'] = $username;
294
+ }
295
+ $icon = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_slack_icon' );
296
+ if ( ! empty( $icon ) ) {
297
+ $alert_meta['icon'] = $icon;
298
+ }
299
+ }
300
+
301
+ return $alert_meta;
302
+ }
303
+ }
alerts/js/alert-type-highlight.js ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* globals jQuery, _streamAlertTypeHighlightExports */
2
+ /* exported streamAlertTypeHighlight */
3
+ var streamAlertTypeHighlight = ( function( $ ) {
4
+ var self = {
5
+ ajaxUrl: '',
6
+ removeAction: '',
7
+ security: ''
8
+ };
9
+
10
+ if ( 'undefined' !== typeof _streamAlertTypeHighlightExports ) {
11
+ $.extend( self, _streamAlertTypeHighlightExports );
12
+ }
13
+
14
+ /**
15
+ * The primary function for this file.
16
+ *
17
+ * @returns void.
18
+ */
19
+ self.init = function() {
20
+ $( document ).ready(
21
+ function() {
22
+
23
+ /**
24
+ * Remove highlights on Record list screen.
25
+ *
26
+ * @returns void.
27
+ */
28
+ $( '.alert-highlight .action-link[href="#"]' ).each(
29
+ function() {
30
+ var actionLink = $( this );
31
+
32
+ /**
33
+ * Ajax call to remove the highlight.
34
+ *
35
+ * @returns void.
36
+ */
37
+ actionLink.click(
38
+ function( e ) {
39
+ var recordId, data;
40
+ e.preventDefault();
41
+ recordId = actionLink.parents( '.alert-highlight' ).attr( 'class' ).match( /record\-id\-[\w-]*\b/ );
42
+ recordId = recordId[0].replace( 'record-id-', '' );
43
+
44
+ data = {
45
+ action: self.removeAction,
46
+ security: self.security,
47
+ recordId: recordId
48
+ };
49
+
50
+ $.post(
51
+ self.ajaxUrl, data, function( response ) {
52
+ if ( true === response.success ) {
53
+ ajaxDone();
54
+ }
55
+ }
56
+ );
57
+
58
+ /**
59
+ * Fires when Ajax complete.
60
+ */
61
+ function ajaxDone() {
62
+ var row = actionLink.parents( '.alert-highlight' ),
63
+ odd = $( '.striped > tbody > :nth-child( odd )' );
64
+ if ( row.is( odd ) ) {
65
+ row.animate(
66
+ { backgroundColor: '#f9f9f9' }, 300, function() {
67
+ row.removeClass( 'alert-highlight' );
68
+ }
69
+ );
70
+ } else {
71
+ row.animate(
72
+ { backgroundColor: '' }, 300, function() {
73
+ row.removeClass( 'alert-highlight' );
74
+ }
75
+ );
76
+ }
77
+ actionLink.remove();
78
+ }
79
+ }
80
+ );
81
+ }
82
+ );
83
+ }
84
+ ); // End document.ready().
85
+ };
86
+
87
+ return self;
88
+
89
+ })( jQuery );
classes/class-admin.php ADDED
@@ -0,0 +1,1057 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ use DateTime;
6
+ use DateTimeZone;
7
+ use DateInterval;
8
+ use \WP_CLI;
9
+ use \WP_Roles;
10
+
11
+ class Admin {
12
+
13
+ /**
14
+ * Hold Plugin class
15
+ *
16
+ * @var Plugin
17
+ */
18
+ public $plugin;
19
+
20
+ /**
21
+ * Holds Network class
22
+ *
23
+ * @var Network
24
+ */
25
+ public $network;
26
+
27
+ /**
28
+ * Holds Live Update class
29
+ *
30
+ * @var Live_Update
31
+ */
32
+ public $live_update;
33
+
34
+ /**
35
+ * Holds Export class
36
+ *
37
+ * @var Export
38
+ */
39
+ public $export;
40
+
41
+ /**
42
+ * Menu page screen id
43
+ *
44
+ * @var string
45
+ */
46
+ public $screen_id = array();
47
+
48
+ /**
49
+ * List table object
50
+ *
51
+ * @var List_Table
52
+ */
53
+ public $list_table = null;
54
+
55
+ /**
56
+ * Option to disable access to Stream
57
+ *
58
+ * @var bool
59
+ */
60
+ public $disable_access = false;
61
+
62
+ /**
63
+ * Class applied to the body of the admin screen
64
+ *
65
+ * @var string
66
+ */
67
+ public $admin_body_class = 'wp_mainwp_stream_screen';
68
+
69
+ /**
70
+ * Slug of the records page
71
+ *
72
+ * @var string
73
+ */
74
+ public $records_page_slug = 'mainwp-reports-page';
75
+
76
+ /**
77
+ * Slug of the settings page
78
+ *
79
+ * @var string
80
+ */
81
+ public $settings_page_slug = 'mainwp-reports-settings';
82
+
83
+ /**
84
+ * Parent page of the records and settings pages
85
+ *
86
+ * @var string
87
+ */
88
+ public $admin_parent_page = 'options-general.php';
89
+
90
+ /**
91
+ * Capability name for viewing records
92
+ *
93
+ * @var string
94
+ */
95
+ public $view_cap = 'view_stream';
96
+
97
+ /**
98
+ * Capability name for viewing settings
99
+ *
100
+ * @var string
101
+ */
102
+ public $settings_cap = 'manage_options';
103
+
104
+ /**
105
+ * Total amount of authors to pre-load
106
+ *
107
+ * @var int
108
+ */
109
+ public $preload_users_max = 50;
110
+
111
+ /**
112
+ * Admin notices, collected and displayed on proper action
113
+ *
114
+ * @var array
115
+ */
116
+ public $notices = array();
117
+
118
+ /**
119
+ * Class constructor.
120
+ *
121
+ * @param Plugin $plugin The main Plugin class.
122
+ */
123
+ public function __construct( $plugin ) {
124
+ $this->plugin = $plugin;
125
+
126
+ add_action( 'init', array( $this, 'init' ) );
127
+
128
+ // Ensure function used in various methods is pre-loaded.
129
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
130
+ require_once ABSPATH . '/wp-admin/includes/plugin.php';
131
+ }
132
+
133
+ // User and role caps.
134
+ //add_filter( 'user_has_cap', array( $this, 'filter_user_caps' ), 10, 4 );
135
+ //add_filter( 'role_has_cap', array( $this, 'filter_role_caps' ), 10, 3 );
136
+
137
+ if ( is_multisite() && is_plugin_active_for_network( $this->plugin->locations['plugin'] ) && ! is_network_admin() ) {
138
+ $options = (array) get_site_option( 'wp_mainwp_stream_network', array() );
139
+ $option = isset( $options['general_site_access'] ) ? absint( $options['general_site_access'] ) : 1;
140
+
141
+ $this->disable_access = ( $option ) ? false : true;
142
+ }
143
+
144
+ // Register settings page.
145
+ if ( ! $this->disable_access ) {
146
+ //add_action( 'admin_menu', array( $this, 'register_menu' ) );
147
+ }
148
+
149
+ // Admin notices.
150
+ add_action( 'admin_notices', array( $this, 'prepare_admin_notices' ) );
151
+ add_action( 'shutdown', array( $this, 'admin_notices' ) );
152
+
153
+ // Add admin body class.
154
+ add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
155
+
156
+ // Plugin action links.
157
+ add_filter(
158
+ 'plugin_action_links', array(
159
+ $this,
160
+ 'plugin_action_links',
161
+ ), 10, 2
162
+ );
163
+
164
+ // Load admin scripts and styles.
165
+ add_action(
166
+ 'admin_enqueue_scripts', array(
167
+ $this,
168
+ 'admin_enqueue_scripts',
169
+ )
170
+ );
171
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
172
+
173
+ // Reset Streams database.
174
+ add_action(
175
+ 'wp_ajax_wp_mainwp_stream_reset', array(
176
+ $this,
177
+ 'wp_ajax_reset',
178
+ )
179
+ );
180
+
181
+ // Uninstall Streams and Deactivate plugin.
182
+ $uninstall = $this->plugin->db->driver->purge_storage( $this->plugin );
183
+
184
+ // Auto purge setup.
185
+ add_action( 'wp_loaded', array( $this, 'purge_schedule_setup' ) );
186
+ add_action(
187
+ 'wp_mainwp_stream_auto_purge', array(
188
+ $this,
189
+ 'purge_scheduled_action',
190
+ )
191
+ );
192
+
193
+ // Ajax users list.
194
+ add_action(
195
+ 'wp_ajax_wp_mainwp_stream_filters', array(
196
+ $this,
197
+ 'ajax_filters',
198
+ )
199
+ );
200
+ }
201
+
202
+ /**
203
+ * Load admin classes
204
+ *
205
+ * @action init
206
+ */
207
+ public function init() {
208
+ $this->network = new Network( $this->plugin );
209
+ $this->live_update = new Live_Update( $this->plugin );
210
+ $this->export = new Export( $this->plugin );
211
+ }
212
+
213
+ /**
214
+ * Output specific updates passed as URL parameters.
215
+ *
216
+ * @action admin_notices
217
+ *
218
+ * @return void
219
+ */
220
+ public function prepare_admin_notices() {
221
+ $message = wp_mainwp_stream_filter_input( INPUT_GET, 'message' );
222
+
223
+ switch ( $message ) {
224
+ case 'settings_reset':
225
+ $this->notice( esc_html__( 'All site settings have been successfully reset.', 'mainwp-child-reports' ) );
226
+ break;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Handle notice messages according to the appropriate context (WP-CLI or the WP Admin)
232
+ *
233
+ * @param string $message Message to output.
234
+ * @param bool $is_error If the message is error_level (true) or warning (false).
235
+ */
236
+ public function notice( $message, $is_error = true ) {
237
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
238
+ $message = strip_tags( $message );
239
+
240
+ if ( $is_error ) {
241
+ WP_CLI::warning( $message );
242
+ } else {
243
+ WP_CLI::success( $message );
244
+ }
245
+ } else {
246
+ // Trigger admin notices late, so that any notices which occur during page load are displayed.
247
+ add_action( 'shutdown', array( $this, 'admin_notices' ) );
248
+
249
+ $notice = compact( 'message', 'is_error' );
250
+
251
+ if ( ! in_array( $notice, $this->notices, true ) ) {
252
+ $this->notices[] = $notice;
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Show an error or other message in the WP Admin
259
+ *
260
+ * @action shutdown
261
+ */
262
+ public function admin_notices() {
263
+ global $allowedposttags;
264
+
265
+ $custom = array(
266
+ 'progress' => array(
267
+ 'class' => true,
268
+ 'id' => true,
269
+ 'max' => true,
270
+ 'style' => true,
271
+ 'value' => true,
272
+ ),
273
+ );
274
+
275
+ $allowed_html = array_merge( $allowedposttags, $custom );
276
+
277
+ ksort( $allowed_html );
278
+
279
+ foreach ( $this->notices as $notice ) {
280
+ $class_name = empty( $notice['is_error'] ) ? 'updated' : 'error';
281
+ $html_message = sprintf( '<div class="%s">%s</div>', esc_attr( $class_name ), wpautop( $notice['message'] ) );
282
+
283
+ echo wp_kses( $html_message, $allowed_html );
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Register menu page
289
+ *
290
+ * @action admin_menu
291
+ *
292
+ * @return void
293
+ */
294
+ // DISABLED
295
+ public function register_menu() {
296
+ /**
297
+ * Filter the main admin menu title
298
+ *
299
+ * @return string
300
+ */
301
+ $main_menu_title = apply_filters( 'wp_mainwp_stream_admin_menu_title', esc_html__( 'MainWP Child Reports', 'mainwp-child-reports' ) );
302
+
303
+ /**
304
+ * Filter the main admin menu position
305
+ *
306
+ * Note: Using longtail decimal string to reduce the chance of position conflicts, see Codex
307
+ *
308
+ * @return string
309
+ */
310
+ $main_menu_position = apply_filters( 'wp_mainwp_stream_menu_position', '2.999999' );
311
+
312
+ /**
313
+ * Filter the main admin page title
314
+ *
315
+ * @return string
316
+ */
317
+ $main_page_title = apply_filters( 'wp_mainwp_stream_admin_page_title', esc_html__( 'Reports Records', 'mainwp-child-reports' ) );
318
+
319
+ $this->screen_id['main'] = add_menu_page(
320
+ $main_page_title,
321
+ $main_menu_title,
322
+ $this->view_cap,
323
+ $this->records_page_slug,
324
+ array( $this, 'render_list_table' ),
325
+ 'div',
326
+ $main_menu_position
327
+ );
328
+
329
+ /**
330
+ * Fires before submenu items are added to the Stream menu
331
+ * allowing plugins to add menu items before Settings
332
+ *
333
+ * @return void
334
+ */
335
+ do_action( 'wp_mainwp_stream_admin_menu' );
336
+
337
+ /**
338
+ * Filter the Settings admin page title
339
+ *
340
+ * @return string
341
+ */
342
+ $settings_page_title = apply_filters( 'wp_mainwp_stream_settings_form_title', esc_html__( 'Reports Settings', 'mainwp-child-reports' ) );
343
+
344
+ $this->screen_id['settings'] = add_submenu_page(
345
+ $this->records_page_slug,
346
+ $settings_page_title,
347
+ esc_html__( 'Settings', 'mainwp-child-reports' ),
348
+ $this->settings_cap,
349
+ $this->settings_page_slug,
350
+ array( $this, 'render_settings_page' )
351
+ );
352
+
353
+ if ( isset( $this->screen_id['main'] ) ) {
354
+ /**
355
+ * Fires just before the Stream list table is registered.
356
+ *
357
+ * @return void
358
+ */
359
+ do_action( 'wp_mainwp_stream_admin_menu_screens' );
360
+
361
+ // Register the list table early, so it associates the column headers with 'Screen settings'.
362
+ add_action(
363
+ 'load-' . $this->screen_id['main'], array(
364
+ $this,
365
+ 'register_list_table',
366
+ )
367
+ );
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Enqueue scripts/styles for admin screen
373
+ *
374
+ * @action admin_enqueue_scripts
375
+ *
376
+ * @param string $hook
377
+ *
378
+ * @return void
379
+ */
380
+ public function admin_enqueue_scripts( $hook ) {
381
+
382
+ wp_register_script( 'wp-mainwp-stream-select2', $this->plugin->locations['url'] . 'ui/lib/select2/js/select2.full.min.js', array( 'jquery' ), '3.5.2', true );
383
+ wp_register_style( 'wp-mainwp-stream-select2', $this->plugin->locations['url'] . 'ui/lib/select2/css/select2.min.css', array(), '3.5.2' );
384
+ wp_register_script( 'wp-mainwp-stream-timeago', $this->plugin->locations['url'] . 'ui/lib/timeago/jquery.timeago.js', array(), '1.4.1', true );
385
+
386
+ $locale = strtolower( substr( get_locale(), 0, 2 ) );
387
+ $file_tmpl = 'ui/lib/timeago/locales/jquery.timeago.%s.js';
388
+
389
+ if ( file_exists( $this->plugin->locations['dir'] . sprintf( $file_tmpl, $locale ) ) ) {
390
+ wp_register_script( 'wp-mainwp-stream-timeago-locale', $this->plugin->locations['url'] . sprintf( $file_tmpl, $locale ), array( 'wp-mainwp-stream-timeago' ), '1' );
391
+ } else {
392
+ wp_register_script( 'wp-mainwp-stream-timeago-locale', $this->plugin->locations['url'] . sprintf( $file_tmpl, 'en' ), array( 'wp-mainwp-stream-timeago' ), '1' );
393
+ }
394
+
395
+ $min = wp_mainwp_stream_min_suffix();
396
+ wp_enqueue_style( 'wp-mainwp-stream-admin', $this->plugin->locations['url'] . 'ui/css/admin.' . $min . 'css', array(), $this->plugin->get_version() );
397
+
398
+ $script_screens = array( 'plugins.php' );
399
+
400
+ //if ( in_array( $hook, $this->screen_id, true ) || in_array( $hook, $script_screens, true ) ) {
401
+ if ( $hook == 'settings_page_mainwp-reports-page' || $hook == 'settings_page_mainwp-reports-settings' || in_array( $hook, $script_screens, true ) ) {
402
+ wp_enqueue_script( 'wp-mainwp-stream-select2' );
403
+ wp_enqueue_style( 'wp-mainwp-stream-select2' );
404
+
405
+ wp_enqueue_script( 'wp-mainwp-stream-timeago' );
406
+ wp_enqueue_script( 'wp-mainwp-stream-timeago-locale' );
407
+
408
+ wp_enqueue_script(
409
+ 'wp-mainwp-stream-admin', $this->plugin->locations['url'] . 'ui/js/admin.' . $min . 'js', array(
410
+ 'jquery',
411
+ 'wp-mainwp-stream-select2',
412
+ ), $this->plugin->get_version()
413
+ );
414
+ wp_enqueue_script(
415
+ 'wp-mainwp-stream-admin-exclude', $this->plugin->locations['url'] . 'ui/js/exclude.' . $min . 'js', array(
416
+ 'jquery',
417
+ 'wp-mainwp-stream-select2',
418
+ ), $this->plugin->get_version()
419
+ );
420
+ wp_enqueue_script(
421
+ 'wp-mainwp-stream-live-updates', $this->plugin->locations['url'] . 'ui/js/live-updates.' . $min . 'js', array(
422
+ 'jquery',
423
+ 'heartbeat',
424
+ ), $this->plugin->get_version()
425
+ );
426
+
427
+ wp_localize_script(
428
+ 'wp-mainwp-stream-admin',
429
+ 'wp_mainwp_stream',
430
+ array(
431
+ 'i18n' => array(
432
+ 'confirm_purge' => esc_html__( 'Are you sure you want to delete all Reports activity records from the database? This cannot be undone.', 'mainwp-child-reports' ),
433
+ 'confirm_defaults' => esc_html__( 'Are you sure you want to reset all site settings to default? This cannot be undone.', 'mainwp-child-reports' ),
434
+ 'confirm_uninstall' => esc_html__( 'Are you sure you want to uninstall and deactivate Stream? This will delete all Reports tables from the database and cannot be undone.', 'mainwp-child-reports' ),
435
+ ),
436
+ 'locale' => esc_js( $locale ),
437
+ 'gmt_offset' => get_option( 'gmt_offset' ),
438
+ )
439
+ );
440
+
441
+ wp_localize_script(
442
+ 'wp-mainwp-stream-live-updates',
443
+ 'wp_mainwp_stream_live_updates',
444
+ array(
445
+ 'current_screen' => $hook,
446
+ 'current_page' => isset( $_GET['paged'] ) ? esc_js( $_GET['paged'] ) : '1', // WPCS: CSRF ok.
447
+ // input var okay, CSRF okay
448
+ 'current_order' => isset( $_GET['order'] ) ? esc_js( $_GET['order'] ) : 'desc', // WPCS: CSRF ok.
449
+ // input var okay, CSRF okay
450
+ 'current_query' => wp_mainwp_stream_json_encode( $_GET ), // WPCS: CSRF ok.
451
+ // input var okay, CSRF okay
452
+ 'current_query_count' => count( $_GET ), // WPCS: CSRF ok.
453
+ // input var okay, CSRF okay
454
+ )
455
+ );
456
+ }
457
+
458
+ /**
459
+ * The maximum number of items that can be updated in bulk without receiving a warning.
460
+ *
461
+ * Stream watches for bulk actions performed in the WordPress Admin (such as updating
462
+ * many posts at once) and warns the user before proceeding if the number of items they
463
+ * are attempting to update exceeds this threshold value. Since Stream will try to save
464
+ * a log for each item, it will take longer than usual to complete the operation.
465
+ *
466
+ * The default threshold is 100 items.
467
+ *
468
+ * @return int
469
+ */
470
+ $bulk_actions_threshold = apply_filters( 'wp_mainwp_stream_bulk_actions_threshold', 100 );
471
+
472
+ wp_enqueue_script( 'wp-mainwp-stream-global', $this->plugin->locations['url'] . 'ui/js/global.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version() );
473
+ wp_localize_script(
474
+ 'wp-mainwp-stream-global',
475
+ 'wp_mainwp_stream_global',
476
+ array(
477
+ 'bulk_actions' => array(
478
+ 'i18n' => array(
479
+ // translators: Placeholder refers to a number of items (e.g. "1,742")
480
+ 'confirm_action' => sprintf( esc_html__( 'Are you sure you want to perform bulk actions on over %s items? This process could take a while to complete.', 'mainwp-child-reports' ), number_format( absint( $bulk_actions_threshold ) ) ),
481
+ ),
482
+ 'threshold' => absint( $bulk_actions_threshold ),
483
+ ),
484
+ 'plugins_screen_url' => self_admin_url( 'plugins.php#stream' ),
485
+ )
486
+ );
487
+ }
488
+
489
+ /**
490
+ * Check whether or not the current admin screen belongs to Stream
491
+ *
492
+ * @return bool
493
+ */
494
+ public function is_stream_screen() {
495
+ if ( is_admin() && false !== strpos( wp_mainwp_stream_filter_input( INPUT_GET, 'page' ), $this->records_page_slug ) ) {
496
+ return true;
497
+ }
498
+
499
+ $screen = get_current_screen();
500
+ if ( is_admin() && Alerts::POST_TYPE === $screen->post_type ) {
501
+ return true;
502
+ }
503
+
504
+ return false;
505
+ }
506
+
507
+ /**
508
+ * Add a specific body class to all Stream admin screens
509
+ *
510
+ * @param string $classes CSS classes to output to body
511
+ *
512
+ * @filter admin_body_class
513
+ *
514
+ * @return string
515
+ */
516
+ public function admin_body_class( $classes ) {
517
+ $stream_classes = array();
518
+
519
+ if ( $this->is_stream_screen() ) {
520
+ $stream_classes[] = $this->admin_body_class;
521
+
522
+ if ( isset( $_GET['page'] ) ) { // CSRF okay
523
+ $stream_classes[] = sanitize_key( $_GET['page'] ); // input var okay, CSRF okay
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Filter the Stream admin body classes
529
+ *
530
+ * @return array
531
+ */
532
+ $stream_classes = apply_filters( 'wp_mainwp_stream_admin_body_classes', $stream_classes );
533
+ $stream_classes = implode( ' ', array_map( 'trim', $stream_classes ) );
534
+
535
+ return sprintf( '%s %s ', $classes, $stream_classes );
536
+ }
537
+
538
+ /**
539
+ * Add menu styles for various WP Admin skins
540
+ *
541
+ * @uses \wp_add_inline_style()
542
+ *
543
+ * @action admin_enqueue_scripts
544
+ */
545
+ public function admin_menu_css() {
546
+ $min = wp_mainwp_stream_min_suffix();
547
+ wp_register_style( 'wp-mainwp-stream-datepicker', $this->plugin->locations['url'] . 'ui/css/datepicker.' . $min . 'css', array(), $this->plugin->get_version() );
548
+ wp_register_style( 'wp-mainwp-stream-icons', $this->plugin->locations['url'] . 'ui/stream-icons/style.css', array(), $this->plugin->get_version() );
549
+
550
+ // Make sure we're working off a clean version
551
+ if ( ! file_exists( ABSPATH . WPINC . '/version.php' ) ) {
552
+ return;
553
+ }
554
+ include ABSPATH . WPINC . '/version.php';
555
+
556
+ if ( ! isset( $wp_version ) ) {
557
+ return;
558
+ }
559
+
560
+ $body_class = $this->admin_body_class;
561
+ $records_page = $this->records_page_slug;
562
+ $stream_url = $this->plugin->locations['url'];
563
+
564
+ if ( version_compare( $wp_version, '3.8-alpha', '>=' ) ) {
565
+ wp_enqueue_style( 'wp-mainwp-stream-icons' );
566
+
567
+ $css = "
568
+ #settings_page_{$records_page} .wp-menu-image:before {
569
+ font-family: 'WP Stream' !important;
570
+ content: '\\73' !important;
571
+ }
572
+ #settings_page_{$records_page} .wp-menu-image {
573
+ background-repeat: no-repeat;
574
+ }
575
+ #menu-posts-feedback .wp-menu-image:before {
576
+ font-family: dashicons !important;
577
+ content: '\\f175';
578
+ }
579
+ #adminmenu #menu-posts-feedback div.wp-menu-image {
580
+ background: none !important;
581
+ background-repeat: no-repeat;
582
+ }
583
+ body.{$body_class} #wpbody-content .wrap h1:nth-child(1):before {
584
+ font-family: 'WP Stream' !important;
585
+ content: '\\73';
586
+ padding: 0 8px 0 0;
587
+ }
588
+ ";
589
+ } else {
590
+ $css = "
591
+ #settings_page_{$records_page} .wp-menu-image {
592
+ background: url( {$stream_url}ui/stream-icons/menuicon-sprite.png ) 0 90% no-repeat;
593
+ }
594
+ /* Retina Stream Menu Icon */
595
+ @media only screen and (-moz-min-device-pixel-ratio: 1.5),
596
+ only screen and (-o-min-device-pixel-ratio: 3/2),
597
+ only screen and (-webkit-min-device-pixel-ratio: 1.5),
598
+ only screen and (min-device-pixel-ratio: 1.5) {
599
+ #settings_page_{$records_page} .wp-menu-image {
600
+ background: url( {$stream_url}ui/stream-icons/menuicon-sprite-2x.png ) 0 90% no-repeat;
601
+ background-size:30px 64px;
602
+ }
603
+ }
604
+ #settings_page_{$records_page}.current .wp-menu-image,
605
+ #settings_page_{$records_page}.wp-has-current-submenu .wp-menu-image,
606
+ #settings_page_{$records_page}:hover .wp-menu-image {
607
+ background-position: top left;
608
+ }
609
+ ";
610
+ }
611
+
612
+ \wp_add_inline_style( 'wp-admin', $css );
613
+ }
614
+
615
+ /**
616
+ * Handle the reset AJAX request to reset logs.
617
+ *
618
+ * @return bool
619
+ */
620
+ public function wp_ajax_reset() {
621
+ check_ajax_referer( 'stream_nonce_reset', 'wp_mainwp_stream_nonce_reset' );
622
+
623
+ if ( ! current_user_can( $this->settings_cap ) ) {
624
+ wp_die(
625
+ esc_html__( "You don't have sufficient privileges to do this action.", 'mainwp-child-reports' )
626
+ );
627
+ }
628
+
629
+ $this->erase_stream_records();
630
+
631
+ if ( defined( 'WP_MAINWP_STREAM_TESTS' ) && WP_MAINWP_STREAM_TESTS ) {
632
+ return true;
633
+ }
634
+
635
+ wp_redirect(
636
+ add_query_arg(
637
+ array(
638
+ 'page' => is_network_admin() ? $this->network->network_settings_page_slug : $this->settings_page_slug,
639
+ 'message' => 'data_erased',
640
+ ),
641
+ self_admin_url( $this->admin_parent_page )
642
+ )
643
+ );
644
+
645
+ exit;
646
+ }
647
+
648
+ private function erase_stream_records() {
649
+ global $wpdb;
650
+
651
+ $where = '';
652
+
653
+ if ( is_multisite() && ! is_plugin_active_for_network( $this->plugin->locations['plugin'] ) ) {
654
+ $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
655
+ }
656
+
657
+ $wpdb->query(
658
+ "DELETE `stream`, `meta`
659
+ FROM {$wpdb->mainwp_stream} AS `stream`
660
+ LEFT JOIN {$wpdb->mainwp_streammeta} AS `meta`
661
+ ON `meta`.`record_id` = `stream`.`ID`
662
+ WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
663
+ );
664
+ }
665
+
666
+ public function purge_schedule_setup() {
667
+ if ( ! wp_next_scheduled( 'wp_mainwp_stream_auto_purge' ) ) {
668
+ wp_schedule_event( time(), 'twicedaily', 'wp_mainwp_stream_auto_purge' );
669
+ }
670
+ }
671
+
672
+ public function purge_scheduled_action() {
673
+ global $wpdb;
674
+
675
+ // Don't purge when in Network Admin unless Stream is network activated
676
+ if (
677
+ is_multisite()
678
+ &&
679
+ is_network_admin()
680
+ &&
681
+ ! is_plugin_active_for_network( $this->plugin->locations['plugin'] )
682
+ ) {
683
+ return;
684
+ }
685
+
686
+ if ( is_multisite() && is_plugin_active_for_network( $this->plugin->locations['plugin'] ) ) {
687
+ $options = (array) get_site_option( 'wp_mainwp_stream_network', array() );
688
+ } else {
689
+ $options = (array) get_option( 'wp_mainwp_stream', array() );
690
+ }
691
+
692
+ if ( ! empty( $options['general_keep_records_indefinitely'] ) || ! isset( $options['general_records_ttl'] ) ) {
693
+ return;
694
+ }
695
+
696
+ $days = $options['general_records_ttl'];
697
+ $timezone = new DateTimeZone( 'UTC' );
698
+ $date = new DateTime( 'now', $timezone );
699
+
700
+ $date->sub( DateInterval::createFromDateString( "$days days" ) );
701
+
702
+ $where = $wpdb->prepare( ' AND `stream`.`created` < %s', $date->format( 'Y-m-d H:i:s' ) );
703
+
704
+ // Multisite but NOT network activated, only purge the current blog
705
+ if ( is_multisite() && ! is_plugin_active_for_network( $this->plugin->locations['plugin'] ) ) {
706
+ $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
707
+ }
708
+
709
+ $wpdb->query(
710
+ "DELETE `stream`, `meta`
711
+ FROM {$wpdb->mainwp_stream} AS `stream`
712
+ LEFT JOIN {$wpdb->mainwp_streammeta} AS `meta`
713
+ ON `meta`.`record_id` = `stream`.`ID`
714
+ WHERE 1=1 {$where};" // @codingStandardsIgnoreLine $where already prepared
715
+ );
716
+ }
717
+
718
+ /**
719
+ * @param array $links
720
+ * @param string $file
721
+ *
722
+ * @filter plugin_action_links
723
+ *
724
+ * @return array
725
+ */
726
+ public function plugin_action_links( $links, $file ) {
727
+ if ( plugin_basename( $this->plugin->locations['dir'] . 'stream.php' ) !== $file ) {
728
+ return $links;
729
+ }
730
+
731
+ // Also don't show links in Network Admin if Stream isn't network enabled
732
+ if ( is_network_admin() && is_multisite() && ! is_plugin_active_for_network( $this->plugin->locations['plugin'] ) ) {
733
+ return $links;
734
+ }
735
+
736
+ if ( is_network_admin() ) {
737
+ $admin_page_url = add_query_arg(
738
+ array(
739
+ 'page' => $this->network->network_settings_page_slug,
740
+ ), network_admin_url( $this->admin_parent_page )
741
+ );
742
+ } else {
743
+ $admin_page_url = add_query_arg(
744
+ array(
745
+ 'page' => $this->settings_page_slug,
746
+ ), admin_url( $this->admin_parent_page )
747
+ );
748
+ }
749
+
750
+ $links[] = sprintf( '<a href="%s">%s</a>', esc_url( $admin_page_url ), esc_html__( 'Settings', 'default' ) );
751
+
752
+ $url = add_query_arg(
753
+ array(
754
+ 'action' => 'wp_mainwp_stream_uninstall',
755
+ 'wp_mainwp_stream_nonce' => wp_create_nonce( 'stream_nonce' ),
756
+ ),
757
+ admin_url( 'admin-ajax.php' )
758
+ );
759
+
760
+ $links[] = sprintf( '<span id="wp_mainwp_stream_uninstall" class="delete"><a href="%s">%s</a></span>', esc_url( $url ), esc_html__( 'Uninstall', 'mainwp-child-reports' ) );
761
+
762
+ return $links;
763
+ }
764
+
765
+ /**
766
+ * Render main page
767
+ */
768
+ // DISABLED
769
+ public function render_list_table() {
770
+ $this->list_table->prepare_items();
771
+ ?>
772
+ <div class="wrap">
773
+ <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
774
+ <?php $this->list_table->display(); ?>
775
+ </div>
776
+ <?php
777
+ }
778
+
779
+ /**
780
+ * Render settings page
781
+ */
782
+ // DISABLED
783
+ public function render_settings_page() {
784
+ $option_key = $this->plugin->settings->option_key;
785
+ $form_action = apply_filters( 'wp_mainwp_stream_settings_form_action', admin_url( 'options.php' ) );
786
+
787
+ $page_description = apply_filters( 'wp_mainwp_stream_settings_form_description', '' );
788
+
789
+ $sections = $this->plugin->settings->get_fields();
790
+ $active_tab = wp_mainwp_stream_filter_input( INPUT_GET, 'tab' );
791
+ $min = wp_mainwp_stream_min_suffix();
792
+ wp_enqueue_script( 'wp-mainwp-stream-settings', $this->plugin->locations['url'] . 'ui/js/settings.' . $min . 'js', array( 'jquery' ), $this->plugin->get_version(), true );
793
+ ?>
794
+ <div class="wrap">
795
+ <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
796
+
797
+ <?php if ( ! empty( $page_description ) ) : ?>
798
+ <p><?php echo esc_html( $page_description ); ?></p>
799
+ <?php endif; ?>
800
+
801
+ <?php settings_errors(); ?>
802
+
803
+ <?php if ( count( $sections ) > 1 ) : ?>
804
+ <h2 class="nav-tab-wrapper">
805
+ <?php $i = 0; ?>
806
+ <?php foreach ( $sections as $section => $data ) : ?>
807
+ <?php $i++; ?>
808
+ <?php $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section ); ?>
809
+ <a href="<?php echo esc_url( add_query_arg( 'tab', $section ) ); ?>" class="nav-tab <?php echo $is_active ? esc_attr( ' nav-tab-active' ) : ''; ?>">
810
+ <?php echo esc_html( $data['title'] ); ?>
811
+ </a>
812
+ <?php endforeach; ?>
813
+ </h2>
814
+ <?php endif; ?>
815
+
816
+ <div class="nav-tab-content" id="tab-content-settings">
817
+ <form method="post" action="<?php echo esc_attr( $form_action ); ?>" enctype="multipart/form-data">
818
+ <div class="settings-sections">
819
+ <?php
820
+ $i = 0;
821
+ foreach ( $sections as $section => $data ) {
822
+ $i++;
823
+
824
+ $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section );
825
+
826
+ if ( $is_active ) {
827
+ settings_fields( $option_key );
828
+ do_settings_sections( $option_key );
829
+ }
830
+ }
831
+ ?>
832
+ </div>
833
+ <?php submit_button(); ?>
834
+ </form>
835
+ </div>
836
+ </div>
837
+ <?php
838
+ }
839
+
840
+ /**
841
+ * Instantiate the list table
842
+ */
843
+ // DISABLED
844
+ public function register_list_table() {
845
+ $this->list_table = new List_Table(
846
+ $this->plugin, array(
847
+ 'screen' => $this->screen_id['main'],
848
+ )
849
+ );
850
+ }
851
+
852
+ /**
853
+ * Check if a particular role has access
854
+ *
855
+ * @param string $role
856
+ *
857
+ * @return bool
858
+ */
859
+ // DISABLED
860
+ private function role_can_view( $role ) {
861
+ if ( in_array( $role, $this->plugin->settings->options['general_role_access'], true ) ) {
862
+ return true;
863
+ }
864
+
865
+ return false;
866
+ }
867
+
868
+ /**
869
+ * Filter user caps to dynamically grant our view cap based on allowed roles
870
+ *
871
+ * @param $allcaps
872
+ * @param $caps
873
+ * @param $args
874
+ * @param $user
875
+ *
876
+ * @filter user_has_cap
877
+ *
878
+ * @return array
879
+ */
880
+ // DISABLED
881
+ public function filter_user_caps( $allcaps, $caps, $args, $user = null ) {
882
+ global $wp_roles;
883
+
884
+ $_wp_roles = isset( $wp_roles ) ? $wp_roles : new WP_Roles();
885
+
886
+ $user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
887
+
888
+ // @see
889
+ // https://github.com/WordPress/WordPress/blob/c67c9565f1495255807069fdb39dac914046b1a0/wp-includes/capabilities.php#L758
890
+ $roles = array_unique(
891
+ array_merge(
892
+ $user->roles,
893
+ array_filter(
894
+ array_keys( $user->caps ),
895
+ array( $_wp_roles, 'is_role' )
896
+ )
897
+ )
898
+ );
899
+
900
+ $stream_view_caps = array( $this->view_cap );
901
+
902
+ foreach ( $caps as $cap ) {
903
+ if ( in_array( $cap, $stream_view_caps, true ) ) {
904
+ foreach ( $roles as $role ) {
905
+ if ( $this->role_can_view( $role ) ) {
906
+ $allcaps[ $cap ] = true;
907
+
908
+ break 2;
909
+ }
910
+ }
911
+ }
912
+ }
913
+
914
+ return $allcaps;
915
+ }
916
+
917
+ /**
918
+ * Filter role caps to dynamically grant our view cap based on allowed roles
919
+ *
920
+ * @filter role_has_cap
921
+ *
922
+ * @param $allcaps
923
+ * @param $cap
924
+ * @param $role
925
+ *
926
+ * @return array
927
+ */
928
+ // DISABLED
929
+ public function filter_role_caps( $allcaps, $cap, $role ) {
930
+ $stream_view_caps = array( $this->view_cap );
931
+
932
+ if ( in_array( $cap, $stream_view_caps, true ) && $this->role_can_view( $role ) ) {
933
+ $allcaps[ $cap ] = true;
934
+ }
935
+
936
+ return $allcaps;
937
+ }
938
+
939
+ /**
940
+ * @action wp_ajax_wp_mainwp_stream_filters
941
+ */
942
+ public function ajax_filters() {
943
+ if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
944
+ wp_die( '-1' );
945
+ }
946
+
947
+ check_ajax_referer( 'mainwp_stream_filters_user_search_nonce', 'nonce' );
948
+
949
+ switch ( wp_mainwp_stream_filter_input( INPUT_GET, 'filter' ) ) {
950
+ case 'user_id':
951
+ $users = array_merge(
952
+ array(
953
+ 0 => (object) array(
954
+ 'display_name' => 'WP-CLI',
955
+ ),
956
+ ),
957
+ get_users()
958
+ );
959
+
960
+ $search = wp_mainwp_stream_filter_input( INPUT_GET, 'q' );
961
+ if ( $search ) {
962
+ // `search` arg for get_users() is not enough
963
+ $users = array_filter(
964
+ $users,
965
+ function ( $user ) use ( $search ) {
966
+ return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( $search ) );
967
+ }
968
+ );
969
+ }
970
+
971
+ if ( count( $users ) > $this->preload_users_max ) {
972
+ $users = array_slice( $users, 0, $this->preload_users_max );
973
+ }
974
+
975
+ // Get gravatar / roles for final result set
976
+ $results = $this->get_users_record_meta( $users );
977
+
978
+ break;
979
+ }
980
+
981
+ if ( isset( $results ) ) {
982
+ echo wp_mainwp_stream_json_encode( $results ); // xss ok
983
+ }
984
+
985
+ die();
986
+ }
987
+
988
+ public function get_users_record_meta( $authors ) {
989
+ $authors_records = array();
990
+
991
+ foreach ( $authors as $user_id => $args ) {
992
+ $author = new Author( $args->ID );
993
+
994
+ $authors_records[ $user_id ] = array(
995
+ 'text' => $author->get_display_name(),
996
+ 'id' => $author->id,
997
+ 'label' => $author->get_display_name(),
998
+ 'icon' => $author->get_avatar_src( 32 ),
999
+ 'title' => '',
1000
+ );
1001
+ }
1002
+
1003
+ return $authors_records;
1004
+ }
1005
+
1006
+ /**
1007
+ * Get user meta in a way that is also safe for VIP
1008
+ *
1009
+ * @param int $user_id
1010
+ * @param string $meta_key
1011
+ * @param bool $single (optional)
1012
+ *
1013
+ * @return mixed
1014
+ */
1015
+ public function get_user_meta( $user_id, $meta_key, $single = true ) {
1016
+ if ( wp_mainwp_stream_is_vip() && function_exists( 'get_user_attribute' ) ) {
1017
+ return get_user_attribute( $user_id, $meta_key );
1018
+ }
1019
+
1020
+ return get_user_meta( $user_id, $meta_key, $single );
1021
+ }
1022
+
1023
+ /**
1024
+ * Update user meta in a way that is also safe for VIP
1025
+ *
1026
+ * @param int $user_id
1027
+ * @param string $meta_key
1028
+ * @param mixed $meta_value
1029
+ * @param mixed $prev_value (optional)
1030
+ *
1031
+ * @return int|bool
1032
+ */
1033
+ public function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) {
1034
+ if ( wp_mainwp_stream_is_vip() && function_exists( 'update_user_attribute' ) ) {
1035
+ return update_user_attribute( $user_id, $meta_key, $meta_value );
1036
+ }
1037
+
1038
+ return update_user_meta( $user_id, $meta_key, $meta_value, $prev_value );
1039
+ }
1040
+
1041
+ /**
1042
+ * Delete user meta in a way that is also safe for VIP
1043
+ *
1044
+ * @param int $user_id
1045
+ * @param string $meta_key
1046
+ * @param mixed $meta_value (optional)
1047
+ *
1048
+ * @return bool
1049
+ */
1050
+ public function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) {
1051
+ if ( wp_mainwp_stream_is_vip() && function_exists( 'delete_user_attribute' ) ) {
1052
+ return delete_user_attribute( $user_id, $meta_key, $meta_value );
1053
+ }
1054
+
1055
+ return delete_user_meta( $user_id, $meta_key, $meta_value );
1056
+ }
1057
+ }
classes/class-alert-trigger.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Alert Trigger abstract class.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert_Trigger
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ abstract class Alert_Trigger {
16
+
17
+ /**
18
+ * Hold the Plugin class
19
+ *
20
+ * @var Plugin
21
+ */
22
+ public $plugin;
23
+
24
+ /**
25
+ * Unique identifier
26
+ *
27
+ * @var string
28
+ */
29
+ public $slug;
30
+
31
+ /**
32
+ * Class constructor
33
+ *
34
+ * @param Plugin $plugin Plugin class.
35
+ */
36
+ public function __construct( $plugin ) {
37
+ $this->plugin = $plugin;
38
+
39
+ add_action( 'wp_mainwp_stream_alert_trigger_form_display', array( $this, 'add_fields' ), 10, 2 );
40
+ add_action( 'wp_mainwp_stream_alert_trigger_form_save', array( $this, 'save_fields' ), 10, 1 );
41
+ add_filter( 'wp_mainwp_stream_alert_trigger_check', array( $this, 'check_record' ), 10, 4 );
42
+ }
43
+
44
+ /**
45
+ * Checks if a record matches the criteria from the trigger.
46
+ *
47
+ * @filter wp_mainwp_stream_alert_trigger_check
48
+ *
49
+ * @param bool $success Status of previous checks.
50
+ * @param int $record_id Record ID.
51
+ * @param array $recordarr Record data.
52
+ * @param Alert $alert The Alert being worked on.
53
+ * @return bool False on failure, otherwise should return original value of $success.
54
+ */
55
+ abstract public function check_record ( $success, $record_id, $recordarr, $alert );
56
+
57
+ /**
58
+ * Adds fields to the trigger form.
59
+ *
60
+ * @action wp_mainwp_stream_alert_trigger_form_display
61
+ *
62
+ * @param Form_Generator $form The Form Object to add to.
63
+ * @param Alert $alert The Alert being worked on.
64
+ * @return void
65
+ */
66
+ abstract public function add_fields( $form, $alert );
67
+
68
+ /**
69
+ * Validate and save Alert object
70
+ *
71
+ * @action wp_mainwp_stream_alert_trigger_form_save
72
+ *
73
+ * @param Alert $alert The Alert being worked on.
74
+ * @return void
75
+ */
76
+ abstract public function save_fields( $alert );
77
+
78
+ /**
79
+ * Returns the trigger's value for the given alert.
80
+ *
81
+ * @param string $context The location this data will be displayed in.
82
+ * @param Alert $alert Alert being processed.
83
+ * @return string
84
+ */
85
+ abstract public function get_display_value( $context = 'normal', $alert );
86
+
87
+ /**
88
+ * Allow connectors to determine if their dependencies is satisfied or not
89
+ *
90
+ * @return bool
91
+ */
92
+ public function is_dependency_satisfied() {
93
+ return true;
94
+ }
95
+ }
classes/class-alert-type.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Alert Type abstract class.
4
+ *
5
+ * Used to register new Alert types.
6
+ *
7
+ * @package WP_MainWP_Stream
8
+ */
9
+
10
+ namespace WP_MainWP_Stream;
11
+
12
+ /**
13
+ * Class Alert_Type
14
+ *
15
+ * @package WP_MainWP_Stream
16
+ */
17
+ abstract class Alert_Type {
18
+
19
+ /**
20
+ * Hold the Plugin class
21
+ *
22
+ * @var Plugin
23
+ */
24
+ public $plugin;
25
+
26
+ /**
27
+ * Unique identifier.
28
+ *
29
+ * @var string
30
+ */
31
+ public $slug;
32
+
33
+ /**
34
+ * Class constructor.
35
+ *
36
+ * @param Plugin $plugin Plugin object.
37
+ */
38
+ public function __construct( $plugin ) {
39
+ $this->plugin = $plugin;
40
+ }
41
+
42
+ /**
43
+ * Alert recipients about the new record
44
+ *
45
+ * @param int $record_id Record ID.
46
+ * @param array $recordarr Record details.
47
+ * @param array $options Alert options.
48
+ */
49
+ abstract public function alert( $record_id, $recordarr, $options );
50
+
51
+ /**
52
+ * Display settings form for configuration of individual alerts
53
+ *
54
+ * @param Alert $alert Alert currently being worked on.
55
+ */
56
+ public function display_fields( $alert ) {
57
+ // Implementation optional, but recommended
58
+ }
59
+
60
+ /**
61
+ * Process settings form for configuration of individual alerts
62
+ *
63
+ * @param Alert $alert Alert currently being worked on.
64
+ */
65
+ public function save_fields( $alert ) {
66
+ // Implementation optional, but recommended
67
+ }
68
+
69
+ /**
70
+ * Allow connectors to determine if their dependencies are satisfied or not
71
+ *
72
+ * @return bool
73
+ */
74
+ public function is_dependency_satisfied() {
75
+ // Implementation optional, but recommended
76
+ return true;
77
+ }
78
+ }
classes/class-alert.php ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Single Alert handler.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alert
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alert {
16
+
17
+ /**
18
+ * Alert post ID
19
+ *
20
+ * @var int
21
+ */
22
+ public $ID;
23
+
24
+ /**
25
+ * Creation date
26
+ *
27
+ * @var string
28
+ */
29
+ public $date;
30
+
31
+ /**
32
+ * Status
33
+ *
34
+ * @var string
35
+ */
36
+ public $status;
37
+
38
+ /**
39
+ * Alert author ID
40
+ *
41
+ * @var int
42
+ */
43
+ public $author;
44
+
45
+ /**
46
+ * Alert type
47
+ *
48
+ * @var string
49
+ */
50
+ public $alert_type;
51
+
52
+ /**
53
+ * Alert meta data
54
+ *
55
+ * @var int
56
+ */
57
+ public $alert_meta;
58
+
59
+ /**
60
+ * Hold Plugin class
61
+ *
62
+ * @var Plugin
63
+ */
64
+ public $plugin;
65
+
66
+ /**
67
+ * Class constructor
68
+ *
69
+ * @param object $item Alert data.
70
+ * @param Plugin $plugin Plugin class.
71
+ * @return void
72
+ */
73
+ public function __construct( $item, $plugin ) {
74
+ $this->plugin = $plugin;
75
+
76
+ $this->ID = isset( $item->ID ) ? $item->ID : null;
77
+ $this->status = isset( $item->status ) ? $item->status : 'wp_mainwp_stream_disabled';
78
+ $this->date = isset( $item->date ) ? $item->date : null;
79
+ $this->author = isset( $item->author ) ? $item->author : null;
80
+
81
+ $this->alert_type = isset( $item->alert_type ) ? $item->alert_type : null;
82
+ $this->alert_meta = isset( $item->alert_meta ) ? $item->alert_meta : array();
83
+ }
84
+
85
+ /**
86
+ * Save state of the Alert to database
87
+ *
88
+ * @return int The Post ID of the alert.
89
+ */
90
+ public function save() {
91
+
92
+ $args = array(
93
+ 'ID' => $this->ID,
94
+ 'post_date' => $this->date,
95
+ 'post_status' => $this->status,
96
+ 'post_content' => '',
97
+ 'post_title' => $this->get_title(),
98
+ 'post_author' => $this->author,
99
+ 'post_type' => Alerts::POST_TYPE,
100
+ );
101
+
102
+ if ( empty( $args['ID'] ) ) {
103
+ unset( $args['ID'] );
104
+ }
105
+
106
+ $post_id = wp_insert_post( $args );
107
+ if ( empty( $args['ID'] ) ) {
108
+ $this->ID = $post_id;
109
+ }
110
+
111
+ $meta = array(
112
+ 'alert_type' => $this->alert_type,
113
+ 'alert_meta' => $this->alert_meta,
114
+ );
115
+
116
+ foreach ( $meta as $key => $value ) {
117
+ $this->update_meta( $key, $value );
118
+ }
119
+
120
+ return $post_id;
121
+ }
122
+
123
+ /**
124
+ * Process settings form data
125
+ *
126
+ * @param array $data Processed post object data.
127
+ * @return array New post object data.
128
+ */
129
+ public function process_settings_form( $data ) {
130
+
131
+ $args = array(
132
+ 'post_date' => $this->date,
133
+ 'post_status' => $this->status,
134
+ 'post_title' => $this->get_title(),
135
+ 'post_author' => $this->author,
136
+ 'post_type' => Alerts::POST_TYPE,
137
+ );
138
+
139
+ foreach ( $args as $key => $value ) {
140
+ $data[ $key ] = $value;
141
+ }
142
+
143
+ $meta_input = array(
144
+ 'alert_type' => $this->alert_type,
145
+ 'alert_meta' => $this->alert_meta,
146
+ );
147
+
148
+ foreach ( $meta_input as $key => $value ) {
149
+ $this->update_meta( $key, $value );
150
+ }
151
+
152
+ return $data;
153
+ }
154
+
155
+ /**
156
+ * Query record meta
157
+ *
158
+ * @param string $meta_key Meta key to retrieve (optional). Otherwise will
159
+ * grab all meta data for the ID.
160
+ * @param bool $single Whether to only retrieve the first value (optional).
161
+ *
162
+ * @return mixed Single value if $single is true, array if false.
163
+ */
164
+ public function get_meta( $meta_key = '', $single = false ) {
165
+ return get_post_meta( $this->ID, $meta_key, $single );
166
+ }
167
+
168
+ /**
169
+ * Update record meta
170
+ *
171
+ * @param string $meta_key Meta key to update.
172
+ * @param string $meta_value Value to update with.
173
+ * @param string $prev_value Previous value to change (optional).
174
+ * @return array
175
+ */
176
+ public function update_meta( $meta_key, $meta_value, $prev_value = '' ) {
177
+ return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
178
+ }
179
+
180
+ /**
181
+ * Determine the title of the alert.
182
+ *
183
+ * @todo enhance human readibility
184
+ * @return string The title of the alert
185
+ */
186
+ public function get_title() {
187
+
188
+ $alert_type = $this->get_alert_type_obj()->name;
189
+
190
+ $output = array();
191
+ foreach ( array( 'action', 'author', 'context' ) as $trigger_type ) {
192
+ $output[ $trigger_type ] = $this->plugin->alerts->alert_triggers[ $trigger_type ]->get_display_value( 'list_table', $this );
193
+ }
194
+ $title = '';
195
+ foreach ( $this->plugin->alerts->alert_triggers as $trigger_type => $trigger_obj ) {
196
+ $value = $trigger_obj->get_display_value( 'list_table', $this );
197
+ $title .= $value . ' > ';
198
+ }
199
+ $title = rtrim( $title, ' > ' );
200
+ return $title;
201
+ }
202
+
203
+ /**
204
+ * Retrive current alert type object
205
+ *
206
+ * @return Alert_Type
207
+ */
208
+ public function get_alert_type_obj() {
209
+ if ( array_key_exists( $this->alert_type, $this->plugin->alerts->alert_types ) ) {
210
+ $obj = $this->plugin->alerts->alert_types[ $this->alert_type ];
211
+ } else {
212
+ $obj = new Alert_Type_None( $this->plugin );
213
+ }
214
+ return $obj;
215
+ }
216
+
217
+ /**
218
+ * Check if record matches trigger criteria.
219
+ *
220
+ * @param int $record_id Record ID.
221
+ * @param array $recordarr Record data.
222
+ * @return bool True if a positive match. False otherwise.
223
+ */
224
+ public function check_record( $record_id, $recordarr ) {
225
+ return apply_filters( 'wp_mainwp_stream_alert_trigger_check', true, $record_id, $recordarr, $this );
226
+ }
227
+
228
+ /**
229
+ * Trigger alert for a specific record.
230
+ *
231
+ * @param int $record_id Record ID.
232
+ * @param int $recordarr Record Data.
233
+ * @return void
234
+ */
235
+ public function send_alert( $record_id, $recordarr ) {
236
+ $this->get_alert_type_obj()->alert( $record_id, $recordarr, $this );
237
+ }
238
+
239
+ /**
240
+ * Record Alerts triggered by a Record.
241
+ *
242
+ * This should be used any time an alert is triggered.
243
+ *
244
+ * Stores the post ID of an Alert triggered by a record.
245
+ *
246
+ * As an example, this exists so that its value(s) can then be used
247
+ * to fetch meta from that Alert in later operations.
248
+ *
249
+ * This also creates the Alert triggered meta if not found.
250
+ *
251
+ * @see Alert_Type_Highlight::alert() for an example.
252
+ *
253
+ * @param object $record The Record.
254
+ * @param string $alert_slug The Alert Type slug.
255
+ * @param array $alert_meta Alert meta.
256
+ *
257
+ * @return bool If the meta was updated successfully.
258
+ */
259
+ public static function update_record_triggered_alerts( $record, $alert_slug, $alert_meta ) {
260
+ if ( ! is_string( $alert_slug ) ) {
261
+ return false;
262
+ }
263
+
264
+ if ( is_array( $record ) ) {
265
+ $record = (object) $record;
266
+ }
267
+ if ( empty( $record->ID ) ) {
268
+ return false;
269
+ }
270
+ $record = new Record( $record );
271
+ $alerts_triggered = $record->get_meta( Alerts::ALERTS_TRIGGERED_META_KEY, true );
272
+
273
+ if ( empty( $alerts_triggered ) || ! is_array( $alerts_triggered ) ) {
274
+ $alerts_triggered = array(
275
+ $alert_slug => $alert_meta,
276
+ );
277
+ } elseif ( ! array_key_exists( $alert_slug, $alerts_triggered ) || ! is_array( $alerts_triggered[ $alert_slug ] ) ) {
278
+ $alerts_triggered[ $alert_slug ] = $alert_meta;
279
+ }
280
+ return $record->update_meta( Alerts::ALERTS_TRIGGERED_META_KEY, $alerts_triggered );
281
+ }
282
+
283
+ /**
284
+ * Get a meta value from the Alert that a Record has triggered.
285
+ *
286
+ * If a Record has triggered an Alert (post), this fetches a specific
287
+ * Alert meta (i.e., "post meta") value from that Alert.
288
+ *
289
+ * First, it gets the array of Alerts that the Record has triggered.
290
+ * Then, using the requested Alert Type, it grabs the first item (post ID) in
291
+ * that Type.
292
+ *
293
+ * Using that ID, it fetches that Alert post's meta, then
294
+ * returns the value of the requested setting (ie., "post meta" field).
295
+ *
296
+ * @see Alert_Type_Highlight::post_class() for an example.
297
+ *
298
+ * @param object $record The Record object.
299
+ * @param string $alert_slug The slug of the Alert Type.
300
+ * @param string $setting The requested meta value of the Alert.
301
+ * @param mixed $default The default value if no value is found.
302
+ *
303
+ * @return mixed
304
+ */
305
+ public function get_single_alert_setting_from_record( $record, $alert_slug, $setting, $default = false ) {
306
+ if ( ! is_object( $record ) || ! is_string( $alert_slug ) || ! is_string( $setting ) ) {
307
+ return false;
308
+ }
309
+ $record = new Record( $record );
310
+ $alerts_triggered = $record->get_meta( Alerts::ALERTS_TRIGGERED_META_KEY, true );
311
+
312
+ // Ensure we have a meta array and that this record has triggered a highlight alert.
313
+ if ( empty( $alerts_triggered ) || ! is_array( $alerts_triggered ) || ! array_key_exists( $alert_slug, $alerts_triggered ) ) {
314
+ return false;
315
+ }
316
+
317
+ $values = $alerts_triggered[ $alert_slug ];
318
+ if ( empty( $values ) ) {
319
+ return false;
320
+ }
321
+
322
+ // Grab an Alert post ID.
323
+ // @todo determine which Alert post takes priority.
324
+ if ( is_array( $values ) ) {
325
+ $post_id = $values[0];
326
+ } else {
327
+ $post_id = $values;
328
+ }
329
+
330
+ if ( ! is_numeric( $post_id ) ) {
331
+ return false;
332
+ }
333
+
334
+ $alert = $this->plugin->alerts->get_alert( $post_id );
335
+
336
+ $value = ! empty( $alert->alert_meta[ $setting ] ) ? $alert->alert_meta[ $setting ] : $default;
337
+ return $value;
338
+ }
339
+ }
classes/class-alerts-list.php ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Listing of Alerts in the WP Admin.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alerts_List
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alerts_List {
16
+ /**
17
+ * Hold the Plugin class
18
+ *
19
+ * @var Plugin
20
+ */
21
+ public $plugin;
22
+
23
+ /**
24
+ * Class constructor.
25
+ *
26
+ * @param Plugin $plugin The main Plugin class.
27
+ */
28
+ public function __construct( $plugin ) {
29
+ $this->plugin = $plugin;
30
+
31
+ add_filter( 'bulk_actions-edit-wp_mainwp_alerts', array( $this, 'suppress_bulk_actions' ), 10, 1 );
32
+ add_filter( 'disable_months_dropdown', array( $this, 'suppress_months_dropdown' ), 10, 2 );
33
+ add_filter( 'post_row_actions', array( $this, 'suppress_quick_edit' ), 10, 1 );
34
+
35
+ // @todo Make more specific
36
+ if ( is_admin() ) {
37
+ add_filter( 'request', array( $this, 'parse_request' ), 10, 2 );
38
+ }
39
+ add_filter( 'views_edit-wp_mainwp_alerts', array( $this, 'manage_views' ) );
40
+
41
+ add_filter( 'manage_wp_mainwp_alerts_posts_columns', array( $this, 'manage_columns' ) );
42
+ add_action( 'manage_wp_mainwp_alerts_posts_custom_column', array( $this, 'column_data' ), 10, 2 );
43
+
44
+ add_action( 'quick_edit_custom_box', array( $this, 'display_custom_quick_edit' ), 10, 2 );
45
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
46
+
47
+ add_filter( 'wp_insert_post_data', array( $this, 'save_alert_inline_edit' ), 99, 2 );
48
+ }
49
+
50
+ /**
51
+ * Default to wp_mainwp_stream_enabled and wp_mainwp_stream_disabled when querying for alerts
52
+ *
53
+ * @filter request
54
+ *
55
+ * @param array $query_vars Arguments for query to populate table.
56
+ * @return array
57
+ */
58
+ public function parse_request( $query_vars ) {
59
+ $screen = get_current_screen();
60
+ if ( 'edit-wp_mainwp_alerts' === $screen->id && Alerts::POST_TYPE === $query_vars['post_type'] && empty( $query_vars['post_status'] ) ) {
61
+ $query_vars['post_status'] = array( 'wp_mainwp_stream_enabled', 'wp_mainwp_stream_disabled' );
62
+ }
63
+ return $query_vars;
64
+ }
65
+
66
+ /**
67
+ * Manage views on the alerts list view
68
+ *
69
+ * @filter views_edit-wp_mainwp_alerts
70
+ *
71
+ * @param array $views View links HTML.
72
+ * @return array
73
+ */
74
+ public function manage_views( $views ) {
75
+
76
+ if ( array_key_exists( 'trash', $views ) ) {
77
+ $trash = $views['trash'];
78
+ unset( $views['trash'] );
79
+ $views['trash'] = $trash;
80
+ }
81
+
82
+ return $views;
83
+ }
84
+
85
+ /**
86
+ * Manages columns on the alerts list view
87
+ *
88
+ * @filter manage_wp_mainwp_alerts_posts_columns
89
+ *
90
+ * @param array $columns Column id -> title array.
91
+ * @return array
92
+ */
93
+ public function manage_columns( $columns ) {
94
+ $columns = array(
95
+ 'cb' => $columns['cb'],
96
+ 'alert_trigger' => __( 'Trigger', 'mainwp-child-reports' ),
97
+ 'alert_type' => __( 'Type', 'mainwp-child-reports' ),
98
+ 'alert_status' => __( 'Status', 'mainwp-child-reports' ),
99
+ );
100
+ return $columns;
101
+ }
102
+
103
+ /**
104
+ * Fills in column data for custom columns.
105
+ *
106
+ * @action manage_wp_mainwp_alerts_posts_custom_column
107
+ *
108
+ * @param string $column_name Column name to show data for.
109
+ * @param int $post_id The post being processed.
110
+ * @return mixed
111
+ */
112
+ public function column_data( $column_name, $post_id ) {
113
+
114
+ $alert = $this->plugin->alerts->get_alert( $post_id );
115
+ if ( ! $alert ) {
116
+ return;
117
+ }
118
+
119
+ switch ( $column_name ) {
120
+ case 'alert_trigger':
121
+ $values = array();
122
+ foreach ( $this->plugin->alerts->alert_triggers as $trigger_type => $trigger_obj ) {
123
+ $value = $trigger_obj->get_display_value( 'list_table', $alert );
124
+ $values[] = '<span class="alert_trigger_value alert_trigger_' . esc_attr( $trigger_type ) . '">' . esc_html( $value ) . '</span>';
125
+ }
126
+ ?>
127
+ <div><?php echo wp_kses_post( join( '', $values ) ); ?></div>
128
+ <div class="row-actions wp-mainwp-stream-show-mobile">
129
+ <?php echo wp_kses_post( $this->custom_column_actions( $post_id ) ); ?>
130
+ <button type="button" class="toggle-row"><span class="screen-reader-text"><?php echo esc_html__( 'Show more details', 'mainwp-child-reports' ); ?></span></button>
131
+ </div>
132
+ <?php
133
+ if ( ! empty( $alert->alert_meta['trigger_connector'] ) ) {
134
+ $trigger_connector = $alert->alert_meta['trigger_connector'];
135
+ } else {
136
+ $trigger_connector = '';
137
+ }
138
+ if ( ! empty( $alert->alert_meta['trigger_context'] ) ) {
139
+ $trigger_context = $alert->alert_meta['trigger_context'];
140
+ } else {
141
+ $trigger_context = '';
142
+ }
143
+ if ( ! empty( $alert->alert_meta['trigger_action'] ) ) {
144
+ $trigger_action = $alert->alert_meta['trigger_action'];
145
+ } else {
146
+ $trigger_action = '';
147
+ }
148
+ ?>
149
+ <input type="hidden" name="wp_mainwp_stream_trigger_connector" value="<?php echo esc_attr( $trigger_connector ); ?>" />
150
+ <input type="hidden" name="wp_mainwp_stream_trigger_context" value="<?php echo esc_attr( $trigger_context ); ?>" />
151
+ <input type="hidden" name="wp_mainwp_stream_trigger_action" value="<?php echo esc_attr( $trigger_action ); ?>" />
152
+ <?php
153
+ echo wp_kses_post( $this->custom_column_actions( $post_id ) );
154
+ break;
155
+ case 'alert_type':
156
+ $alert_type = $alert->alert_type;
157
+ if ( ! empty( $this->plugin->alerts->alert_types[ $alert_type ]->name ) ) {
158
+ $alert_name = $this->plugin->alerts->alert_types[ $alert_type ]->name;
159
+ } else {
160
+ $alert_name = 'Untitled Alert';
161
+ }
162
+ ?>
163
+ <input type="hidden" name="wp_mainwp_stream_alert_type" value="<?php echo esc_attr( $alert->alert_type ); ?>" />
164
+ <strong class="row-title"><?php echo esc_html( $alert_name ); ?></strong>
165
+ <?php
166
+ if ( ! empty( $alert->alert_meta['color'] ) ) {
167
+ ?>
168
+ <input type="hidden" name="wp_mainwp_stream_highlight_color" value="<?php echo esc_attr( $alert->alert_meta['color'] ); ?>" />
169
+ <?php
170
+ }
171
+ if ( ! empty( $alert->alert_meta['email_recipient'] ) ) {
172
+ ?>
173
+ <input type="hidden" name="wp_mainwp_stream_email_recipient" value="<?php echo esc_attr( $alert->alert_meta['email_recipient'] ); ?>" />
174
+ <?php
175
+ }
176
+ if ( ! empty( $alert->alert_meta['email_subject'] ) ) {
177
+ ?>
178
+ <input type="hidden" name="wp_mainwp_stream_email_subject" value="<?php echo esc_attr( $alert->alert_meta['email_subject'] ); ?>" />
179
+ <?php
180
+ }
181
+ if ( ! empty( $alert->alert_meta['event_name'] ) ) {
182
+ ?>
183
+ <input type="hidden" name="wp_mainwp_stream_ifttt_event_name" value="<?php echo esc_attr( $alert->alert_meta['event_name'] ); ?>" />
184
+ <?php
185
+ }
186
+ if ( ! empty( $alert->alert_meta['maker_key'] ) ) {
187
+ ?>
188
+ <input type="hidden" name="wp_mainwp_stream_ifttt_maker_key" value="<?php echo esc_attr( $alert->alert_meta['maker_key'] ); ?>" />
189
+ <?php
190
+ }
191
+ if ( ! empty( $alert->alert_meta['slack_webhook'] ) ) {
192
+ ?>
193
+ <input type="hidden" name="wp_mainwp_stream_slack_webhook" value="<?php echo esc_attr( $alert->alert_meta['slack_webhook'] ); ?>" />
194
+ <?php
195
+ }
196
+ if ( ! empty( $alert->alert_meta['slack_channel'] ) ) {
197
+ ?>
198
+ <input type="hidden" name="wp_mainwp_stream_slack_channel" value="<?php echo esc_attr( $alert->alert_meta['slack_channel'] ); ?>" />
199
+ <?php
200
+ }
201
+ if ( ! empty( $alert->alert_meta['slack_username'] ) ) {
202
+ ?>
203
+ <input type="hidden" name="wp_mainwp_stream_slack_username" value="<?php echo esc_attr( $alert->alert_meta['slack_username'] ); ?>" />
204
+ <?php
205
+ }
206
+ if ( ! empty( $alert->alert_meta['slack_icon'] ) ) {
207
+ ?>
208
+ <input type="hidden" name="wp_mainwp_stream_slack_icon" value="<?php echo esc_attr( $alert->alert_meta['slack_icon'] ); ?>" />
209
+ <?php
210
+ }
211
+ break;
212
+ case 'alert_status':
213
+ $post_status_object = get_post_status_object( get_post_status( $post_id ) );
214
+ if ( ! empty( $post_status_object ) ) {
215
+ echo esc_html( $post_status_object->label );
216
+ }
217
+ ?>
218
+ <input type="hidden" name="wp_mainwp_stream_alert_status" value="<?php echo esc_attr( $post_status_object->name ); ?>" />
219
+ <?php
220
+ break;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Remove 'edit' action from bulk actions
226
+ *
227
+ * @filter bulk_actions-edit-wp_mainwp_alerts
228
+ *
229
+ * @param array $actions List of bulk actions available.
230
+ * @return array
231
+ */
232
+ public function suppress_bulk_actions( $actions ) {
233
+ unset( $actions['edit'] );
234
+ return $actions;
235
+ }
236
+
237
+ /**
238
+ * Remove quick edit action from inline edit actions
239
+ *
240
+ * @filter post_row_actions
241
+ *
242
+ * @param array $actions List of inline edit actions available.
243
+ * @return array
244
+ */
245
+ public function suppress_quick_edit( $actions ) {
246
+ if ( Alerts::POST_TYPE !== get_post_type() ) {
247
+ return $actions;
248
+ }
249
+ unset( $actions['edit'] );
250
+ unset( $actions['view'] );
251
+ unset( $actions['trash'] );
252
+ unset( $actions['inline hide-if-no-js'] );
253
+ return $actions;
254
+ }
255
+
256
+ /**
257
+ * Remove months dropdown from Alerts list page
258
+ *
259
+ * @filter disable_months_dropdown
260
+ *
261
+ * @param bool $status Status of months dropdown enabling.
262
+ * @param string $post_type Post type status is related to.
263
+ * @return bool
264
+ */
265
+ public function suppress_months_dropdown( $status, $post_type ) {
266
+ if ( Alerts::POST_TYPE === $post_type ) {
267
+ $status = true;
268
+ }
269
+ return $status;
270
+ }
271
+
272
+ /**
273
+ * Custom column actions for alerts main screen
274
+ *
275
+ * @param int $post_id The current post ID.
276
+ *
277
+ * @return string
278
+ */
279
+ public function custom_column_actions( $post_id ) {
280
+ $post_status = wp_mainwp_stream_filter_input( INPUT_GET, 'post_status' );
281
+ ob_start();
282
+ if ( 'trash' !== $post_status ) {
283
+ $bare_url = admin_url( 'post.php?post=' . $post_id . '&action=trash' );
284
+ $nonce_url = wp_nonce_url( $bare_url, 'trash-post_' . $post_id );
285
+ ?>
286
+ <div class="row-actions">
287
+ <span class="inline hide-if-no-js"><a href="#" class="editinline" aria-label="Quick edit “Hello world!” inline"><?php esc_html_e( 'Edit' ); ?></a>&nbsp;|&nbsp;</span>
288
+ <span class="trash">
289
+ <a href="<?php echo esc_url( $nonce_url ); ?>" class="submitdelete"><?php esc_html_e( 'Trash', 'mainwp-child-reports' ); ?></a>
290
+ </span>
291
+ </div>
292
+ <?php
293
+ }
294
+ return ob_get_clean();
295
+ }
296
+
297
+ /**
298
+ * Display a custom quick edit form.
299
+ */
300
+ public function display_custom_quick_edit() {
301
+ static $fired = false;
302
+ if ( false !== $fired ) {
303
+ return;
304
+ }
305
+ $screen = get_current_screen();
306
+ if ( 'edit-wp_mainwp_alerts' !== $screen->id ) {
307
+ return;
308
+ }
309
+ wp_nonce_field( plugin_basename( __FILE__ ), Alerts::POST_TYPE . '_edit_nonce' );
310
+ $box_type = array(
311
+ 'triggers',
312
+ 'notification',
313
+ 'submit',
314
+ );
315
+ ?>
316
+ <legend class="inline-edit-legend"><?php esc_html_e( 'Edit', 'mainwp-child-reports' ); ?></legend>
317
+ <?php
318
+ foreach ( $box_type as $type ) : // @todo remove inline styles.
319
+ ?>
320
+ <fieldset class="inline-edit-col inline-edit-<?php echo esc_attr( Alerts::POST_TYPE ); ?>">
321
+ <?php
322
+ $function_name = 'display_' . $type . '_box';
323
+ $the_post = get_post();
324
+ call_user_func( array( $this->plugin->alerts, $function_name ), $the_post );
325
+ ?>
326
+ </fieldset>
327
+ <?php
328
+ endforeach;
329
+ $fired = true;
330
+ }
331
+
332
+ /**
333
+ * Enqueue scripts for the alerts list screen.
334
+ *
335
+ * @param string $page The current page name.
336
+ */
337
+ public function enqueue_scripts( $page ) {
338
+ $screen = get_current_screen();
339
+ if ( 'edit-wp_mainwp_alerts' !== $screen->id ) {
340
+ return;
341
+ }
342
+ $min = wp_mainwp_stream_min_suffix();
343
+ wp_register_script( 'wp-mainwp-stream-alerts-list-js', $this->plugin->locations['url'] . 'ui/js/alerts-list.' . $min . 'js', array( 'wp-mainwp-stream-alerts', 'jquery' ) );
344
+ wp_enqueue_script( 'wp-mainwp-stream-alerts-list-js' );
345
+ wp_register_style( 'wp-mainwp-stream-alerts-list-css', $this->plugin->locations['url'] . 'ui/css/alerts-list.' . $min . 'css' );
346
+ wp_enqueue_style( 'wp-mainwp-stream-alerts-list-css' );
347
+ wp_enqueue_style( 'wp-mainwp-stream-select2' );
348
+ }
349
+
350
+ /**
351
+ * Save alert meta after using the inline editor.
352
+ *
353
+ * @param array $data Filtered post data.
354
+ * @param array $postarr Raw post data.
355
+ *
356
+ * @return array
357
+ */
358
+ public function save_alert_inline_edit( $data, $postarr ) {
359
+ if ( did_action( 'customize_preview_init' ) || empty( $postarr['ID'] ) ) {
360
+ return $data;
361
+ }
362
+
363
+ $post_id = $postarr['ID'];
364
+ $post_type = wp_mainwp_stream_filter_input( INPUT_POST, 'post_type' );
365
+ if ( Alerts::POST_TYPE !== $post_type ) {
366
+ return $data;
367
+ }
368
+ if ( ! current_user_can( 'edit_post', $post_id ) ) {
369
+ return $data;
370
+ }
371
+
372
+ $nonce = wp_mainwp_stream_filter_input( INPUT_POST, Alerts::POST_TYPE . '_edit_nonce' );
373
+ if ( null === $nonce || ! wp_verify_nonce( $nonce, plugin_basename( __FILE__ ) ) ) {
374
+ return $data;
375
+ }
376
+
377
+ $trigger_author = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_author' );
378
+ $trigger_connector_and_context = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_connector_or_context' );
379
+ $trigger_connector_and_context_split = explode( '-', $trigger_connector_and_context );
380
+ $trigger_connector = $trigger_connector_and_context_split[0];
381
+ $trigger_context = $trigger_connector_and_context_split[1];
382
+
383
+ $trigger_action = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_action' );
384
+ $alert_type = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_alert_type' );
385
+ $alert_status = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_alert_status' );
386
+ $data['post_status'] = $alert_status;
387
+
388
+ update_post_meta( $post_id, 'alert_type', $alert_type );
389
+
390
+ $alert_meta = array(
391
+ 'trigger_author' => $trigger_author,
392
+ 'trigger_connector' => $trigger_connector,
393
+ 'trigger_action' => $trigger_action,
394
+ 'trigger_context' => $trigger_context,
395
+ );
396
+ $alert_meta = apply_filters( 'wp_mainwp_alerts_save_meta', $alert_meta, $alert_type );
397
+ update_post_meta( $post_id, 'alert_meta', $alert_meta );
398
+ return $data;
399
+ }
400
+ }
classes/class-alerts.php ADDED
@@ -0,0 +1,818 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Alerts feature class.
4
+ *
5
+ * @package WP_MainWP_Stream
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ /**
11
+ * Class Alerts
12
+ *
13
+ * @package WP_MainWP_Stream
14
+ */
15
+ class Alerts {
16
+
17
+ /**
18
+ * Alerts post type slug
19
+ */
20
+ const POST_TYPE = 'wp_mainwp_alerts';
21
+
22
+ /**
23
+ * Triggered Alerts meta key for Records
24
+ */
25
+ const ALERTS_TRIGGERED_META_KEY = 'wp_mainwp_alerts_triggered';
26
+
27
+ /**
28
+ * Hold Plugin class
29
+ *
30
+ * @var Plugin
31
+ */
32
+ public $plugin;
33
+
34
+ /**
35
+ * Post meta prefix
36
+ *
37
+ * @var string
38
+ */
39
+ public $meta_prefix = 'wp_mainwp_stream';
40
+
41
+ /**
42
+ * Alert Types
43
+ *
44
+ * @var array
45
+ */
46
+ public $alert_types = array();
47
+
48
+ /**
49
+ * Alert Triggers
50
+ *
51
+ * @var array
52
+ */
53
+ public $alert_triggers = array();
54
+
55
+ /**
56
+ * Class constructor.
57
+ *
58
+ * @param Plugin $plugin The main Plugin class.
59
+ */
60
+ public function __construct( $plugin ) {
61
+ $this->plugin = $plugin;
62
+
63
+ // Register custom post type.
64
+ add_action( 'init', array( $this, 'register_post_type' ) );
65
+
66
+ // Add custom post type to menu.
67
+ //add_action( 'wp_mainwp_stream_admin_menu', array( $this, 'register_menu' ) );
68
+
69
+ // Add scripts to post screens.
70
+ add_action( 'admin_enqueue_scripts', array(
71
+ $this,
72
+ 'register_scripts',
73
+ ) );
74
+
75
+ add_action( 'network_admin_menu', array(
76
+ $this,
77
+ 'change_menu_link_url',
78
+ ), 99 );
79
+
80
+ add_filter( 'wp_mainwp_stream_record_inserted', array(
81
+ $this,
82
+ 'check_records',
83
+ ), 10, 2 );
84
+
85
+ add_action( 'wp_ajax_load_alerts_settings', array(
86
+ $this,
87
+ 'load_alerts_settings',
88
+ ) );
89
+ add_action( 'wp_ajax_get_actions', array( $this, 'get_actions' ) );
90
+ add_action( 'wp_ajax_save_new_alert', array(
91
+ $this,
92
+ 'save_new_alert',
93
+ ) );
94
+ add_action(
95
+ 'wp_ajax_get_new_alert_triggers_notifications', array(
96
+ $this,
97
+ 'get_new_alert_triggers_notifications',
98
+ )
99
+ );
100
+
101
+ $this->load_alert_types();
102
+ $this->load_alert_triggers();
103
+
104
+ add_filter( 'wp_mainwp_stream_action_links_posts', array(
105
+ $this,
106
+ 'change_alert_action_links',
107
+ ), 11, 2 );
108
+
109
+ }
110
+
111
+ /**
112
+ * Load alert_type classes
113
+ *
114
+ * @return void
115
+ */
116
+ public function load_alert_types() {
117
+ $alert_types = array(
118
+ 'none',
119
+ 'highlight',
120
+ 'email',
121
+ 'ifttt',
122
+ 'slack',
123
+ );
124
+
125
+ $classes = array();
126
+ foreach ( $alert_types as $alert_type ) {
127
+ $file_location = $this->plugin->locations['dir'] . '/alerts/class-alert-type-' . $alert_type . '.php';
128
+ if ( file_exists( $file_location ) ) {
129
+ include_once $file_location;
130
+ $class_name = sprintf( '\WP_MainWP_Stream\Alert_Type_%s', str_replace( '-', '_', $alert_type ) );
131
+ if ( ! class_exists( $class_name ) ) {
132
+ continue;
133
+ }
134
+ $class = new $class_name( $this->plugin );
135
+ if ( ! property_exists( $class, 'slug' ) ) {
136
+ continue;
137
+ }
138
+ $classes[ $class->slug ] = $class;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Allows for adding additional alert_types via classes that extend Notifier.
144
+ *
145
+ * @param array $classes An array of Notifier objects. In the format alert_type_slug => Notifier_Class()
146
+ */
147
+ $this->alert_types = apply_filters( 'wp_mainwp_stream_alert_types', $classes );
148
+
149
+ // Ensure that all alert_types extend Notifier.
150
+ foreach ( $this->alert_types as $key => $alert_type ) {
151
+ if ( ! $this->is_valid_alert_type( $alert_type ) ) {
152
+ unset( $this->alert_types[ $key ] );
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Load alert_type classes
159
+ *
160
+ * @return void
161
+ */
162
+ public function load_alert_triggers() {
163
+ $alert_triggers = array(
164
+ 'author',
165
+ 'context',
166
+ 'action',
167
+ );
168
+
169
+ $classes = array();
170
+ foreach ( $alert_triggers as $alert_trigger ) {
171
+ $file_location = $this->plugin->locations['dir'] . '/alerts/class-alert-trigger-' . $alert_trigger . '.php';
172
+ if ( file_exists( $file_location ) ) {
173
+ include_once $file_location;
174
+ $class_name = sprintf( '\WP_MainWP_Stream\Alert_Trigger_%s', str_replace( '-', '_', $alert_trigger ) );
175
+ if ( ! class_exists( $class_name ) ) {
176
+ continue;
177
+ }
178
+ $class = new $class_name( $this->plugin );
179
+ if ( ! property_exists( $class, 'slug' ) ) {
180
+ continue;
181
+ }
182
+ $classes[ $class->slug ] = $class;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Allows for adding additional alert_triggers via classes that extend Notifier.
188
+ *
189
+ * @param array $classes An array of Notifier objects. In the format alert_trigger_slug => Notifier_Class()
190
+ */
191
+ $this->alert_triggers = apply_filters( 'wp_mainwp_stream_alert_triggers', $classes );
192
+
193
+ // Ensure that all alert_triggers extend Notifier.
194
+ foreach ( $this->alert_triggers as $key => $alert_trigger ) {
195
+ if ( ! $this->is_valid_alert_trigger( $alert_trigger ) ) {
196
+ unset( $this->alert_triggers[ $key ] );
197
+ }
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Checks whether a Alert Type class is valid
203
+ *
204
+ * @param Alert_Type $alert_type The class to check.
205
+ *
206
+ * @return bool
207
+ */
208
+ public function is_valid_alert_type( $alert_type ) {
209
+ if ( ! is_a( $alert_type, 'WP_MainWP_Stream\Alert_Type' ) ) {
210
+ return false;
211
+ }
212
+
213
+ if ( ! method_exists( $alert_type, 'is_dependency_satisfied' ) || ! $alert_type->is_dependency_satisfied() ) {
214
+ return false;
215
+ }
216
+
217
+ return true;
218
+ }
219
+
220
+ /**
221
+ * Checks whether a Alert Trigger class is valid
222
+ *
223
+ * @param Alert_Trigger $alert_trigger The class to check.
224
+ *
225
+ * @return bool
226
+ */
227
+ public function is_valid_alert_trigger( $alert_trigger ) {
228
+ if ( ! is_a( $alert_trigger, 'WP_MainWP_Stream\Alert_Trigger' ) ) {
229
+ return false;
230
+ }
231
+
232
+ if ( ! method_exists( $alert_trigger, 'is_dependency_satisfied' ) || ! $alert_trigger->is_dependency_satisfied() ) {
233
+ return false;
234
+ }
235
+
236
+ return true;
237
+ }
238
+
239
+ /**
240
+ * Checks record being processed against active alerts.
241
+ *
242
+ * @filter wp_mainwp_stream_record_inserted
243
+ *
244
+ * @param int $record_id The record being processed.
245
+ * @param array $recordarr Record data.
246
+ *
247
+ * @return array
248
+ */
249
+ public function check_records( $record_id, $recordarr ) {
250
+ $args = array(
251
+ 'post_type' => self::POST_TYPE,
252
+ 'post_status' => 'wp_mainwp_stream_enabled',
253
+ );
254
+
255
+ $alerts = new \WP_Query( $args );
256
+ foreach ( $alerts->posts as $alert ) {
257
+ $alert = $this->get_alert( $alert->ID );
258
+
259
+ $status = $alert->check_record( $record_id, $recordarr );
260
+ if ( $status ) {
261
+ $alert->send_alert( $record_id, $recordarr ); // @todo send_alert expects int, not array.
262
+ }
263
+ }
264
+
265
+ return $recordarr;
266
+
267
+ }
268
+
269
+ /**
270
+ * Register scripts for page load
271
+ *
272
+ * @action admin_enqueue_scripts
273
+ *
274
+ * @return void
275
+ */
276
+ public function register_scripts() {
277
+ $screen = get_current_screen();
278
+ if ( 'edit-wp_mainwp_alerts' === $screen->id ) {
279
+
280
+ $min = wp_mainwp_stream_min_suffix();
281
+ wp_register_script( 'wp-mainwp-stream-alerts', $this->plugin->locations['url'] . 'ui/js/alerts.' . $min . 'js', array(
282
+ 'wp-mainwp-stream-select2',
283
+ 'jquery',
284
+ 'inline-edit-post',
285
+ ) );
286
+
287
+ wp_localize_script(
288
+ 'wp-mainwp-stream-alerts', 'streamAlerts',
289
+ array(
290
+ 'any' => __( 'Any', 'mainwp-child-reports' ),
291
+ 'anyContext' => __( 'Any Context', 'mainwp-child-reports' ),
292
+ )
293
+ );
294
+ wp_enqueue_script( 'wp-mainwp-stream-alerts' );
295
+ wp_enqueue_style( 'wp-mainwp-stream-select2' );
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Register custom post type
301
+ *
302
+ * @action init
303
+ *
304
+ * @return void
305
+ */
306
+ public function register_post_type() {
307
+ $labels = array(
308
+ 'name' => _x( 'Alerts', 'post type general name', 'mainwp-child-reports' ),
309
+ 'singular_name' => _x( 'Alert', 'post type singular name', 'mainwp-child-reports' ),
310
+ 'menu_name' => _x( 'Alerts', 'admin menu', 'mainwp-child-reports' ),
311
+ 'name_admin_bar' => _x( 'Alert', 'add new on admin bar', 'mainwp-child-reports' ),
312
+ 'add_new' => _x( 'Add New', 'book', 'mainwp-child-reports' ),
313
+ 'add_new_item' => __( 'Add New Alert', 'mainwp-child-reports' ),
314
+ 'new_item' => __( 'New Alert', 'mainwp-child-reports' ),
315
+ 'edit_item' => __( 'Edit Alert', 'mainwp-child-reports' ),
316
+ 'view_item' => __( 'View Alert', 'mainwp-child-reports' ),
317
+ 'all_items' => __( 'Alerts', 'mainwp-child-reports' ),
318
+ 'search_items' => __( 'Search Alerts', 'mainwp-child-reports' ),
319
+ 'parent_item_colon' => __( 'Parent Alerts:', 'mainwp-child-reports' ),
320
+ 'not_found' => __( 'No alerts found.', 'mainwp-child-reports' ),
321
+ 'not_found_in_trash' => __( 'No alerts found in Trash.', 'mainwp-child-reports' ),
322
+ );
323
+
324
+ $args = array(
325
+ 'labels' => $labels,
326
+ 'description' => __( 'Alerts for Stream.', 'mainwp-child-reports' ),
327
+ 'public' => false,
328
+ 'publicly_queryable' => false,
329
+ 'exclude_from_search' => true,
330
+ 'show_ui' => true,
331
+ 'show_in_menu' => false, // @see modify_admin_menu
332
+ 'supports' => false,
333
+ 'capabilities' => array(
334
+ 'publish_posts' => 'manage_options',
335
+ 'edit_others_posts' => 'manage_options',
336
+ 'delete_posts' => 'manage_options',
337
+ 'delete_others_posts' => 'manage_options',
338
+ 'read_private_posts' => 'manage_options',
339
+ 'edit_post' => 'manage_options',
340
+ 'delete_post' => 'manage_options',
341
+ 'read_post' => 'manage_options',
342
+ ),
343
+ );
344
+
345
+ register_post_type( self::POST_TYPE, $args );
346
+
347
+ $args = array(
348
+ 'label' => _x( 'Enabled', 'alert', 'mainwp-child-reports' ),
349
+ 'public' => false,
350
+ 'show_in_admin_all_list' => true,
351
+ 'show_in_admin_status_list' => true,
352
+ // translators: Placeholder refers to a number of items (e.g. "42")
353
+ 'label_count' => _n_noop( 'Enabled <span class="count">(%s)</span>', 'Enabled <span class="count">(%s)</span>', 'mainwp-child-reports' ),
354
+ );
355
+
356
+ register_post_status( 'wp_mainwp_stream_enabled', $args );
357
+
358
+ $args = array(
359
+ 'label' => _x( 'Disabled', 'alert', 'mainwp-child-reports' ),
360
+ 'public' => false,
361
+ 'internal' => false,
362
+ 'show_in_admin_all_list' => true,
363
+ 'show_in_admin_status_list' => true,
364
+ // translators: Placeholder refers to a number of items (e.g. "42")
365
+ 'label_count' => _n_noop( 'Disabled <span class="count">(%s)</span>', 'Disabled <span class="count">(%s)</span>', 'mainwp-child-reports' ),
366
+ );
367
+
368
+ register_post_status( 'wp_mainwp_stream_disabled', $args );
369
+ }
370
+
371
+ /**
372
+ * Return alert object of the given ID
373
+ *
374
+ * @param string $post_id Post ID for the alert.
375
+ *
376
+ * @return Alert
377
+ */
378
+ public function get_alert( $post_id = '' ) {
379
+ if ( ! $post_id ) {
380
+ $obj = new Alert( null, $this->plugin );
381
+
382
+ return $obj;
383
+ }
384
+
385
+ $post = get_post( $post_id );
386
+
387
+ $alert_type = get_post_meta( $post_id, 'alert_type', true );
388
+ $alert_meta = get_post_meta( $post_id, 'alert_meta', true );
389
+
390
+ $obj = (object) array(
391
+ 'ID' => $post->ID,
392
+ 'status' => $post->post_status,
393
+ 'date' => $post->post_date,
394
+ 'author' => $post->post_author,
395
+ 'alert_type' => $alert_type,
396
+ 'alert_meta' => $alert_meta,
397
+ );
398
+
399
+ return new Alert( $obj, $this->plugin );
400
+
401
+ }
402
+
403
+ /**
404
+ * Add custom post type to menu
405
+ *
406
+ * @action admin_menu
407
+ *
408
+ * @return void
409
+ */
410
+ // DISABLED
411
+ public function register_menu() {
412
+ add_submenu_page(
413
+ $this->plugin->admin->records_page_slug,
414
+ __( 'Alerts', 'mainwp-child-reports' ),
415
+ __( 'Alerts', 'mainwp-child-reports' ),
416
+ 'manage_options',
417
+ 'edit.php?post_type=wp_mainwp_alerts'
418
+ );
419
+ }
420
+
421
+ /**
422
+ * Modify the Stream > Alerts Network Admin Menu link.
423
+ *
424
+ * In self::register_menu(), the Alerts submenu item
425
+ * is essentially set to go to the Site's admin area.
426
+ *
427
+ * However, on the Network admin, we need to redirect
428
+ * it to the first site in the network, as this is
429
+ * where the true Network Alerts settings page is located.
430
+ *
431
+ * @action network_admin_menu
432
+ * @return bool
433
+ */
434
+ public function change_menu_link_url() {
435
+ global $submenu;
436
+
437
+ $parent = 'wp_mainwp_stream';
438
+ $page = 'edit.php?post_type=wp_mainwp_alerts';
439
+
440
+ // If we're not on the Stream menu item, return.
441
+ if ( ! isset( $submenu[ $parent ] ) ) {
442
+ return false;
443
+ }
444
+
445
+ // Get the first existing Site in the Network.
446
+ $sites = wp_mainwp_stream_get_sites(
447
+ array(
448
+ 'limit' => 5, // Limit the size of the query.
449
+ )
450
+ );
451
+
452
+ $site_id = '1';
453
+
454
+ // Function wp_get_sites() can return an empty array if the network is too large.
455
+ if ( ! empty( $sites ) && ! empty( $sites[0]->blog_id ) ) {
456
+ $site_id = $sites[0]->blog_id;
457
+ }
458
+
459
+ $new_url = get_admin_url( $site_id, $page );
460
+
461
+ foreach ( $submenu[ $parent ] as $key => $value ) {
462
+ // Set correct URL for the menu item.
463
+ if ( $page === $value[2] ) {
464
+ // This hack is not kosher, see the docblock for an explanation.
465
+ $submenu[ $parent ][ $key ][2] = $new_url; // override okay
466
+ break;
467
+ }
468
+ }
469
+
470
+ return true;
471
+ }
472
+
473
+ /**
474
+ * Display Alert Type Meta Box
475
+ *
476
+ * @param \WP_Post|array $post Post object for current alert.
477
+ *
478
+ * @return void
479
+ */
480
+ public function display_notification_box( $post = array() ) {
481
+ $alert_type = 'none';
482
+ if ( is_object( $post ) ) {
483
+ $alert = $this->get_alert( $post->ID );
484
+ $alert_type = $alert->alert_type;
485
+ }
486
+ $form = new Form_Generator();
487
+
488
+ $field_html = $form->render_field(
489
+ 'select', array(
490
+ 'id' => 'wp_mainwp_stream_alert_type',
491
+ 'name' => 'wp_mainwp_stream_alert_type',
492
+ 'value' => $alert_type,
493
+ 'options' => $this->get_notification_values(),
494
+ 'placeholder' => __( 'No Alert', 'mainwp-child-reports' ),
495
+ 'title' => 'Alert Type:',
496
+ )
497
+ );
498
+
499
+ echo '<label>' . esc_html__( 'Alert me by', 'mainwp-child-reports' ) . '</label>';
500
+ echo $field_html; // Xss ok.
501
+
502
+ echo '<div id="wp_mainwp_stream_alert_type_form">';
503
+ if ( is_object( $alert ) ) {
504
+ $alert->get_alert_type_obj()->display_fields( $alert );
505
+ } else {
506
+ $this->plugin->alerts->alert_types['none']->display_fields( array() );
507
+ }
508
+
509
+ echo '</div>';
510
+ }
511
+
512
+ /**
513
+ * Returns settings form HTML for AJAX use
514
+ *
515
+ * @action wp_ajax_load_alerts_settings
516
+ *
517
+ * @return void
518
+ */
519
+ public function load_alerts_settings() {
520
+ $alert = array();
521
+ $post_id = wp_mainwp_stream_filter_input( INPUT_POST, 'post_id' );
522
+ if ( ! empty( $post_id ) ) {
523
+ $alert = $this->get_alert( $post_id );
524
+ if ( ! $alert ) {
525
+ wp_send_json_error(
526
+ array(
527
+ 'message' => 'Could not find alert.',
528
+ )
529
+ );
530
+ }
531
+ }
532
+
533
+ $alert_type = wp_mainwp_stream_filter_input( INPUT_POST, 'alert_type' );
534
+ if ( empty( $alert_type ) ) {
535
+ $alert_type = 'none';
536
+ }
537
+ if ( ! array_key_exists( $alert_type, $this->alert_types ) ) {
538
+ wp_send_json_error(
539
+ array(
540
+ 'message' => 'Could not find alert type.',
541
+ )
542
+ );
543
+ }
544
+
545
+ ob_start();
546
+ $this->alert_types[ $alert_type ]->display_fields( $alert );
547
+ $output = ob_get_contents();
548
+ ob_end_clean();
549
+
550
+ $data = array(
551
+ 'html' => $output,
552
+ );
553
+ wp_send_json_success( $data );
554
+ }
555
+
556
+ /**
557
+ * Display Trigger Meta Box
558
+ *
559
+ * @param \WP_Post|array $post Post object for current alert.
560
+ *
561
+ * @return void
562
+ */
563
+ public function display_triggers_box( $post = array() ) {
564
+ if ( is_object( $post ) ) {
565
+ $alert = $this->get_alert( $post->ID );
566
+ } else {
567
+ $alert = array();
568
+ }
569
+ $form = new Form_Generator();
570
+ do_action( 'wp_mainwp_stream_alert_trigger_form_display', $form, $alert );
571
+ // @TODO use human readable text.
572
+ echo '<label>' . esc_html__( 'Alert me when', 'mainwp-child-reports' ) . '</label>';
573
+ echo $form->render_fields(); // Xss ok.
574
+ wp_nonce_field( 'save_alert', 'wp_mainwp_alerts_nonce' );
575
+ }
576
+
577
+ /**
578
+ * Display Submit Box
579
+ *
580
+ * @param \WP_Post $post Post object for current alert.
581
+ *
582
+ * @return void
583
+ */
584
+ public function display_submit_box( $post ) {
585
+ if ( empty( $post ) ) {
586
+ return;
587
+ }
588
+
589
+ $post_status = $post->post_status;
590
+ if ( 'auto-draft' === $post_status ) {
591
+ $post_status = 'wp_mainwp_stream_enabled';
592
+ }
593
+ ?>
594
+ <div class="submitbox" id="submitpost">
595
+ <div id="minor-publishing">
596
+ <div id="misc-publishing-actions">
597
+ <div class="misc-pub-section misc-pub-post-status">
598
+ <label for="wp_mainwp_stream_alert_status"><?php esc_html_e( 'Status', 'mainwp-child-reports' ); ?></label>
599
+ <select name='wp_mainwp_stream_alert_status' id='wp_mainwp_stream_alert_status'>
600
+ <option<?php selected( $post_status, 'wp_mainwp_stream_enabled' ); ?>
601
+ value='wp_mainwp_stream_enabled'><?php esc_html_e( 'Enabled', 'mainwp-child-reports' ); ?></option>
602
+ <option<?php selected( $post_status, 'wp_mainwp_stream_disabled' ); ?>
603
+ value='wp_mainwp_stream_disabled'><?php esc_html_e( 'Disabled', 'mainwp-child-reports' ); ?></option>
604
+ </select>
605
+ </div>
606
+ </div>
607
+ <div class="clear"></div>
608
+ </div>
609
+
610
+ <div id="major-publishing-actions">
611
+ <div id="delete-action">
612
+ <?php
613
+ if ( current_user_can( 'delete_post', $post->ID ) ) {
614
+ if ( ! EMPTY_TRASH_DAYS ) {
615
+ $delete_text = __( 'Delete Permanently', 'mainwp-child-reports' );
616
+ } else {
617
+ $delete_text = __( 'Move to Trash', 'mainwp-child-reports' );
618
+ }
619
+ ?>
620
+ <a class="submitdelete deletion" href="<?php echo get_delete_post_link( $post->ID ); ?>">
621
+ <?php esc_html( $delete_text ); ?>
622
+ </a>
623
+ <?php
624
+ }
625
+ ?>
626
+ </div>
627
+ <div id="publishing-action">
628
+ <span class="spinner"></span>
629
+ <?php submit_button( __( 'Save' ), 'primary button-large', 'publish', false ); ?>
630
+ </div>
631
+ <div class="clear"></div>
632
+ </div>
633
+ </div>
634
+ <?php
635
+ }
636
+
637
+ /**
638
+ * Display Status Box
639
+ *
640
+ * @return void
641
+ */
642
+ public function display_status_box() {
643
+ ?>
644
+ <div id="minor-publishing">
645
+ <div id="misc-publishing-actions">
646
+ <div class="misc-pub-section misc-pub-post-status">
647
+ <label for="wp_mainwp_stream_alert_status">
648
+ <span class="title"><?php esc_html_e( 'Status:', 'mainwp-child-reports' ); ?></span>
649
+ <span class="input-text-wrap">
650
+ <select name='wp_mainwp_stream_alert_status' id='wp_mainwp_stream_alert_status'>
651
+ <option selected value='wp_mainwp_stream_enabled'><?php esc_html_e( 'Enabled', 'mainwp-child-reports' ); ?></option>
652
+ <option value='wp_mainwp_stream_disabled'><?php esc_html_e( 'Disabled', 'mainwp-child-reports' ); ?></option>
653
+ </select>
654
+ </span>
655
+ </label>
656
+ </div>
657
+ </div>
658
+ <div class="clear"></div>
659
+ </div>
660
+ <?php
661
+ }
662
+
663
+ /**
664
+ * Return all notification values
665
+ *
666
+ * @return array
667
+ */
668
+ public function get_notification_values() {
669
+ $result = array();
670
+ $names = wp_list_pluck( $this->alert_types, 'name', 'slug' );
671
+ foreach ( $names as $slug => $name ) {
672
+ $result[ $slug ] = $name;
673
+ }
674
+
675
+ return $result;
676
+ }
677
+
678
+ /**
679
+ * Update actions dropdown options based on the connector selected.
680
+ */
681
+ public function get_actions() {
682
+ $connector_name = wp_mainwp_stream_filter_input( INPUT_POST, 'connector' );
683
+ $stream_connectors = wp_mainwp_stream_get_instance()->connectors;
684
+ if ( ! empty( $connector_name ) ) {
685
+ if ( isset( $stream_connectors->connectors[ $connector_name ] ) ) {
686
+ $connector = $stream_connectors->connectors[ $connector_name ];
687
+ if ( method_exists( $connector, 'get_action_labels' ) ) {
688
+ $actions = $connector->get_action_labels();
689
+ }
690
+ }
691
+ } else {
692
+ $actions = $stream_connectors->term_labels['stream_action'];
693
+ }
694
+ ksort( $actions );
695
+ wp_send_json_success( $actions );
696
+ }
697
+
698
+ /**
699
+ * Save a new alert
700
+ */
701
+ public function save_new_alert() {
702
+ check_ajax_referer( 'save_alert', 'wp_mainwp_alerts_nonce' );
703
+ $trigger_author = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_author' );
704
+ $trigger_connector_and_context = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_context' );
705
+ if ( false !== strpos( $trigger_connector_and_context, '-' ) ) {
706
+ // This is a connector with a context such as posts-post.
707
+ $trigger_connector_and_context_split = explode( '-', $trigger_connector_and_context );
708
+ $trigger_connector = $trigger_connector_and_context_split[0];
709
+ $trigger_context = $trigger_connector_and_context_split[1];
710
+ } else {
711
+ if ( ! empty( $trigger_connector_and_context ) ) {
712
+ // This is a parent connector with no dash such as posts.
713
+ $trigger_connector = $trigger_connector_and_context;
714
+ $trigger_context = '';
715
+ } else {
716
+ // There is no connector or context.
717
+ $trigger_connector = '';
718
+ $trigger_context = '';
719
+ }
720
+ }
721
+
722
+ $trigger_action = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_trigger_action' );
723
+ $alert_type = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_alert_type' );
724
+ $alert_status = wp_mainwp_stream_filter_input( INPUT_POST, 'wp_mainwp_stream_alert_status' );
725
+
726
+ // Insert the post into the database
727
+ $item = (object) array(
728
+ 'alert_type' => $alert_type,
729
+ 'alert_meta' => array(
730
+ 'trigger_author' => $trigger_author,
731
+ 'trigger_connector' => $trigger_connector,
732
+ 'trigger_action' => $trigger_action,
733
+ 'trigger_context' => $trigger_context,
734
+ ),
735
+ 'alert_status' => $alert_status,
736
+ );
737
+ $alert = new Alert( $item, $this->plugin );
738
+ $title = $alert->get_title();
739
+ $post_id = wp_insert_post(
740
+ array(
741
+ 'post_status' => $alert_status,
742
+ 'post_type' => 'wp_mainwp_alerts',
743
+ 'post_title' => $title,
744
+ )
745
+ );
746
+ if ( empty( $post_id ) ) {
747
+ wp_send_json_error();
748
+ }
749
+ add_post_meta( $post_id, 'alert_type', $alert_type );
750
+
751
+ $alert_meta = array(
752
+ 'trigger_author' => $trigger_author,
753
+ 'trigger_connector' => $trigger_connector,
754
+ 'trigger_action' => $trigger_action,
755
+ 'trigger_context' => $trigger_context,
756
+ );
757
+ $alert_meta = apply_filters( 'wp_mainwp_alerts_save_meta', $alert_meta, $alert_type );
758
+ add_post_meta( $post_id, 'alert_meta', $alert_meta );
759
+ wp_send_json_success(
760
+ array(
761
+ 'success' => true,
762
+ )
763
+ );
764
+ }
765
+
766
+ /**
767
+ *
768
+ */
769
+ public function get_new_alert_triggers_notifications() {
770
+ ob_start();
771
+ ?>
772
+ <fieldset class="inline-edit-col inline-edit-wp_mainwp_alerts inline-edit-add-new-triggers">
773
+ <legend class="inline-edit-legend">Add New</legend>
774
+ <?php $GLOBALS['wp_mainwp_stream']->alerts->display_triggers_box(); ?>
775
+ </fieldset>
776
+ <fieldset class="inline-edit-col inline-edit-wp_mainwp_alerts inline-edit-add-new-notifications">
777
+ <?php $GLOBALS['wp_mainwp_stream']->alerts->display_notification_box(); ?>
778
+ </fieldset>
779
+ <fieldset class="inline-edit-col inline-edit-wp_mainwp_alerts inline-edit-add-new-status">
780
+ <?php $GLOBALS['wp_mainwp_stream']->alerts->display_status_box(); ?>
781
+ </fieldset>
782
+ <?php
783
+ $html = ob_get_clean();
784
+ wp_send_json_success(
785
+ array(
786
+ 'success' => true,
787
+ 'html' => $html,
788
+ )
789
+ );
790
+ }
791
+
792
+ /**
793
+ * Add action links to Stream drop row in admin list screen
794
+ *
795
+ * @filter wp_mainwp_stream_action_links_{connector}
796
+ *
797
+ * @param array $links Previous links registered
798
+ * @param Record $record Stream record
799
+ *
800
+ * @return array Action links
801
+ */
802
+ public function change_alert_action_links( $links, $record ) {
803
+ $post = get_post( $record->object_id );
804
+
805
+ if ( $post && self::POST_TYPE === $post->post_type && $post->post_status === $record->get_meta( 'new_status', true ) ) {
806
+ if ( 'trash' !== $post->post_status ) {
807
+ $connector_posts = new \WP_MainWP_Stream\Connector_Posts();
808
+ $post_type_name = $connector_posts->get_post_type_name( get_post_type( $post->ID ) );
809
+
810
+ // translators: Placeholder refers to the post type singular name (e.g. "Post")
811
+ $links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = admin_url( 'edit.php?post_type=wp_mainwp_alerts#post-' . $post->ID );
812
+ unset( $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] );
813
+ }
814
+ }
815
+
816
+ return $links;
817
+ }
818
+ }
classes/class-author.php ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Author {
5
+ /**
6
+ * @var int
7
+ */
8
+ public $id;
9
+
10
+ /**
11
+ * @var array
12
+ */
13
+ public $meta = array();
14
+
15
+ /**
16
+ * @var \WP_User
17
+ */
18
+ protected $user;
19
+
20
+ /**
21
+ * Class constructor.
22
+ *
23
+ * @param int $user_id The user ID.
24
+ * @param array $user_meta The user meta array.
25
+ */
26
+ public function __construct( $user_id, $user_meta = array() ) {
27
+ $this->id = absint( $user_id );
28
+ $this->meta = $user_meta;
29
+
30
+ if ( $this->id ) {
31
+ $this->user = new \WP_User( $this->id );
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get various user meta data
37
+ *
38
+ * @param string $name
39
+ *
40
+ * @throws \Exception
41
+ *
42
+ * @return string
43
+ */
44
+ public function __get( $name ) {
45
+ if ( 'display_name' === $name ) {
46
+ return $this->get_display_name();
47
+ } elseif ( 'avatar_img' === $name ) {
48
+ return $this->get_avatar_img();
49
+ } elseif ( 'avatar_src' === $name ) {
50
+ return $this->get_avatar_src();
51
+ } elseif ( 'role' === $name ) {
52
+ return $this->get_role();
53
+ } elseif ( 'agent' === $name ) {
54
+ return $this->get_agent();
55
+ } elseif ( ! empty( $this->user ) && 0 !== $this->user->ID ) {
56
+ return $this->user->$name;
57
+ } else {
58
+ throw new \Exception( "Unrecognized magic '$name'" );
59
+ }
60
+ }
61
+
62
+ /**
63
+ * @return string
64
+ */
65
+ public function __toString() {
66
+ return $this->get_display_name();
67
+ }
68
+
69
+ /**
70
+ * Get the display name of the user
71
+ *
72
+ * @return string
73
+ */
74
+ public function get_display_name() {
75
+ if ( 0 === $this->id ) {
76
+ if ( isset( $this->meta['system_user_name'] ) ) {
77
+ return esc_html( $this->meta['system_user_name'] );
78
+ } elseif ( 'wp_cli' === $this->get_current_agent() ) {
79
+ return 'WP-CLI'; // No translation needed
80
+ }
81
+ return esc_html__( 'N/A', 'mainwp-child-reports' );
82
+ } else {
83
+ if ( $this->is_deleted() ) {
84
+ if ( ! empty( $this->meta['display_name'] ) ) {
85
+ return $this->meta['display_name'];
86
+ } elseif ( ! empty( $this->meta['user_login'] ) ) {
87
+ return $this->meta['user_login'];
88
+ } else {
89
+ return esc_html__( 'N/A', 'mainwp-child-reports' );
90
+ }
91
+ } elseif ( ! empty( $this->user->display_name ) ) {
92
+ return $this->user->display_name;
93
+ } else {
94
+ return $this->user->user_login;
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get the agent of the user
101
+ *
102
+ * @return string
103
+ */
104
+ public function get_agent() {
105
+ $agent = '';
106
+
107
+ if ( ! empty( $this->meta['agent'] ) ) {
108
+ $agent = $this->meta['agent'];
109
+ } elseif ( ! empty( $this->meta['is_wp_cli'] ) ) {
110
+ $agent = 'wp_cli'; // legacy
111
+ }
112
+
113
+ return $agent;
114
+ }
115
+
116
+ /**
117
+ * Return a Gravatar image as an HTML element.
118
+ *
119
+ * This function will not return an avatar if "Show Avatars" is unchecked in Settings > Discussion.
120
+ *
121
+ * @param int $size (optional) Size of Gravatar to return (in pixels), max is 512, default is 80
122
+ *
123
+ * @return string|bool An img HTML element, or false if avatars are disabled
124
+ */
125
+ public function get_avatar_img( $size = 80 ) {
126
+ if ( ! get_option( 'show_avatars' ) ) {
127
+ return false;
128
+ }
129
+
130
+ if ( 0 === $this->id ) {
131
+ $stream = wp_mainwp_stream_get_instance();
132
+ $url = $stream->locations['url'] . 'ui/stream-icons/wp-cli.png';
133
+ $avatar = sprintf( '<img alt="%1$s" src="%2$s" class="avatar avatar-%3$s photo" height="%3$s" width="%3$s">', esc_attr( $this->get_display_name() ), esc_url( $url ), esc_attr( $size ) );
134
+ } else {
135
+ if ( $this->is_deleted() && isset( $this->meta['user_email'] ) ) {
136
+ $email = $this->meta['user_email'];
137
+ $avatar = get_avatar( $email, $size );
138
+ } else {
139
+ $avatar = get_avatar( $this->id, $size );
140
+ }
141
+ }
142
+
143
+ return $avatar;
144
+ }
145
+
146
+ /**
147
+ * Return the URL of a Gravatar image.
148
+ *
149
+ * @param int $size (optional) Size of Gravatar to return (in pixels), max is 512, default is 80
150
+ *
151
+ * @return string|bool Gravatar image URL, or false on failure
152
+ */
153
+ public function get_avatar_src( $size = 80 ) {
154
+ $img = $this->get_avatar_img( $size );
155
+
156
+ if ( ! $img ) {
157
+ return false;
158
+ }
159
+
160
+ if ( 1 === preg_match( '/src=([\'"])(.*?)\1/', $img, $matches ) ) {
161
+ $src = html_entity_decode( $matches[2] );
162
+ } else {
163
+ return false;
164
+ }
165
+
166
+ return $src;
167
+ }
168
+
169
+ /**
170
+ * Tries to find a label for the record's user_role.
171
+ *
172
+ * If the user_role exists, use the label associated with it.
173
+ *
174
+ * Otherwise, if there is a user role label stored as Stream meta then use that.
175
+ * Otherwise, if the user exists, use the label associated with their current role.
176
+ * Otherwise, use the role slug as the label.
177
+ *
178
+ * @return string
179
+ */
180
+ public function get_role() {
181
+ global $wp_roles;
182
+
183
+ $user_role = '';
184
+
185
+ if ( ! empty( $this->meta['user_role'] ) && isset( $wp_roles->role_names[ $this->meta['user_role'] ] ) ) {
186
+ $user_role = $wp_roles->role_names[ $this->meta['user_role'] ];
187
+ } elseif ( ! empty( $this->meta['user_role_label'] ) ) {
188
+ $user_role = $this->meta['user_role_label'];
189
+ } elseif ( isset( $this->user->roles[0] ) && isset( $wp_roles->role_names[ $this->user->roles[0] ] ) ) {
190
+ $user_role = $wp_roles->role_names[ $this->user->roles[0] ];
191
+ } elseif ( is_multisite() && is_super_admin( $this->id ) ) {
192
+ $user_role = $wp_roles->role_names['administrator'];
193
+ }
194
+
195
+ return $user_role;
196
+ }
197
+
198
+ /**
199
+ * True if user no longer exists, otherwise false
200
+ *
201
+ * @return bool
202
+ */
203
+ public function is_deleted() {
204
+ return ( 0 !== $this->id && 0 === $this->user->ID );
205
+ }
206
+
207
+ /**
208
+ * True if user is WP-CLI, otherwise false
209
+ *
210
+ * @return bool
211
+ */
212
+ public function is_wp_cli() {
213
+ return ( 'wp_cli' === $this->get_agent() );
214
+ }
215
+
216
+ /**
217
+ * True if doing WP Cron, otherwise false
218
+ *
219
+ * Note: If native WP Cron has been disabled and you are
220
+ * hitting the cron endpoint with a system cron job, this
221
+ * method will always return false.
222
+ *
223
+ * @return bool
224
+ */
225
+ public function is_doing_wp_cron() {
226
+ return (
227
+ wp_mainwp_stream_is_cron_enabled()
228
+ &&
229
+ defined( 'DOING_CRON' )
230
+ &&
231
+ DOING_CRON
232
+ );
233
+ }
234
+
235
+ /**
236
+ * Look at the environment to detect if an agent is being used
237
+ *
238
+ * @return string
239
+ */
240
+ public function get_current_agent() {
241
+ $agent = '';
242
+ if ( defined( '\WP_CLI' ) && \WP_CLI ) {
243
+ $agent = 'wp_cli';
244
+ } elseif ( $this->is_doing_wp_cron() ) {
245
+ $agent = 'wp_cron';
246
+ }
247
+
248
+ /**
249
+ * Filter the current agent string
250
+ *
251
+ * @return string
252
+ */
253
+ $agent = apply_filters( 'wp_mainwp_stream_current_agent', $agent );
254
+
255
+ return $agent;
256
+ }
257
+
258
+ /**
259
+ * Get the agent label
260
+ *
261
+ * @param string $agent
262
+ *
263
+ * @return string
264
+ */
265
+ public function get_agent_label( $agent ) {
266
+ if ( 'wp_cli' === $agent ) {
267
+ $label = esc_html__( 'via WP-CLI', 'mainwp-child-reports' );
268
+ } elseif ( 'wp_cron' === $agent ) {
269
+ $label = esc_html__( 'during WP Cron', 'mainwp-child-reports' );
270
+ } else {
271
+ $label = '';
272
+ }
273
+
274
+ /**
275
+ * Filter agent labels
276
+ *
277
+ * @param string $agent
278
+ *
279
+ * @return string
280
+ */
281
+ $label = apply_filters( 'wp_mainwp_stream_agent_label', $label, $agent );
282
+
283
+ return $label;
284
+ }
285
+ }
classes/class-cli.php ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Stream command for WP-CLI
4
+ *
5
+ * @see https://github.com/wp-cli/wp-cli
6
+ */
7
+
8
+ namespace WP_MainWP_Stream;
9
+
10
+ class CLI extends \WP_CLI_Command {
11
+
12
+ /**
13
+ * Query a set of Stream records.
14
+ *
15
+ * ## OPTIONS
16
+ *
17
+ * [--fields=<fields>]
18
+ * : Limit the output to specific object fields.
19
+ *
20
+ * [--<field>=<value>]
21
+ * : One or more args to pass to WP_MainWP_Stream_Query.
22
+ *
23
+ * [--format=<format>]
24
+ * : Accepted values: table, count, json, json_pretty, csv. Default: table
25
+ *
26
+ * ## AVAILABLE FIELDS TO QUERY
27
+ *
28
+ * You can build a query from these fields:
29
+ *
30
+ * * user_id
31
+ * * user_id__in
32
+ * * user_id__not_in
33
+ * * user_role
34
+ * * user_role__in
35
+ * * user_role__not_in
36
+ * * date
37
+ * * date_from
38
+ * * date_to
39
+ * * date_after
40
+ * * date_before
41
+ * * ip
42
+ * * ip__in
43
+ * * ip__not_in
44
+ * * connector
45
+ * * connector__in
46
+ * * connector__not_in
47
+ * * context
48
+ * * context__in
49
+ * * context__not_in
50
+ * * action
51
+ * * action__in
52
+ * * action__not_in
53
+ * * search
54
+ * * search_field
55
+ * * record
56
+ * * record__in
57
+ * * record__not_in
58
+ * * records_per_page
59
+ * * paged
60
+ * * order
61
+ * * orderby
62
+ *
63
+ * ## AVAILABLE FIELDS
64
+ *
65
+ * These fields will be displayed by default for each post:
66
+ *
67
+ * * created
68
+ * * ip
69
+ * * user_id
70
+ * * user_role
71
+ * * summary
72
+ *
73
+ * These fields are optionally available:
74
+ *
75
+ * * ID
76
+ * * site_id
77
+ * * blog_id
78
+ * * object_id
79
+ * * connector
80
+ * * context
81
+ * * action
82
+ *
83
+ * ## EXAMPLES
84
+ *
85
+ * wp stream query --user_role__not_in=administrator --date_after=2015-01-01T12:00:00
86
+ * wp stream query --user_id=1 --action=login --records_per_page=50 --fields=created
87
+ *
88
+ * @see WP_MainWP_Stream_Query
89
+ * @see https://github.com/wp-stream/stream/wiki/WP-CLI-Command
90
+ * @see https://github.com/wp-stream/stream/wiki/Query-Reference
91
+ */
92
+ public function query( $args, $assoc_args ) {
93
+ unset( $args );
94
+
95
+ $query_args = array();
96
+ $formatted_records = array();
97
+
98
+ $this->connection();
99
+
100
+ if ( empty( $assoc_args['fields'] ) ) {
101
+ $fields = array(
102
+ 'created',
103
+ 'ip',
104
+ 'user_id',
105
+ 'user_role',
106
+ 'summary',
107
+ );
108
+ } else {
109
+ $fields = explode( ',', $assoc_args['fields'] );
110
+ }
111
+
112
+ foreach ( $assoc_args as $key => $value ) {
113
+ if ( 'format' === $key ) {
114
+ continue;
115
+ }
116
+
117
+ $query_args[ $key ] = $value;
118
+ }
119
+
120
+ $query_args['fields'] = implode( ',', $fields );
121
+
122
+ $records = wp_mainwp_stream_get_instance()->db->query( $query_args );
123
+
124
+ // Make structure Formatter compatible
125
+ foreach ( (array) $records as $key => $record ) {
126
+ $formatted_records[ $key ] = array();
127
+
128
+ // Catch any fields missing in records
129
+ foreach ( $fields as $field ) {
130
+ if ( ! array_key_exists( $field, $record ) ) {
131
+ $record->$field = null;
132
+ }
133
+ }
134
+
135
+ foreach ( $record as $field_name => $field ) {
136
+
137
+ $formatted_records[ $key ] = array_merge(
138
+ $formatted_records[ $key ],
139
+ $this->format_field( $field_name, $field )
140
+ );
141
+ }
142
+ }
143
+
144
+ if ( isset( $assoc_args['format'] ) && 'table' !== $assoc_args['format'] ) {
145
+ if ( 'count' === $assoc_args['format'] ) {
146
+ \WP_CLI::line( count( $records ) );
147
+ }
148
+
149
+ if ( 'json' === $assoc_args['format'] ) {
150
+ \WP_CLI::line( wp_mainwp_stream_json_encode( $formatted_records ) );
151
+ }
152
+
153
+ if ( 'json_pretty' === $assoc_args['format'] ) {
154
+ if ( version_compare( PHP_VERSION, '5.4', '<' ) ) {
155
+ \WP_CLI::line( wp_mainwp_stream_json_encode( $formatted_records ) ); // xss ok
156
+ } else {
157
+ \WP_CLI::line( wp_mainwp_stream_json_encode( $formatted_records, JSON_PRETTY_PRINT ) ); // xss ok
158
+ }
159
+ }
160
+
161
+ if ( 'csv' === $assoc_args['format'] ) {
162
+ \WP_CLI::line( $this->csv_format( $formatted_records ) );
163
+ }
164
+
165
+ return;
166
+ }
167
+
168
+ $formatter = new \WP_CLI\Formatter(
169
+ $assoc_args,
170
+ $fields
171
+ );
172
+
173
+ $formatter->display_items( $formatted_records );
174
+ }
175
+
176
+ /**
177
+ * Convert any field to a flat array.
178
+ *
179
+ * @param string $name The output array element name
180
+ * @param mixed $object Any value to be converted to an array
181
+ *
182
+ * @return array The flat array
183
+ */
184
+ private function format_field( $name, $object ) {
185
+ $array = array();
186
+
187
+ if ( is_object( $object ) ) {
188
+ foreach ( $object as $key => $property ) {
189
+ $array = array_merge( $array, $this->format_field( $name . '.' . $key, $property ) );
190
+ }
191
+ } elseif ( is_array( $object ) ) {
192
+ $array[ $name ] = $object[0];
193
+ } else {
194
+ $array[ $name ] = $object;
195
+ }
196
+
197
+ return $array;
198
+ }
199
+
200
+ /**
201
+ * Convert an array of flat records to CSV
202
+ *
203
+ * @param array $array The input array of records
204
+ *
205
+ * @return string The CSV output
206
+ */
207
+ private function csv_format( $array ) {
208
+ $output = fopen( 'php://output', 'w' ); // @codingStandardsIgnoreLine Clever output for WP CLI using php://output
209
+
210
+ foreach ( $array as $line ) {
211
+ fputcsv( $output, $line ); // @codingStandardsIgnoreLine
212
+ }
213
+
214
+ fclose( $output ); // @codingStandardsIgnoreLine
215
+ }
216
+
217
+ /**
218
+ * Checks for a Stream connection and displays an error or success message.
219
+ *
220
+ * @return void
221
+ */
222
+ private function connection() {
223
+ $query = wp_mainwp_stream_get_instance()->db->query(
224
+ array(
225
+ 'records_per_page' => 1,
226
+ 'fields' => 'created',
227
+ )
228
+ );
229
+
230
+ if ( ! $query ) {
231
+ \WP_CLI::error( esc_html__( 'SITE IS DISCONNECTED', 'mainwp-child-reports' ) );
232
+ }
233
+ }
234
+ }
classes/class-connector.php ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ abstract class Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = null;
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array();
18
+
19
+ /**
20
+ * Store delayed logs
21
+ *
22
+ * @var array
23
+ */
24
+ public $delayed = array();
25
+
26
+ /**
27
+ * Previous Stream entry in same request
28
+ *
29
+ * @var int
30
+ */
31
+ public $prev_stream = null;
32
+
33
+ /**
34
+ * Register connector in the WP Admin
35
+ *
36
+ * @var bool
37
+ */
38
+ public $register_admin = true;
39
+
40
+ /**
41
+ * Register connector in the WP Frontend
42
+ *
43
+ * @var bool
44
+ */
45
+ public $register_frontend = true;
46
+
47
+ /**
48
+ * Register all context hooks
49
+ */
50
+ public function register() {
51
+ foreach ( $this->actions as $action ) {
52
+ add_action( $action, array( $this, 'callback' ), 10, 99 );
53
+ }
54
+
55
+ add_filter( 'wp_mainwp_stream_action_links_' . $this->name, array( $this, 'action_links' ), 10, 2 );
56
+ }
57
+
58
+ /**
59
+ * Callback for all registered hooks throughout Stream
60
+ * Looks for a class method with the convention: "callback_{action name}"
61
+ */
62
+ public function callback() {
63
+ $action = current_filter();
64
+ $callback = array( $this, 'callback_' . preg_replace( '/[^A-Za-z0-9_\-]/', '_', $action ) ); // to fix A-Z charater in callback name
65
+
66
+ // For the sake of testing, trigger an action with the name of the callback
67
+ if ( defined( 'WP_MAINWP_STREAM_TESTS' ) && WP_MAINWP_STREAM_TESTS ) {
68
+ /**
69
+ * Action fires during testing to test the current callback
70
+ *
71
+ * @param array $callback Callback name
72
+ */
73
+ do_action( 'wp_mainwp_stream_test_' . $callback[1] );
74
+ }
75
+
76
+ // Call the real function
77
+ if ( is_callable( $callback ) ) {
78
+ return call_user_func_array( $callback, func_get_args() );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Add action links to Stream drop row in admin list screen
84
+ *
85
+ * @param array $links Previous links registered
86
+ * @param object $record Stream record
87
+ *
88
+ * @filter wp_mainwp_stream_action_links_{connector}
89
+ *
90
+ * @return array Action links
91
+ */
92
+ public function action_links( $links, $record ) {
93
+ unset( $record );
94
+ return $links;
95
+ }
96
+
97
+ /**
98
+ * Log handler
99
+ *
100
+ * @param string $message sprintf-ready error message string
101
+ * @param array $args sprintf (and extra) arguments to use
102
+ * @param int $object_id Target object id
103
+ * @param string $context Context of the event
104
+ * @param string $action Action of the event
105
+ * @param int $user_id User responsible for the event
106
+ *
107
+ * @return bool
108
+ */
109
+ public function log( $message, $args, $object_id, $context, $action, $user_id = null ) {
110
+ $connector = $this->name;
111
+
112
+ $data = apply_filters(
113
+ 'wp_mainwp_stream_log_data',
114
+ compact( 'connector', 'message', 'args', 'object_id', 'context', 'action', 'user_id' )
115
+ );
116
+
117
+ if ( ! $data ) {
118
+ return false;
119
+ } else {
120
+ $connector = $data['connector'];
121
+ $message = $data['message'];
122
+ $args = $data['args'];
123
+ $object_id = $data['object_id'];
124
+ $context = $data['context'];
125
+ $action = $data['action'];
126
+ $user_id = $data['user_id'];
127
+ }
128
+
129
+ $created_timestamp = null;
130
+
131
+ if ( !empty( $context ) && is_array($args) ) {
132
+ if ( $context == "plugins" ) {
133
+
134
+ if (isset($args['slug']) && ( $args['slug'] == 'mainwp-child/mainwp-child.php' || $args['slug'] == 'mainwp-child-reports/mainwp-child-reports.php' )) {
135
+ $options = (array) get_option( 'wp_mainwp_stream', array() );
136
+ if ( ! empty( $options['general_hide_child_plugins'] ) ) {
137
+ return false; // return, do not log child/reports plugin
138
+ }
139
+ $branding_text = wp_mainwp_stream_get_instance()->child_helper->get_branding_title();
140
+ if ( !empty( $branding_text ) ) {
141
+ if ($args['slug'] == 'mainwp-child/mainwp-child.php') {
142
+ $args['name'] = $branding_text;
143
+ } else {
144
+ $args['name'] = $branding_text . ' Reports';
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ $addition_connector = '';
151
+
152
+ $mainwp_addition_connector = array(
153
+ 'mainwp_backups',
154
+ 'mainwp_maintenances',
155
+ 'mainwp_sucuri',
156
+ 'mainwp_wordfence'
157
+ );
158
+
159
+ if ( in_array( $connector, $mainwp_addition_connector )) {
160
+ $addition_connector = $connector;
161
+ }
162
+
163
+
164
+ $created_timestamp = 0;
165
+ if ( !empty( $addition_connector ) ) {
166
+
167
+ if ( is_array($args) ) {
168
+ if (isset($args['backup_time'])) {
169
+ $created_timestamp = $args['backup_time'];
170
+ } else if (isset($args['scan_time'])) {
171
+ $created_timestamp = $args['scan_time'];
172
+ }
173
+ }
174
+
175
+ if ( empty( $created_timestamp ) )
176
+ return;
177
+
178
+ $query_args = array( 'connector' => $addition_connector, 'created' => date("Y-m-d H:i:s", $created_timestamp ) );
179
+
180
+ $created_item = wp_mainwp_stream_get_instance()->db->get_records( $query_args );
181
+
182
+ if ( $created_item )
183
+ return;
184
+ }
185
+ }
186
+
187
+
188
+ return call_user_func_array( array( wp_mainwp_stream_get_instance()->log, 'log' ), compact( 'connector', 'message', 'args', 'object_id', 'context', 'action', 'user_id', 'created_timestamp' ) );
189
+ }
190
+
191
+ /**
192
+ * Save log data till shutdown, so other callbacks would be able to override
193
+ *
194
+ * @param string $handle Special slug to be shared with other actions
195
+ * @note param mixed $arg1 Extra arguments to sent to log()
196
+ * @note param param mixed $arg2, etc..
197
+ */
198
+ public function delayed_log( $handle ) {
199
+ $args = func_get_args();
200
+
201
+ array_shift( $args );
202
+
203
+ $this->delayed[ $handle ] = $args;
204
+
205
+ add_action( 'shutdown', array( $this, 'delayed_log_commit' ) );
206
+ }
207
+
208
+ /**
209
+ * Commit delayed logs saved by @delayed_log
210
+ */
211
+ public function delayed_log_commit() {
212
+ foreach ( $this->delayed as $handle => $args ) {
213
+ call_user_func_array( array( $this, 'log' ), $args );
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Compare two values and return changed keys if they are arrays
219
+ *
220
+ * @param mixed $old_value Value before change
221
+ * @param mixed $new_value Value after change
222
+ * @param bool|int $deep Get array children changes keys as well, not just parents
223
+ *
224
+ * @return array
225
+ */
226
+ public function get_changed_keys( $old_value, $new_value, $deep = false ) {
227
+ if ( ! is_array( $old_value ) && ! is_array( $new_value ) ) {
228
+ return array();
229
+ }
230
+
231
+ if ( ! is_array( $old_value ) ) {
232
+ return array_keys( $new_value );
233
+ }
234
+
235
+ if ( ! is_array( $new_value ) ) {
236
+ return array_keys( $old_value );
237
+ }
238
+
239
+ $diff = array_udiff_assoc(
240
+ $old_value,
241
+ $new_value,
242
+ function( $value1, $value2 ) {
243
+ // Compare potentially complex nested arrays
244
+ return wp_json_encode( $value1 ) !== wp_json_encode( $value2 );
245
+ }
246
+ );
247
+
248
+ $result = array_keys( $diff );
249
+
250
+ // find unexisting keys in old or new value
251
+ $common_keys = array_keys( array_intersect_key( $old_value, $new_value ) );
252
+ $unique_keys_old = array_values( array_diff( array_keys( $old_value ), $common_keys ) );
253
+ $unique_keys_new = array_values( array_diff( array_keys( $new_value ), $common_keys ) );
254
+
255
+ $result = array_merge( $result, $unique_keys_old, $unique_keys_new );
256
+
257
+ // remove numeric indexes
258
+ $result = array_filter(
259
+ $result,
260
+ function( $value ) {
261
+ // @codingStandardsIgnoreStart
262
+ // check if is not valid number (is_int, is_numeric and ctype_digit are not enough)
263
+ return (string) (int) $value !== (string) $value;
264
+ // @codingStandardsIgnoreEnd
265
+ }
266
+ );
267
+
268
+ $result = array_values( array_unique( $result ) );
269
+
270
+ if ( false === $deep ) {
271
+ return $result; // Return an numerical based array with changed TOP PARENT keys only
272
+ }
273
+
274
+ $result = array_fill_keys( $result, null );
275
+
276
+ foreach ( $result as $key => $val ) {
277
+ if ( in_array( $key, $unique_keys_old, true ) ) {
278
+ $result[ $key ] = false; // Removed
279
+ } elseif ( in_array( $key, $unique_keys_new, true ) ) {
280
+ $result[ $key ] = true; // Added
281
+ } elseif ( $deep ) { // Changed, find what changed, only if we're allowed to explore a new level
282
+ if ( is_array( $old_value[ $key ] ) && is_array( $new_value[ $key ] ) ) {
283
+ $inner = array();
284
+ $parent = $key;
285
+ $deep--;
286
+ $changed = $this->get_changed_keys( $old_value[ $key ], $new_value[ $key ], $deep );
287
+ foreach ( $changed as $child => $change ) {
288
+ $inner[ $parent . '::' . $child ] = $change;
289
+ }
290
+ $result[ $key ] = 0; // Changed parent which has a changed children
291
+ $result = array_merge( $result, $inner );
292
+ }
293
+ }
294
+ }
295
+
296
+ return $result;
297
+ }
298
+
299
+ /**
300
+ * Allow connectors to determine if their dependencies is satisfied or not
301
+ *
302
+ * @return bool
303
+ */
304
+ public function is_dependency_satisfied() {
305
+ return true;
306
+ }
307
+ }
classes/class-connectors.php ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connectors {
5
+ /**
6
+ * Hold Plugin class
7
+ * @var Plugin
8
+ */
9
+ public $plugin;
10
+
11
+ /**
12
+ * Connectors registered
13
+ *
14
+ * @var array
15
+ */
16
+ public $connectors = array();
17
+
18
+ /**
19
+ * Contexts registered to Connectors
20
+ *
21
+ * @var array
22
+ */
23
+ public $contexts = array();
24
+
25
+ /**
26
+ * Action taxonomy terms
27
+ * Holds slug to localized label association
28
+ *
29
+ * @var array
30
+ */
31
+ public $term_labels = array(
32
+ 'stream_connector' => array(),
33
+ 'stream_context' => array(),
34
+ 'stream_action' => array(),
35
+ );
36
+
37
+ /**
38
+ * Admin notice messages
39
+ *
40
+ * @var array
41
+ */
42
+ protected $admin_notices = array();
43
+
44
+ /**
45
+ * Class constructor.
46
+ *
47
+ * @param Plugin $plugin The main Plugin class.
48
+ */
49
+ public function __construct( $plugin ) {
50
+ $this->plugin = $plugin;
51
+ $this->load_connectors();
52
+ }
53
+
54
+ /**
55
+ * Load built-in connectors
56
+ */
57
+ public function load_connectors() {
58
+ $connectors = array(
59
+ // Core
60
+ //'blogs', // DISABLED
61
+ 'comments',
62
+ 'editor',
63
+ 'installer',
64
+ 'media',
65
+ 'menus',
66
+ 'posts',
67
+ 'settings',
68
+ 'taxonomies',
69
+ 'users',
70
+ 'widgets',
71
+
72
+ // Extras
73
+ 'acf',
74
+ 'bbpress',
75
+ 'buddypress',
76
+ 'edd',
77
+ 'gravityforms',
78
+ 'jetpack',
79
+ 'user-switching',
80
+ 'woocommerce',
81
+ 'wordpress-seo',
82
+
83
+ // MainWP
84
+ 'mainwp-backups',
85
+ 'mainwp-maintenance',
86
+ 'mainwp-sucuri',
87
+ 'mainwp-wordfence',
88
+ );
89
+
90
+ $classes = array();
91
+ foreach ( $connectors as $connector ) {
92
+ include_once $this->plugin->locations['dir'] . '/connectors/class-connector-' . $connector . '.php';
93
+ $class_name = sprintf( '\WP_MainWP_Stream\Connector_%s', str_replace( '-', '_', $connector ) );
94
+ if ( ! class_exists( $class_name ) ) {
95
+ continue;
96
+ }
97
+ $class = new $class_name( $this->plugin->log );
98
+
99
+ // Check if the Connector extends WP_MainWP_Stream\Connector
100
+ if ( ! is_subclass_of( $class, 'WP_MainWP_Stream\Connector' ) ) {
101
+ continue;
102
+ }
103
+
104
+ // Check if the Connector is allowed to be registered in the WP Admin
105
+ if ( is_admin() && ! $class->register_admin ) {
106
+ continue;
107
+ }
108
+
109
+ // Check if the Connector is allowed to be registered in the WP Frontend
110
+ if ( ! is_admin() && ! $class->register_frontend ) {
111
+ continue;
112
+ }
113
+
114
+ if ( $class->is_dependency_satisfied() ) {
115
+ $classes[ $class->name ] = $class;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Allows for adding additional connectors via classes that extend Connector.
121
+ *
122
+ * @param array $classes An array of Connector objects.
123
+ */
124
+ $this->connectors = apply_filters( 'wp_mainwp_stream_connectors', $classes );
125
+
126
+ if ( empty( $this->connectors ) ) {
127
+ return;
128
+ }
129
+
130
+ foreach ( $this->connectors as $connector ) {
131
+ if ( ! method_exists( $connector, 'get_label' ) ) {
132
+ continue;
133
+ }
134
+ $this->term_labels['stream_connector'][ $connector->name ] = $connector->get_label();
135
+ }
136
+
137
+ // Get excluded connectors
138
+ $excluded_connectors = array();
139
+
140
+ foreach ( $this->connectors as $connector ) {
141
+ if ( ! method_exists( $connector, 'get_label' ) ) {
142
+ // translators: Placeholder refers to a Connector class name, intended to provide help to developers (e.g. "Connector_BuddyPress")
143
+ $this->plugin->admin->notice( sprintf( __( '%s class wasn\'t loaded because it doesn\'t implement the get_label method.', 'mainwp-child-reports' ), $connector->name, 'Connector' ), true );
144
+ continue;
145
+ }
146
+ if ( ! method_exists( $connector, 'register' ) ) {
147
+ // translators: Placeholder refers to a Connector class name, intended to provide help to developers (e.g. "Connector_BuddyPress")
148
+ $this->plugin->admin->notice( sprintf( __( '%s class wasn\'t loaded because it doesn\'t implement the register method.', 'mainwp-child-reports' ), $connector->name, 'Connector' ), true );
149
+ continue;
150
+ }
151
+ if ( ! method_exists( $connector, 'get_context_labels' ) ) {
152
+ // translators: Placeholder refers to a Connector class name, intended to provide help to developers (e.g. "Connector_BuddyPress")
153
+ $this->plugin->admin->notice( sprintf( __( '%s class wasn\'t loaded because it doesn\'t implement the get_context_labels method.', 'mainwp-child-reports' ), $connector->name, 'Connector' ), true );
154
+ continue;
155
+ }
156
+ if ( ! method_exists( $connector, 'get_action_labels' ) ) {
157
+ // translators: Placeholder refers to a Connector class name, intended to provide help to developers (e.g. "Connector_BuddyPress")
158
+ $this->plugin->admin->notice( sprintf( __( '%s class wasn\'t loaded because it doesn\'t implement the get_action_labels method.', 'mainwp-child-reports' ), $connector->name, 'Connector' ), true );
159
+ continue;
160
+ }
161
+
162
+ // Check if the connectors extends the Connector class, if not skip it.
163
+ if ( ! is_subclass_of( $connector, '\WP_MainWP_Stream\Connector' ) ) {
164
+ // translators: Placeholder refers to a Connector class name, intended to provide help to developers (e.g. "Connector_BuddyPress")
165
+ $this->plugin->admin->notice( sprintf( __( '%1$s class wasn\'t loaded because it doesn\'t extends the %2$s class.', 'mainwp-child-reports' ), $connector->name, 'Connector' ), true );
166
+ continue;
167
+ }
168
+
169
+ // Store connector label
170
+ if ( ! in_array( $connector->name, $this->term_labels['stream_connector'], true ) ) {
171
+ $this->term_labels['stream_connector'][ $connector->name ] = $connector->get_label();
172
+ }
173
+
174
+ $connector_name = $connector->name;
175
+ $is_excluded = in_array( $connector_name, $excluded_connectors, true );
176
+
177
+ /**
178
+ * Allows excluded connectors to be overridden and registered.
179
+ *
180
+ * @param bool $is_excluded True if excluded, otherwise false.
181
+ * @param string $connector The current connector's slug.
182
+ * @param array $excluded_connectors An array of all excluded connector slugs.
183
+ */
184
+ $is_excluded_connector = apply_filters( 'wp_mainwp_stream_check_connector_is_excluded', $is_excluded, $connector_name, $excluded_connectors );
185
+
186
+ if ( $is_excluded_connector ) {
187
+ continue;
188
+ }
189
+
190
+ $connector->register();
191
+
192
+ // Link context labels to their connector
193
+ $this->contexts[ $connector->name ] = $connector->get_context_labels();
194
+
195
+ // Add new terms to our label lookup array
196
+ $this->term_labels['stream_action'] = array_merge(
197
+ $this->term_labels['stream_action'],
198
+ $connector->get_action_labels()
199
+ );
200
+ $this->term_labels['stream_context'] = array_merge(
201
+ $this->term_labels['stream_context'],
202
+ $connector->get_context_labels()
203
+ );
204
+ }
205
+
206
+ $labels = $this->term_labels['stream_connector'];
207
+
208
+ /**
209
+ * Fires after all connectors have been registered.
210
+ *
211
+ * @param array $labels All register connectors labels array
212
+ * @param Connectors $connectors The Connectors object
213
+ */
214
+ do_action( 'wp_mainwp_stream_after_connectors_registration', $labels, $this );
215
+ }
216
+ }
includes/date-interval.php → classes/class-date-interval.php RENAMED
@@ -1,136 +1,117 @@
1
- <?php
2
-
3
- // Load Carbon to Handle dates much easier
4
- if ( ! class_exists( 'Carbon\Carbon' ) ) {
5
- require_once MAINWP_WP_STREAM_INC_DIR . 'vendor/Carbon.php';
6
- }
7
-
8
- use Carbon\Carbon;
9
-
10
- class MainWP_WP_Stream_Date_Interval {
11
-
12
- public $intervals;
13
-
14
- public function __construct() {
15
- // Filter the Predefined list of intervals to make it work
16
- add_filter( 'mainwp_wp_stream_predefined_date_intervals', array( $this, 'filter_predefined_intervals' ), 20 );
17
-
18
- // Get all default intervals
19
- $this->intervals = $this->get_predefined_intervals();
20
- }
21
-
22
- public function get_predefined_intervals() {
23
- $timezone = get_option( 'timezone_string' );
24
-
25
- if ( empty( $timezone ) ) {
26
- $gmt_offset = (int) get_option( 'gmt_offset' );
27
- $timezone = timezone_name_from_abbr( null, $gmt_offset * 3600, true );
28
- if ( false === $timezone ) {
29
- $timezone = timezone_name_from_abbr( null, $gmt_offset * 3600, false );
30
- }
31
- if ( false === $timezone ) {
32
- $timezone = null;
33
- }
34
- }
35
-
36
- return apply_filters(
37
- 'mainwp_wp_stream_predefined_date_intervals',
38
- array(
39
- 'today' => array(
40
- 'label' => esc_html__( 'Today', 'default' ),
41
- 'start' => Carbon::today( $timezone )->startOfDay(),
42
- 'end' => Carbon::today( $timezone )->startOfDay(),
43
- ),
44
- 'yesterday' => array(
45
- 'label' => esc_html__( 'Yesterday', 'mainwp-child-reports' ),
46
- 'start' => Carbon::today( $timezone )->startOfDay()->subDay(),
47
- 'end' => Carbon::today( $timezone )->startOfDay()->subSecond(),
48
- ),
49
- 'last-7-days' => array(
50
- 'label' => sprintf( esc_html__( 'Last %d Days', 'mainwp-child-reports' ), 7 ),
51
- 'start' => Carbon::today( $timezone )->subDays( 7 ),
52
- 'end' => Carbon::today( $timezone ),
53
- ),
54
- 'last-14-days' => array(
55
- 'label' => sprintf( esc_html__( 'Last %d Days', 'mainwp-child-reports' ), 14 ),
56
- 'start' => Carbon::today( $timezone )->subDays( 14 ),
57
- 'end' => Carbon::today( $timezone ),
58
- ),
59
- 'last-30-days' => array(
60
- 'label' => sprintf( esc_html__( 'Last %d Days', 'mainwp-child-reports' ), 30 ),
61
- 'start' => Carbon::today( $timezone )->subDays( 30 ),
62
- 'end' => Carbon::today( $timezone ),
63
- ),
64
- 'this-month' => array(
65
- 'label' => esc_html__( 'This Month', 'mainwp-child-reports' ),
66
- 'start' => Carbon::today( $timezone )->day( 1 ),
67
- ),
68
- 'last-month' => array(
69
- 'label' => esc_html__( 'Last Month', 'mainwp-child-reports' ),
70
- 'start' => Carbon::today( $timezone )->day( 1 )->subMonth(),
71
- 'end' => Carbon::today( $timezone )->day( 1 )->subSecond(),
72
- ),
73
- 'last-3-months' => array(
74
- 'label' => sprintf( esc_html__( 'Last %d Months', 'mainwp-child-reports' ), 3 ),
75
- 'start' => Carbon::today( $timezone )->subMonths( 3 ),
76
- 'end' => Carbon::today( $timezone ),
77
- ),
78
- 'last-6-months' => array(
79
- 'label' => sprintf( esc_html__( 'Last %d Months', 'mainwp-child-reports' ), 6 ),
80
- 'start' => Carbon::today( $timezone )->subMonths( 6 ),
81
- 'end' => Carbon::today( $timezone ),
82
- ),
83
- 'last-12-months' => array(
84
- 'label' => sprintf( esc_html__( 'Last %d Months', 'mainwp-child-reports' ), 12 ),
85
- 'start' => Carbon::today( $timezone )->subMonths( 12 ),
86
- 'end' => Carbon::today( $timezone ),
87
- ),
88
- 'this-year' => array(
89
- 'label' => esc_html__( 'This Year', 'mainwp-child-reports' ),
90
- 'start' => Carbon::today( $timezone )->day( 1 )->month( 1 ),
91
- ),
92
- 'last-year' => array(
93
- 'label' => esc_html__( 'Last Year', 'mainwp-child-reports' ),
94
- 'start' => Carbon::today( $timezone )->day( 1 )->month( 1 )->subYear(),
95
- 'end' => Carbon::today( $timezone )->day( 1 )->month( 1 )->subSecond(),
96
- ),
97
- ),
98
- $timezone
99
- );
100
- }
101
-
102
- public function filter_predefined_intervals( $intervals ) {
103
- $query = mainwp_wp_stream_query(
104
- array(
105
- 'order' => 'ASC',
106
- 'orderby' => 'created',
107
- 'records_per_page' => 1,
108
- 'ignore_context' => true,
109
- )
110
- );
111
-
112
- $first_stream_item = reset( $query );
113
-
114
- if ( false === $first_stream_item ) {
115
- return array();
116
- }
117
-
118
- $first_stream_date = \Carbon\Carbon::parse( $first_stream_item->created );
119
-
120
- foreach ( $intervals as $key => $interval ) {
121
- if ( ! isset( $interval['start'] ) || false === $interval['start'] ) {
122
- $intervals[ $key ]['start'] = $interval['start'] = $first_stream_date;
123
- }
124
- if ( ! isset( $interval['end'] ) || false === $interval['end'] ) {
125
- $intervals[ $key ]['end'] = $interval['end'] = \Carbon\Carbon::now();
126
- }
127
- if ( ! is_a( $interval['start'], '\Carbon\Carbon' ) || ! is_a( $interval['end'], '\Carbon\Carbon' ) ) {
128
- unset( $intervals[ $key ] );
129
- continue;
130
- }
131
- }
132
-
133
- return $intervals;
134
- }
135
-
136
- }
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ // Load Carbon to Handle dates much easier
5
+ if ( ! class_exists( 'Carbon\Carbon' ) ) {
6
+ require_once wp_mainwp_stream_get_instance()->locations['inc_dir'] . 'lib/Carbon.php';
7
+ }
8
+
9
+ use Carbon\Carbon;
10
+
11
+ class Date_Interval {
12
+ /**
13
+ * Contains an array of all available intervals
14
+ *
15
+ * @var array $intervals
16
+ */
17
+ public $intervals;
18
+
19
+ /**
20
+ * Class constructor
21
+ */
22
+ public function __construct() {
23
+ // Get all default intervals
24
+ $this->intervals = $this->get_predefined_intervals();
25
+ }
26
+
27
+ /**
28
+ * @return mixed
29
+ */
30
+ public function get_predefined_intervals() {
31
+ $timezone = get_option( 'timezone_string' );
32
+
33
+ if ( empty( $timezone ) ) {
34
+ $gmt_offset = (int) get_option( 'gmt_offset' );
35
+ $timezone = timezone_name_from_abbr( null, $gmt_offset * 3600, true );
36
+ if ( false === $timezone ) {
37
+ $timezone = timezone_name_from_abbr( null, $gmt_offset * 3600, false );
38
+ }
39
+ if ( false === $timezone ) {
40
+ $timezone = null;
41
+ }
42
+ }
43
+
44
+ return apply_filters(
45
+ 'wp_mainwp_stream_predefined_date_intervals',
46
+ array(
47
+ 'today' => array(
48
+ 'label' => esc_html__( 'Today', 'mainwp-child-reports' ),
49
+ 'start' => Carbon::today( $timezone )->startOfDay(),
50
+ 'end' => Carbon::today( $timezone )->endOfDay(),
51
+ ),
52
+ 'yesterday' => array(
53
+ 'label' => esc_html__( 'Yesterday', 'mainwp-child-reports' ),
54
+ 'start' => Carbon::today( $timezone )->startOfDay()->subDay(),
55
+ 'end' => Carbon::today( $timezone )->startOfDay()->subSecond(),
56
+ ),
57
+ 'last-7-days' => array(
58
+ // translators: Placeholder refers to a number of days (e.g. "7")
59
+ 'label' => sprintf( esc_html__( 'Last %d Days', 'mainwp-child-reports' ), 7 ),
60
+ 'start' => Carbon::today( $timezone )->subDays( 7 ),
61
+ 'end' => Carbon::today( $timezone ),
62
+ ),
63
+ 'last-14-days' => array(
64
+ // translators: Placeholder refers to a number of days (e.g. "7")
65
+ 'label' => sprintf( esc_html__( 'Last %d Days', 'mainwp-child-reports' ), 14 ),
66
+ 'start' => Carbon::today( $timezone )->subDays( 14 ),
67
+ 'end' => Carbon::today( $timezone ),
68
+ ),
69
+ 'last-30-days' => array(
70
+ // translators: Placeholder refers to a number of days (e.g. "7")
71
+ 'label' => sprintf( esc_html__( 'Last %d Days', 'mainwp-child-reports' ), 30 ),
72
+ 'start' => Carbon::today( $timezone )->subDays( 30 ),
73
+ 'end' => Carbon::today( $timezone ),
74
+ ),
75
+ 'this-month' => array(
76
+ 'label' => esc_html__( 'This Month', 'mainwp-child-reports' ),
77
+ 'start' => Carbon::today( $timezone )->startOfMonth(),
78
+ 'end' => Carbon::today( $timezone )->endOfMonth(),
79
+ ),
80
+ 'last-month' => array(
81
+ 'label' => esc_html__( 'Last Month', 'mainwp-child-reports' ),
82
+ 'start' => Carbon::today( $timezone )->startOfMonth()->subMonth(),
83
+ 'end' => Carbon::today( $timezone )->startOfMonth()->subSecond(),
84
+ ),
85
+ 'last-3-months' => array(
86
+ // translators: Placeholder refers to a number of months (e.g. "3")
87
+ 'label' => sprintf( esc_html__( 'Last %d Months', 'mainwp-child-reports' ), 3 ),
88
+ 'start' => Carbon::today( $timezone )->subMonths( 3 ),
89
+ 'end' => Carbon::today( $timezone ),
90
+ ),
91
+ 'last-6-months' => array(
92
+ // translators: Placeholder refers to a number of months (e.g. "3")
93
+ 'label' => sprintf( esc_html__( 'Last %d Months', 'mainwp-child-reports' ), 6 ),
94
+ 'start' => Carbon::today( $timezone )->subMonths( 6 ),
95
+ 'end' => Carbon::today( $timezone ),
96
+ ),
97
+ 'last-12-months' => array(
98
+ // translators: Placeholder refers to a number of months (e.g. "3")
99
+ 'label' => sprintf( esc_html__( 'Last %d Months', 'mainwp-child-reports' ), 12 ),
100
+ 'start' => Carbon::today( $timezone )->subMonths( 12 ),
101
+ 'end' => Carbon::today( $timezone ),
102
+ ),
103
+ 'this-year' => array(
104
+ 'label' => esc_html__( 'This Year', 'mainwp-child-reports' ),
105
+ 'start' => Carbon::today( $timezone )->startOfYear(),
106
+ 'end' => Carbon::today( $timezone )->endOfYear(),
107
+ ),
108
+ 'last-year' => array(
109
+ 'label' => esc_html__( 'Last Year', 'mainwp-child-reports' ),
110
+ 'start' => Carbon::today( $timezone )->startOfYear()->subYear(),
111
+ 'end' => Carbon::today( $timezone )->startOfYear()->subSecond(),
112
+ ),
113
+ ),
114
+ $timezone
115
+ );
116
+ }
117
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/class-db-driver-wpdb.php ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class DB_Driver_WPDB implements DB_Driver {
5
+ /**
6
+ * Holds Query class
7
+ *
8
+ * @var Query
9
+ */
10
+ protected $query;
11
+
12
+ /**
13
+ * Hold records table name
14
+ *
15
+ * @var string
16
+ */
17
+ public $table;
18
+
19
+ /**
20
+ * Hold meta table name
21
+ *
22
+ * @var string
23
+ */
24
+ public $table_meta;
25
+
26
+ /**
27
+ * Class constructor.
28
+ */
29
+ public function __construct() {
30
+ $this->query = new Query( $this );
31
+
32
+ global $wpdb;
33
+ $prefix = apply_filters( 'wp_mainwp_stream_db_tables_prefix', $wpdb->base_prefix );
34
+
35
+ $this->table = $prefix . 'mainwp_stream';
36
+ $this->table_meta = $prefix . 'mainwp_stream_meta';
37
+
38
+ $wpdb->mainwp_stream = $this->table;
39
+ $wpdb->mainwp_streammeta = $this->table_meta;
40
+
41
+ // Hack for get_metadata
42
+ $wpdb->recordmeta = $this->table_meta;
43
+ }
44
+
45
+ /**
46
+ * Insert a record.
47
+ *
48
+ * @param array $data Data to insert.
49
+ *
50
+ * @return int
51
+ */
52
+ public function insert_record( $data ) {
53
+ global $wpdb;
54
+
55
+ if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
56
+ return false;
57
+ }
58
+
59
+ $meta = $data['meta'];
60
+ unset( $data['meta'] );
61
+
62
+ $result = $wpdb->insert( $this->table, $data );
63
+ if ( ! $result ) {
64
+ return false;
65
+ }
66
+
67
+ $record_id = $wpdb->insert_id;
68
+
69
+ // Insert record meta
70
+ foreach ( (array) $meta as $key => $vals ) {
71
+ foreach ( (array) $vals as $val ) {
72
+ $this->insert_meta( $record_id, $key, $val );
73
+ }
74
+ }
75
+
76
+ return $record_id;
77
+ }
78
+
79
+ /**
80
+ * Insert record meta
81
+ *
82
+ * @param int $record_id
83
+ * @param string $key
84
+ * @param string $val
85
+ *
86
+ * @return array
87
+ */
88
+ public function insert_meta( $record_id, $key, $val ) {
89
+ global $wpdb;
90
+
91
+ $result = $wpdb->insert(
92
+ $this->table_meta,
93
+ array(
94
+ 'record_id' => $record_id,
95
+ 'meta_key' => $key,
96
+ 'meta_value' => $val,
97
+ )
98
+ );
99
+
100
+ return $result;
101
+ }
102
+
103
+ /**
104
+ * Retrieve records
105
+ *
106
+ * @param array $args
107
+ *
108
+ * @return array
109
+ */
110
+ public function get_records( $args ) {
111
+ return $this->query->query( $args );
112
+ }
113
+
114
+ /**
115
+ * Returns array of existing values for requested column.
116
+ * Used to fill search filters with only used items, instead of all items.
117
+ *
118
+ * GROUP BY allows query to find just the first occurrence of each value in the column,
119
+ * increasing the efficiency of the query.
120
+ *
121
+ * @param string $column
122
+ *
123
+ * @return array
124
+ */
125
+ public function get_column_values( $column ) {
126
+ global $wpdb;
127
+ return (array) $wpdb->get_results(
128
+ "SELECT DISTINCT $column FROM $wpdb->mainwp_stream", // @codingStandardsIgnoreLine can't prepare column name
129
+ 'ARRAY_A'
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Public getter to return table names
135
+ *
136
+ * @return array
137
+ */
138
+ public function get_table_names() {
139
+ return array(
140
+ $this->table,
141
+ $this->table_meta,
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Init storage.
147
+ *
148
+ * @param \WP_MainWP_Stream\Plugin $plugin Instance of the plugin.
149
+ * @return \WP_MainWP_Stream\Install
150
+ */
151
+ public function setup_storage( $plugin ) {
152
+ return new Install( $plugin );
153
+ }
154
+
155
+ /**
156
+ * Purge storage.
157
+ *
158
+ * @param \WP_MainWP_Stream\Plugin $plugin Instance of the plugin.
159
+ * @return \WP_MainWP_Stream\Uninstall
160
+ */
161
+ public function purge_storage( $plugin ) {
162
+ $uninstall = new Uninstall( $plugin );
163
+ add_action( 'wp_ajax_wp_mainwp_stream_uninstall', array( $uninstall, 'uninstall' ) );
164
+
165
+ return $uninstall;
166
+ }
167
+
168
+ }
classes/class-db-driver.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ interface DB_Driver {
5
+ /**
6
+ * Insert a record
7
+ *
8
+ * @param array $data
9
+ *
10
+ * @return int
11
+ */
12
+ public function insert_record( $data );
13
+
14
+ /**
15
+ * Retrieve records
16
+ *
17
+ * @param array $args
18
+ *
19
+ * @return array
20
+ */
21
+ public function get_records( $args );
22
+
23
+ /**
24
+ * Returns array of existing values for requested column.
25
+ * Used to fill search filters with only used items, instead of all items.
26
+ *
27
+ * @param string $column
28
+ *
29
+ * @return array
30
+ */
31
+ public function get_column_values( $column );
32
+
33
+ /**
34
+ * Public getter to return table names
35
+ *
36
+ * @return array
37
+ */
38
+ public function get_table_names();
39
+
40
+ /**
41
+ * Init storage.
42
+ *
43
+ * @param \WP_MainWP_Stream\Plugin $plugin Instance of the plugin.
44
+ */
45
+ public function setup_storage( $plugin );
46
+
47
+ /**
48
+ * Purge storage.
49
+ *
50
+ * @param \WP_MainWP_Stream\Plugin $plugin Instance of the plugin.
51
+ */
52
+ public function purge_storage( $plugin );
53
+ }
classes/class-db.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class DB {
5
+ /**
6
+ * Hold the Driver class
7
+ *
8
+ * @var DB_Driver
9
+ */
10
+ public $driver;
11
+
12
+ /**
13
+ * Number of records in last request
14
+ *
15
+ * @var int
16
+ */
17
+ protected $found_records_count = 0;
18
+
19
+ /**
20
+ * Class constructor.
21
+ *
22
+ * @param DB_Driver $driver Driver we want to use.
23
+ */
24
+ public function __construct( $driver ) {
25
+ $this->driver = $driver;
26
+ }
27
+
28
+ /**
29
+ * Insert a record
30
+ *
31
+ * @param array $record
32
+ *
33
+ * @return int
34
+ */
35
+ public function insert( $record ) {
36
+ if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Filter allows modification of record information
42
+ *
43
+ * @param array $record
44
+ *
45
+ * @return array
46
+ */
47
+ $record = apply_filters( 'wp_mainwp_stream_record_array', $record );
48
+
49
+ array_walk(
50
+ $record, function( &$value, &$key ) {
51
+ if ( ! is_array( $value ) ) {
52
+ $value = strip_tags( $value );
53
+ }
54
+ }
55
+ );
56
+
57
+ if ( empty( $record ) ) {
58
+ return false;
59
+ }
60
+
61
+ $fields = array( 'object_id', 'site_id', 'blog_id', 'user_id', 'user_role', 'created', 'summary', 'ip', 'connector', 'context', 'action', 'meta' );
62
+ $data = array_intersect_key( $record, array_flip( $fields ) );
63
+
64
+ $record_id = $this->driver->insert_record( $data );
65
+
66
+ if ( ! $record_id ) {
67
+ /**
68
+ * Fires on a record insertion error
69
+ *
70
+ * @param array $record
71
+ * @param mixed $result
72
+ */
73
+ do_action( 'wp_mainwp_stream_record_insert_error', $record, false );
74
+
75
+ return false;
76
+ }
77
+
78
+ /**
79
+ * Fires after a record has been inserted
80
+ *
81
+ * @param int $record_id
82
+ * @param array $record
83
+ */
84
+ do_action( 'wp_mainwp_stream_record_inserted', $record_id, $record );
85
+
86
+ return absint( $record_id );
87
+ }
88
+
89
+ /**
90
+ * Returns array of existing values for requested column.
91
+ * Used to fill search filters with only used items, instead of all items.
92
+ *
93
+ * GROUP BY allows query to find just the first occurrence of each value in the column,
94
+ * increasing the efficiency of the query.
95
+ *
96
+ * @see assemble_records
97
+ * @since 1.0.4
98
+ *
99
+ * @param string $column
100
+ *
101
+ * @return array
102
+ */
103
+ public function existing_records( $column ) {
104
+ // Sanitize column
105
+ $allowed_columns = array( 'ID', 'site_id', 'blog_id', 'object_id', 'user_id', 'user_role', 'created', 'summary', 'connector', 'context', 'action', 'ip' );
106
+ if ( ! in_array( $column, $allowed_columns, true ) ) {
107
+ return array();
108
+ }
109
+
110
+ $rows = $this->driver->get_column_values( $column );
111
+
112
+ if ( is_array( $rows ) && ! empty( $rows ) ) {
113
+ $output_array = array();
114
+
115
+ foreach ( $rows as $row ) {
116
+ foreach ( $row as $cell => $value ) {
117
+ $output_array[ $value ] = $value;
118
+ }
119
+ }
120
+
121
+ return (array) $output_array;
122
+ }
123
+
124
+ $column = sprintf( 'stream_%s', $column );
125
+
126
+ $term_labels = wp_mainwp_stream_get_instance()->connectors->term_labels;
127
+ return isset( $term_labels[ $column ] ) ? $term_labels[ $column ] : array();
128
+ }
129
+
130
+ /**
131
+ * Get stream records
132
+ *
133
+ * @param array Query args
134
+ *
135
+ * @return array Stream Records
136
+ */
137
+ public function get_records( $args ) {
138
+ $defaults = array(
139
+ // Search param
140
+ 'search' => null,
141
+ 'search_field' => 'summary',
142
+ 'record_after' => null, // Deprecated, use date_after instead
143
+ // Date-based filters
144
+ 'date' => null, // Ex: 2015-07-01
145
+ 'date_from' => null, // Ex: 2015-07-01
146
+ 'date_to' => null, // Ex: 2015-07-01
147
+ 'date_after' => null, // Ex: 2015-07-01T15:19:21+00:00
148
+ 'date_before' => null, // Ex: 2015-07-01T15:19:21+00:00
149
+ // Record ID filters
150
+ 'record' => null,
151
+ 'record__in' => array(),
152
+ 'record__not_in' => array(),
153
+ // Pagination params
154
+ 'records_per_page' => get_option( 'posts_per_page', 20 ),
155
+ 'paged' => 1,
156
+ // Order
157
+ 'order' => 'desc',
158
+ 'orderby' => 'date',
159
+ // Fields selection
160
+ 'fields' => array(),
161
+ 'created' => null,
162
+ );
163
+
164
+ // Additional property fields
165
+ $properties = array(
166
+ 'user_id' => null,
167
+ 'user_role' => null,
168
+ 'ip' => null,
169
+ 'object_id' => null,
170
+ 'site_id' => null,
171
+ 'blog_id' => null,
172
+ 'connector' => null,
173
+ 'context' => null,
174
+ 'action' => null,
175
+ );
176
+
177
+ /**
178
+ * Filter allows additional query properties to be added
179
+ *
180
+ * @return array Array of query properties
181
+ */
182
+ $properties = apply_filters( 'wp_mainwp_stream_query_properties', $properties );
183
+
184
+ // Add property fields to defaults, including their __in/__not_in variations
185
+ foreach ( $properties as $property => $default ) {
186
+ if ( ! isset( $defaults[ $property ] ) ) {
187
+ $defaults[ $property ] = $default;
188
+ }
189
+
190
+ $defaults[ "{$property}__in" ] = array();
191
+ $defaults[ "{$property}__not_in" ] = array();
192
+ }
193
+
194
+ $args = wp_parse_args( $args, $defaults );
195
+
196
+ /**
197
+ * Filter allows additional arguments to query $args
198
+ *
199
+ * @return array Array of query arguments
200
+ */
201
+ $args = apply_filters( 'wp_mainwp_stream_query_args', $args );
202
+
203
+ $result = (array) $this->driver->get_records( $args );
204
+ $this->found_records_count = isset( $result['count'] ) ? $result['count'] : 0;
205
+
206
+ return empty( $result['items'] ) ? array() : $result['items'];
207
+ }
208
+
209
+ /**
210
+ * Helper function, backwards compatibility
211
+ *
212
+ * @param array $args Query args
213
+ *
214
+ * @return array Stream Records
215
+ */
216
+ public function query( $args ) {
217
+ return $this->get_records( $args );
218
+ }
219
+
220
+ /**
221
+ * Return the number of records found in last request
222
+ *
223
+ * return int
224
+ */
225
+ public function get_found_records_count() {
226
+ return $this->found_records_count;
227
+ }
228
+
229
+ /**
230
+ * Public getter to return table names
231
+ *
232
+ * @return array
233
+ */
234
+ public function get_table_names() {
235
+ return $this->driver->get_table_names();
236
+ }
237
+ }
classes/class-export.php ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Export {
5
+ /**
6
+ * Hold Plugin class
7
+ *
8
+ * @var Plugin
9
+ */
10
+ public $plugin;
11
+
12
+ /**
13
+ * Hold registered exporters
14
+ *
15
+ * @var array
16
+ */
17
+ protected $exporters = array();
18
+
19
+ /**
20
+ * Class constructor
21
+ *
22
+ * @param Plugin $plugin The plugin object.
23
+ */
24
+ public function __construct( $plugin ) {
25
+ $this->plugin = $plugin;
26
+
27
+ if ( 'wp_mainwp_stream' === wp_mainwp_stream_filter_input( INPUT_GET, 'page' ) ) {
28
+ add_action( 'admin_init', array( $this, 'render_download' ) );
29
+ add_action( 'wp_mainwp_stream_record_actions_menu', array( $this, 'actions_menu_export_items' ) );
30
+ $this->register_exporters();
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Outputs download file to user based on selected exporter
36
+ *
37
+ * @return void
38
+ */
39
+ public function render_download() {
40
+ $nonce = wp_mainwp_stream_filter_input( INPUT_GET, 'stream_record_actions_nonce' );
41
+ if ( ! wp_verify_nonce( $nonce, 'stream_record_actions_nonce' ) ) {
42
+ return;
43
+ }
44
+
45
+ $action = wp_mainwp_stream_filter_input( INPUT_GET, 'record-actions' );
46
+ if ( strpos( $action, 'export-' ) !== 0 ) {
47
+ return;
48
+ }
49
+
50
+ $output_type = str_replace( 'export-', '', $action );
51
+ if ( ! array_key_exists( $output_type, $this->get_exporters() ) ) {
52
+ return;
53
+ }
54
+
55
+ $this->plugin->admin->register_list_table();
56
+ $list_table = $this->plugin->admin->list_table;
57
+ $list_table->prepare_items();
58
+ add_filter( 'mainwp_stream_records_per_page', array( $this, 'disable_paginate' ) );
59
+ add_filter( 'wp_mainwp_stream_list_table_columns', array( $this, 'expand_columns' ), 10, 1 );
60
+
61
+ $records = $list_table->get_records();
62
+ $columns = $list_table->get_columns();
63
+ $output = array();
64
+ foreach ( $records as $item ) {
65
+ $output[] = $this->build_record( $item, $columns );
66
+ }
67
+
68
+ $exporters = $this->get_exporters();
69
+ $exporter = $exporters[ $output_type ];
70
+ $exporter->output_file( $output, $columns );
71
+ }
72
+
73
+ /**
74
+ * Add Export options to record actions menu
75
+ *
76
+ * @return array
77
+ */
78
+ public function actions_menu_export_items( $action_menu_items ) {
79
+ foreach ( $this->get_exporters() as $exporter ) {
80
+ $action = 'export-' . $exporter->slug;
81
+ // translators: Placeholder refers to an export format (e.g. "CSV")
82
+ $action_menu_items[ $action ] = sprintf( __( 'Export as %s', 'mainwp-child-reports' ), $exporter->name );
83
+ }
84
+
85
+ return $action_menu_items;
86
+ }
87
+
88
+ /**
89
+ * Extracts data from Records
90
+ *
91
+ * @param array $item Post to extract data from.
92
+ * @param array $columns Columns being extracted.
93
+ * @return array Numerically-indexed array with extracted data.
94
+ */
95
+ public function build_record( $item, $columns ) {
96
+ $record = new Record( $item );
97
+
98
+ $row_out = array();
99
+ foreach ( array_keys( $columns ) as $column_name ) {
100
+ switch ( $column_name ) {
101
+ case 'date':
102
+ $created = date( 'Y-m-d H:i:s', strtotime( $record->created ) );
103
+ $row_out[ $column_name ] = get_date_from_gmt( $created, 'Y/m/d h:i:s A' );
104
+ break;
105
+
106
+ case 'summary':
107
+ $row_out[ $column_name ] = $record->summary;
108
+ break;
109
+
110
+ case 'user_id':
111
+ $user = new Author( (int) $record->user_id, (array) $record->user_meta );
112
+ $row_out[ $column_name ] = $user->get_display_name();
113
+ break;
114
+
115
+ case 'connector':
116
+ $row_out[ $column_name ] = $record->connector;
117
+ break;
118
+
119
+ case 'context':
120
+ $row_out[ $column_name ] = $record->context;
121
+ break;
122
+
123
+ case 'action':
124
+ $row_out[ $column_name ] = $record->{$column_name};
125
+ break;
126
+
127
+ case 'blog_id':
128
+ $row_out[ $column_name ] = $record->blog_id;
129
+ break;
130
+
131
+ case 'ip':
132
+ $row_out[ $column_name ] = $record->{$column_name};
133
+ break;
134
+ }
135
+ }
136
+
137
+ return $row_out;
138
+ }
139
+
140
+ /**
141
+ * Increase pagination limit for CSV Output
142
+ *
143
+ * @param int $records_per_page Old limit for records_per_page.
144
+ * @return int
145
+ */
146
+ public function disable_paginate( $records_per_page ) {
147
+ return 10000;
148
+ }
149
+
150
+ /**
151
+ * Expand columns for CSV Output
152
+ *
153
+ * @param array $columns Columns currently registered to the list table being exported.
154
+ * @return array New columns for exporting.
155
+ */
156
+ public function expand_columns( $columns ) {
157
+ $new_columns = array(
158
+ 'date' => $columns['date'],
159
+ 'summary' => $columns['summary'],
160
+ 'user_id' => $columns['user_id'],
161
+ 'connector' => __( 'Connector', 'mainwp-child-reports' ),
162
+ 'context' => $columns['context'],
163
+ 'action' => $columns['action'],
164
+ 'ip' => $columns['ip'],
165
+ );
166
+
167
+ if ( is_multisite() && is_plugin_active_for_network( $this->plugin->locations['plugin'] ) ) {
168
+ $new_columns['blog_id'] = __( 'Blog ID', 'mainwp-child-reports' );
169
+ }
170
+
171
+ return $new_columns;
172
+ }
173
+
174
+ /**
175
+ * Registers all available exporters
176
+ *
177
+ * @return void
178
+ */
179
+ public function register_exporters() {
180
+ $exporters = array(
181
+ 'csv',
182
+ 'json',
183
+ );
184
+
185
+ $classes = array();
186
+ foreach ( $exporters as $exporter ) {
187
+ include_once $this->plugin->locations['dir'] . '/exporters/class-exporter-' . $exporter . '.php';
188
+ $class_name = sprintf( '\WP_MainWP_Stream\Exporter_%s', str_replace( '-', '_', $exporter ) );
189
+ if ( ! class_exists( $class_name ) ) {
190
+ continue;
191
+ }
192
+ $class = new $class_name();
193
+ if ( ! property_exists( $class, 'slug' ) ) {
194
+ continue;
195
+ }
196
+ $classes[ $class->slug ] = $class;
197
+ }
198
+
199
+ /**
200
+ * Allows for adding additional exporters via classes that extend Exporter.
201
+ *
202
+ * @param array $classes An array of Exporter objects. In the format exporter_slug => Exporter_Class()
203
+ */
204
+ $this->exporters = apply_filters( 'wp_mainwp_stream_exporters', $classes );
205
+
206
+ // Ensure that all exporters extend Exporter
207
+ foreach ( $this->exporters as $key => $exporter ) {
208
+ if ( ! $this->is_valid_exporter( $exporter ) ) {
209
+ unset( $this->exporters[ $key ] );
210
+ }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Checks whether an exporter class is valid
216
+ *
217
+ * @param Exporter $exporter The class to check.
218
+ * @return bool
219
+ */
220
+ public function is_valid_exporter( $exporter ) {
221
+ if ( ! is_a( $exporter, 'WP_MainWP_Stream\Exporter' ) ) {
222
+ return false;
223
+ }
224
+
225
+ if ( ! method_exists( $exporter, 'is_dependency_satisfied' ) || ! $exporter->is_dependency_satisfied() ) {
226
+ return false;
227
+ }
228
+
229
+ return true;
230
+ }
231
+
232
+
233
+ /**
234
+ * Returns an array with all available exporters
235
+ *
236
+ * @return array
237
+ */
238
+ public function get_exporters() {
239
+ return $this->exporters;
240
+ }
241
+ }
classes/class-exporter.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ abstract class Exporter {
5
+ /**
6
+ * Exporter name
7
+ *
8
+ * @var string
9
+ */
10
+ public $name;
11
+
12
+ /**
13
+ * Exporter slug
14
+ *
15
+ * @var string
16
+ */
17
+ public $slug;
18
+
19
+ /**
20
+ * Output formatted data for download
21
+ *
22
+ * @param array $data Array of data to output.
23
+ * @param array $columns Column names included in data set.
24
+ * @return void
25
+ */
26
+ abstract public function output_file( $data, $columns );
27
+
28
+ /**
29
+ * Allow connectors to determine if their dependencies is satisfied or not
30
+ *
31
+ * @return bool
32
+ */
33
+ public function is_dependency_satisfied() {
34
+ return true;
35
+ }
36
+ }
includes/filter-input.php → classes/class-filter-input.php RENAMED
@@ -1,120 +1,116 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Filter_Input {
4
-
5
- public static $filter_callbacks = array(
6
- FILTER_DEFAULT => null,
7
- // Validate
8
- FILTER_VALIDATE_BOOLEAN => 'is_bool',
9
- FILTER_VALIDATE_EMAIL => 'is_email',
10
- FILTER_VALIDATE_FLOAT => 'is_float',
11
- FILTER_VALIDATE_INT => 'is_int',
12
- FILTER_VALIDATE_IP => array( 'MainWP_WP_Stream_Filter_Input', 'is_ip_address' ),
13
- FILTER_VALIDATE_REGEXP => array( 'MainWP_WP_Stream_Filter_Input', 'is_regex' ),
14
- FILTER_VALIDATE_URL => 'wp_http_validate_url',
15
- // Sanitize
16
- FILTER_SANITIZE_EMAIL => 'sanitize_email',
17
- FILTER_SANITIZE_ENCODED => 'esc_url_raw',
18
- FILTER_SANITIZE_NUMBER_FLOAT => 'floatval',
19
- FILTER_SANITIZE_NUMBER_INT => 'intval',
20
- FILTER_SANITIZE_SPECIAL_CHARS => 'htmlspecialchars',
21
- FILTER_SANITIZE_STRING => 'sanitize_text_field',
22
- FILTER_SANITIZE_URL => 'esc_url_raw',
23
- // Other
24
- FILTER_UNSAFE_RAW => null,
25
- );
26
-
27
- public static function super( $type, $variable_name, $filter = null, $options = array() ) {
28
- $super = null;
29
-
30
- switch ( $type ) {
31
- case INPUT_POST :
32
- $super = $_POST;
33
- break;
34
- case INPUT_GET :
35
- $super = $_GET;
36
- break;
37
- case INPUT_COOKIE :
38
- $super = $_COOKIE;
39
- break;
40
- case INPUT_ENV :
41
- $super = $_ENV;
42
- break;
43
- case INPUT_SERVER :
44
- $super = $_SERVER;
45
- break;
46
- }
47
-
48
- if ( is_null( $super ) ) {
49
- throw new Exception( __( 'Invalid use, type must be one of INPUT_* family.', 'mainwp-child-reports' ) );
50
- }
51
-
52
- $var = isset( $super[ $variable_name ] ) ? $super[ $variable_name ] : null;
53
- $var = self::filter( $var, $filter, $options );
54
-
55
- return $var;
56
- }
57
-
58
- public static function filter( $var, $filter = null, $options = array() ) {
59
- // Default filter is a sanitizer, not validator
60
- $filter_type = 'sanitizer';
61
-
62
- // Only filter value if it is not null
63
- if ( isset( $var ) && $filter && FILTER_DEFAULT !== $filter ) {
64
- if ( ! isset( self::$filter_callbacks[ $filter ] ) ) {
65
- throw new Exception( __( 'Filter not supported.', 'mainwp-child-reports' ) );
66
- }
67
-
68
- $filter_callback = self::$filter_callbacks[ $filter ];
69
- $result = call_user_func( $filter_callback, $var );
70
-
71
- $filter_type = ( $filter < 500 ) ? 'validator' : 'sanitizer';
72
- if ( 'validator' === $filter_type ) { // Validation functions
73
- if ( ! $result ) {
74
- $var = false;
75
- }
76
- } else { // Santization functions
77
- $var = $result;
78
- }
79
- }
80
-
81
- // Detect FILTER_REQUIRE_ARRAY flag
82
- if ( isset( $var ) && is_int( $options ) && FILTER_REQUIRE_ARRAY === $options ) {
83
- if ( ! is_array( $var ) ) {
84
- $var = ( 'validator' === $filter_type ) ? false : null;
85
- }
86
- }
87
-
88
- // Polyfill the `default` attribute only, for now.
89
- if ( is_array( $options ) && ! empty( $options['options']['default'] ) ) {
90
- if ( 'validator' === $filter_type && false === $var ) {
91
- $var = $options['options']['default'];
92
- } elseif ( 'sanitizer' === $filter_type && null === $var ) {
93
- $var = $options['options']['default'];
94
- }
95
- }
96
-
97
- return $var;
98
- }
99
-
100
- public static function is_regex( $var ) {
101
- // @codingStandardsIgnoreStart
102
- $test = @preg_match( $var, '' );
103
- // @codingStandardsIgnoreEnd
104
-
105
- return $test !== false;
106
- }
107
-
108
- public static function is_ip_address( $var ) {
109
- return false !== WP_Http::is_ip_address( $var );
110
- }
111
-
112
- }
113
-
114
- function mainwp_wp_stream_filter_input( $type, $variable_name, $filter = null, $options = array() ) {
115
- return call_user_func_array( array( 'MainWP_WP_Stream_Filter_Input', 'super' ), func_get_args() );
116
- }
117
-
118
- function mainwp_wp_stream_filter_var( $var, $filter = null, $options = array() ) {
119
- return call_user_func_array( array( 'MainWP_WP_Stream_Filter_Input', 'filter' ), func_get_args() );
120
- }
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Filter_Input {
5
+ public static $filter_callbacks = array(
6
+ FILTER_DEFAULT => null,
7
+ // Validate
8
+ FILTER_VALIDATE_BOOLEAN => 'is_bool',
9
+ FILTER_VALIDATE_EMAIL => 'is_email',
10
+ FILTER_VALIDATE_FLOAT => 'is_float',
11
+ FILTER_VALIDATE_INT => 'is_int',
12
+ FILTER_VALIDATE_IP => array( __CLASS__, 'is_ip_address' ),
13
+ FILTER_VALIDATE_REGEXP => array( __CLASS__, 'is_regex' ),
14
+ FILTER_VALIDATE_URL => 'wp_http_validate_url',
15
+ // Sanitize
16
+ FILTER_SANITIZE_EMAIL => 'sanitize_email',
17
+ FILTER_SANITIZE_ENCODED => 'esc_url_raw',
18
+ FILTER_SANITIZE_NUMBER_FLOAT => 'floatval',
19
+ FILTER_SANITIZE_NUMBER_INT => 'intval',
20
+ FILTER_SANITIZE_SPECIAL_CHARS => 'htmlspecialchars',
21
+ FILTER_SANITIZE_STRING => 'sanitize_text_field',
22
+ FILTER_SANITIZE_URL => 'esc_url_raw',
23
+ // Other
24
+ FILTER_UNSAFE_RAW => null,
25
+ );
26
+
27
+ public static function super( $type, $variable_name, $filter = null, $options = array() ) {
28
+ $super = null;
29
+
30
+ // @codingStandardsIgnoreStart
31
+ switch ( $type ) {
32
+ case INPUT_POST :
33
+ $super = $_POST;
34
+ break;
35
+ case INPUT_GET :
36
+ $super = $_GET;
37
+ break;
38
+ case INPUT_COOKIE :
39
+ $super = $_COOKIE;
40
+ break;
41
+ case INPUT_ENV :
42
+ $super = $_ENV;
43
+ break;
44
+ case INPUT_SERVER :
45
+ $super = $_SERVER;
46
+ break;
47
+ }
48
+ // @codingStandardsIgnoreEnd
49
+
50
+ if ( is_null( $super ) ) {
51
+ throw new \Exception( esc_html__( 'Invalid use, type must be one of INPUT_* family.', 'mainwp-child-reports' ) );
52
+ }
53
+
54
+ $var = isset( $super[ $variable_name ] ) ? $super[ $variable_name ] : null;
55
+ $var = self::filter( $var, $filter, $options );
56
+
57
+ return $var;
58
+ }
59
+
60
+ public static function filter( $var, $filter = null, $options = array() ) {
61
+ // Default filter is a sanitizer, not validator
62
+ $filter_type = 'sanitizer';
63
+
64
+ // Only filter value if it is not null
65
+ if ( isset( $var ) && $filter && FILTER_DEFAULT !== $filter ) {
66
+ if ( ! isset( self::$filter_callbacks[ $filter ] ) ) {
67
+ throw new \Exception( esc_html__( 'Filter not supported.', 'mainwp-child-reports' ) );
68
+ }
69
+
70
+ $filter_callback = self::$filter_callbacks[ $filter ];
71
+ $result = call_user_func( $filter_callback, $var );
72
+
73
+ // filter_var / filter_input treats validation/sanitization filters the same
74
+ // they both return output and change the var value, this shouldn't be the case here.
75
+ // We'll do a boolean check on validation function, and let sanitizers change the value
76
+ $filter_type = ( $filter < 500 ) ? 'validator' : 'sanitizer';
77
+ if ( 'validator' === $filter_type ) { // Validation functions
78
+ if ( ! $result ) {
79
+ $var = false;
80
+ }
81
+ } else { // Santization functions
82
+ $var = $result;
83
+ }
84
+ }
85
+
86
+ // Detect FILTER_REQUIRE_ARRAY flag
87
+ if ( isset( $var ) && is_int( $options ) && FILTER_REQUIRE_ARRAY === $options ) {
88
+ if ( ! is_array( $var ) ) {
89
+ $var = ( 'validator' === $filter_type ) ? false : null;
90
+ }
91
+ }
92
+
93
+ // Polyfill the `default` attribute only, for now.
94
+ if ( is_array( $options ) && ! empty( $options['options']['default'] ) ) {
95
+ if ( 'validator' === $filter_type && false === $var ) {
96
+ $var = $options['options']['default'];
97
+ } elseif ( 'sanitizer' === $filter_type && null === $var ) {
98
+ $var = $options['options']['default'];
99
+ }
100
+ }
101
+
102
+ return $var;
103
+ }
104
+
105
+ public static function is_regex( $var ) {
106
+ // @codingStandardsIgnoreStart
107
+ $test = @preg_match( $var, '' );
108
+ // @codingStandardsIgnoreEnd
109
+
110
+ return false !== $test;
111
+ }
112
+
113
+ public static function is_ip_address( $var ) {
114
+ return false !== \WP_Http::is_ip_address( $var );
115
+ }
116
+ }
 
 
 
 
classes/class-form-generator.php ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Form_Generator {
5
+
6
+ /**
7
+ * List of all registered fields.
8
+ *
9
+ * @var array
10
+ */
11
+ public $fields = array();
12
+
13
+ /**
14
+ * Adds a new field to the form.
15
+ *
16
+ * @param string $field_type The type of field being added.
17
+ * @param array $args Options for the field. See render_field().
18
+ * @return void
19
+ */
20
+ public function add_field( $field_type, $args ) {
21
+ $this->fields[] = array(
22
+ 'type' => $field_type,
23
+ 'args' => $args,
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Renders all fields currently registered.
29
+ *
30
+ * @return string
31
+ */
32
+ public function render_fields() {
33
+ $output = '';
34
+ foreach ( $this->fields as $data ) {
35
+ $output .= $this->render_field( $data['type'], $data['args'] );
36
+ }
37
+ return $output;
38
+ }
39
+
40
+ /**
41
+ * Renders all fields currently registered as a table.
42
+ *
43
+ * @return string
44
+ */
45
+ public function render_fields_table() {
46
+ $output = '<table class="form-table">';
47
+ foreach ( $this->fields as $data ) {
48
+ $title = ( array_key_exists( 'title', $data['args'] ) ) ? $data['args']['title'] : '';
49
+
50
+ $output .= '<tr><th>' . $title . '</th><td>';
51
+ $output .= $this->render_field( $data['type'], $data['args'] );
52
+ $output .= '</td><tr>';
53
+ }
54
+ $output .= '</table>';
55
+ return $output;
56
+ }
57
+
58
+ /**
59
+ * Renders a single field.
60
+ *
61
+ * @param string $field_type The type of field being rendered.
62
+ * @param array $args The options for the field type.
63
+ *
64
+ * @return string
65
+ */
66
+ public function render_field( $field_type, $args ) {
67
+ $args = wp_parse_args(
68
+ $args, array(
69
+ 'name' => '',
70
+ 'value' => '',
71
+ 'options' => array(),
72
+ 'description' => '',
73
+ 'classes' => '',
74
+ 'data' => array(),
75
+ 'multiple' => false,
76
+ )
77
+ );
78
+
79
+ $output = '';
80
+ switch ( $field_type ) {
81
+ case 'text':
82
+ $output = sprintf(
83
+ '<input type="text" name="%1$s" id="%1$s" class="%2$s" value="%3$s" />',
84
+ esc_attr( $args['name'] ),
85
+ esc_attr( $args['classes'] ),
86
+ esc_attr( $args['value'] )
87
+ );
88
+ break;
89
+ case 'hidden':
90
+ $output = sprintf(
91
+ '<input type="hidden" name="%1$s" id="%1$s" class="%2$s" value="%3$s" />',
92
+ esc_attr( $args['name'] ),
93
+ esc_attr( $args['classes'] ),
94
+ esc_attr( $args['value'] )
95
+ );
96
+ break;
97
+ case 'select':
98
+ $current_value = $args['value'];
99
+
100
+ $output = sprintf(
101
+ '<select name="%1$s" class="%2$s" id="%1$s">',
102
+ esc_attr( $args['name'] ),
103
+ esc_attr( $args['classes'] )
104
+ );
105
+
106
+ foreach ( $args['options'] as $value => $label ) {
107
+ $output .= sprintf(
108
+ '<option value="%1$s" %2$s>%3$s</option>',
109
+ esc_attr( $value ),
110
+ selected( $value === $current_value, true, false ),
111
+ esc_html( $label )
112
+ );
113
+ }
114
+ $output .= '</select>';
115
+ break;
116
+ case 'select2':
117
+ $values = array();
118
+
119
+ $multiple = ( $args['multiple'] ) ? 'multiple ' : '';
120
+ $output = sprintf(
121
+ '<select name="%1$s" id="%1$s" class="select2-select %2$s" %3$s%4$s>',
122
+ esc_attr( $args['name'] ),
123
+ esc_attr( $args['classes'] ),
124
+ $this->prepare_data_attributes_string( $args['data'] ),
125
+ $multiple
126
+ );
127
+
128
+ if ( array_key_exists( 'placeholder', $args['data'] ) && ! $multiple ) {
129
+ $output .= '<option value=""></option>';
130
+ }
131
+
132
+ foreach ( $args['options'] as $parent ) {
133
+ $parent = wp_parse_args(
134
+ $parent, array(
135
+ 'value' => '',
136
+ 'text' => '',
137
+ 'children' => array(),
138
+ )
139
+ );
140
+ if ( empty( $parent['value'] ) ) {
141
+ continue;
142
+ }
143
+ if ( is_array( $args['value'] ) ) {
144
+ $selected = selected( in_array( $parent['value'], $args['value'], true ), true, false );
145
+ } else {
146
+ $selected = selected( $args['value'], $parent['value'], false );
147
+ }
148
+ $output .= sprintf(
149
+ '<option class="parent" value="%1$s" %3$s>%2$s</option>',
150
+ $parent['value'],
151
+ $parent['text'],
152
+ $selected
153
+ );
154
+ $values[] = $parent['value'];
155
+ if ( ! empty( $parent['children'] ) ) {
156
+ foreach ( $parent['children'] as $child ) {
157
+ $output .= sprintf(
158
+ '<option class="child" value="%1$s" %3$s>%2$s</option>',
159
+ $child['value'],
160
+ $child['text'],
161
+ selected( $args['value'], $child['value'], false )
162
+ );
163
+ $values[] = $child['value'];
164
+ }
165
+ $output .= '</optgroup>';
166
+ }
167
+ }
168
+
169
+ $selected_values = explode( ',', $args['value'] );
170
+ foreach ( $selected_values as $selected_value ) {
171
+ if ( ! empty( $selected_value ) && ! in_array( $selected_value, array_map( 'strval', $values ), true ) ) {
172
+ $output .= sprintf(
173
+ '<option value="%1$s" %2$s>%1$s</option>',
174
+ $selected_value,
175
+ selected( true, true, false )
176
+ );
177
+ }
178
+ }
179
+
180
+ $output .= '</select>';
181
+ break;
182
+ case 'checkbox':
183
+ $output = sprintf(
184
+ '<input type="checkbox" name="%1$s" id="%1$s" value="1" %3$s>%2$s',
185
+ $args['name'],
186
+ $args['text'],
187
+ checked( $args['value'], true, false )
188
+ );
189
+ break;
190
+ default:
191
+ $output = apply_filters( 'wp_mainwp_stream_form_render_field', $output, $field_type, $args );
192
+ break;
193
+ }
194
+
195
+ $output .= ! empty( $args['description'] ) ? sprintf( '<p class="description">%s</p>', $args['description'] ) : null;
196
+
197
+ return $output;
198
+ }
199
+
200
+ /**
201
+ * Prepares string with HTML data attributes
202
+ *
203
+ * @param array $data List of key/value data pairs to prepare.
204
+ * @return string
205
+ */
206
+ public function prepare_data_attributes_string( $data ) {
207
+ $output = '';
208
+ foreach ( $data as $key => $value ) {
209
+ $key = 'data-' . esc_attr( $key );
210
+ $output .= $key . '="' . esc_attr( $value ) . '" ';
211
+ }
212
+ return $output;
213
+ }
214
+ }
classes/class-install.php ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Install {
5
+ /**
6
+ * Hold Plugin class
7
+ * @var Plugin
8
+ */
9
+ public $plugin;
10
+
11
+ /**
12
+ * Option key to store database version
13
+ *
14
+ * @var string
15
+ */
16
+ public $option_key = 'wp_mainwp_stream_db';
17
+
18
+ /**
19
+ * Holds version of database at last update
20
+ *
21
+ * @var string
22
+ */
23
+ public $db_version;
24
+
25
+ /**
26
+ * URL to the Stream Admin settings page.
27
+ *
28
+ * @var string
29
+ */
30
+ public $stream_url;
31
+
32
+ /**
33
+ * Array of version numbers that require database update
34
+ *
35
+ * @var array
36
+ */
37
+ public $update_versions;
38
+
39
+ /**
40
+ * Holds status of whether it's safe to run Stream or not
41
+ *
42
+ * @var bool
43
+ */
44
+ public $update_required = false;
45
+
46
+ /**
47
+ * Holds status of whether the database update worked
48
+ *
49
+ * @var bool
50
+ */
51
+ public $success_db;
52
+
53
+ /**
54
+ * Class constructor
55
+ */
56
+ public function __construct( $plugin ) {
57
+ $this->plugin = $plugin;
58
+
59
+ $this->db_version = $this->get_db_version();
60
+ $this->stream_url = self_admin_url( $this->plugin->admin->admin_parent_page . '&page=' . $this->plugin->admin->settings_page_slug );
61
+
62
+ // Check DB and display an admin notice if there are tables missing
63
+ add_action( 'init', array( $this, 'verify_db' ) );
64
+
65
+ // Install the plugin
66
+ add_action( 'wp_mainwp_stream_before_db_notices', array( $this, 'check' ) );
67
+
68
+ register_activation_hook( $this->plugin->locations['plugin'], array( $this, 'check' ) );
69
+ }
70
+
71
+ /**
72
+ * Check db version, create/update table schema accordingly
73
+ * If database update required admin notice will be given
74
+ * on the plugin update screen
75
+ *
76
+ * @return void
77
+ */
78
+ public function check() {
79
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
80
+ return;
81
+ }
82
+
83
+ $update_to_new_child_report = false;
84
+
85
+ if ( empty( $this->db_version ) ) {
86
+ $this->install( $this->plugin->get_version() );
87
+ if ( !empty( $this->get_old_child_report_db_version() ) ) {
88
+ $update_to_new_child_report = true;
89
+ } else {
90
+ return;
91
+ }
92
+ }
93
+
94
+ if ( ! $update_to_new_child_report && $this->plugin->get_version() === $this->db_version ) {
95
+ return;
96
+ }
97
+
98
+ $update = null;
99
+ if ( isset( $_REQUEST['wp_mainwp_stream_update'] ) && wp_verify_nonce( 'wp_mainwp_stream_update_db' ) ) {
100
+ $update = esc_attr( $_REQUEST['wp_mainwp_stream_update'] );
101
+ }
102
+
103
+ if ( ! $update ) {
104
+ $this->update_required = true;
105
+ $this->success_db = $this->update(
106
+ $this->db_version, $this->plugin->get_version(), array(
107
+ 'type' => 'auto',
108
+ )
109
+ );
110
+ }
111
+
112
+ if ( 'update_and_continue' === $update ) {
113
+ $this->success_db = $this->update(
114
+ $this->db_version, $this->plugin->get_version(), array(
115
+ 'type' => 'user',
116
+ )
117
+ );
118
+ }
119
+
120
+ $versions = $this->db_update_versions();
121
+
122
+ if ( ! $this->success_db && version_compare( end( $versions ), $this->db_version, '>' ) ) {
123
+ add_action( 'all_admin_notices', array( $this, 'update_notice_hook' ) );
124
+ return;
125
+ }
126
+
127
+ $this->update_db_option();
128
+ }
129
+
130
+ /**
131
+ * Verify that the required DB tables exists
132
+ *
133
+ * @return void
134
+ */
135
+ public function verify_db() {
136
+ /**
137
+ * Filter will halt install() if set to true
138
+ *
139
+ * @param bool
140
+ *
141
+ * @return bool
142
+ */
143
+ if ( apply_filters( 'wp_mainwp_stream_no_tables', false ) ) {
144
+ return;
145
+ }
146
+
147
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
148
+ require_once ABSPATH . '/wp-admin/includes/plugin.php';
149
+ }
150
+
151
+ /**
152
+ * Fires before admin notices are triggered for missing database tables.
153
+ */
154
+ do_action( 'wp_mainwp_stream_before_db_notices' );
155
+
156
+ global $wpdb;
157
+
158
+ $database_message = '';
159
+ $uninstall_message = '';
160
+
161
+ // Check if all needed DB is present
162
+ $missing_tables = array();
163
+
164
+ foreach ( $this->plugin->db->get_table_names() as $table_name ) {
165
+ $table_search = $wpdb->get_var(
166
+ $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name )
167
+ );
168
+ if ( $table_search !== $table_name ) {
169
+ $missing_tables[] = $table_name;
170
+ }
171
+ }
172
+
173
+ if ( $missing_tables ) {
174
+ $database_message .= sprintf(
175
+ '%s <strong>%s</strong>',
176
+ _n(
177
+ 'The following table is not present in the WordPress database:',
178
+ 'The following tables are not present in the WordPress database:',
179
+ count( $missing_tables ),
180
+ 'mainwp-child-reports'
181
+ ),
182
+ esc_html( implode( ', ', $missing_tables ) )
183
+ );
184
+ }
185
+
186
+ if ( is_plugin_active_for_network( $this->plugin->locations['plugin'] ) && current_user_can( 'manage_network_plugins' ) ) {
187
+ $uninstall_message = sprintf(
188
+ // translators: Placeholders refer to HTML Link tags (e.g. "<a href="https://foo.com/wp-admin/">")
189
+ __( 'Please %1$suninstall%2$s the Reports plugin and activate it again.', 'mainwp-child-reports' ),
190
+ '<a href="' . network_admin_url( 'plugins.php#stream' ) . '">',
191
+ '</a>'
192
+ );
193
+ } elseif ( current_user_can( 'activate_plugins' ) ) {
194
+ $uninstall_message = sprintf(
195
+ // translators: Placeholders refer to HTML Link tags (e.g. "<a href="https://foo.com/wp-admin/">")
196
+ __( 'Please %1$suninstall%2$s the Reports plugin and activate it again.', 'mainwp-child-reports' ),
197
+ '<a href="' . admin_url( 'plugins.php#stream' ) . '">',
198
+ '</a>'
199
+ );
200
+ }
201
+
202
+ if ( ! empty( $database_message ) ) {
203
+ $this->plugin->admin->notice( $database_message );
204
+
205
+ if ( ! empty( $uninstall_message ) ) {
206
+ $this->plugin->admin->notice( $uninstall_message );
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Register a routine to be called when stream or a stream connector has been updated
213
+ * It works by comparing the current version with the version previously stored in the database.
214
+ *
215
+ * @param string $file A reference to the main plugin file
216
+ * @param string $callback The function to run when the hook is called.
217
+ * @param string $version The version to which the plugin is updating.
218
+ *
219
+ * @return void
220
+ */
221
+ public function register_update_hook( $file, $callback, $version ) {
222
+ if ( ! is_admin() ) {
223
+ return;
224
+ }
225
+
226
+ $plugin = plugin_basename( $file );
227
+
228
+ if ( is_plugin_active_for_network( $plugin ) ) {
229
+ $current_versions = get_site_option( $this->option_key . '_connectors', array() );
230
+ $network = true;
231
+ } elseif ( is_plugin_active( $plugin ) ) {
232
+ $current_versions = get_option( $this->option_key . '_connectors', array() );
233
+ $network = false;
234
+ } else {
235
+ return;
236
+ }
237
+
238
+ if ( version_compare( $version, $current_versions[ $plugin ], '>' ) ) {
239
+ call_user_func( $callback, $current_versions[ $plugin ], $network );
240
+
241
+ $current_versions[ $plugin ] = $version;
242
+ }
243
+
244
+ if ( $network ) {
245
+ update_site_option( $this->option_key . '_registered_connectors', $current_versions );
246
+ } else {
247
+ update_option( $this->option_key . '_registered_connectors', $current_versions );
248
+ }
249
+ }
250
+
251
+
252
+ public static function get_old_child_report_db_version() {
253
+
254
+ $version = get_site_option( 'mainwp_child_reports_db' );
255
+
256
+ return $version;
257
+ }
258
+
259
+ /**
260
+ * @return string
261
+ */
262
+ public function get_db_version() {
263
+ return get_site_option( $this->option_key );
264
+ }
265
+
266
+ /**
267
+ * @return void
268
+ */
269
+ public function update_db_option() {
270
+ if ( $this->success_db ) {
271
+ $success_op = update_site_option( $this->option_key, $this->plugin->get_version() );
272
+ }
273
+
274
+ if ( ! empty( $this->success_db ) ) {
275
+ return;
276
+ }
277
+
278
+ wp_die(
279
+ esc_html__( 'There was an error updating the Reports database. Please try again.', 'mainwp-child-reports' ),
280
+ esc_html__( 'Database Update Error', 'mainwp-child-reports' ),
281
+ array(
282
+ 'response' => 200,
283
+ 'back_link' => 1,
284
+ )
285
+ );
286
+ }
287
+
288
+ /**
289
+ * Added to the admin_notices hook when file plugin version is higher than database plugin version
290
+ *
291
+ * @action admin_notices
292
+ *
293
+ * @return void
294
+ */
295
+ public function update_notice_hook() {
296
+ if ( ! current_user_can( $this->plugin->admin->view_cap ) ) {
297
+ return;
298
+ }
299
+
300
+ $update = null;
301
+ if ( isset( $_REQUEST['wp_mainwp_stream_update'] ) && wp_verify_nonce( 'wp_mainwp_stream_update_db' ) ) {
302
+ $update = esc_attr( $_REQUEST['wp_mainwp_stream_update'] );
303
+ }
304
+
305
+ if ( ! $update ) {
306
+ $this->prompt_update();
307
+
308
+ return;
309
+ }
310
+
311
+ if ( 'update_and_continue' === $update ) {
312
+ $this->prompt_update_status();
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Action hook callback function
318
+ *
319
+ * Adds the user controlled database upgrade routine to the plugins updated page.
320
+ * When database update is complete page will refresh with dismissible message to user.
321
+ *
322
+ * @return void
323
+ */
324
+ public function prompt_update() {
325
+ ?>
326
+ <div class="error">
327
+ <form method="post" action="<?php echo esc_url( remove_query_arg( 'wp_mainwp_stream_update' ) ); ?>">
328
+ <?php wp_nonce_field( 'wp_mainwp_stream_update_db' ); ?>
329
+ <input type="hidden" name="wp_mainwp_stream_update" value="update_and_continue"/>
330
+ <p><strong><?php esc_html_e( 'Reports Database Update Required', 'mainwp-child-reports' ); ?></strong></p>
331
+ <p><?php esc_html_e( 'Reports has updated! Before we send you on your way, we need to update your database to the newest version.', 'mainwp-child-reports' ); ?></p>
332
+ <p><?php esc_html_e( 'This process could take a little while, so please be patient.', 'mainwp-child-reports' ); ?></p>
333
+ <?php submit_button( esc_html__( 'Update Database', 'mainwp-child-reports' ), 'primary', 'stream-update-db-submit' ); ?>
334
+ </form>
335
+ </div>
336
+ <?php
337
+ }
338
+
339
+ /**
340
+ * When user initiates a database update this function calls the update methods, checks for success
341
+ * updates the stream_db version number in the database and outputs a success and continue message
342
+ *
343
+ * @return void
344
+ */
345
+ public function prompt_update_status() {
346
+ check_admin_referer( 'wp_mainwp_stream_update_db' );
347
+
348
+ $this->update_db_option();
349
+ ?>
350
+ <div class="updated">
351
+ <form method="post" action="<?php echo esc_url( remove_query_arg( 'wp_mainwp_stream_update' ) ); ?>" style="display:inline;">
352
+ <p><strong><?php esc_html_e( 'Update Complete', 'mainwp-child-reports' ); ?></strong></p>
353
+ <p>
354
+ <?php
355
+ printf(
356
+ // translators: Placeholders refer to version numbers (e.g. "4.2")
357
+ esc_html__( 'Your Reports database has been successfully updated from %1$s to %2$s!', 'mainwp-child-reports' ),
358
+ esc_html( $this->db_version ),
359
+ esc_html( $this->plugin->get_version() )
360
+ );
361
+ ?>
362
+ </p>
363
+ <?php submit_button( esc_html__( 'Continue', 'mainwp-child-reports' ), 'secondary', false ); ?>
364
+ </form>
365
+ </div>
366
+ <?php
367
+ }
368
+
369
+ /**
370
+ * Array of database versions that require and updates
371
+ *
372
+ * To add your own stream extension database update routine
373
+ * use the filter and return the version that requires an update
374
+ * You must also make the callback function available in the global namespace on plugins loaded
375
+ * use the wp_mainwp_stream_update_{version_number} version number must be a string of characters that represent the version with no periods
376
+ *
377
+ * @return array
378
+ */
379
+ public function db_update_versions() {
380
+ $db_update_versions = array(
381
+ '3.0.0', /* @version 3.0.0 Drop the stream_context table, changes to stream table */
382
+ '3.0.2', /* @version 3.0.2 Fix uppercase values in stream table, connector column */
383
+ '3.0.8', /* @version 3.0.8 Increase size of user role IDs, user_roll column */
384
+ );
385
+
386
+ /**
387
+ * Filter to alter the DB update versions array
388
+ *
389
+ * @param array $db_update_versions
390
+ *
391
+ * @return array
392
+ */
393
+ return apply_filters( 'wp_mainwp_stream_db_update_versions', $db_update_versions );
394
+ }
395
+
396
+ /**
397
+ * Database user controlled update routine
398
+ *
399
+ * @param int $db_version
400
+ * @param int $current_version
401
+ * @param array $update_args
402
+ *
403
+ * @return mixed Version number on success, true on no update needed, mysql error message on error
404
+ */
405
+ public function update( $db_version, $current_version, $update_args ) {
406
+ $versions = $this->db_update_versions();
407
+ include_once $this->plugin->locations['inc_dir'] . 'db-updates.php';
408
+
409
+ foreach ( $versions as $version ) {
410
+ if ( ! isset( $update_args['type'] ) ) {
411
+ $update_args['type'] = 'user';
412
+ }
413
+
414
+ $function = 'wp_mainwp_stream_update_' . ( 'user' === $update_args['type'] ? '' : $update_args['type'] . '_' ) . str_ireplace( '.', '', $version );
415
+
416
+ if ( version_compare( $db_version, $version, '<' ) ) {
417
+ $result = function_exists( $function ) ? call_user_func( $function, $db_version, $current_version ) : $current_version;
418
+
419
+ if ( $current_version !== $result ) {
420
+ return false;
421
+ }
422
+ }
423
+ }
424
+
425
+ return $current_version;
426
+ }
427
+
428
+ /**
429
+ * Initial database install routine
430
+ *
431
+ * @param string $current_version
432
+ *
433
+ * @return string
434
+ */
435
+ public function install( $current_version ) {
436
+ global $wpdb;
437
+
438
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
439
+
440
+ $sql = "CREATE TABLE {$wpdb->base_prefix}mainwp_stream (
441
+ ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
442
+ site_id bigint(20) unsigned NOT NULL DEFAULT '1',
443
+ blog_id bigint(20) unsigned NOT NULL DEFAULT '1',
444
+ object_id bigint(20) unsigned NULL,
445
+ user_id bigint(20) unsigned NOT NULL DEFAULT '0',
446
+ user_role varchar(50) NOT NULL DEFAULT '',
447
+ summary longtext NOT NULL,
448
+ created datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
449
+ connector varchar(100) NOT NULL,
450
+ context varchar(100) NOT NULL,
451
+ action varchar(100) NOT NULL,
452
+ ip varchar(39) NULL,
453
+ PRIMARY KEY (ID),
454
+ KEY site_id (site_id),
455
+ KEY blog_id (blog_id),
456
+ KEY object_id (object_id),
457
+ KEY user_id (user_id),
458
+ KEY created (created),
459
+ KEY connector (connector),
460
+ KEY context (context),
461
+ KEY action (action)
462
+ )";
463
+
464
+ if ( ! empty( $wpdb->charset ) ) {
465
+ $sql .= " CHARACTER SET $wpdb->charset";
466
+ }
467
+
468
+ if ( ! empty( $wpdb->collate ) ) {
469
+ $sql .= " COLLATE $wpdb->collate";
470
+ }
471
+
472
+ $sql .= ';';
473
+
474
+ \dbDelta( $sql );
475
+
476
+ if ( ! empty( $wpdb->charset ) ) {
477
+ $sql .= " CHARACTER SET $wpdb->charset";
478
+ }
479
+
480
+ if ( ! empty( $wpdb->collate ) ) {
481
+ $sql .= " COLLATE $wpdb->collate";
482
+ }
483
+
484
+ $sql .= ';';
485
+
486
+ \dbDelta( $sql );
487
+
488
+ $sql = "CREATE TABLE {$wpdb->base_prefix}mainwp_stream_meta (
489
+ meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
490
+ record_id bigint(20) unsigned NOT NULL,
491
+ meta_key varchar(200) NOT NULL,
492
+ meta_value varchar(255) NOT NULL,
493
+ PRIMARY KEY (meta_id),
494
+ KEY record_id (record_id),
495
+ KEY meta_key (meta_key(191)),
496
+ KEY meta_value (meta_value(191))
497
+ )";
498
+
499
+ if ( ! empty( $wpdb->charset ) ) {
500
+ $sql .= " CHARACTER SET $wpdb->charset";
501
+ }
502
+
503
+ if ( ! empty( $wpdb->collate ) ) {
504
+ $sql .= " COLLATE $wpdb->collate";
505
+ }
506
+
507
+ $sql .= ';';
508
+
509
+ \dbDelta( $sql );
510
+
511
+ update_site_option( $this->option_key, $this->plugin->get_version() );
512
+
513
+ return $current_version;
514
+ }
515
+ }
classes/class-list-table.php ADDED
@@ -0,0 +1,1077 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ class List_Table extends \WP_List_Table {
6
+
7
+ /**
8
+ * Hold Plugin class
9
+ *
10
+ * @var Plugin
11
+ */
12
+ public $plugin;
13
+
14
+ /**
15
+ * Class constructor.
16
+ *
17
+ * @param Plugin $plugin The main Plugin class.
18
+ * @param array $args
19
+ */
20
+ public function __construct( $plugin, $args = array() ) {
21
+ $this->plugin = $plugin;
22
+
23
+ $screen_id = isset( $args['screen'] ) ? $args['screen'] : null;
24
+
25
+ /**
26
+ * Filter the list table screen ID
27
+ *
28
+ * @return string
29
+ */
30
+ $screen_id = apply_filters( 'wp_mainwp_stream_list_table_screen_id', $screen_id );
31
+
32
+ parent::__construct(
33
+ array(
34
+ 'post_type' => 'mainwp-child-reports',
35
+ 'plural' => 'records',
36
+ 'screen' => $screen_id,
37
+ )
38
+ );
39
+
40
+ add_screen_option(
41
+ 'per_page',
42
+ array(
43
+ 'default' => 20,
44
+ 'label' => __( 'Records per page', 'mainwp-child-reports' ),
45
+ 'option' => 'edit_mainwp_stream_per_page',
46
+ )
47
+ );
48
+
49
+ // Check for default hidden columns
50
+ $this->get_hidden_columns();
51
+
52
+ add_filter( 'screen_settings', array(
53
+ $this,
54
+ 'screen_controls',
55
+ ), 10, 2 );
56
+ add_filter( 'set-screen-option', array(
57
+ $this,
58
+ 'set_screen_option',
59
+ ), 10, 3 );
60
+
61
+ set_screen_options();
62
+ }
63
+
64
+ public function extra_tablenav( $which ) {
65
+ if ( 'top' === $which ) {
66
+ echo $this->filters_form(); // xss ok
67
+ }
68
+ }
69
+
70
+ public function no_items() {
71
+ ?>
72
+ <div class="stream-list-table-no-items">
73
+ <p><?php esc_html_e( 'Sorry, no activity records were found.', 'mainwp-child-reports' ); ?></p>
74
+ </div>
75
+ <?php
76
+ }
77
+
78
+ public function get_columns() {
79
+ /**
80
+ * Allows devs to add new columns to table
81
+ *
82
+ * @return array
83
+ */
84
+ return apply_filters(
85
+ 'wp_mainwp_stream_list_table_columns',
86
+ array(
87
+ 'date' => __( 'Date', 'mainwp-child-reports' ),
88
+ 'summary' => __( 'Summary', 'mainwp-child-reports' ),
89
+ 'user_id' => __( 'User', 'mainwp-child-reports' ),
90
+ 'context' => __( 'Context', 'mainwp-child-reports' ),
91
+ 'action' => __( 'Action', 'mainwp-child-reports' ),
92
+ 'ip' => __( 'IP Address', 'mainwp-child-reports' ),
93
+ )
94
+ );
95
+ }
96
+
97
+ public function get_sortable_columns() {
98
+ return array(
99
+ 'date' => array( 'date', false ),
100
+ );
101
+ }
102
+
103
+ public function get_hidden_columns() {
104
+ $user = wp_get_current_user();
105
+ if ( ! $user ) {
106
+ return array();
107
+ }
108
+ // Directly checking the user meta; to check whether user has changed screen option or not
109
+ $hidden = $this->plugin->admin->get_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', true );
110
+
111
+ // If user meta is not found; add the default hidden column 'id'
112
+ if ( ! $hidden ) {
113
+ $hidden = array( 'id' );
114
+ $this->plugin->admin->update_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', $hidden );
115
+ }
116
+
117
+ return $hidden;
118
+ }
119
+
120
+ public function prepare_items() {
121
+ $columns = $this->get_columns();
122
+ $sortable = $this->get_sortable_columns();
123
+ $hidden = $this->get_hidden_columns();
124
+ $primary = $columns['summary'];
125
+
126
+ $this->_column_headers = array(
127
+ $columns,
128
+ $hidden,
129
+ $sortable,
130
+ $primary,
131
+ );
132
+
133
+ $this->items = $this->get_records();
134
+
135
+ $total_items = $this->get_total_found_rows();
136
+
137
+ $this->set_pagination_args(
138
+ array(
139
+ 'total_items' => $total_items,
140
+ 'per_page' => $this->get_items_per_page( 'edit_mainwp_stream_per_page', 20 ),
141
+ )
142
+ );
143
+ }
144
+
145
+ public function get_records() {
146
+ $args = array();
147
+
148
+ // Parse sorting params
149
+ $order = wp_mainwp_stream_filter_input( INPUT_GET, 'order' );
150
+ if ( $order ) {
151
+ $args['order'] = $order;
152
+ }
153
+
154
+ $orderby = wp_mainwp_stream_filter_input( INPUT_GET, 'orderby' );
155
+ if ( $orderby ) {
156
+ $args['orderby'] = $orderby;
157
+ }
158
+
159
+ $params = array(
160
+ 'search',
161
+ 'date',
162
+ 'date_from',
163
+ 'date_to',
164
+ 'date_after',
165
+ 'date_before',
166
+ );
167
+
168
+ foreach ( $params as $param ) {
169
+ $value = wp_mainwp_stream_filter_input( INPUT_GET, $param );
170
+
171
+ if ( $value ) {
172
+ $args[ $param ] = $value;
173
+ }
174
+ }
175
+
176
+ // Additional filter properties
177
+ $properties = array(
178
+ 'record',
179
+ 'site_id',
180
+ 'blog_id',
181
+ 'object_id',
182
+ 'user_id',
183
+ 'user_role',
184
+ 'ip',
185
+ 'connector',
186
+ 'context',
187
+ 'action',
188
+ );
189
+
190
+ // Add property fields to defaults, including their __in/__not_in variations
191
+ foreach ( $properties as $property ) {
192
+ $value = wp_mainwp_stream_filter_input( INPUT_GET, $property );
193
+
194
+ // Allow 0 values
195
+ if ( isset( $value ) && '' !== $value && false !== $value ) {
196
+ $args[ $property ] = $value;
197
+ }
198
+
199
+ $value_in = wp_mainwp_stream_filter_input( INPUT_GET, $property . '__in' );
200
+
201
+ if ( $value_in ) {
202
+ $args[ $property . '__in' ] = explode( ',', $value_in );
203
+ }
204
+
205
+ $value_not_in = wp_mainwp_stream_filter_input( INPUT_GET, $property . '__not_in' );
206
+
207
+ if ( $value_not_in ) {
208
+ $args[ $property . '__not_in' ] = explode( ',', $value_not_in );
209
+ }
210
+ }
211
+
212
+ $args['paged'] = $this->get_pagenum();
213
+
214
+ if ( isset( $args['context'] ) && 0 === strpos( $args['context'], 'group-' ) ) {
215
+ $args['connector'] = str_replace( 'group-', '', $args['context'] );
216
+ $args['context'] = '';
217
+ }
218
+
219
+ if ( ! isset( $args['records_per_page'] ) ) {
220
+ $args['records_per_page'] = $this->get_items_per_page( 'edit_mainwp_stream_per_page', 20 );
221
+ }
222
+ $args['records_per_page'] = apply_filters( 'mainwp_stream_records_per_page', $args['records_per_page'] );
223
+
224
+ $items = $this->plugin->db->get_records( $args );
225
+
226
+ return $items;
227
+ }
228
+
229
+ /**
230
+ * Get last query found rows
231
+ *
232
+ * @return integer
233
+ */
234
+ public function get_total_found_rows() {
235
+ return $this->plugin->db->get_found_records_count();
236
+ }
237
+
238
+ public function column_default( $item, $column_name ) {
239
+ $out = '';
240
+ $record = new Record( $item );
241
+
242
+ switch ( $column_name ) {
243
+ case 'date':
244
+ $created = date( 'Y-m-d H:i:s', strtotime( $record->created ) );
245
+ $date_string = sprintf(
246
+ '<time datetime="%s" class="relative-time record-created">%s</time>',
247
+ wp_mainwp_stream_get_iso_8601_extended_date( strtotime( $record->created ) ),
248
+ get_date_from_gmt( $created, 'Y/m/d' )
249
+ );
250
+ $out = $this->column_link( $date_string, 'date', get_date_from_gmt( $created, 'Y/m/d' ) );
251
+ $out .= '<br />';
252
+ $out .= get_date_from_gmt( $created, 'h:i:s A' );
253
+ break;
254
+
255
+ case 'summary':
256
+ $out = $record->summary;
257
+ $object_title = $record->get_object_title();
258
+ // translators: Placeholder refers to the title of any object, like a Post (e.g. "Hello World")
259
+ $view_all_text = $object_title ? sprintf( esc_html__( 'View all activity for "%s"', 'mainwp-child-reports' ), esc_attr( $object_title ) ) : esc_html__( 'View all activity for this object', 'mainwp-child-reports' );
260
+
261
+ if ( $record->object_id ) {
262
+ $out .= $this->column_link(
263
+ '<span class="dashicons dashicons-search stream-filter-object-id"></span>',
264
+ array(
265
+ 'object_id' => $record->object_id,
266
+ 'context' => $record->context,
267
+ ),
268
+ null,
269
+ esc_attr( $view_all_text )
270
+ );
271
+ }
272
+ $out .= $this->get_action_links( $record );
273
+ break;
274
+
275
+ case 'user_id':
276
+ $user = new Author( (int) $record->user_id, (array) $record->user_meta );
277
+
278
+ $filtered_records_url = add_query_arg(
279
+ array(
280
+ 'page' => $this->plugin->admin->records_page_slug,
281
+ 'user_id' => absint( $user->id ),
282
+ ),
283
+ self_admin_url( $this->plugin->admin->admin_parent_page )
284
+ );
285
+
286
+ $out = sprintf(
287
+ '<a href="%s">%s <span>%s</span></a>%s%s%s',
288
+ $filtered_records_url,
289
+ $user->get_avatar_img( 80 ),
290
+ $user->get_display_name(),
291
+ $user->is_deleted() ? sprintf( '<br /><small class="deleted">%s</small>', esc_html__( 'Deleted User', 'mainwp-child-reports' ) ) : '',
292
+ sprintf( '<br /><small>%s</small>', $user->get_role() ),
293
+ sprintf( '<br /><small>%s</small>', $user->get_agent_label( $user->get_agent() ) )
294
+ );
295
+ break;
296
+
297
+ case 'context':
298
+ $connector_title = $this->get_term_title( $record->{'connector'}, 'connector' );
299
+ $context_title = $this->get_term_title( $record->{'context'}, 'context' );
300
+
301
+ $out = $this->column_link( $connector_title, 'connector', $item->{'connector'} );
302
+ $out .= '<br />&#8627;&nbsp;';
303
+ $out .= $this->column_link(
304
+ $context_title,
305
+ array(
306
+ 'connector' => $record->{'connector'},
307
+ 'context' => $record->{'context'},
308
+ )
309
+ );
310
+ break;
311
+
312
+ case 'action':
313
+ $out = $this->column_link( $this->get_term_title( $record->{$column_name}, $column_name ), $column_name, $record->{$column_name} );
314
+ break;
315
+
316
+ case 'blog_id':
317
+ $blog = ( $record->blog_id && is_multisite() ) ? get_blog_details( $record->blog_id ) : $this->plugin->admin->network->get_network_blog();
318
+ $out = $this->column_link( $blog->blogname, 'blog_id', $blog->blog_id );
319
+ break;
320
+
321
+ case 'ip':
322
+ $out = $this->column_link( $record->{$column_name}, 'ip', $record->{$column_name} );
323
+ break;
324
+
325
+ default:
326
+ /**
327
+ * Registers new Columns to be inserted into the table. The cell contents of this column is set
328
+ * below with 'wp_mainwp_stream_insert_column_default_'
329
+ *
330
+ * @return array
331
+ */
332
+ $new_columns = array();
333
+ $inserted_columns = apply_filters( 'wp_mainwp_stream_register_column_defaults', $new_columns );
334
+
335
+ if ( ! empty( $inserted_columns ) && is_array( $inserted_columns ) ) {
336
+ foreach ( $inserted_columns as $column_title ) {
337
+ /**
338
+ * If column title inserted via wp_mainwp_stream_register_column_defaults ($column_title) exists
339
+ * among columns registered with get_columns ($column_name) and there is an action associated
340
+ * with this column, do the action
341
+ *
342
+ * Also, note that the action name must include the $column_title registered
343
+ * with wp_mainwp_stream_register_column_defaults
344
+ */
345
+ if ( $column_title === $column_name && has_filter( "wp_mainwp_stream_insert_column_default_{$column_title}" ) ) {
346
+ /**
347
+ * Allows for the addition of content under a specified column.
348
+ *
349
+ * @param object $record Contents of the row
350
+ *
351
+ * @return string
352
+ */
353
+ $out = apply_filters( "wp_mainwp_stream_insert_column_default_{$column_title}", $column_name, $record );
354
+ } else {
355
+ $out = $column_name;
356
+ }
357
+ }
358
+ } else {
359
+ $out = $column_name;
360
+ }
361
+ }
362
+
363
+ $allowed_tags = wp_kses_allowed_html( 'post' );
364
+ $allowed_tags['time'] = array(
365
+ 'datetime' => true,
366
+ 'class' => true,
367
+ );
368
+ $allowed_tags['img']['srcset'] = true;
369
+
370
+ echo wp_kses( $out, $allowed_tags );
371
+ }
372
+
373
+ public function get_action_links( $record ) {
374
+ $out = '';
375
+
376
+ /**
377
+ * Filter allows modification of action links for a specific connector
378
+ *
379
+ * @param array
380
+ * @param Record
381
+ *
382
+ * @return array Action links for this connector
383
+ */
384
+ $action_links = apply_filters( 'wp_mainwp_stream_action_links_' . $record->connector, array(), $record );
385
+
386
+ /**
387
+ * Filter allows addition of custom links for a specific connector
388
+ *
389
+ * @param array
390
+ * @param Record
391
+ *
392
+ * @return array Custom links for this connector
393
+ */
394
+ $custom_links = apply_filters( 'wp_mainwp_stream_custom_action_links_' . $record->connector, array(), $record );
395
+
396
+ if ( $action_links || $custom_links ) {
397
+ $out .= '<div class="row-actions">';
398
+ }
399
+
400
+ $links = array();
401
+ if ( $action_links && is_array( $action_links ) ) {
402
+ foreach ( $action_links as $al_title => $al_href ) {
403
+ $links[] = sprintf(
404
+ '<span><a href="%s" class="action-link">%s</a></span>',
405
+ $al_href,
406
+ $al_title
407
+ );
408
+ }
409
+ }
410
+
411
+ if ( $custom_links && is_array( $custom_links ) ) {
412
+ foreach ( $custom_links as $key => $link ) {
413
+ $links[] = $link;
414
+ }
415
+ }
416
+
417
+ $out .= implode( ' | ', $links );
418
+
419
+ if ( $action_links || $custom_links ) {
420
+ $out .= '</div>';
421
+ }
422
+
423
+ return $out;
424
+ }
425
+
426
+ public function column_link( $display, $key, $value = null, $title = null ) {
427
+ $url = add_query_arg(
428
+ array(
429
+ 'page' => $this->plugin->admin->records_page_slug,
430
+ ),
431
+ self_admin_url( $this->plugin->admin->admin_parent_page )
432
+ );
433
+
434
+ $args = ! is_array( $key ) ? array(
435
+ $key => $value,
436
+ ) : $key;
437
+
438
+ foreach ( $args as $k => $v ) {
439
+ $url = add_query_arg( $k, $v, $url );
440
+ }
441
+
442
+ return sprintf(
443
+ '<a href="%s" title="%s">%s</a>',
444
+ esc_url( $url ),
445
+ esc_attr( $title ),
446
+ $display
447
+ );
448
+ }
449
+
450
+ public function get_term_title( $term, $type ) {
451
+ if ( ! isset( $this->plugin->connectors->term_labels[ 'stream_' . $type ][ $term ] ) ) {
452
+ return $term;
453
+ }
454
+
455
+ return $this->plugin->connectors->term_labels[ 'stream_' . $type ][ $term ];
456
+ }
457
+
458
+ /**
459
+ * Assembles records for display in search filters
460
+ *
461
+ * Gathers list of all users/connectors, then compares it to
462
+ * results of existing records. All items that do not exist in records
463
+ * get assigned a disabled value of "true".
464
+ *
465
+ * @param string $column List table column name
466
+ *
467
+ * @return array Options to be displayed in search filters
468
+ */
469
+ public function assemble_records( $column ) {
470
+ // @todo eliminate special condition for authors, especially using a WP_User object as the value; should use string or stringifiable object
471
+ if ( 'user_id' === $column ) {
472
+ $all_records = array();
473
+
474
+ // If the number of users exceeds the max users constant value then return an empty array and use AJAX instead
475
+ $user_count = count_users();
476
+ $total_users = $user_count['total_users'];
477
+
478
+ if ( $total_users > $this->plugin->admin->preload_users_max ) {
479
+ $selected_user = wp_mainwp_stream_filter_input( INPUT_GET, 'user_id' );
480
+ if ( $selected_user ) {
481
+ $user = new Author( $selected_user );
482
+
483
+ return array(
484
+ $selected_user => $user->get_display_name(),
485
+ );
486
+ } else {
487
+ return array();
488
+ }
489
+ }
490
+
491
+ $users = array_map(
492
+ function ( $user_id ) {
493
+ return new Author( $user_id );
494
+ },
495
+ get_users(
496
+ array(
497
+ 'fields' => 'ID',
498
+ )
499
+ )
500
+ );
501
+
502
+ if ( is_multisite() && is_super_admin() ) {
503
+ $super_admins = array_map(
504
+ function ( $login ) {
505
+ $user = get_user_by( 'login', $login );
506
+
507
+ return new Author( $user->ID );
508
+ },
509
+ get_super_admins()
510
+ );
511
+ $users = array_unique( array_merge( $users, $super_admins ) );
512
+ }
513
+
514
+ $users[] = new Author(
515
+ 0, array(
516
+ 'is_wp_cli' => true,
517
+ )
518
+ );
519
+
520
+ foreach ( $users as $user ) {
521
+ $all_records[ $user->id ] = $user->get_display_name();
522
+ }
523
+ } else {
524
+ $prefixed_column = sprintf( 'stream_%s', $column );
525
+ $all_records = $this->plugin->connectors->term_labels[ $prefixed_column ];
526
+ }
527
+
528
+ $existing_records = $this->plugin->db->existing_records( $column );
529
+ $active_records = array();
530
+ $disabled_records = array();
531
+
532
+ foreach ( $all_records as $record => $label ) {
533
+ if ( array_key_exists( $record, $existing_records ) ) {
534
+ $active_records[ $record ] = array(
535
+ 'label' => $label,
536
+ 'disabled' => '',
537
+ );
538
+ } else {
539
+ $disabled_records[ $record ] = array(
540
+ 'label' => $label,
541
+ 'disabled' => 'disabled="disabled"',
542
+ );
543
+ }
544
+ }
545
+
546
+ // Remove WP-CLI pseudo user if no records with user=0 exist
547
+ if ( isset( $disabled_records[0] ) ) {
548
+ unset( $disabled_records[0] );
549
+ }
550
+
551
+ $sort = function ( $a, $b ) use ( $column ) {
552
+ $label_a = (string) $a['label'];
553
+ $label_b = (string) $b['label'];
554
+
555
+ if ( $label_a === $label_b ) {
556
+ return 0;
557
+ }
558
+
559
+ return ( strtolower( $label_a ) < strtolower( $label_b ) ) ? - 1 : 1;
560
+ };
561
+
562
+ uasort( $active_records, $sort );
563
+ uasort( $disabled_records, $sort );
564
+
565
+ // Not using array_merge() in order to preserve the array index for the users dropdown which uses the user_id as the key
566
+ $all_records = $active_records + $disabled_records;
567
+
568
+ return $all_records;
569
+ }
570
+
571
+ public function get_filters() {
572
+ $filters = array();
573
+
574
+ $date_interval = new Date_Interval();
575
+
576
+ $filters['date'] = array(
577
+ 'title' => __( 'dates', 'mainwp-child-reports' ),
578
+ 'items' => $date_interval->intervals,
579
+ );
580
+
581
+ $users = $this->get_users_dropdown_items(
582
+ $this->assemble_records( 'user_id' )
583
+ );
584
+
585
+ $filters['user_id'] = array(
586
+ 'title' => __( 'users', 'mainwp-child-reports' ),
587
+ 'items' => $users,
588
+ 'ajax' => count( $users ) <= 0,
589
+ );
590
+
591
+ $filters['context'] = array(
592
+ 'title' => __( 'contexts', 'mainwp-child-reports' ),
593
+ 'items' => $this->assemble_records( 'context' ),
594
+ );
595
+
596
+ $filters['action'] = array(
597
+ 'title' => __( 'actions', 'mainwp-child-reports' ),
598
+ 'items' => $this->assemble_records( 'action' ),
599
+ );
600
+
601
+ /**
602
+ * Filter allows additional filters in the list table dropdowns
603
+ * Note the format of the filters above, with they key and array
604
+ * containing a title and array of items.
605
+ *
606
+ * @return array
607
+ */
608
+ return apply_filters( 'wp_mainwp_stream_list_table_filters', $filters );
609
+ }
610
+
611
+ public function filters_form() {
612
+ $filters = $this->get_filters();
613
+
614
+ $filters_string = sprintf( '<input type="hidden" name="page" value="%s" />', $this->plugin->admin->records_page_slug );
615
+ $filters_string .= sprintf( '<span class="filter_info hidden">%s</span>', esc_html__( 'Show filter controls via the screen options tab above.', 'mainwp-child-reports' ) );
616
+
617
+ foreach ( $filters as $name => $data ) {
618
+
619
+ $data = wp_parse_args(
620
+ $data, array(
621
+ 'title' => '',
622
+ 'items' => array(),
623
+ 'ajax' => false,
624
+ )
625
+ );
626
+
627
+ if ( 'date' === $name ) {
628
+ $filters_string .= $this->filter_date( $data['items'] );
629
+ } else {
630
+ if ( 'context' === $name ) {
631
+ // Add Connectors as parents, and apply the Contexts as children
632
+ $connectors = $this->assemble_records( 'connector' );
633
+ $context_items = array();
634
+
635
+ foreach ( $connectors as $connector => $item ) {
636
+ $context_items[ $connector ]['label'] = $item['label'];
637
+
638
+ foreach ( $data['items'] as $context_value => $context_item ) {
639
+ if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context_value, $this->plugin->connectors->contexts[ $connector ] ) ) {
640
+ $context_items[ $connector ]['children'][ $context_value ] = $context_item;
641
+ }
642
+ }
643
+
644
+ if ( isset( $context_items[ $connector ]['children'] ) ) {
645
+ $labels = wp_list_pluck( $context_items[ $connector ]['children'], 'label' );
646
+
647
+ // Sort child items by label
648
+ array_multisort( $labels, SORT_ASC, $context_items[ $connector ]['children'] );
649
+ }
650
+ }
651
+
652
+ foreach ( $context_items as $context_value => $context_item ) {
653
+ if ( ! isset( $context_item['children'] ) || empty( $context_item['children'] ) ) {
654
+ unset( $context_items[ $context_value ] );
655
+ }
656
+ }
657
+
658
+ $data['items'] = $context_items;
659
+
660
+ $labels = wp_list_pluck( $data['items'], 'label' );
661
+
662
+ // Sort top-level items by label
663
+ array_multisort( $labels, SORT_ASC, $data['items'] );
664
+
665
+ // Output a hidden input to handle the connector value
666
+ $filters_string .= sprintf(
667
+ '<input type="hidden" name="connector" class="record-filter-connector" value="%s" />',
668
+ esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'connector' ) )
669
+ );
670
+ }
671
+
672
+ $filters_string .= $this->filter_select( $name, $data['title'], $data['items'], $data['ajax'] );
673
+ }
674
+ }
675
+
676
+ $filters_string .= sprintf( '<input type="submit" id="record-query-submit" class="button" value="%s" />', __( 'Filter', 'mainwp-child-reports' ) );
677
+
678
+ // Parse all query vars into an array
679
+ $query_vars = array();
680
+
681
+ if ( isset( $_SERVER['QUERY_STRING'] ) ) {
682
+ parse_str( urldecode( $_SERVER['QUERY_STRING'] ), $query_vars );
683
+ }
684
+
685
+ // Ignore certain query vars and query vars that are empty
686
+ foreach ( $query_vars as $query_var => $value ) {
687
+ if ( '' === $value || 'page' === $query_var || 'paged' === $query_var ) {
688
+ unset( $query_vars[ $query_var ] );
689
+ }
690
+ }
691
+
692
+ $url = add_query_arg(
693
+ array(
694
+ 'page' => $this->plugin->admin->records_page_slug,
695
+ ),
696
+ self_admin_url( $this->plugin->admin->admin_parent_page )
697
+ );
698
+
699
+ // Display reset action if records are being filtered
700
+ if ( ! empty( $query_vars ) ) {
701
+ $filters_string .= sprintf( '<a href="%s" id="record-query-reset"><span class="dashicons dashicons-dismiss"></span> <span class="record-query-reset-text">%s</span></a>', esc_url( $url ), __( 'Reset filters', 'mainwp-child-reports' ) );
702
+ }
703
+
704
+ return sprintf( '<div class="alignleft actions">%s</div>', $filters_string ); // xss ok
705
+ }
706
+
707
+ public function filter_select( $name, $title, $items, $ajax = false ) {
708
+ if ( $ajax ) {
709
+ $out = sprintf(
710
+ '<input type="hidden" name="%s" class="chosen-select" value="%s" data-placeholder="%s" />',
711
+ esc_attr( $name ),
712
+ esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, $name ) ),
713
+ esc_attr( $title )
714
+ );
715
+ } else {
716
+ $options = array( '<option value=""></option>' );
717
+ $selected = wp_mainwp_stream_filter_input( INPUT_GET, $name );
718
+
719
+ foreach ( $items as $key => $item ) {
720
+ $value = isset( $item['children'] ) ? 'group-' . $key : $key;
721
+ $option_args = array(
722
+ 'value' => $value,
723
+ 'selected' => selected( $value, $selected, false ),
724
+ 'disabled' => isset( $item['disabled'] ) ? $item['disabled'] : null,
725
+ 'icon' => isset( $item['icon'] ) ? $item['icon'] : null,
726
+ 'group' => isset( $item['children'] ) ? $key : null,
727
+ 'tooltip' => isset( $item['tooltip'] ) ? $item['tooltip'] : null,
728
+ 'class' => isset( $item['children'] ) ? 'level-1' : null,
729
+ 'label' => isset( $item['label'] ) ? $item['label'] : null,
730
+ );
731
+ $options[] = $this->filter_option( $option_args );
732
+
733
+ if ( isset( $item['children'] ) ) {
734
+ foreach ( $item['children'] as $child_value => $child_item ) {
735
+ $option_args = array(
736
+ 'value' => $child_value,
737
+ 'selected' => selected( $child_value, $selected, false ),
738
+ 'disabled' => isset( $child_item['disabled'] ) ? $child_item['disabled'] : null,
739
+ 'icon' => isset( $child_item['icon'] ) ? $child_item['icon'] : null,
740
+ 'group' => $key,
741
+ 'tooltip' => isset( $child_item['tooltip'] ) ? $child_item['tooltip'] : null,
742
+ 'class' => 'level-2',
743
+ 'label' => isset( $child_item['label'] ) ? '- ' . $child_item['label'] : null,
744
+ );
745
+ $options[] = $this->filter_option( $option_args );
746
+ }
747
+ }
748
+ }
749
+ $out = sprintf(
750
+ '<select name="%s" class="chosen-select" data-placeholder="%s">%s</select>',
751
+ esc_attr( $name ),
752
+ // translators: Placeholder refers to the title of the dropdown menu (e.g. "users")
753
+ sprintf( esc_attr__( 'Show all %s', 'mainwp-child-reports' ), $title ),
754
+ implode( '', $options )
755
+ );
756
+ }
757
+
758
+ return $out;
759
+ }
760
+
761
+ public function filter_option( $args ) {
762
+ $defaults = array(
763
+ 'value' => null,
764
+ 'selected' => null,
765
+ 'disabled' => null,
766
+ 'icon' => null,
767
+ 'group' => null,
768
+ 'tooltip' => null,
769
+ 'class' => null,
770
+ 'label' => null,
771
+ );
772
+ wp_parse_args( $args, $defaults );
773
+
774
+ return sprintf(
775
+ '<option value="%s" %s %s %s %s %s class="%s">%s</option>',
776
+ esc_attr( $args['value'] ),
777
+ $args['selected'],
778
+ $args['disabled'],
779
+ $args['icon'] ? sprintf( 'data-icon="%s"', esc_attr( $args['icon'] ) ) : null,
780
+ $args['group'] ? sprintf( 'data-group="%s"', esc_attr( $args['group'] ) ) : null,
781
+ $args['tooltip'] ? sprintf( 'title="%s"', esc_attr( $args['tooltip'] ) ) : null,
782
+ $args['class'] ? esc_attr( $args['class'] ) : null,
783
+ esc_html( $args['label'] )
784
+ );
785
+ }
786
+
787
+ public function filter_search() {
788
+ $search = null;
789
+ if ( isset( $_GET['search'] ) ) { // CSRF okay
790
+ $search = esc_attr( wp_unslash( $_GET['search'] ) ); // input var okay, CSRF okay
791
+ }
792
+ $out = sprintf(
793
+ '<p class="search-box">
794
+ <label class="screen-reader-text" for="record-search-input">%1$s:</label>
795
+ <input type="search" id="record-search-input" name="search" value="%2$s" />
796
+ <input type="submit" name="" id="search-submit" class="button" value="%1$s" />
797
+ </p>',
798
+ esc_attr__( 'Search Records', 'mainwp-child-reports' ),
799
+ $search
800
+ );
801
+
802
+ return $out;
803
+ }
804
+
805
+ public function filter_date( $items ) {
806
+ wp_enqueue_style( 'jquery-ui' );
807
+ wp_enqueue_style( 'wp-mainwp-stream-datepicker' );
808
+ wp_enqueue_script( 'jquery-ui-datepicker' );
809
+
810
+ $date_predefined = wp_mainwp_stream_filter_input( INPUT_GET, 'date_predefined' );
811
+ $date_from = wp_mainwp_stream_filter_input( INPUT_GET, 'date_from' );
812
+ $date_to = wp_mainwp_stream_filter_input( INPUT_GET, 'date_to' );
813
+
814
+ ob_start();
815
+ ?>
816
+ <div class="date-interval">
817
+
818
+ <select class="field-predefined hide-if-no-js chosen-select" name="date_predefined" data-placeholder="<?php esc_attr_e( 'All Time', 'mainwp-child-reports' ); ?>">
819
+ <option></option>
820
+ <option value="custom" <?php selected( 'custom' === $date_predefined ); ?>><?php esc_attr_e( 'Custom', 'mainwp-child-reports' ); ?></option>
821
+ <?php
822
+ foreach ( $items as $key => $interval ) {
823
+ $end = isset( $interval['end'] ) ? $interval['end']->format( 'Y/m/d' ) : null;
824
+
825
+ printf(
826
+ '<option value="%s" data-from="%s" data-to="%s" %s>%s</option>',
827
+ esc_attr( $key ),
828
+ esc_attr( $interval['start']->format( 'Y/m/d' ) ),
829
+ esc_attr( $end ),
830
+ selected( $key === $date_predefined ),
831
+ esc_html( $interval['label'] )
832
+ );
833
+ }
834
+ ?>
835
+ </select>
836
+
837
+ <div class="date-inputs">
838
+ <div class="box">
839
+ <i class="date-remove dashicons"></i>
840
+ <input type="text" name="date_from" class="date-picker field-from" placeholder="<?php esc_attr_e( 'Start Date', 'mainwp-child-reports' ); ?>" value="<?php echo esc_attr( $date_from ); ?>"/>
841
+ </div>
842
+ <span class="connector dashicons"></span>
843
+
844
+ <div class="box">
845
+ <i class="date-remove dashicons"></i>
846
+ <input type="text" name="date_to" class="date-picker field-to" placeholder="<?php esc_attr_e( 'End Date', 'mainwp-child-reports' ); ?>" value="<?php echo esc_attr( $date_to ); ?>"/>
847
+ </div>
848
+ </div>
849
+
850
+ </div>
851
+ <?php
852
+
853
+ return ob_get_clean();
854
+ }
855
+
856
+ /**
857
+ * Output a Select dropdown of actions relating to the Stream records
858
+ *
859
+ * @return string
860
+ */
861
+ public function record_actions_form() {
862
+ /**
863
+ * Filter the records screen actions dropdown menu
864
+ *
865
+ * @return array Should be in the format of action_slug => 'Action Name'
866
+ */
867
+ $actions = apply_filters( 'wp_mainwp_stream_record_actions_menu', array() );
868
+
869
+ if ( empty( $actions ) ) {
870
+ return '';
871
+ }
872
+
873
+ ob_start();
874
+ printf( '<div class="alignleft actions recordactions"><select name="%s">', esc_attr( 'record-actions' ) );
875
+ printf( '<option value="">%s</option>', esc_attr__( 'Record Actions', 'mainwp-child-reports' ) );
876
+ foreach ( $actions as $value => $name ) {
877
+ printf(
878
+ '<option value="%s">%s</option>',
879
+ esc_attr( $value ),
880
+ esc_attr( $name )
881
+ );
882
+ }
883
+ echo '</select></div>';
884
+ wp_nonce_field( 'stream_record_actions_nonce', 'stream_record_actions_nonce' );
885
+
886
+ printf( '<input type="hidden" name="page" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'page' ) ) );
887
+ printf( '<input type="hidden" name="date_predefined" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'date_predefined' ) ) );
888
+ printf( '<input type="hidden" name="date_from" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'date_from' ) ) );
889
+ printf( '<input type="hidden" name="date_to" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'date_to' ) ) );
890
+ printf( '<input type="hidden" name="user_id" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'user_id' ) ) );
891
+ printf( '<input type="hidden" name="connector" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'connector' ) ) );
892
+ printf( '<input type="hidden" name="context" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'context' ) ) );
893
+ printf( '<input type="hidden" name="action" value="%s">', esc_attr( wp_mainwp_stream_filter_input( INPUT_GET, 'action' ) ) );
894
+
895
+ printf( '<input type="submit" name="" id="record-actions-submit" class="button" value="%s">', esc_attr__( 'Apply', 'mainwp-child-reports' ) );
896
+ echo '<div class="clear"></div>';
897
+
898
+ return ob_get_clean();
899
+ }
900
+
901
+ public function display() {
902
+ $url = self_admin_url( $this->plugin->admin->admin_parent_page );
903
+
904
+ echo '<form method="get" action="' . esc_url( $url ) . '" id="record-filter-form">';
905
+ echo $this->filter_search(); // xss ok
906
+ parent::display();
907
+ echo '</form>';
908
+
909
+ echo '<form method="get" action="' . esc_url( $url ) . '" id="record-actions-form">';
910
+ echo $this->record_actions_form(); // xss ok
911
+ echo '</form>';
912
+ }
913
+
914
+ public function single_row( $item ) {
915
+ $classes = apply_filters( 'wp_mainwp_stream_record_classes', array(), $item );
916
+ $class_string = '';
917
+ if ( ! empty( $classes ) ) {
918
+ $class_string = ' class="' . esc_attr( join( ' ', $classes ) ) . '"';
919
+ }
920
+
921
+ echo sprintf( '<tr%s>', $class_string ); // xss ok
922
+ $this->single_row_columns( $item );
923
+ echo '</tr>';
924
+ }
925
+
926
+ public function display_tablenav( $which ) {
927
+ if ( 'top' === $which ) :
928
+ ?>
929
+ <div class="tablenav <?php echo esc_attr( $which ); ?>">
930
+ <?php
931
+ $this->pagination( $which );
932
+ $this->extra_tablenav( $which );
933
+ ?>
934
+
935
+ <br class="clear"/>
936
+ </div>
937
+ <?php else : ?>
938
+ <div class="tablenav <?php echo esc_attr( $which ); ?>">
939
+ <?php
940
+ /**
941
+ * Fires after the list table is displayed.
942
+ */
943
+ do_action( 'wp_mainwp_stream_after_list_table' );
944
+ $this->pagination( $which );
945
+ $this->extra_tablenav( $which );
946
+ ?>
947
+
948
+ <br class="clear"/>
949
+ </div>
950
+ <?php
951
+ endif;
952
+ }
953
+
954
+ public function set_screen_option( $dummy, $option, $value ) {
955
+ if ( 'edit_mainwp_stream_per_page' === $option ) {
956
+ return $value;
957
+ } else {
958
+ return $dummy;
959
+ }
960
+ }
961
+
962
+ public function set_live_update_option( $dummy, $option, $value ) {
963
+ unset( $value );
964
+
965
+ // @codingStandardsIgnoreStart
966
+ if (
967
+ $this->plugin->admin->live_update->user_meta_key === $option
968
+ &&
969
+ isset( $_POST[ $this->plugin->admin->live_update->user_meta_key ] )
970
+ ) {
971
+ $value = esc_attr( $_POST[ $this->plugin->admin->live_update->user_meta_key ] ); //input var okay
972
+
973
+ return $value;
974
+ }
975
+
976
+ // @codingStandardsIgnoreEnd
977
+
978
+ return $dummy;
979
+ }
980
+
981
+ public function screen_controls( $status, $args ) {
982
+ unset( $status );
983
+ unset( $args );
984
+
985
+ $user_id = get_current_user_id();
986
+ $option = $this->plugin->admin->get_user_meta( $user_id, $this->plugin->admin->live_update->user_meta_key, true );
987
+ $heartbeat = wp_script_is( 'heartbeat', 'done' ) ? 'true' : 'false';
988
+
989
+ if ( 'on' === $option && 'false' === $heartbeat ) {
990
+ $option = 'off';
991
+
992
+ $this->plugin->admin->update_user_meta( $user_id, $this->plugin->admin->live_update->user_meta_key, 'off' );
993
+ }
994
+
995
+ $nonce = wp_create_nonce( $this->plugin->admin->live_update->user_meta_key . '_nonce' );
996
+
997
+ ob_start();
998
+ ?>
999
+ <fieldset>
1000
+ <h5><?php esc_html_e( 'Live updates', 'mainwp-child-reports' ); ?></h5>
1001
+
1002
+ <div>
1003
+ <input type="hidden" name="mainwp_stream_live_update_nonce" id="mainwp_stream_live_update_nonce" value="<?php echo esc_attr( $nonce ); ?>"/>
1004
+ </div>
1005
+ <div>
1006
+ <input type="hidden" name="mainwp_enable_live_update_user" id="mainwp_enable_live_update_user" value="<?php echo absint( $user_id ); ?>"/>
1007
+ </div>
1008
+ <div class="metabox-prefs stream-live-update-checkbox">
1009
+ <label for="enable_live_update">
1010
+ <input type="checkbox" value="on" name="enable_live_update" id="enable_live_update" data-heartbeat="<?php echo esc_attr( $heartbeat ); ?>" <?php checked( $option, 'on' ); ?> />
1011
+ <?php esc_html_e( 'Enabled', 'mainwp-child-reports' ); ?>
1012
+ <span class="spinner"></span>
1013
+ </label>
1014
+ </div>
1015
+ </fieldset>
1016
+ <?php
1017
+ return ob_get_clean();
1018
+ }
1019
+
1020
+ /**
1021
+ * This function is use to map List table column name with excluded setting keys
1022
+ *
1023
+ * @param string $column List table column name
1024
+ *
1025
+ * @return string setting name for that column
1026
+ */
1027
+ public function get_column_excluded_setting_key( $column ) {
1028
+ switch ( $column ) {
1029
+ case 'connector':
1030
+ $output = 'connectors';
1031
+ break;
1032
+ case 'context':
1033
+ $output = 'contexts';
1034
+ break;
1035
+ case 'action':
1036
+ $output = 'action';
1037
+ break;
1038
+ case 'ip':
1039
+ $output = 'ip_addresses';
1040
+ break;
1041
+ case 'user_id':
1042
+ $output = 'users';
1043
+ break;
1044
+ default:
1045
+ $output = false;
1046
+ }
1047
+
1048
+ return $output;
1049
+ }
1050
+
1051
+ /**
1052
+ * Get users as dropdown items
1053
+ *
1054
+ * @param array $users
1055
+ *
1056
+ * @return array
1057
+ */
1058
+ public function get_users_dropdown_items( $users ) {
1059
+ $record_meta = array();
1060
+
1061
+ foreach ( $users as $user_id => $args ) {
1062
+ $user = new Author( $user_id );
1063
+ $disabled = isset( $args['disabled'] ) ? $args['disabled'] : null;
1064
+
1065
+ $record_meta[ $user_id ] = array(
1066
+ 'text' => $user->get_display_name(),
1067
+ 'id' => $user_id,
1068
+ 'label' => $user->get_display_name(),
1069
+ 'icon' => $user->get_avatar_src( 32 ),
1070
+ 'title' => '',
1071
+ 'disabled' => $disabled,
1072
+ );
1073
+ }
1074
+
1075
+ return $record_meta;
1076
+ }
1077
+ }
classes/class-live-update.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Live_Update {
5
+ /**
6
+ * Hold Plugin class
7
+ * @var Plugin
8
+ */
9
+ public $plugin;
10
+
11
+ /**
12
+ * User meta key/identifier
13
+ *
14
+ * @var string
15
+ */
16
+ public $user_meta_key = 'mainwp_reports_stream_live_update_records';
17
+
18
+ /**
19
+ * List table object instance
20
+ *
21
+ * @var List_Table
22
+ */
23
+ public $list_table = null;
24
+
25
+ /**
26
+ * Class constructor.
27
+ *
28
+ * @param Plugin $plugin The main Plugin class.
29
+ */
30
+ public function __construct( $plugin ) {
31
+ $this->plugin = $plugin;
32
+
33
+ // Heartbeat live update
34
+ add_filter( 'heartbeat_received', array( $this, 'heartbeat_received' ), 10, 2 );
35
+
36
+ // Enable / Disable live update per user
37
+ add_action( 'wp_ajax_mainwp_stream_enable_live_update', array( $this, 'enable_live_update' ) );
38
+ }
39
+
40
+ /**
41
+ * Ajax function to enable/disable live update
42
+ *
43
+ * @return string Ajax respsonse back in JSON format
44
+ */
45
+ public function enable_live_update() {
46
+ check_ajax_referer( $this->user_meta_key . '_nonce', 'nonce' );
47
+
48
+ $input = array(
49
+ 'checked' => FILTER_SANITIZE_STRING,
50
+ 'user' => FILTER_SANITIZE_STRING,
51
+ 'heartbeat' => FILTER_SANITIZE_STRING,
52
+ );
53
+
54
+ $input = filter_input_array( INPUT_POST, $input );
55
+
56
+ if ( false === $input ) {
57
+ wp_send_json_error( 'Error in live update checkbox' );
58
+ }
59
+
60
+ $checked = ( 'checked' === $input['checked'] ) ? 'on' : 'off';
61
+
62
+ $user = (int) $input['user'];
63
+
64
+ if ( 'false' === $input['heartbeat'] ) {
65
+ $this->plugin->admin->update_user_meta( $user, $this->user_meta_key, 'off' );
66
+
67
+ wp_send_json_error( esc_html__( "Live updates could not be enabled because Heartbeat is not loaded.\n\nYour hosting provider or another plugin may have disabled it for performance reasons.", 'mainwp-child-reports' ) );
68
+
69
+ return;
70
+ }
71
+
72
+ $success = $this->plugin->admin->update_user_meta( $user, $this->user_meta_key, $checked );
73
+
74
+ if ( $success ) {
75
+ wp_send_json_success( ( 'on' === $checked ) ? 'Live Updates enabled' : 'Live Updates disabled' );
76
+ } else {
77
+ wp_send_json_error( 'Live Updates checkbox error' );
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Sends updated actions to the list table view
83
+ *
84
+ * @todo Fix reliability issues with sidebar widgets
85
+ *
86
+ * @uses gather_updated_items
87
+ * @uses generate_row
88
+ *
89
+ * @param array $response Response to heartbeat
90
+ * @param array $data Data from heartbeat
91
+ *
92
+ * @return array Data sent to heartbeat
93
+ */
94
+ public function live_update( $response, $data ) {
95
+ unset( $response );
96
+
97
+ if ( ! isset( $data['wp-mainwp-stream-heartbeat-last-time'] ) ) {
98
+ return array();
99
+ }
100
+
101
+ $last_time = $data['wp-mainwp-stream-heartbeat-last-time'];
102
+ $query = $data['wp-mainwp-stream-heartbeat-query'];
103
+
104
+ if ( empty( $query ) ) {
105
+ $query = array();
106
+ }
107
+
108
+ // Decode the query
109
+ $query = json_decode( wp_kses_stripslashes( $query ) );
110
+
111
+ $updated_items = $this->gather_updated_items( $last_time, (array) $query );
112
+
113
+ if ( ! empty( $updated_items ) ) {
114
+ ob_start();
115
+
116
+ foreach ( $updated_items as $item ) {
117
+ $this->list_table->single_row( $item );
118
+ }
119
+
120
+ $send = ob_get_clean();
121
+ } else {
122
+ $send = '';
123
+ }
124
+
125
+ return $send;
126
+ }
127
+
128
+ /**
129
+ * Sends Updated Actions to the List Table View
130
+ *
131
+ * @param int $last_time Timestamp of last update
132
+ * @param array $args Query args
133
+ *
134
+ * @return array Array of recently updated items
135
+ */
136
+ public function gather_updated_items( $last_time, $args = array() ) {
137
+ unset( $args );
138
+
139
+ if ( false === $last_time ) {
140
+ return '';
141
+ }
142
+
143
+ if ( empty( $this->list_table->items ) ) {
144
+ return '';
145
+ }
146
+
147
+ $items = array();
148
+
149
+ foreach ( $this->list_table->items as $item ) {
150
+ if ( strtotime( $item->created ) > strtotime( $last_time ) ) {
151
+ $items[] = $item;
152
+ } else {
153
+ break;
154
+ }
155
+ }
156
+
157
+ return $items;
158
+ }
159
+
160
+ /**
161
+ * Handles live updates for Stream Post List
162
+ *
163
+ * @action heartbeat_recieved
164
+ *
165
+ * @param array $response Response to be sent to heartbeat tick
166
+ * @param array $data Data from heartbeat send
167
+ *
168
+ * @return array Data sent to heartbeat tick
169
+ */
170
+ public function heartbeat_received( $response, $data ) {
171
+ // Only fire when Stream is requesting a live update
172
+ if ( ! isset( $data['wp-mainwp-stream-heartbeat'] ) ) {
173
+ return $response;
174
+ }
175
+
176
+ $enable_stream_update = ( 'off' !== $this->plugin->admin->get_user_meta( get_current_user_id(), $this->user_meta_key ) );
177
+
178
+ // Register list table
179
+ $this->list_table = new List_Table(
180
+ $this->plugin, array(
181
+ 'screen' => 'settings_page_' . $this->plugin->admin->records_page_slug,
182
+ )
183
+ );
184
+ $this->list_table->prepare_items();
185
+
186
+ $total_items = isset( $this->list_table->_pagination_args['total_items'] ) ? $this->list_table->_pagination_args['total_items'] : null;
187
+ $total_pages = isset( $this->list_table->_pagination_args['total_pages'] ) ? $this->list_table->_pagination_args['total_pages'] : null;
188
+
189
+ if ( isset( $data['wp-mainwp-stream-heartbeat'] ) && isset( $total_items ) ) {
190
+ $response['total_items'] = $total_items;
191
+ // translators: Placeholder refers to a number of items (e.g. "42")
192
+ $response['total_items_i18n'] = sprintf( _n( '%d item', '%d items', $total_items ), number_format_i18n( $total_items ) );
193
+ }
194
+
195
+ if ( isset( $data['wp-mainwp-stream-heartbeat'] ) && 'live-update' === $data['wp-mainwp-stream-heartbeat'] && $enable_stream_update ) {
196
+
197
+ if ( ! empty( $data['wp-mainwp-stream-heartbeat'] ) ) {
198
+ if ( isset( $total_pages ) ) {
199
+ $response['total_pages'] = $total_pages;
200
+ $response['total_pages_i18n'] = number_format_i18n( $total_pages );
201
+
202
+ $query_args = json_decode( $data['wp-mainwp-stream-heartbeat-query'], true );
203
+ $query_args['paged'] = $total_pages;
204
+
205
+ $response['last_page_link'] = add_query_arg( $query_args, admin_url( 'admin.php' ) );
206
+ } else {
207
+ $response['total_pages'] = 0;
208
+ }
209
+ }
210
+
211
+ $response['wp-mainwp-stream-heartbeat'] = $this->live_update( $response, $data );
212
+
213
+ } else {
214
+ $response['log'] = 'fail';
215
+ }
216
+
217
+ return $response;
218
+ }
219
+ }
classes/class-log.php ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ class Log {
6
+
7
+ /**
8
+ * Hold Plugin class
9
+ *
10
+ * @var Plugin
11
+ */
12
+ public $plugin;
13
+
14
+ /**
15
+ * Hold Current visitors IP Address.
16
+ *
17
+ * @var string
18
+ */
19
+ private $ip_address;
20
+
21
+
22
+ /**
23
+ * Previous Stream record ID, used for chaining same-session records
24
+ *
25
+ * @var int
26
+ */
27
+ private $prev_record;
28
+
29
+ /**
30
+ * Class constructor.
31
+ *
32
+ * @param Plugin $plugin The main Plugin class.
33
+ */
34
+ public function __construct( $plugin ) {
35
+ $this->plugin = $plugin;
36
+
37
+ // Support proxy mode by checking the `X-Forwarded-For` header first.
38
+ $ip_address = wp_mainwp_stream_filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_VALIDATE_IP );
39
+ $ip_address = $ip_address ? $ip_address : wp_mainwp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
40
+
41
+ $this->ip_address = $ip_address;
42
+
43
+ // Ensure function used in various methods is pre-loaded.
44
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
45
+ require_once ABSPATH . '/wp-admin/includes/plugin.php';
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Log handler
51
+ *
52
+ * @param Connector $connector Connector responsible for logging the event.
53
+ * @param string $message sprintf-ready error message string.
54
+ * @param array $args sprintf (and extra) arguments to use.
55
+ * @param int $object_id Target object id.
56
+ * @param string $context Context of the event.
57
+ * @param string $action Action of the event.
58
+ * @param int $user_id User responsible for the event.
59
+ *
60
+ * @return mixed True if updated, otherwise false|WP_Error
61
+ */
62
+ public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null, $created_timestamp = 0 ) {
63
+ global $wp_roles;
64
+
65
+ if ( is_null( $user_id ) ) {
66
+ $user_id = get_current_user_id();
67
+ }
68
+
69
+ if ( is_null( $object_id ) ) {
70
+ $object_id = 0;
71
+ }
72
+
73
+ $wp_cron_tracking = isset( $this->plugin->settings->options['advanced_wp_cron_tracking'] ) ? $this->plugin->settings->options['advanced_wp_cron_tracking'] : false;
74
+ $author = new Author( $user_id );
75
+ $agent = $author->get_current_agent();
76
+
77
+ // WP Cron tracking requires opt-in and WP Cron to be enabled.
78
+ if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
79
+ return false;
80
+ }
81
+
82
+ $user = new \WP_User( $user_id );
83
+
84
+ if ( $this->is_record_excluded( $connector, $context, $action, $user ) ) {
85
+ return false;
86
+ }
87
+
88
+ $user_meta = array(
89
+ 'user_email' => (string) ! empty( $user->user_email ) ? $user->user_email : '',
90
+ 'display_name' => (string) $author->get_display_name(),
91
+ 'user_login' => (string) ! empty( $user->user_login ) ? $user->user_login : '',
92
+ 'user_role_label' => (string) $author->get_role(),
93
+ 'agent' => (string) $agent,
94
+ );
95
+
96
+ if ( 'wp_cli' === $agent && function_exists( 'posix_getuid' ) ) {
97
+ $uid = posix_getuid();
98
+ $user_info = posix_getpwuid( $uid );
99
+
100
+ $user_meta['system_user_id'] = (int) $uid;
101
+ $user_meta['system_user_name'] = (string) $user_info['name'];
102
+ }
103
+
104
+ // Prevent any meta with null values from being logged.
105
+ $stream_meta = array_filter(
106
+ $args,
107
+ function ( $var ) {
108
+ return ! is_null( $var );
109
+ }
110
+ );
111
+
112
+ // Add user meta to Stream meta.
113
+ $stream_meta['user_meta'] = $user_meta;
114
+
115
+ // Get the current time in milliseconds.
116
+ $iso_8601_extended_date = wp_mainwp_stream_get_iso_8601_extended_date( $created_timestamp ); // $created_timestamp = 0 is current time
117
+
118
+ if ( ! empty( $user->roles ) ) {
119
+ $roles = array_values( $user->roles );
120
+ $role = $roles[0];
121
+ } elseif ( is_multisite() && is_super_admin() && $wp_roles->is_role( 'administrator' ) ) {
122
+ $role = 'administrator';
123
+ } else {
124
+ $role = '';
125
+ }
126
+
127
+ $recordarr = array(
128
+ 'object_id' => (int) $object_id,
129
+ 'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
130
+ 'blog_id' => (int) apply_filters( 'wp_mainwp_stream_blog_id_logged', get_current_blog_id() ),
131
+ 'user_id' => (int) $user_id,
132
+ 'user_role' => (string) $role,
133
+ 'created' => (string) $iso_8601_extended_date,
134
+ 'summary' => (string) vsprintf( $message, $args ),
135
+ 'connector' => (string) $connector,
136
+ 'context' => (string) $context,
137
+ 'action' => (string) $action,
138
+ 'ip' => (string) $this->ip_address,
139
+ 'meta' => (array) $stream_meta,
140
+ );
141
+
142
+ if ( 0 === $recordarr['object_id'] ) {
143
+ unset( $recordarr['object_id'] );
144
+ }
145
+
146
+
147
+ $result = $this->plugin->db->insert( $recordarr );
148
+
149
+ // This is helpful in development environments:
150
+ // error_log( $this->debug_backtrace( $recordarr ) );
151
+
152
+ return $result;
153
+ }
154
+
155
+ /**
156
+ * This function is use to check whether or not a record should be excluded from the log.
157
+ *
158
+ * @param string $connector Name of the connector being logged.
159
+ * @param string $context Name of the context being logged.
160
+ * @param string $action Name of the action being logged.
161
+ * @param \WP_User $user The user being logged.
162
+ * @param string $ip IP address being logged.
163
+ *
164
+ * @return bool
165
+ */
166
+ public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
167
+ if ( is_null( $user ) ) {
168
+ $user = wp_get_current_user();
169
+ }
170
+
171
+ if ( is_null( $ip ) ) {
172
+ $ip = $this->ip_address;
173
+ } else {
174
+ $ip = wp_mainwp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
175
+ }
176
+
177
+ if ( ! empty( $user->roles ) ) {
178
+ $roles = array_values( $user->roles );
179
+ $role = $roles[0];
180
+ } else {
181
+ $role = '';
182
+ }
183
+ $record = array(
184
+ 'connector' => $connector,
185
+ 'context' => $context,
186
+ 'action' => $action,
187
+ 'author' => $user->ID,
188
+ 'role' => $role,
189
+ 'ip_address' => $ip,
190
+ );
191
+
192
+ $exclude_settings = isset( $this->plugin->settings->options['exclude_rules'] ) ? $this->plugin->settings->options['exclude_rules'] : array();
193
+
194
+ if ( is_multisite() && is_plugin_active_for_network( $this->plugin->locations['plugin'] ) && ! is_network_admin() ) {
195
+ $multisite_options = (array) get_site_option( 'wp_mainwp_stream_network', array() );
196
+ $multisite_exclude_settings = isset( $multisite_options['exclude_rules'] ) ? $multisite_options['exclude_rules'] : array();
197
+
198
+ if ( ! empty( $multisite_exclude_settings ) ) {
199
+ foreach ( $multisite_exclude_settings['exclude_row'] as $key => $rule ) {
200
+ $exclude_settings['exclude_row'][] = $multisite_exclude_settings['exclude_row'][ $key ];
201
+ $exclude_settings['author_or_role'][] = $multisite_exclude_settings['author_or_role'][ $key ];
202
+ $exclude_settings['connector'][] = $multisite_exclude_settings['connector'][ $key ];
203
+ $exclude_settings['context'][] = $multisite_exclude_settings['context'][ $key ];
204
+ $exclude_settings['action'][] = $multisite_exclude_settings['action'][ $key ];
205
+ $exclude_settings['ip_address'][] = $multisite_exclude_settings['ip_address'][ $key ];
206
+ }
207
+ }
208
+ }
209
+
210
+ $exclude_record = false;
211
+
212
+ if ( isset( $exclude_settings['exclude_row'] ) && ! empty( $exclude_settings['exclude_row'] ) ) {
213
+ foreach ( $exclude_settings['exclude_row'] as $key => $value ) {
214
+ // Prepare values.
215
+ $author_or_role = isset( $exclude_settings['author_or_role'][ $key ] ) ? $exclude_settings['author_or_role'][ $key ] : '';
216
+ $connector = isset( $exclude_settings['connector'][ $key ] ) ? $exclude_settings['connector'][ $key ] : '';
217
+ $context = isset( $exclude_settings['context'][ $key ] ) ? $exclude_settings['context'][ $key ] : '';
218
+ $action = isset( $exclude_settings['action'][ $key ] ) ? $exclude_settings['action'][ $key ] : '';
219
+ $ip_address = isset( $exclude_settings['ip_address'][ $key ] ) ? $exclude_settings['ip_address'][ $key ] : '';
220
+
221
+ $exclude = array(
222
+ 'connector' => ! empty( $connector ) ? $connector : null,
223
+ 'context' => ! empty( $context ) ? $context : null,
224
+ 'action' => ! empty( $action ) ? $action : null,
225
+ 'ip_address' => ! empty( $ip_address ) ? $ip_address : null,
226
+ 'author' => is_numeric( $author_or_role ) ? absint( $author_or_role ) : null,
227
+ 'role' => ( ! empty( $author_or_role ) && ! is_numeric( $author_or_role ) ) ? $author_or_role : null,
228
+ );
229
+
230
+ $exclude_rules = array_filter( $exclude, 'strlen' );
231
+
232
+ if ( ! empty( $exclude_rules ) ) {
233
+ $matches_exclusion_rule = true;
234
+
235
+ foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
236
+ if ( 'ip_address' === $exclude_key ) {
237
+ $ip_addresses = explode( ',', $exclude_value );
238
+ if ( ! in_array( $record['ip_address'], $ip_addresses, true ) ) {
239
+ $matches_exclusion_rule = false;
240
+ break;
241
+ }
242
+ } elseif ( $record[ $exclude_key ] !== $exclude_value ) {
243
+ $matches_exclusion_rule = false;
244
+ break;
245
+ }
246
+ }
247
+
248
+ if ( $matches_exclusion_rule ) {
249
+ $exclude_record = true;
250
+ break;
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Filters whether or not a record should be excluded from the log.
258
+ *
259
+ * If true, the record is not logged.
260
+ *
261
+ * @param array $exclude_record Whether the record should excluded.
262
+ * @param array $recordarr The record to log.
263
+ *
264
+ * @return bool
265
+ */
266
+ return apply_filters( 'wp_mainwp_stream_is_record_excluded', $exclude_record, $record );
267
+ }
268
+
269
+ /**
270
+ * Helper function to send a full backtrace of calls to the PHP error log for debugging
271
+ *
272
+ * @param array $recordarr Record argument array.
273
+ *
274
+ * @return string
275
+ */
276
+ public function debug_backtrace( $recordarr ) {
277
+ if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
278
+ return __( 'Debug backtrace requires at least PHP 5.3.6', 'wp_stream' );
279
+ }
280
+
281
+ // Record details.
282
+ $summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
283
+ $author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
284
+ $connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
285
+ $context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
286
+ $action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
287
+
288
+ // Stream meta.
289
+ $stream_meta = isset( $recordarr['meta'] ) ? $recordarr['meta'] : null;
290
+
291
+ unset( $stream_meta['user_meta'] );
292
+
293
+ if ( $stream_meta ) {
294
+ array_walk(
295
+ $stream_meta, function ( &$value, $key ) {
296
+ $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
297
+ }
298
+ );
299
+ $stream_meta = implode( ', ', $stream_meta );
300
+ }
301
+
302
+ // User meta.
303
+ $user_meta = isset( $recordarr['meta']['user_meta'] ) ? $recordarr['meta']['user_meta'] : null;
304
+
305
+ if ( $user_meta ) {
306
+ array_walk(
307
+ $user_meta, function ( &$value, $key ) {
308
+ $value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
309
+ }
310
+ );
311
+
312
+ $user_meta = implode( ', ', $user_meta );
313
+ }
314
+
315
+ // Debug backtrace.
316
+ ob_start();
317
+
318
+ // @codingStandardsIgnoreStart
319
+ debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
320
+ // @codingStandardsIgnoreEnd
321
+
322
+ $backtrace = ob_get_clean();
323
+ $backtrace = array_values( array_filter( explode( "\n", $backtrace ) ) );
324
+
325
+ $output = sprintf(
326
+ "Pro Reports Debug Backtrace\n\n Summary | %s\n Author | %s\n Connector | %s\n Context | %s\n Action | %s\nReports Meta | %s\nAuthor Meta | %s\n\n%s\n",
327
+ $summary,
328
+ $author,
329
+ $connector,
330
+ $context,
331
+ $action,
332
+ $stream_meta,
333
+ $user_meta,
334
+ implode( "\n", $backtrace )
335
+ );
336
+
337
+ return $output;
338
+ }
339
+ }
classes/class-mainwp-child-report-helper.php ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class MainWP_Child_Report_Helper {
5
+
6
+ public static $instance;
7
+ public $branding_options = null;
8
+ public $branding_title = null;
9
+ public $setting_fields = array();
10
+ public $plugin;
11
+ public $list_table = null;
12
+
13
+ function __construct( $plugin = null ) {
14
+ $this->plugin = $plugin;
15
+
16
+ add_action( 'admin_menu', array( &$this, 'admin_menu' ) );
17
+ add_filter( 'mainwp_wp_stream_settings_form_action', array( $this, 'settings_form_action' ) );
18
+ add_filter('updraftplus_save_last_backup', array( __CLASS__, 'hook_updraftplus_save_last_backup' ));
19
+ // hmbkp_backup_complete
20
+ add_action('mainwp_child_reports_log', array( __CLASS__, 'hook_reports_log' ), 10, 1);
21
+ add_filter( 'all_plugins', array( $this, 'modify_plugin_header' ) );
22
+ add_filter( 'plugin_row_meta', array( &$this, 'plugin_row_meta' ), 10, 2 );
23
+ add_filter( 'wp_mainwp_stream_settings_option_fields', array( $this, 'get_hide_child_report_fields' ) );
24
+ $this->init_branding_options();
25
+ }
26
+
27
+ public static function get_instance() {
28
+ if ( empty( self::$instance ) ) {
29
+ $class = __CLASS__;
30
+ self::$instance = new $class;
31
+ }
32
+
33
+ return self::$instance;
34
+ }
35
+
36
+ public function admin_menu() {
37
+ $opts = $this->branding_options;
38
+ $hide = is_array($opts) && isset($opts['hide_child_reports']) && ($opts['hide_child_reports'] == 'hide');
39
+ if ( ! $hide ) {
40
+ // Register settings page
41
+ add_filter( 'mainwp-child-init-subpages', array( $this, 'init_subpages' ) );
42
+ }
43
+ }
44
+
45
+ function settings_form_action( $action ) {
46
+ if ( is_network_admin() ) {
47
+ $current_page = wp_mainwp_stream_filter_input( INPUT_GET, 'page' );
48
+ $action = add_query_arg( array( 'action' => $current_page ), 'edit.php' );
49
+ }
50
+ return $action;
51
+ }
52
+
53
+ public function init_branding_options() {
54
+ return $this->get_branding_options();
55
+ }
56
+
57
+ public function get_branding_options() {
58
+ if ( $this->branding_options === null ) {
59
+
60
+ $opts = get_option( 'mainwp_child_branding_settings' ); // settings from mainwp-child plugin
61
+ // this is new update
62
+ if ( is_array($opts) ) {
63
+ if (isset($opts['cancelled_branding'])) { // if it was set
64
+ $cancelled_branding = $opts['cancelled_branding'];
65
+ } else {
66
+ $disconnected = isset( $opts['branding_disconnected'] ) ? $opts['branding_disconnected'] : '';
67
+ $preserve_branding = isset( $opts['preserve_branding'] ) ? $opts['preserve_branding'] : '';
68
+ $cancelled_branding = ( $disconnected === 'yes' ) && ! $preserve_branding;
69
+ $opts['cancelled_branding'] = $cancelled_branding;
70
+ }
71
+ $branding_header = isset( $opts['branding_header'] ) ? $opts['branding_header'] : '';
72
+ } else { // to compatible will old code
73
+ $opts = array();
74
+ $opts['hide'] = get_option( 'mainwp_branding_child_hide' );
75
+ $opts['branding_header'] = get_option( 'mainwp_branding_plugin_header' );
76
+ $cancelled_branding = ( get_option( 'mainwp_child_branding_disconnected' ) === 'yes' ) && ! get_option( 'mainwp_branding_preserve_branding' );
77
+ $opts['cancelled_branding'] = $cancelled_branding;
78
+ $branding_header = $opts['branding_header'];
79
+ }
80
+
81
+ if ( ! $cancelled_branding && ( is_array( $branding_header ) && ! empty( $branding_header['name'] ) ) ) {
82
+ $this->branding_title = stripslashes( $branding_header['name'] );
83
+ } else {
84
+ $this->branding_title = '';
85
+ }
86
+
87
+ $this->branding_options = $opts;
88
+ }
89
+
90
+ return $this->branding_options;
91
+ }
92
+
93
+
94
+ public function modify_plugin_header( $plugins ) {
95
+ $_opts = $this->branding_options;
96
+ $is_hide = isset( $_opts['hide'] ) ? $_opts['hide'] : '';
97
+ $cancelled_branding = isset( $_opts['cancelled_branding'] ) ? $_opts['cancelled_branding'] : false;
98
+ $branding_header = isset( $_opts['branding_header'] ) ? $_opts['branding_header'] : '';
99
+
100
+ if ( $cancelled_branding ) {
101
+ return $plugins;
102
+ }
103
+
104
+ if ( 'T' === $is_hide ) {
105
+ foreach ( $plugins as $key => $value ) {
106
+ $plugin_slug = basename( $key, '.php' );
107
+ if ( 'mainwp-child-reports' === $plugin_slug ) {
108
+ unset( $plugins[ $key ] );
109
+ }
110
+ }
111
+ return $plugins;
112
+ }
113
+
114
+ if ( is_array( $branding_header ) && ! empty( $branding_header['name'] ) ) {
115
+ return $this->update_plugin_header( $plugins, $branding_header );
116
+ } else {
117
+ return $plugins;
118
+ }
119
+ }
120
+
121
+ public function update_plugin_header( $plugins, $header ) {
122
+ $plugin_key = '';
123
+ foreach ( $plugins as $key => $value ) {
124
+ $plugin_slug = basename( $key, '.php' );
125
+ if ( 'mainwp-child-reports' === $plugin_slug ) {
126
+ $plugin_key = $key;
127
+ $plugin_data = $value;
128
+ }
129
+ }
130
+
131
+ if ( ! empty( $plugin_key ) ) {
132
+ $plugin_data['Name'] = stripslashes( $header['name'] . " reports" );
133
+ $plugin_data['Description'] = stripslashes( $header['description'] );
134
+ $plugin_data['Author'] = stripslashes( $header['author'] );
135
+ $plugin_data['AuthorURI'] = stripslashes( $header['authoruri'] );
136
+ if ( ! empty( $header['pluginuri'] ) ) {
137
+ $plugin_data['PluginURI'] = stripslashes( $header['pluginuri'] );
138
+ }
139
+ $plugins[ $plugin_key ] = $plugin_data;
140
+ }
141
+
142
+ return $plugins;
143
+ }
144
+
145
+ public function is_branding() {
146
+
147
+ $_opts = $this->branding_options;
148
+ $is_hide = isset( $_opts['hide'] ) ? $_opts['hide'] : '';
149
+ $cancelled_branding = isset( $_opts['cancelled_branding'] ) ? $_opts['cancelled_branding'] : false;
150
+ $branding_header = isset( $_opts['branding_header'] ) ? $_opts['branding_header'] : array();
151
+
152
+ if ( $cancelled_branding ) {
153
+ return false;
154
+ }
155
+ // hide
156
+ if ( 'T' === $is_hide ) {
157
+ return true;
158
+ }
159
+ if ( is_array( $branding_header ) && !empty( $branding_header['name'] ) ) {
160
+ return true;
161
+ }
162
+ return false;
163
+
164
+ }
165
+
166
+
167
+ public function plugin_row_meta( $plugin_meta, $plugin_file ) {
168
+ if ( WP_MAINWP_STREAM_PLUGIN !== $plugin_file ) {
169
+ return $plugin_meta;
170
+ }
171
+
172
+ if ( ! $this->is_branding() ) {
173
+ return $plugin_meta;
174
+ }
175
+ // hide View details links
176
+ $meta_total = count( $plugin_meta );
177
+ for ( $i = 0; $i < $meta_total; $i++ ) {
178
+ $str_meta = $plugin_meta[ $i ];
179
+ if ( strpos( $str_meta, 'plugin-install.php?tab=plugin-information' ) ) {
180
+ unset( $plugin_meta[ $i ] );
181
+ break;
182
+ }
183
+ }
184
+
185
+ return $plugin_meta;
186
+ }
187
+
188
+ public function init_subpages( $subPages = array() ) {
189
+
190
+ if ( is_network_admin() && ! is_plugin_active_for_network( WP_MAINWP_STREAM_PLUGIN ) ) {
191
+ return $subPages;
192
+ }
193
+
194
+ $branding_text = $this->branding_title;
195
+
196
+ if (empty($branding_text)) {
197
+ $branding_text = 'Child Reports';
198
+ } else {
199
+ $branding_text = $branding_text . ' Reports';
200
+ }
201
+
202
+ $subPages[] = array('title' => $branding_text, 'slug' => 'reports-page' , 'callback' => array( $this, 'render_reports_page' ) , 'load_callback' => array( $this, 'register_list_table' ));
203
+ $subPages[] = array('title' => $branding_text . ' Settings', 'slug' => 'reports-settings' , 'callback' => array( $this, 'render_settings_page' ) );
204
+
205
+ return $subPages;
206
+ }
207
+
208
+ public static function hook_updraftplus_save_last_backup($last_backup) {
209
+
210
+ if (!is_array($last_backup))
211
+ return $last_backup;
212
+
213
+ if (isset($last_backup['backup_time'])) {
214
+ if (empty($last_backup['success']))
215
+ return $last_backup;
216
+
217
+ $backup_time = $last_backup['backup_time'];
218
+ $backup = $last_backup['backup_array'];
219
+
220
+ $message = "";
221
+ $backup_type = "";
222
+ if (isset($backup['db'])) {
223
+ $message .= "database, ";
224
+ $backup_type .= "database, ";
225
+ }
226
+ if (isset($backup['plugins'])) {
227
+ $message .= "plugins, ";
228
+ $backup_type .= "plugins, ";
229
+ }
230
+
231
+ if (isset($backup['themes'])) {
232
+ $message .= "themes, ";
233
+ $backup_type .= "themes, ";
234
+ }
235
+
236
+ $message = rtrim($message, ', ');
237
+ $message = "Updraftplus backup " . $message ." finished";
238
+
239
+ $backup_type = rtrim($backup_type, ', ');
240
+
241
+ $size = "N/A";
242
+ if (isset($backup['db-size'])) {
243
+ $size = $backup['db-size'];
244
+ } else if (isset($backup['themes-size'])) {
245
+ $size = $backup['themes-size'];
246
+ }
247
+ $destination = "";
248
+
249
+ // to logging updraftplus backup
250
+ do_action("updraftplus_backup", $destination , $message, __('Finished', 'mainwp-child-reports'), $backup_type, $backup_time);
251
+ }
252
+ return $last_backup;
253
+ }
254
+
255
+ public static function hook_reports_log($ext_name = '') {
256
+ do_action('mainwp_child_log', $ext_name);
257
+ }
258
+
259
+ public function get_hide_child_report_fields( $fields ) {
260
+
261
+ $branding_text = $this->get_branding_title();
262
+ $branding_name = !empty($branding_text) ? $branding_text : 'MainWP Child';
263
+ $chkbox_label = 'Hide ' . $branding_name . ' Reports from reports';
264
+ $chkbox_desc = 'If selected, the ' . $branding_name . ' Reports plugin will be left out from reports for this site.';
265
+
266
+ $new_fields['general']['fields'][] = array(
267
+ 'name' => 'hide_child_plugins',
268
+ 'title' => $chkbox_label,
269
+ 'after_field' => __( 'Enabled', 'mainwp-child-reports' ),
270
+ 'default' => 0,
271
+ 'desc' => $chkbox_desc,
272
+ 'type' => 'checkbox',
273
+ );
274
+
275
+ $fields = array_merge_recursive( $new_fields, $fields );
276
+ return $fields;
277
+
278
+ }
279
+
280
+ public function register_list_table() {
281
+ $this->list_table = new List_Table(
282
+ $this->plugin, array(
283
+ 'screen' => 'settings_page_' . $this->plugin->admin->records_page_slug,
284
+ )
285
+ );
286
+ }
287
+
288
+ public function render_list_table() {
289
+ $this->list_table->prepare_items();
290
+ echo '<div class="mainwp_child_reports_wrap">';
291
+ $this->list_table->display();
292
+ echo '</div>';
293
+ }
294
+
295
+ public function render_reports_page() {
296
+ do_action('mainwp-child-pageheader', 'reports-page');
297
+ $this->render_list_table();
298
+ do_action('mainwp-child-pagefooter', 'reports-page');
299
+ }
300
+
301
+ public function render_settings_page() {
302
+ $option_key = $this->plugin->settings->option_key;
303
+ $form_action = apply_filters( 'mainwp_wp_stream_settings_form_action', admin_url( 'options.php' ) );
304
+ do_action('mainwp-child-pageheader', 'reports-settings');
305
+ ?>
306
+ <div class="postbox">
307
+ <div class="inside">
308
+ <form method="post" action="<?php echo esc_attr( $form_action ) ?>" enctype="multipart/form-data">
309
+ <?php
310
+ settings_fields( $option_key );
311
+ do_settings_sections( $option_key );
312
+ submit_button();
313
+ ?>
314
+ </form>
315
+ </div>
316
+ </div>
317
+ <?php
318
+ do_action('mainwp-child-pagefooter', 'reports-settings');
319
+ }
320
+
321
+ public function get_branding_title() {
322
+ return $this->branding_title;
323
+ }
324
+ }
classes/class-network.php ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Network {
5
+ /**
6
+ * Hold Plugin class
7
+ * @var Plugin
8
+ */
9
+ public $plugin;
10
+
11
+ public $network_settings_page_slug = 'wp_mainwp_stream_network_settings';
12
+
13
+ public $default_settings_page_slug = 'wp_mainwp_stream_default_settings';
14
+
15
+ public function __construct( $plugin ) {
16
+ $this->plugin = $plugin;
17
+
18
+ // Always add default site_id/blog_id params when multisite
19
+ if ( is_multisite() ) {
20
+ add_filter( 'wp_mainwp_stream_query_args', array( $this, 'network_query_args' ) );
21
+ }
22
+
23
+ // Bail early if not network-activated
24
+ if ( ! $this->is_network_activated() ) {
25
+ return;
26
+ }
27
+
28
+ // Actions
29
+ add_action( 'init', array( $this, 'ajax_network_admin' ) );
30
+
31
+ // DISABLED
32
+ //add_action( 'network_admin_menu', array( $this->plugin->admin, 'register_menu' ) );
33
+ //add_action( 'network_admin_menu', array( $this, 'admin_menu_screens' ) );
34
+ //add_action( 'admin_menu', array( $this, 'admin_menu_screens' ) );
35
+
36
+ add_action( 'network_admin_notices', array( $this->plugin->admin, 'admin_notices' ) );
37
+ add_action( 'wpmuadminedit', array( $this, 'network_options_action' ) );
38
+ add_action( 'update_site_option_' . $this->plugin->settings->network_options_key, array( $this, 'updated_option_ttl_remove_records' ), 10, 3 );
39
+
40
+ // Filters
41
+ add_filter( 'wp_mainwp_stream_blog_id_logged', array( $this, 'blog_id_logged' ) );
42
+ add_filter( 'wp_mainwp_stream_admin_page_title', array( $this, 'network_admin_page_title' ) );
43
+ add_filter( 'wp_mainwp_stream_list_table_screen_id', array( $this, 'list_table_screen_id' ) );
44
+ add_filter( 'wp_mainwp_stream_list_table_filters', array( $this, 'list_table_filters' ) );
45
+ add_filter( 'wp_mainwp_stream_list_table_columns', array( $this, 'network_admin_columns' ) );
46
+ add_filter( 'wp_mainwp_stream_settings_form_action', array( $this, 'settings_form_action' ) );
47
+ add_filter( 'wp_mainwp_stream_settings_form_description', array( $this, 'settings_form_description' ) );
48
+ //add_filter( 'wp_mainwp_stream_settings_option_fields', array( $this, 'get_network_admin_fields' ) );
49
+ add_filter( 'wp_mainwp_stream_serialized_labels', array( $this, 'get_settings_translations' ) );
50
+ add_filter( 'wp_mainwp_stream_connectors', array( $this, 'hide_blogs_connector' ) );
51
+ }
52
+
53
+ /**
54
+ * Workaround to get admin-ajax.php to know when the request is from the Network Admin
55
+ *
56
+ * @return bool
57
+ *
58
+ * @action init
59
+ *
60
+ * @see https://core.trac.wordpress.org/ticket/22589
61
+ */
62
+ public function ajax_network_admin() {
63
+ if (
64
+ defined( 'DOING_AJAX' )
65
+ &&
66
+ DOING_AJAX
67
+ &&
68
+ preg_match( '#^' . network_admin_url() . '#i', $_SERVER['HTTP_REFERER'] )
69
+ ) {
70
+ define( 'WP_NETWORK_ADMIN', true );
71
+ return WP_NETWORK_ADMIN;
72
+ }
73
+
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * Builds a stdClass object used when displaying actions done in network administration
79
+ *
80
+ * @return object
81
+ */
82
+ public function get_network_blog() {
83
+ $blog = new \stdClass();
84
+ $blog->blog_id = 0;
85
+ $blog->blogname = esc_html__( 'Network Admin', 'mainwp-child-reports' );
86
+
87
+ return $blog;
88
+ }
89
+
90
+ /**
91
+ * Returns true if Stream is network activated, otherwise false
92
+ *
93
+ * @return bool
94
+ */
95
+ public function is_network_activated() {
96
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
97
+ require_once ABSPATH . '/wp-admin/includes/plugin.php';
98
+ }
99
+
100
+ if ( $this->is_mustuse() ) {
101
+ return true;
102
+ }
103
+
104
+ return is_plugin_active_for_network( $this->plugin->locations['plugin'] );
105
+ }
106
+
107
+ /**
108
+ * Returns true if Stream is a must-use plugin, otherwise false
109
+ *
110
+ * @return bool
111
+ */
112
+ public function is_mustuse() {
113
+
114
+ $stream_php = trailingslashit( WPMU_PLUGIN_DIR ) . $this->plugin->locations['plugin'];
115
+
116
+ if ( file_exists( $stream_php ) && class_exists( 'WP_MainWP_Stream\Plugin' ) ) {
117
+ return true;
118
+ }
119
+
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Add Network Settings and Default Settings menu items
125
+ *
126
+ * @return array
127
+ */
128
+ // DISABLED
129
+ public function admin_menu_screens() {
130
+ if ( ! is_network_admin() ) {
131
+ return;
132
+ }
133
+
134
+ remove_submenu_page( $this->plugin->admin->records_page_slug, 'wp_mainwp_stream_settings' );
135
+ remove_submenu_page( $this->plugin->admin->records_page_slug, 'edit.php?post_type=wp_mainwp_alerts' );
136
+
137
+ $this->plugin->admin->screen_id['network_settings'] = add_submenu_page(
138
+ $this->plugin->admin->records_page_slug,
139
+ __( 'Reports Network Settings', 'mainwp-child-reports' ),
140
+ __( 'Network Settings', 'mainwp-child-reports' ),
141
+ $this->plugin->admin->settings_cap,
142
+ $this->network_settings_page_slug,
143
+ array( $this->plugin->admin, 'render_settings_page' )
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Remove records when records TTL is shortened
149
+ *
150
+ * @param string $option_key
151
+ * @param array $old_value
152
+ * @param array $new_value
153
+ *
154
+ * @action update_option_wp_stream
155
+ * @return void
156
+ */
157
+ public function updated_option_ttl_remove_records( $option_key, $new_value, $old_value ) {
158
+ unset( $option_key );
159
+ $this->plugin->settings->updated_option_ttl_remove_records( $old_value, $new_value );
160
+ }
161
+
162
+ /**
163
+ * Adjust the action of the settings form when in the Network Admin
164
+ *
165
+ * @param $action
166
+ *
167
+ * @return string
168
+ */
169
+ public function settings_form_action( $action ) {
170
+ if ( is_network_admin() ) {
171
+ $current_page = wp_mainwp_stream_filter_input( INPUT_GET, 'page' );
172
+ $action = add_query_arg(
173
+ array(
174
+ 'action' => $current_page,
175
+ ), 'edit.php'
176
+ );
177
+ }
178
+
179
+ return $action;
180
+ }
181
+
182
+ /**
183
+ * Add a description to each of the Settings pages in the Network Admin
184
+ *
185
+ * @param $description
186
+ *
187
+ * @return string
188
+ */
189
+ public function settings_form_description( $description ) {
190
+ if ( ! is_network_admin() ) {
191
+ return '';
192
+ }
193
+
194
+ $current_page = wp_mainwp_stream_filter_input( INPUT_GET, 'page' );
195
+
196
+ switch ( $current_page ) {
197
+ case $this->network_settings_page_slug:
198
+ $description = __( 'These settings apply to all sites on the network.', 'mainwp-child-reports' );
199
+ break;
200
+ case $this->default_settings_page_slug:
201
+ $description = __( 'These default settings will apply to new sites created on the network. These settings do not alter existing sites.', 'mainwp-child-reports' );
202
+ break;
203
+ }
204
+
205
+ return $description;
206
+ }
207
+
208
+ /**
209
+ * Adjusts the settings fields displayed in various network admin screens
210
+ *
211
+ * @param $fields
212
+ *
213
+ * @return mixed
214
+ */
215
+ public function get_network_admin_fields( $fields ) {
216
+ if ( ! $this->is_network_activated() ) {
217
+ return $fields;
218
+ }
219
+
220
+ $stream_hidden_options = apply_filters(
221
+ 'wp_mainwp_stream_hidden_option_fields',
222
+ array(
223
+ 'general' => array(
224
+ 'records_ttl',
225
+ ),
226
+ 'advanced' => array(
227
+ 'delete_all_records',
228
+ ),
229
+ )
230
+ );
231
+
232
+ $network_hidden_options = apply_filters(
233
+ 'wp_mainwp_stream_network_option_fields',
234
+ array(
235
+ 'general' => array(
236
+ 'role_access',
237
+ ),
238
+ 'exclude' => array(
239
+ 'authors',
240
+ 'roles',
241
+ 'connectors',
242
+ 'contexts',
243
+ 'actions',
244
+ 'ip_addresses',
245
+ 'hide_previous_records',
246
+ ),
247
+ )
248
+ );
249
+
250
+ // Remove settings based on context
251
+ if ( $this->plugin->settings->network_options_key === $this->plugin->settings->option_key ) {
252
+ $hidden_options = $network_hidden_options;
253
+ } else {
254
+ $hidden_options = $stream_hidden_options;
255
+ }
256
+
257
+ foreach ( $fields as $section_key => $section ) {
258
+ foreach ( $section['fields'] as $key => $field ) {
259
+ if ( ! isset( $hidden_options[ $section_key ] ) ) {
260
+ continue;
261
+ }
262
+
263
+ if ( in_array( $field['name'], $hidden_options[ $section_key ], true ) ) {
264
+ unset( $fields[ $section_key ]['fields'][ $key ] );
265
+ }
266
+ }
267
+ }
268
+
269
+ // Add settings based on context
270
+ if ( $this->plugin->settings->network_options_key === $this->plugin->settings->option_key ) {
271
+ $new_fields['general']['fields'][] = array(
272
+ 'name' => 'site_access',
273
+ 'title' => __( 'Site Access', 'mainwp-child-reports' ),
274
+ 'after_field' => __( 'Enabled', 'mainwp-child-reports' ),
275
+ 'default' => 1,
276
+ 'desc' => __( 'Allow sites on this network to view their Reports activity. Leave unchecked to only allow Reports to be viewed in the Network Admin.', 'mainwp-child-reports' ),
277
+ 'type' => 'checkbox',
278
+ );
279
+
280
+ $fields = array_merge_recursive( $new_fields, $fields );
281
+ }
282
+
283
+ // Remove empty settings sections
284
+ foreach ( $fields as $section_key => $section ) {
285
+ if ( empty( $section['fields'] ) ) {
286
+ unset( $fields[ $section_key ] );
287
+ }
288
+ }
289
+
290
+ return $fields;
291
+ }
292
+
293
+ /**
294
+ * Get translations of serialized Stream Network settings
295
+ *
296
+ * @filter wp_mainwp_stream_serialized_labels
297
+ *
298
+ * @return array Multidimensional array of fields
299
+ */
300
+ public function get_settings_translations( $labels ) {
301
+ $network_key = $this->plugin->settings->network_options_key;
302
+
303
+ if ( ! isset( $labels[ $network_key ] ) ) {
304
+ $labels[ $network_key ] = array();
305
+ }
306
+
307
+ foreach ( $this->plugin->settings->get_fields() as $section_slug => $section ) {
308
+ foreach ( $section['fields'] as $field ) {
309
+ $labels[ $network_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
310
+ }
311
+ }
312
+
313
+ return $labels;
314
+ }
315
+
316
+ /**
317
+ * Wrapper for the settings API to work on the network settings page
318
+ */
319
+ public function network_options_action() {
320
+ $allowed_referers = array(
321
+ $this->network_settings_page_slug,
322
+ $this->default_settings_page_slug,
323
+ );
324
+
325
+ if ( ! isset( $_GET['action'] ) || ! in_array( $_GET['action'], $allowed_referers, true ) ) { // CSRF okay
326
+ return;
327
+ }
328
+
329
+ $options = isset( $_POST['option_page'] ) ? explode( ',', stripslashes( $_POST['option_page'] ) ) : null; // CSRF okay
330
+
331
+ if ( $options ) {
332
+
333
+ foreach ( $options as $option ) {
334
+ $option = trim( $option );
335
+ $value = null;
336
+ $sections = $this->plugin->settings->get_fields();
337
+
338
+ foreach ( $sections as $section_name => $section ) {
339
+ foreach ( $section['fields'] as $field_idx => $field ) {
340
+ $option_key = $section_name . '_' . $field['name'];
341
+
342
+ // @codingStandardsIgnoreStart
343
+ if ( isset( $_POST[ $option ][ $option_key ] ) ) {
344
+ $value[ $option_key ] = $_POST[ $option ][ $option_key ];
345
+ } else {
346
+ $value[ $option_key ] = false;
347
+ }
348
+ // @codingStandardsIgnoreEnd
349
+ }
350
+ }
351
+
352
+ if ( ! is_array( $value ) ) {
353
+ $value = trim( $value );
354
+ }
355
+
356
+ update_site_option( $option, $value );
357
+ }
358
+ }
359
+
360
+ if ( ! count( get_settings_errors() ) ) {
361
+ add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'mainwp-child-reports' ), 'updated' );
362
+ }
363
+
364
+ set_transient( 'settings_errors', get_settings_errors(), 30 );
365
+
366
+ $go_back = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
367
+
368
+ wp_redirect( $go_back );
369
+
370
+ exit;
371
+ }
372
+
373
+ /**
374
+ * Add the Site filter to the Network records screen
375
+ *
376
+ * @filter wp_mainwp_stream_list_table_filters
377
+ *
378
+ * @param $filters
379
+ *
380
+ * @return array
381
+ */
382
+ public function list_table_filters( $filters ) {
383
+ if ( ! is_network_admin() || wp_is_large_network() ) {
384
+ return $filters;
385
+ }
386
+
387
+ $blogs = array();
388
+
389
+ // Display network blog as the first option
390
+ $network_blog = $this->get_network_blog();
391
+
392
+ $blogs[ $network_blog->blog_id ] = array(
393
+ 'label' => $network_blog->blogname,
394
+ 'disabled' => '',
395
+ );
396
+
397
+ // add all sites
398
+ foreach ( wp_mainwp_stream_get_sites() as $blog ) {
399
+ $blog_data = get_blog_details( $blog->blog_id );
400
+
401
+ $blogs[ $blog->blog_id ] = array(
402
+ 'label' => $blog_data->blogname,
403
+ 'disabled' => '',
404
+ );
405
+ }
406
+
407
+ $filters['blog_id'] = array(
408
+ 'title' => __( 'sites', 'mainwp-child-reports' ),
409
+ 'items' => $blogs,
410
+ );
411
+
412
+ return $filters;
413
+ }
414
+
415
+ /**
416
+ * Add the Site toggle to screen options in network admin
417
+ *
418
+ * @param $filters
419
+ *
420
+ * @return array
421
+ */
422
+ public function toggle_filters( $filters ) {
423
+ if ( is_network_admin() ) {
424
+ $filters['blog_id'] = esc_html__( 'Site', 'mainwp-child-reports' );
425
+ }
426
+
427
+ return $filters;
428
+ }
429
+
430
+ /**
431
+ * Add the network suffix to the $screen_id when in the network admin
432
+ *
433
+ * @param $screen_id
434
+ *
435
+ * @return string
436
+ */
437
+ public function list_table_screen_id( $screen_id ) {
438
+ if ( $screen_id && is_network_admin() ) {
439
+ if ( '-network' !== substr( $screen_id, -8 ) ) {
440
+ $screen_id .= '-network';
441
+ }
442
+ }
443
+
444
+ return $screen_id;
445
+ }
446
+
447
+ /**
448
+ * Set blog_id for network admin activity
449
+ *
450
+ * @return int
451
+ */
452
+ public function blog_id_logged( $blog_id ) {
453
+ return is_network_admin() ? 0 : $blog_id;
454
+ }
455
+
456
+ /**
457
+ * Customize query args on multisite installs
458
+ *
459
+ * @filter wp_mainwp_stream_query_args
460
+ *
461
+ * @param array $args
462
+ *
463
+ * @return array
464
+ */
465
+ public function network_query_args( $args ) {
466
+ $args['site_id'] = is_numeric( $args['site_id'] ) ? $args['site_id'] : get_current_site()->id;
467
+ $args['blog_id'] = is_numeric( $args['blog_id'] ) ? $args['blog_id'] : ( is_network_admin() ? null : get_current_blog_id() );
468
+
469
+ return $args;
470
+ }
471
+
472
+ /**
473
+ * Add site count to the page title in the network admin
474
+ *
475
+ * @filter wp_mainwp_stream_admin_page_title
476
+ *
477
+ * @param string $page_title
478
+ *
479
+ * @return string
480
+ */
481
+ public function network_admin_page_title( $page_title ) {
482
+ if ( is_network_admin() ) {
483
+ // translators: Placeholder refers to a number of sites on the network (e.g. "42")
484
+ $site_count = sprintf( _n( '%d site', '%d sites', get_blog_count(), 'mainwp-child-reports' ), number_format( get_blog_count() ) );
485
+ $page_title = sprintf( '%s (%s)', $page_title, $site_count );
486
+ }
487
+
488
+ return $page_title;
489
+ }
490
+
491
+ /**
492
+ * Add the Site column to the network stream records
493
+ *
494
+ * @param $columns
495
+ *
496
+ * @return mixed
497
+ */
498
+ public function network_admin_columns( $columns ) {
499
+ if ( is_network_admin() || $this->ajax_network_admin() ) {
500
+ $columns = array_merge(
501
+ array_slice( $columns, 0, -1 ),
502
+ array(
503
+ 'blog_id' => esc_html__( 'Site', 'mainwp-child-reports' ),
504
+ ),
505
+ array_slice( $columns, -1 )
506
+ );
507
+ }
508
+
509
+ return $columns;
510
+ }
511
+
512
+ /**
513
+ * Prevent the Blogs connector from loading when not in Network Admin
514
+ *
515
+ * @param $connectors
516
+ *
517
+ * @return mixed
518
+ */
519
+ public function hide_blogs_connector( $connectors ) {
520
+ if ( ! is_network_admin() ) {
521
+ return array_diff( $connectors, array( 'Connector_Blogs' ) );
522
+ }
523
+
524
+ return $connectors;
525
+ }
526
+ }
classes/class-plugin.php ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Plugin {
5
+ /**
6
+ * Plugin version number
7
+ *
8
+ * @const string
9
+ */
10
+ const VERSION = '3.5';
11
+
12
+ /**
13
+ * WP-CLI command
14
+ *
15
+ * @const string
16
+ */
17
+ const WP_CLI_COMMAND = 'mainwp_stream';
18
+
19
+ /**
20
+ * @var Admin
21
+ */
22
+ public $admin;
23
+
24
+ /**
25
+ * @var Alerts
26
+ */
27
+ public $alerts;
28
+
29
+ /**
30
+ * @var Alerts_List
31
+ */
32
+ public $alerts_list;
33
+
34
+ /**
35
+ * @var Connectors
36
+ */
37
+ public $connectors;
38
+
39
+ /**
40
+ * @var DB
41
+ */
42
+ public $db;
43
+
44
+ /**
45
+ * @var Log
46
+ */
47
+ public $log;
48
+
49
+ /**
50
+ * @var Settings
51
+ */
52
+ public $settings;
53
+
54
+ /**
55
+ * @var Install
56
+ */
57
+ public $install;
58
+
59
+ /**
60
+ * URLs and Paths used by the plugin
61
+ *
62
+ * @var array
63
+ */
64
+ public $locations = array();
65
+
66
+
67
+ /**
68
+ * @var Child_Helper
69
+ */
70
+ public $child_helper;
71
+
72
+ /**
73
+ * Class constructor
74
+ */
75
+ public function __construct() {
76
+ $locate = $this->locate_plugin();
77
+
78
+ $this->locations = array(
79
+ 'plugin' => $locate['plugin_basename'],
80
+ 'dir' => $locate['dir_path'],
81
+ 'url' => $locate['dir_url'],
82
+ 'inc_dir' => $locate['dir_path'] . 'includes/',
83
+ 'class_dir' => $locate['dir_path'] . 'classes/',
84
+ );
85
+
86
+ spl_autoload_register( array( $this, 'autoload' ) );
87
+
88
+ // Load helper functions
89
+ require_once $this->locations['inc_dir'] . 'functions.php';
90
+
91
+ // Load DB helper interface/class
92
+ $driver_class = apply_filters( 'wp_mainwp_stream_db_driver', '\WP_MainWP_Stream\DB_Driver_WPDB' );
93
+ $driver = null;
94
+
95
+ if ( class_exists( $driver_class ) ) {
96
+ $driver = new $driver_class();
97
+ $this->db = new DB( $driver );
98
+ }
99
+
100
+ $error = false;
101
+ if ( ! $this->db ) {
102
+ $error = esc_html__( 'Stream: Could not load chosen DB driver.', 'mainwp-child-reports' );
103
+ } elseif ( ! $driver instanceof DB_Driver ) {
104
+ $error = esc_html__( 'Stream: DB driver must implement DB Driver interface.', 'mainwp-child-reports' );
105
+ }
106
+
107
+ if ( $error ) {
108
+ wp_die(
109
+ esc_html( $error ),
110
+ esc_html__( 'Reports DB Error', 'mainwp-child-reports' )
111
+ );
112
+ }
113
+
114
+ // Load languages
115
+ add_action( 'plugins_loaded', array( $this, 'i18n' ) );
116
+
117
+ // Load logger class
118
+ $this->log = apply_filters( 'wp_mainwp_stream_log_handler', new Log( $this ) );
119
+
120
+ // Load settings and connectors after widgets_init and before the default init priority
121
+ add_action( 'init', array( $this, 'init' ), 9 );
122
+
123
+ // Add frontend indicator
124
+ add_action( 'wp_head', array( $this, 'frontend_indicator' ) );
125
+
126
+ // Change DB driver after plugin loaded if any add-ons want to replace
127
+ add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 );
128
+
129
+ // Load admin area classes
130
+ if ( is_admin() || ( defined( 'WP_MAINWP_STREAM_DEV_DEBUG' ) && WP_MAINWP_STREAM_DEV_DEBUG ) || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
131
+ $this->admin = new Admin( $this );
132
+ $this->install = $driver->setup_storage( $this );
133
+ } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
134
+ $this->admin = new Admin( $this, $driver );
135
+ }
136
+ $this->child_helper = new MainWP_Child_Report_Helper( $this );
137
+
138
+ // Load WP-CLI command
139
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
140
+ \WP_CLI::add_command( self::WP_CLI_COMMAND, 'WP_MainWP_Stream\CLI' );
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Autoloader for classes
146
+ *
147
+ * @param string $class
148
+ */
149
+ public function autoload( $class ) {
150
+ if ( ! preg_match( '/^(?P<namespace>.+)\\\\(?P<autoload>[^\\\\]+)$/', $class, $matches ) ) {
151
+ return;
152
+ }
153
+
154
+ static $reflection;
155
+
156
+ if ( empty( $reflection ) ) {
157
+ $reflection = new \ReflectionObject( $this );
158
+ }
159
+
160
+ if ( $reflection->getNamespaceName() !== $matches['namespace'] ) {
161
+ return;
162
+ }
163
+
164
+ $autoload_name = $matches['autoload'];
165
+ $autoload_dir = \trailingslashit( $this->locations['class_dir'] );
166
+ $autoload_path = sprintf( '%sclass-%s.php', $autoload_dir, strtolower( str_replace( '_', '-', $autoload_name ) ) );
167
+
168
+ if ( is_readable( $autoload_path ) ) {
169
+ require_once $autoload_path;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Loads the translation files.
175
+ *
176
+ * @action plugins_loaded
177
+ */
178
+ public function i18n() {
179
+ load_plugin_textdomain( 'mainwp-child-reports', false, dirname( $this->locations['plugin'] ) . '/languages/' );
180
+ }
181
+
182
+ /*
183
+ * Load Settings, Notifications, and Connectors
184
+ *
185
+ * @action init
186
+ */
187
+ public function init() {
188
+ $this->settings = new Settings( $this );
189
+ $this->connectors = new Connectors( $this );
190
+ $this->alerts = new Alerts( $this );
191
+ $this->alerts_list = new Alerts_List( $this );
192
+
193
+ }
194
+
195
+ /**
196
+ * Displays an HTML comment in the frontend head to indicate that Stream is activated,
197
+ * and which version of Stream is currently in use.
198
+ *
199
+ * @action wp_head
200
+ *
201
+ * @return string|void An HTML comment, or nothing if the value is filtered out.
202
+ */
203
+ public function frontend_indicator() {
204
+ $comment = sprintf( 'Reports WordPress user activity plugin v%s', esc_html( $this->get_version() ) ); // Localization not needed
205
+
206
+ /**
207
+ * Filter allows the HTML output of the frontend indicator comment
208
+ * to be altered or removed, if desired.
209
+ *
210
+ * @return string The content of the HTML comment
211
+ */
212
+ $comment = apply_filters( 'wp_mainwp_stream_frontend_indicator', $comment );
213
+
214
+ if ( ! empty( $comment ) ) {
215
+ echo sprintf( "<!-- %s -->\n", esc_html( $comment ) ); // xss ok
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Version of plugin_dir_url() which works for plugins installed in the plugins directory,
221
+ * and for plugins bundled with themes.
222
+ *
223
+ * @throws \Exception
224
+ *
225
+ * @return array
226
+ */
227
+ private function locate_plugin() {
228
+ $dir_url = trailingslashit( plugins_url( '', dirname( __FILE__ ) ) );
229
+ $dir_path = plugin_dir_path( dirname( __FILE__ ) );
230
+ $dir_basename = basename( $dir_path );
231
+ $plugin_basename = trailingslashit( $dir_basename ) . $dir_basename . '.php';
232
+
233
+ return compact( 'dir_url', 'dir_path', 'dir_basename', 'plugin_basename' );
234
+ }
235
+
236
+ /**
237
+ * Getter for the version number.
238
+ *
239
+ * @return string
240
+ */
241
+ public function get_version() {
242
+ return self::VERSION;
243
+ }
244
+
245
+ /**
246
+ * Change plugin database driver in case driver plugin loaded after stream
247
+ */
248
+ public function plugins_loaded() {
249
+ // Load DB helper interface/class
250
+ $driver_class = apply_filters( 'wp_mainwp_stream_db_driver', '\WP_MainWP_Stream\DB_Driver_WPDB' );
251
+
252
+ if ( class_exists( $driver_class ) ) {
253
+ $driver = new $driver_class();
254
+ $this->db = new DB( $driver );
255
+ }
256
+ }
257
+ }
classes/class-preview-list-table.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ class Preview_List_Table extends List_Table {
6
+
7
+ /**
8
+ * Class constructor.
9
+ *
10
+ * @param Plugin $plugin Plugin object.
11
+ *
12
+ * @return void
13
+ */
14
+ public function __construct( $plugin ) {
15
+ $this->plugin = $plugin;
16
+ parent::__construct( $plugin );
17
+ }
18
+
19
+ /**
20
+ * Sets up the records for display.
21
+ *
22
+ * @param array $items List of items for display.
23
+ *
24
+ * @return void
25
+ */
26
+ public function set_records( $items ) {
27
+ $columns = $this->get_columns();
28
+ $sortable = $this->get_sortable_columns();
29
+ $hidden = $this->get_hidden_columns();
30
+ $primary = $columns['summary'];
31
+
32
+ $this->_column_headers = array(
33
+ $columns,
34
+ $hidden,
35
+ $sortable,
36
+ $primary,
37
+ );
38
+
39
+ $this->items = $items;
40
+ }
41
+
42
+ /**
43
+ * Display the table
44
+ *
45
+ * @since 3.1.0
46
+ * @access public
47
+ */
48
+ public function display() {
49
+ ?>
50
+ <table class="wp-list-table <?php esc_attr( implode( ' ', $this->get_table_classes() ) ); ?>">
51
+ <thead>
52
+ <tr>
53
+ <?php $this->print_column_headers(); ?>
54
+ </tr>
55
+ </thead>
56
+
57
+ <tbody id="the-list">
58
+ <?php $this->display_rows_or_placeholder(); ?>
59
+ </tbody>
60
+
61
+ <tfoot>
62
+ <tr>
63
+ <?php $this->print_column_headers( false ); ?>
64
+ </tr>
65
+ </tfoot>
66
+
67
+ </table>
68
+ <?php
69
+ }
70
+ }
classes/class-query.php ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Query {
5
+ /**
6
+ * Hold the number of records found
7
+ *
8
+ * @var int
9
+ */
10
+ public $found_records = 0;
11
+
12
+ /**
13
+ * Query records
14
+ *
15
+ * @param array Query args
16
+ *
17
+ * @return array Stream Records
18
+ */
19
+ public function query( $args ) {
20
+ global $wpdb;
21
+
22
+ $join = '';
23
+ $where = '';
24
+
25
+ /**
26
+ * PARSE CORE PARAMS
27
+ */
28
+ if ( is_numeric( $args['site_id'] ) ) {
29
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.site_id = %d", $args['site_id'] );
30
+ }
31
+
32
+ if ( is_numeric( $args['blog_id'] ) ) {
33
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.blog_id = %d", $args['blog_id'] );
34
+ }
35
+
36
+ if ( is_numeric( $args['object_id'] ) ) {
37
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.object_id = %d", $args['object_id'] );
38
+ }
39
+
40
+ if ( is_numeric( $args['user_id'] ) ) {
41
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.user_id = %d", $args['user_id'] );
42
+ }
43
+
44
+ if ( ! empty( $args['user_role'] ) ) {
45
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.user_role = %s", $args['user_role'] );
46
+ }
47
+
48
+ if ( ! empty( $args['search'] ) ) {
49
+ $field = ! empty( $args['search_field'] ) ? $args['search_field'] : 'summary';
50
+
51
+ // Sanitize field
52
+ $allowed_fields = array( 'ID', 'site_id', 'blog_id', 'object_id', 'user_id', 'user_role', 'created', 'summary', 'connector', 'context', 'action', 'ip' );
53
+ if ( in_array( $field, $allowed_fields, true ) ) {
54
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.{$field} LIKE %s", "%{$args['search']}%" ); // @codingStandardsIgnoreLine can't prepare column name
55
+ }
56
+ }
57
+
58
+ if ( ! empty( $args['connector'] ) ) {
59
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.connector = %s", $args['connector'] );
60
+ }
61
+
62
+ if ( ! empty( $args['context'] ) ) {
63
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.context = %s", $args['context'] );
64
+ }
65
+
66
+ if ( ! empty( $args['action'] ) ) {
67
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.action = %s", $args['action'] );
68
+ }
69
+
70
+ if ( ! empty( $args['ip'] ) ) {
71
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.ip = %s", wp_mainwp_stream_filter_var( $args['ip'], FILTER_VALIDATE_IP ) );
72
+ }
73
+
74
+ /**
75
+ * PARSE DATE PARAM FAMILY
76
+ */
77
+ if ( ! empty( $args['date'] ) ) {
78
+ $args['date_from'] = $args['date'];
79
+ $args['date_to'] = $args['date'];
80
+ }
81
+
82
+ if ( ! empty( $args['date_from'] ) ) {
83
+ $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date_from'] . ' 00:00:00' ) ) );
84
+ $where .= $wpdb->prepare( " AND DATE($wpdb->mainwp_stream.created) >= %s", $date );
85
+ }
86
+
87
+ if ( ! empty( $args['date_to'] ) ) {
88
+ $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date_to'] . ' 23:59:59' ) ) );
89
+ $where .= $wpdb->prepare( " AND DATE($wpdb->mainwp_stream.created) <= %s", $date );
90
+ }
91
+
92
+ if ( ! empty( $args['date_after'] ) ) {
93
+ $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date_after'] ) ) );
94
+ $where .= $wpdb->prepare( " AND DATE($wpdb->mainwp_stream.created) > %s", $date );
95
+ }
96
+
97
+ if ( ! empty( $args['date_before'] ) ) {
98
+ $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date_before'] ) ) );
99
+ $where .= $wpdb->prepare( " AND DATE($wpdb->mainwp_stream.created) < %s", $date );
100
+ }
101
+
102
+
103
+ // mainwp custom parameter
104
+ if ( ! empty( $args['created'] ) ) {
105
+ $created = strtotime( $args['created'] );
106
+ $date = get_gmt_from_date( date( 'Y-m-d H:i:s', $created + 5 ) );
107
+ $where .= $wpdb->prepare( " AND DATE($wpdb->mainwp_stream.created) <= %s", $date );
108
+ $date = get_gmt_from_date( date( 'Y-m-d H:i:s', $created - 5 ) );
109
+ $where .= $wpdb->prepare( " AND DATE($wpdb->mainwp_stream.created) >= %s", $date );
110
+ }
111
+
112
+ /**
113
+ * PARSE __IN PARAM FAMILY
114
+ */
115
+ $ins = array();
116
+
117
+ foreach ( $args as $arg => $value ) {
118
+ if ( '__in' === substr( $arg, -4 ) ) {
119
+ $ins[ $arg ] = $value;
120
+ }
121
+ }
122
+
123
+ if ( ! empty( $ins ) ) {
124
+ foreach ( $ins as $key => $value ) {
125
+ if ( empty( $value ) || ! is_array( $value ) ) {
126
+ continue;
127
+ }
128
+
129
+ $field = str_replace( array( 'record_', '__in' ), '', $key );
130
+ $field = empty( $field ) ? 'ID' : $field;
131
+ $type = is_numeric( array_shift( $value ) ) ? '%d' : '%s';
132
+
133
+ if ( ! empty( $value ) ) {
134
+ $format = '(' . join( ',', array_fill( 0, count( $value ), $type ) ) . ')';
135
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.%s IN {$format}", $field, $value ); // @codingStandardsIgnoreLine prepare okay
136
+ }
137
+ }
138
+ }
139
+
140
+ /**
141
+ * PARSE __NOT_IN PARAM FAMILY
142
+ */
143
+ $not_ins = array();
144
+
145
+ foreach ( $args as $arg => $value ) {
146
+ if ( '__not_in' === substr( $arg, -8 ) ) {
147
+ $not_ins[ $arg ] = $value;
148
+ }
149
+ }
150
+
151
+ if ( ! empty( $not_ins ) ) {
152
+ foreach ( $not_ins as $key => $value ) {
153
+ if ( empty( $value ) || ! is_array( $value ) ) {
154
+ continue;
155
+ }
156
+
157
+ $field = str_replace( array( 'record_', '__not_in' ), '', $key );
158
+ $field = empty( $field ) ? 'ID' : $field;
159
+ $type = is_numeric( array_shift( $value ) ) ? '%d' : '%s';
160
+
161
+ if ( ! empty( $value ) ) {
162
+ $format = '(' . join( ',', array_fill( 0, count( $value ), $type ) ) . ')';
163
+ $where .= $wpdb->prepare( " AND $wpdb->mainwp_stream.%s NOT IN {$format}", $field, $value ); // @codingStandardsIgnoreLine prepare okay
164
+ }
165
+ }
166
+ }
167
+
168
+ // exclude child/report plugins from log results
169
+ if (isset($args['hide_child_reports']) && $args['hide_child_reports']) {
170
+ $child_record_ids = array();
171
+ $sql_meta = "SELECT record_id FROM $wpdb->mainwp_streammeta WHERE meta_key = 'slug' AND (meta_value = 'mainwp-child/mainwp-child.php' OR meta_value = 'mainwp-child-reports/mainwp-child-reports.php')";
172
+ $ret = $wpdb->get_results( $sql_meta, 'ARRAY_A' );
173
+
174
+ if ( is_array($ret) && count($ret)> 0 ) {
175
+ foreach($ret as $val) {
176
+ $child_record_ids[] = $val['record_id'];
177
+ }
178
+ }
179
+ if (count($child_record_ids) > 0) {
180
+ $where .= " AND $wpdb->mainwp_stream.ID NOT IN (" . implode(",", $child_record_ids). ") ";
181
+ }
182
+ }
183
+
184
+
185
+ /**
186
+ * PARSE PAGINATION PARAMS
187
+ */
188
+ $limits = '';
189
+ $page = absint( $args['paged'] );
190
+ $per_page = absint( $args['records_per_page'] );
191
+
192
+ if ( $per_page >= 0 ) {
193
+ $offset = absint( ( $page - 1 ) * $per_page );
194
+ $limits = "LIMIT {$offset}, {$per_page}";
195
+ }
196
+
197
+ /**
198
+ * PARSE ORDER PARAMS
199
+ */
200
+ $order = esc_sql( $args['order'] );
201
+ $orderby = esc_sql( $args['orderby'] );
202
+ $orderable = array( 'ID', 'site_id', 'blog_id', 'object_id', 'user_id', 'user_role', 'summary', 'created', 'connector', 'context', 'action' );
203
+
204
+ if ( in_array( $orderby, $orderable, true ) ) {
205
+ $orderby = sprintf( '%s.%s', $wpdb->mainwp_stream, $orderby );
206
+ } elseif ( 'meta_value_num' === $orderby && ! empty( $args['meta_key'] ) ) {
207
+ $orderby = "CAST($wpdb->mainwp_streammeta.meta_value AS SIGNED)";
208
+ } elseif ( 'meta_value' === $orderby && ! empty( $args['meta_key'] ) ) {
209
+ $orderby = "$wpdb->mainwp_streammeta.meta_value";
210
+ } else {
211
+ $orderby = "$wpdb->mainwp_stream.ID";
212
+ }
213
+
214
+ $orderby = "ORDER BY {$orderby} {$order}";
215
+
216
+ /**
217
+ * PARSE FIELDS PARAMETER
218
+ */
219
+ $fields = (array) $args['fields'];
220
+ $selects = array();
221
+
222
+ if ( ! empty( $fields ) ) {
223
+ foreach ( $fields as $field ) {
224
+ // We'll query the meta table later
225
+ if ( 'meta' === $field ) {
226
+ continue;
227
+ }
228
+
229
+ $selects[] = sprintf( "$wpdb->mainwp_stream.%s", $field );
230
+ }
231
+ } else {
232
+ $selects[] = "$wpdb->mainwp_stream.*";
233
+ }
234
+
235
+ $select = implode( ', ', $selects );
236
+
237
+ /**
238
+ * BUILD THE FINAL QUERY
239
+ */
240
+ $query = "SELECT SQL_CALC_FOUND_ROWS {$select}
241
+ FROM $wpdb->mainwp_stream
242
+ {$join}
243
+ WHERE 1=1 {$where}
244
+ {$orderby}
245
+ {$limits}";
246
+
247
+ /**
248
+ * Filter allows the final query to be modified before execution
249
+ *
250
+ * @param string $query
251
+ * @param array $args
252
+ *
253
+ * @return string
254
+ */
255
+
256
+ $query = apply_filters( 'wp_mainwp_stream_db_query', $query, $args );
257
+
258
+ $items = $wpdb->get_results( $query ); // @codingStandardsIgnoreLine $query already prepared
259
+
260
+ $found_row = $items ? absint( $wpdb->get_var( 'SELECT FOUND_ROWS()' ) ) : 0;
261
+
262
+ // mainwp-child custom query
263
+ if ( isset( $args['with-meta'] ) && $args['with-meta'] && is_array( $items ) && $items ) {
264
+ $ids = array_map( 'absint', wp_list_pluck( $items, 'ID' ) );
265
+ // to fix issue long query
266
+ $start_slice = 0;
267
+ $max_slice = 100;
268
+
269
+ while( $start_slice <= count($ids)) {
270
+ $slice_ids = array_slice($ids, $start_slice, $max_slice);
271
+ $start_slice += $max_slice;
272
+
273
+ if (!empty($slice_ids)) {
274
+ $sql_meta = sprintf(
275
+ "SELECT * FROM $wpdb->mainwp_streammeta WHERE record_id IN ( %s )",
276
+ implode( ',', $slice_ids )
277
+ );
278
+
279
+ $meta_records = $wpdb->get_results( $sql_meta );
280
+ $ids_flip = array_flip( $ids );
281
+
282
+ foreach ( $meta_records as $meta_record ) {
283
+ if ( !empty($meta_record->meta_value) ) {
284
+ $items[ $ids_flip[ $meta_record->record_id ] ]->meta[ $meta_record->meta_key ][] = $meta_record->meta_value;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+
292
+ $result = array();
293
+ /**
294
+ * QUERY THE DATABASE FOR RESULTS
295
+ */
296
+ $result['items'] = $items;
297
+ $result['count'] = $found_row;
298
+
299
+ return $result;
300
+ }
301
+ }
classes/class-record.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Record {
5
+ public $ID;
6
+ public $created;
7
+ public $site_id;
8
+ public $blog_id;
9
+ public $object_id;
10
+ public $user_id;
11
+ public $user_role;
12
+ public $user_meta;
13
+ public $summary;
14
+ public $connector;
15
+ public $context;
16
+ public $action;
17
+ public $ip;
18
+ public $meta;
19
+
20
+ public function __construct( $item ) {
21
+ $this->ID = isset( $item->ID ) ? $item->ID : null;
22
+ $this->created = isset( $item->created ) ? $item->created : null;
23
+ $this->site_id = isset( $item->site_id ) ? $item->site_id : null;
24
+ $this->blog_id = isset( $item->blog_id ) ? $item->blog_id : null;
25
+ $this->object_id = isset( $item->object_id ) ? $item->object_id : null;
26
+ $this->user_id = isset( $item->user_id ) ? $item->user_id : null;
27
+ $this->user_role = isset( $item->user_role ) ? $item->user_role : null;
28
+ $this->user_meta = isset( $item->meta['user_meta'] ) ? $item->meta['user_meta'] : null;
29
+ $this->summary = isset( $item->summary ) ? $item->summary : null;
30
+ $this->connector = isset( $item->connector ) ? $item->connector : null;
31
+ $this->context = isset( $item->context ) ? $item->context : null;
32
+ $this->action = isset( $item->action ) ? $item->action : null;
33
+ $this->ip = isset( $item->ip ) ? $item->ip : null;
34
+ $this->meta = isset( $item->meta ) ? $item->meta : null;
35
+
36
+ if ( isset( $this->meta['user_meta'] ) ) {
37
+ unset( $this->meta['user_meta'] );
38
+ }
39
+ }
40
+
41
+ public function save() {
42
+ if ( ! $this->validate() ) {
43
+ return new \WP_Error( 'validation-error', esc_html__( 'Could not validate record data.', 'mainwp-child-reports' ) );
44
+ }
45
+
46
+ return wp_mainwp_stream_get_instance()->db->insert( (array) $this );
47
+ }
48
+
49
+ public function populate( array $raw ) {
50
+ $keys = get_class_vars( $this );
51
+ $data = array_intersect_key( $raw, $keys );
52
+ foreach ( $data as $key => $val ) {
53
+ $this->{$key} = $val;
54
+ }
55
+ }
56
+
57
+ public function validate() {
58
+ return true;
59
+ }
60
+
61
+ /**
62
+ * Query record meta
63
+ *
64
+ * @param string $meta_key (optional)
65
+ * @param bool $single (optional)
66
+ *
67
+ * @return array
68
+ */
69
+ public function get_meta( $meta_key = '', $single = false ) {
70
+ return get_metadata( 'record', $this->ID, $meta_key, $single );
71
+ }
72
+
73
+ /**
74
+ * Update record meta
75
+ *
76
+ * @param string $meta_key
77
+ * @param mixed $meta_value
78
+ * @param mixed $prev_value (optional)
79
+ *
80
+ * @return bool
81
+ */
82
+ public function update_meta( $meta_key, $meta_value, $prev_value = '' ) {
83
+ return update_metadata( 'record', $this->ID, $meta_key, $meta_value, $prev_value );
84
+ }
85
+
86
+ /**
87
+ * Determine the title of an object that a record is for.
88
+ *
89
+ * @param object Record object
90
+ * @return mixed The title of the object as a string, otherwise false
91
+ */
92
+ public function get_object_title() {
93
+ if ( ! isset( $this->object_id ) || empty( $this->object_id ) ) {
94
+ return false;
95
+ }
96
+
97
+ $output = false;
98
+
99
+ if ( isset( $this->meta->post_title ) && ! empty( $this->meta->post_title ) ) {
100
+ $output = (string) $this->meta->post_title;
101
+ } elseif ( isset( $this->meta->display_name ) && ! empty( $this->meta->display_name ) ) {
102
+ $output = (string) $this->meta->display_name;
103
+ } elseif ( isset( $this->meta->name ) && ! empty( $this->meta->name ) ) {
104
+ $output = (string) $this->meta->name;
105
+ }
106
+
107
+ return $output;
108
+ }
109
+ }
classes/class-settings.php ADDED
@@ -0,0 +1,1153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ use \WP_Roles;
6
+ use \WP_User;
7
+ use \WP_User_Query;
8
+
9
+ class Settings {
10
+
11
+ /**
12
+ * Hold Plugin class
13
+ * @var Plugin
14
+ */
15
+ public $plugin;
16
+
17
+ /**
18
+ * Settings key/identifier
19
+ *
20
+ * @var string
21
+ */
22
+ public $option_key = 'wp_mainwp_stream';
23
+
24
+ /**
25
+ * Network settings key/identifier
26
+ *
27
+ * @var string
28
+ */
29
+ public $network_options_key = 'wp_mainwp_stream_network';
30
+
31
+ /**
32
+ * Plugin settings
33
+ *
34
+ * @var array
35
+ */
36
+ public $options = array();
37
+
38
+ /**
39
+ * Settings fields
40
+ *
41
+ * @var array
42
+ */
43
+ public $fields = array();
44
+
45
+ /**
46
+ * Class constructor.
47
+ *
48
+ * @param Plugin $plugin The main Plugin class.
49
+ */
50
+ public function __construct( $plugin ) {
51
+ $this->plugin = $plugin;
52
+
53
+ $this->option_key = $this->get_option_key();
54
+ $this->options = $this->get_options();
55
+
56
+ // Register settings, and fields
57
+ add_action( 'admin_init', array( $this, 'register_settings' ) );
58
+
59
+ // Remove records when records TTL is shortened
60
+ add_action(
61
+ 'update_option_' . $this->option_key, array(
62
+ $this,
63
+ 'updated_option_ttl_remove_records',
64
+ ), 10, 2
65
+ );
66
+
67
+ // Apply label translations for settings
68
+ add_filter(
69
+ 'wp_mainwp_stream_serialized_labels', array(
70
+ $this,
71
+ 'get_settings_translations',
72
+ )
73
+ );
74
+
75
+ // Ajax callback function to search users
76
+ add_action( 'wp_ajax_stream_get_users', array( $this, 'get_users' ) );
77
+
78
+ // Ajax callback function to search IPs
79
+ add_action( 'wp_ajax_stream_get_ips', array( $this, 'get_ips' ) );
80
+ }
81
+
82
+ /**
83
+ * Ajax callback function to search users, used on exclude setting page
84
+ *
85
+ * @uses \WP_User_Query
86
+ */
87
+ public function get_users() {
88
+ if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
89
+ return;
90
+ }
91
+
92
+ check_ajax_referer( 'stream_get_users', 'nonce' );
93
+
94
+ $response = (object) array(
95
+ 'status' => false,
96
+ 'message' => esc_html__( 'There was an error in the request', 'mainwp-child-reports' ),
97
+ );
98
+
99
+ $search = '';
100
+ $input = wp_mainwp_stream_filter_input( INPUT_POST, 'find' );
101
+
102
+ if ( ! isset( $input['term'] ) ) {
103
+ $search = wp_unslash( trim( $input['term'] ) );
104
+ }
105
+
106
+ $request = (object) array(
107
+ 'find' => $search,
108
+ );
109
+
110
+ add_filter(
111
+ 'user_search_columns', array(
112
+ $this,
113
+ 'add_display_name_search_columns',
114
+ ), 10, 3
115
+ );
116
+
117
+ $users = new WP_User_Query(
118
+ array(
119
+ 'search' => "*{$request->find}*",
120
+ 'search_columns' => array(
121
+ 'user_login',
122
+ 'user_nicename',
123
+ 'user_email',
124
+ 'user_url',
125
+ ),
126
+ 'orderby' => 'display_name',
127
+ 'number' => $this->plugin->admin->preload_users_max,
128
+ )
129
+ );
130
+
131
+ remove_filter(
132
+ 'user_search_columns', array(
133
+ $this,
134
+ 'add_display_name_search_columns',
135
+ ), 10
136
+ );
137
+
138
+ if ( 0 === $users->get_total() ) {
139
+ wp_send_json_error( $response );
140
+ }
141
+ $users_array = $users->results;
142
+
143
+ if ( is_multisite() && is_super_admin() ) {
144
+ $super_admins = get_super_admins();
145
+ foreach ( $super_admins as $admin ) {
146
+ $user = get_user_by( 'login', $admin );
147
+ $users_array[] = $user;
148
+ }
149
+ }
150
+
151
+ $response->status = true;
152
+ $response->message = '';
153
+ $response->roles = $this->get_roles();
154
+ $response->users = array();
155
+ $users_added_to_response = array();
156
+
157
+ foreach ( $users_array as $key => $user ) {
158
+ // exclude duplications:
159
+ if ( array_key_exists( $user->ID, $users_added_to_response ) ) {
160
+ continue;
161
+ } else {
162
+ $users_added_to_response[ $user->ID ] = true;
163
+ }
164
+
165
+ $author = new Author( $user->ID );
166
+
167
+ $args = array(
168
+ 'id' => $author->ID,
169
+ 'text' => $author->display_name,
170
+ );
171
+
172
+ $args['tooltip'] = esc_attr(
173
+ sprintf(
174
+ // translators: Placeholders refers to a user ID, a username, an email address, and a user role (e.g. "42", "administrator", "foo@bar.com", "subscriber").
175
+ __( 'ID: %1$d\nUser: %2$s\nEmail: %3$s\nRole: %4$s', 'mainwp-child-reports' ),
176
+ $author->id,
177
+ $author->user_login,
178
+ $author->user_email,
179
+ ucwords( $author->get_role() )
180
+ )
181
+ );
182
+
183
+ $args['icon'] = $author->get_avatar_src( 32 );
184
+
185
+ $response->users[] = $args;
186
+ }
187
+
188
+ usort(
189
+ $response->users,
190
+ function ( $a, $b ) {
191
+ return strcmp( $a['text'], $b['text'] );
192
+ }
193
+ );
194
+
195
+ if ( empty( $search ) || preg_match( '/wp|cli|system|unknown/i', $search ) ) {
196
+ $author = new Author( 0 );
197
+ $response->users[] = array(
198
+ 'id' => '0',
199
+ 'text' => $author->get_display_name(),
200
+ 'icon' => $author->get_avatar_src( 32 ),
201
+ 'tooltip' => esc_html__( 'Actions performed by the system when a user is not logged in (e.g. auto site upgrader, or invoking WP-CLI without --user)', 'mainwp-child-reports' ),
202
+ );
203
+ }
204
+
205
+ wp_send_json_success( $response );
206
+ }
207
+
208
+ /**
209
+ * Ajax callback function to search IP addresses, used on exclude setting page
210
+ */
211
+ public function get_ips() {
212
+ if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( $this->plugin->admin->settings_cap ) ) {
213
+ return;
214
+ }
215
+
216
+ check_ajax_referer( 'stream_get_ips', 'nonce' );
217
+
218
+ $ips = $this->plugin->db->existing_records( 'ip' );
219
+ $find = wp_mainwp_stream_filter_input( INPUT_POST, 'find' );
220
+
221
+ if ( isset( $find['term'] ) && '' !== $find['term'] ) {
222
+ $ips = array_filter(
223
+ $ips,
224
+ function ( $ip ) use ( $find ) {
225
+ return 0 === strpos( $ip, $find['term'] );
226
+ }
227
+ );
228
+ }
229
+
230
+ if ( $ips ) {
231
+ wp_send_json_success( $ips );
232
+ } else {
233
+ wp_send_json_error();
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Filter the columns to search in a WP_User_Query search.
239
+ *
240
+ * @param array $search_columns Array of column names to be searched.
241
+ * @param string $search Text being searched.
242
+ * @param \WP_User_Query $query current WP_User_Query instance.
243
+ *
244
+ * @return array
245
+ */
246
+ public function add_display_name_search_columns( $search_columns, $search, $query ) {
247
+ unset( $search );
248
+ unset( $query );
249
+
250
+ $search_columns[] = 'display_name';
251
+
252
+ return $search_columns;
253
+ }
254
+
255
+ /**
256
+ * Returns the option key
257
+ *
258
+ * @return string
259
+ */
260
+ public function get_option_key() {
261
+ $option_key = $this->option_key;
262
+
263
+ $current_page = wp_mainwp_stream_filter_input( INPUT_GET, 'page' );
264
+
265
+ if ( ! $current_page ) {
266
+ $current_page = wp_mainwp_stream_filter_input( INPUT_GET, 'action' );
267
+ }
268
+
269
+ if ( 'wp_mainwp_stream_network_settings' === $current_page ) {
270
+ $option_key = $this->network_options_key;
271
+ }
272
+
273
+ return apply_filters( 'wp_mainwp_stream_settings_option_key', $option_key );
274
+ }
275
+
276
+ /**
277
+ * Return settings fields
278
+ *
279
+ * @return array
280
+ */
281
+ public function get_fields() {
282
+
283
+ $branding_text = wp_mainwp_stream_get_instance()->child_helper->get_branding_title();
284
+ $branding_name = !empty($branding_text) ? $branding_text : 'MainWP Child';
285
+
286
+ $fields = array(
287
+ 'general' => array(
288
+ 'title' => esc_html__( 'General', 'mainwp-child-reports' ),
289
+ 'fields' => array(
290
+ // array(
291
+ // 'name' => 'role_access',
292
+ // 'title' => esc_html__( 'Role Access', 'mainwp-child-reports' ),
293
+ // 'type' => 'multi_checkbox',
294
+ // 'desc' => esc_html__( 'Users from the selected roles above will have permission to view Stream Records. However, only site Administrators can access Stream Settings.', 'mainwp-child-reports' ),
295
+ // 'choices' => $this->get_roles(),
296
+ // 'default' => array( 'administrator' ),
297
+ // ),
298
+ array(
299
+ 'name' => 'records_ttl',
300
+ 'title' => esc_html__( 'Keep Records for', 'mainwp-child-reports' ),
301
+ 'type' => 'number',
302
+ 'class' => 'small-text',
303
+ 'desc' => esc_html__( 'Maximum number of days to keep activity records.', 'mainwp-child-reports' ),
304
+ 'default' => 30,
305
+ 'min' => 1,
306
+ 'max' => 999,
307
+ 'step' => 1,
308
+ 'after_field' => esc_html__( 'days', 'mainwp-child-reports' ),
309
+ ),
310
+ array(
311
+ 'name' => 'keep_records_indefinitely',
312
+ 'title' => esc_html__( 'Keep Records Indefinitely', 'mainwp-child-reports' ),
313
+ 'type' => 'checkbox',
314
+ 'desc' => sprintf( '<strong>%s</strong> %s', esc_html__( 'Not recommended.', 'mainwp-child-reports' ), esc_html__( 'Purging old records helps to keep your WordPress installation running optimally.', 'mainwp-child-reports' ) ),
315
+ 'after_field' => esc_html__( 'Enabled', 'mainwp-child-reports' ),
316
+ 'default' => 0,
317
+ ),
318
+ ),
319
+ ),
320
+ 'exclude' => array(
321
+ 'title' => esc_html__( 'Exclude', 'mainwp-child-reports' ),
322
+ 'fields' => array(
323
+ array(
324
+ 'name' => 'rules',
325
+ 'title' => esc_html__( 'Exclude Rules', 'mainwp-child-reports' ),
326
+ 'type' => 'rule_list',
327
+ 'desc' => esc_html__( 'Create rules to exclude certain kinds of activity from being recorded by ' . $branding_name . ' Reports.', 'mainwp-child-reports' ),
328
+ 'default' => array(),
329
+ 'nonce' => 'stream_get_ips',
330
+ ),
331
+ ),
332
+ ),
333
+ 'advanced' => array(
334
+ 'title' => esc_html__( 'Advanced', 'mainwp-child-reports' ),
335
+ 'fields' => array(
336
+ array(
337
+ 'name' => 'comment_flood_tracking',
338
+ 'title' => esc_html__( 'Comment Flood Tracking', 'mainwp-child-reports' ),
339
+ 'type' => 'checkbox',
340
+ 'desc' => esc_html__( 'WordPress will automatically prevent duplicate comments from flooding the database. By default, ' . $branding_name . ' Reports does not track these attempts unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'mainwp-child-reports' ),
341
+ 'after_field' => esc_html__( 'Enabled', 'mainwp-child-reports' ),
342
+ 'default' => 0,
343
+ ),
344
+ array(
345
+ 'name' => 'delete_all_records',
346
+ 'title' => esc_html__( 'Reset ' . $branding_name . ' Reports Database', 'mainwp-child-reports' ),
347
+ 'type' => 'link',
348
+ 'href' => add_query_arg(
349
+ array(
350
+ 'action' => 'wp_mainwp_stream_reset',
351
+ 'wp_mainwp_stream_nonce_reset' => wp_create_nonce( 'stream_nonce_reset' ),
352
+ ),
353
+ admin_url( 'admin-ajax.php' )
354
+ ),
355
+ 'class' => 'warning',
356
+ 'desc' => esc_html__( 'Warning: This will delete all activity records from the database.', 'mainwp-child-reports' ),
357
+ 'default' => 0,
358
+ 'sticky' => 'bottom',
359
+ ),
360
+ ),
361
+ ),
362
+ );
363
+
364
+ // If Akismet is active, allow Admins to opt-in to Akismet tracking
365
+ if ( class_exists( 'Akismet' ) ) {
366
+ $akismet_tracking = array(
367
+ 'name' => 'akismet_tracking',
368
+ 'title' => esc_html__( 'Akismet Tracking', 'mainwp-child-reports' ),
369
+ 'type' => 'checkbox',
370
+ 'desc' => esc_html__( 'Akismet already keeps statistics for comment attempts that it blocks as SPAM. By default, ' . $branding_name . ' Reports does not track these attempts unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'mainwp-child-reports' ),
371
+ 'after_field' => esc_html__( 'Enabled', 'mainwp-child-reports' ),
372
+ 'default' => 0,
373
+ );
374
+
375
+ array_push( $fields['advanced']['fields'], $akismet_tracking );
376
+ }
377
+
378
+ // If WP Cron is enabled, allow Admins to opt-in to WP Cron tracking
379
+ if ( wp_mainwp_stream_is_cron_enabled() ) {
380
+ $wp_cron_tracking = array(
381
+ 'name' => 'wp_cron_tracking',
382
+ 'title' => esc_html__( 'WP Cron Tracking', 'mainwp-child-reports' ),
383
+ 'type' => 'checkbox',
384
+ 'desc' => esc_html__( 'By default, ' . $branding_name . ' Reports does not track activity performed by WordPress cron events unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'mainwp-child-reports' ),
385
+ 'after_field' => esc_html__( 'Enabled', 'mainwp-child-reports' ),
386
+ 'default' => 0,
387
+ );
388
+
389
+ array_push( $fields['advanced']['fields'], $wp_cron_tracking );
390
+ }
391
+
392
+ /**
393
+ * Filter allows for modification of options fields
394
+ *
395
+ * @return array Array of option fields
396
+ */
397
+ $this->fields = apply_filters( 'wp_mainwp_stream_settings_option_fields', $fields );
398
+
399
+ // Sort option fields in each tab by title ASC
400
+ foreach ( $this->fields as $tab => $options ) {
401
+ $titles = array();
402
+
403
+ foreach ( $options['fields'] as $field ) {
404
+ $prefix = null;
405
+
406
+ if ( ! empty( $field['sticky'] ) ) {
407
+ $prefix = ( 'bottom' === $field['sticky'] ) ? 'ZZZ' : 'AAA';
408
+ }
409
+
410
+ $titles[] = $prefix . $field['title'];
411
+ }
412
+
413
+ array_multisort( $titles, SORT_ASC, $this->fields[ $tab ]['fields'] );
414
+ }
415
+
416
+ return $this->fields;
417
+ }
418
+
419
+ /**
420
+ * Returns a list of options based on the current screen.
421
+ *
422
+ * @return array
423
+ */
424
+ public function get_options() {
425
+ $option_key = $this->option_key;
426
+ $defaults = $this->get_defaults( $option_key );
427
+
428
+ /**
429
+ * Filter allows for modification of options
430
+ *
431
+ * @param array
432
+ *
433
+ * @return array Updated array of options
434
+ */
435
+ return apply_filters(
436
+ 'wp_mainwp_stream_settings_options',
437
+ wp_parse_args(
438
+ is_network_admin() ? (array) get_site_option( $option_key, array() ) : (array) get_option( $option_key, array() ),
439
+ $defaults
440
+ ),
441
+ $option_key
442
+ );
443
+ }
444
+
445
+ /**
446
+ * Iterate through registered fields and extract default values
447
+ *
448
+ * @return array
449
+ */
450
+ public function get_defaults() {
451
+ $fields = $this->get_fields();
452
+ $defaults = array();
453
+
454
+ foreach ( $fields as $section_name => $section ) {
455
+ foreach ( $section['fields'] as $field ) {
456
+ $defaults[ $section_name . '_' . $field['name'] ] = isset( $field['default'] ) ? $field['default'] : null;
457
+ }
458
+ }
459
+
460
+ return (array) $defaults;
461
+ }
462
+
463
+ /**
464
+ * Registers settings fields and sections
465
+ *
466
+ * @return void
467
+ */
468
+ public function register_settings() {
469
+ $sections = $this->get_fields();
470
+
471
+ register_setting(
472
+ $this->option_key, $this->option_key, array(
473
+ $this,
474
+ 'sanitize_settings',
475
+ )
476
+ );
477
+
478
+ foreach ( $sections as $section_name => $section ) {
479
+ add_settings_section(
480
+ $section_name,
481
+ null,
482
+ '__return_false',
483
+ $this->option_key
484
+ );
485
+
486
+ foreach ( $section['fields'] as $field_idx => $field ) {
487
+ if ( ! isset( $field['type'] ) ) { // No field type associated, skip, no GUI
488
+ continue;
489
+ }
490
+
491
+ add_settings_field(
492
+ $field['name'],
493
+ $field['title'],
494
+ ( isset( $field['callback'] ) ? $field['callback'] : array(
495
+ $this,
496
+ 'output_field',
497
+ ) ),
498
+ $this->option_key,
499
+ $section_name,
500
+ $field + array(
501
+ 'section' => $section_name,
502
+ 'label_for' => sprintf( '%s_%s_%s', $this->option_key, $section_name, $field['name'] ),
503
+ // xss ok
504
+ )
505
+ );
506
+ }
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Sanitization callback for settings field values before save
512
+ *
513
+ * @param array $input
514
+ *
515
+ * @return array
516
+ */
517
+ public function sanitize_settings( $input ) {
518
+ $output = array();
519
+ $sections = $this->get_fields();
520
+
521
+ foreach ( $sections as $section => $data ) {
522
+ if ( empty( $data['fields'] ) || ! is_array( $data['fields'] ) ) {
523
+ continue;
524
+ }
525
+
526
+ foreach ( $data['fields'] as $field ) {
527
+ $type = ! empty( $field['type'] ) ? $field['type'] : null;
528
+ $name = ! empty( $field['name'] ) ? sprintf( '%s_%s', $section, $field['name'] ) : null;
529
+
530
+ if ( empty( $type ) || ! isset( $input[ $name ] ) || '' === $input[ $name ] ) {
531
+ continue;
532
+ }
533
+
534
+ // Sanitize depending on the type of field.
535
+ switch ( $type ) {
536
+ case 'number':
537
+ $output[ $name ] = is_numeric( $input[ $name ] ) ? intval( trim( $input[ $name ] ) ) : '';
538
+ break;
539
+ case 'checkbox':
540
+ $output[ $name ] = is_numeric( $input[ $name ] ) ? absint( trim( $input[ $name ] ) ) : '';
541
+ break;
542
+ default:
543
+ if ( is_array( $input[ $name ] ) ) {
544
+ $output[ $name ] = $input[ $name ];
545
+
546
+ // Support all values in multidimentional arrays too.
547
+ array_walk_recursive(
548
+ $output[ $name ], function ( &$v, $k ) {
549
+ $v = trim( $v );
550
+ }
551
+ );
552
+ } else {
553
+ $output[ $name ] = trim( $input[ $name ] );
554
+ }
555
+ }
556
+ }
557
+ }
558
+
559
+ return $output;
560
+ }
561
+
562
+ /**
563
+ * Compile HTML needed for displaying the field
564
+ *
565
+ * @param array $field Field settings
566
+ *
567
+ * @return string HTML to be displayed
568
+ */
569
+ public function render_field( $field ) {
570
+ $output = null;
571
+ $type = isset( $field['type'] ) ? $field['type'] : null;
572
+ $section = isset( $field['section'] ) ? $field['section'] : null;
573
+ $name = isset( $field['name'] ) ? $field['name'] : null;
574
+ $class = isset( $field['class'] ) ? $field['class'] : null;
575
+ $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : null;
576
+ $description = isset( $field['desc'] ) ? $field['desc'] : null;
577
+ $href = isset( $field['href'] ) ? $field['href'] : null;
578
+ $rows = isset( $field['rows'] ) ? $field['rows'] : 10;
579
+ $cols = isset( $field['cols'] ) ? $field['cols'] : 50;
580
+ $after_field = isset( $field['after_field'] ) ? $field['after_field'] : null;
581
+ $default = isset( $field['default'] ) ? $field['default'] : null;
582
+ $min = isset( $field['min'] ) ? $field['min'] : 0;
583
+ $max = isset( $field['max'] ) ? $field['max'] : 999;
584
+ $step = isset( $field['step'] ) ? $field['step'] : 1;
585
+ $title = isset( $field['title'] ) ? $field['title'] : null;
586
+ $nonce = isset( $field['nonce'] ) ? $field['nonce'] : null;
587
+
588
+ if ( isset( $field['value'] ) ) {
589
+ $current_value = $field['value'];
590
+ } else {
591
+ if ( isset( $this->options[ $section . '_' . $name ] ) ) {
592
+ $current_value = $this->options[ $section . '_' . $name ];
593
+ } else {
594
+ $current_value = null;
595
+ }
596
+ }
597
+
598
+ $option_key = $this->option_key;
599
+
600
+ if ( is_callable( $current_value ) ) {
601
+ $current_value = call_user_func( $current_value );
602
+ }
603
+
604
+ if ( ! $type || ! $section || ! $name ) {
605
+ return '';
606
+ }
607
+
608
+ if ( 'multi_checkbox' === $type && ( empty( $field['choices'] ) || ! is_array( $field['choices'] ) ) ) {
609
+ return '';
610
+ }
611
+
612
+ switch ( $type ) {
613
+ case 'text':
614
+ case 'number':
615
+ $output = sprintf(
616
+ '<input type="%1$s" name="%2$s[%3$s_%4$s]" id="%2$s_%3$s_%4$s" class="%5$s" placeholder="%6$s" min="%7$d" max="%8$d" step="%9$d" value="%10$s" /> %11$s',
617
+ esc_attr( $type ),
618
+ esc_attr( $option_key ),
619
+ esc_attr( $section ),
620
+ esc_attr( $name ),
621
+ esc_attr( $class ),
622
+ esc_attr( $placeholder ),
623
+ esc_attr( $min ),
624
+ esc_attr( $max ),
625
+ esc_attr( $step ),
626
+ esc_attr( $current_value ),
627
+ wp_kses_post( $after_field )
628
+ );
629
+ break;
630
+ case 'textarea':
631
+ $output = sprintf(
632
+ '<textarea name="%1$s[%2$s_%3$s]" id="%1$s_%2$s_%3$s" class="%4$s" placeholder="%5$s" rows="%6$d" cols="%7$d">%8$s</textarea> %9$s',
633
+ esc_attr( $option_key ),
634
+ esc_attr( $section ),
635
+ esc_attr( $name ),
636
+ esc_attr( $class ),
637
+ esc_attr( $placeholder ),
638
+ absint( $rows ),
639
+ absint( $cols ),
640
+ esc_textarea( $current_value ),
641
+ wp_kses_post( $after_field )
642
+ );
643
+ break;
644
+ case 'checkbox':
645
+ if ( isset( $current_value ) ) {
646
+ $value = $current_value;
647
+ } elseif ( isset( $default ) ) {
648
+ $value = $default;
649
+ } else {
650
+ $value = 0;
651
+ }
652
+
653
+ $output = sprintf(
654
+ '<label><input type="checkbox" name="%1$s[%2$s_%3$s]" id="%1$s[%2$s_%3$s]" value="1" %4$s /> %5$s</label>',
655
+ esc_attr( $option_key ),
656
+ esc_attr( $section ),
657
+ esc_attr( $name ),
658
+ checked( $value, 1, false ),
659
+ wp_kses_post( $after_field )
660
+ );
661
+ break;
662
+ case 'multi_checkbox':
663
+ $output = sprintf(
664
+ '<div id="%1$s[%2$s_%3$s]"><fieldset>',
665
+ esc_attr( $option_key ),
666
+ esc_attr( $section ),
667
+ esc_attr( $name )
668
+ );
669
+ // Fallback if nothing is selected.
670
+ $output .= sprintf(
671
+ '<input type="hidden" name="%1$s[%2$s_%3$s][]" value="__placeholder__" />',
672
+ esc_attr( $option_key ),
673
+ esc_attr( $section ),
674
+ esc_attr( $name )
675
+ );
676
+ $current_value = (array) $current_value;
677
+ $choices = $field['choices'];
678
+ if ( is_callable( $choices ) ) {
679
+ $choices = call_user_func( $choices );
680
+ }
681
+ foreach ( $choices as $value => $label ) {
682
+ $output .= sprintf(
683
+ '<label>%1$s <span>%2$s</span></label><br />',
684
+ sprintf(
685
+ '<input type="checkbox" name="%1$s[%2$s_%3$s][]" value="%4$s" %5$s />',
686
+ esc_attr( $option_key ),
687
+ esc_attr( $section ),
688
+ esc_attr( $name ),
689
+ esc_attr( $value ),
690
+ checked( in_array( $value, $current_value, true ), true, false )
691
+ ),
692
+ esc_html( $label )
693
+ );
694
+ }
695
+ $output .= '</fieldset></div>';
696
+ break;
697
+ case 'select':
698
+ $current_value = $this->options[ $section . '_' . $name ];
699
+ $default_value = isset( $default['value'] ) ? $default['value'] : '-1';
700
+ $default_name = isset( $default['name'] ) ? $default['name'] : 'Choose Setting';
701
+
702
+ $output = sprintf(
703
+ '<select name="%1$s[%2$s_%3$s]" class="%1$s_%2$s_%3$s">',
704
+ esc_attr( $option_key ),
705
+ esc_attr( $section ),
706
+ esc_attr( $name )
707
+ );
708
+ $output .= sprintf(
709
+ '<option value="%1$s" %2$s>%3$s</option>',
710
+ esc_attr( $default_value ),
711
+ checked( $default_value === $current_value, true, false ),
712
+ esc_html( $default_name )
713
+ );
714
+ foreach ( $field['choices'] as $value => $label ) {
715
+ $output .= sprintf(
716
+ '<option value="%1$s" %2$s>%3$s</option>',
717
+ esc_attr( $value ),
718
+ checked( $value === $current_value, true, false ),
719
+ esc_html( $label )
720
+ );
721
+ }
722
+ $output .= '</select>';
723
+ break;
724
+ case 'file':
725
+ $output = sprintf(
726
+ '<input type="file" name="%1$s[%2$s_%3$s]" class="%4$s">',
727
+ esc_attr( $option_key ),
728
+ esc_attr( $section ),
729
+ esc_attr( $name ),
730
+ esc_attr( $class )
731
+ );
732
+ break;
733
+ case 'link':
734
+ $output = sprintf(
735
+ '<a id="%1$s_%2$s_%3$s" class="%4$s" href="%5$s">%6$s</a>',
736
+ esc_attr( $option_key ),
737
+ esc_attr( $section ),
738
+ esc_attr( $name ),
739
+ esc_attr( $class ),
740
+ esc_attr( $href ),
741
+ esc_attr( $title )
742
+ );
743
+ break;
744
+ case 'select2':
745
+ if ( ! isset( $current_value ) ) {
746
+ $current_value = '';
747
+ }
748
+
749
+ $data_values = array();
750
+
751
+ if ( isset( $field['choices'] ) ) {
752
+ $choices = $field['choices'];
753
+ if ( is_callable( $choices ) ) {
754
+ $param = ( isset( $field['param'] ) ) ? $field['param'] : null;
755
+ $choices = call_user_func( $choices, $param );
756
+ }
757
+ foreach ( $choices as $key => $value ) {
758
+ if ( is_array( $value ) ) {
759
+ $child_values = array();
760
+ if ( isset( $value['children'] ) ) {
761
+ $child_values = array();
762
+ foreach ( $value['children'] as $child_key => $child_value ) {
763
+ $child_values[] = array(
764
+ 'id' => $child_key,
765
+ 'text' => $child_value,
766
+ );
767
+ }
768
+ }
769
+ if ( isset( $value['label'] ) ) {
770
+ $data_values[] = array(
771
+ 'id' => $key,
772
+ 'text' => $value['label'],
773
+ 'children' => $child_values,
774
+ );
775
+ }
776
+ } else {
777
+ $data_values[] = array(
778
+ 'id' => $key,
779
+ 'text' => $value,
780
+ );
781
+ }
782
+ }
783
+ $class .= ' with-source';
784
+ }
785
+
786
+ $input_html = sprintf(
787
+ '<input type="hidden" name="%1$s[%2$s_%3$s]" data-values=\'%4$s\' value="%5$s" class="select2-select %6$s" data-placeholder="%7$s" />',
788
+ esc_attr( $option_key ),
789
+ esc_attr( $section ),
790
+ esc_attr( $name ),
791
+ esc_attr( wp_mainwp_stream_json_encode( $data_values ) ),
792
+ esc_attr( $current_value ),
793
+ esc_attr( $class ),
794
+ // translators: Placeholder refers to the title of the dropdown menu (e.g. "users")
795
+ sprintf( esc_html__( 'Any %s', 'mainwp-child-reports' ), $title )
796
+ );
797
+
798
+ $output = sprintf(
799
+ '<div class="%1$s_%2$s_%3$s">%4$s</div>',
800
+ esc_attr( $option_key ),
801
+ esc_attr( $section ),
802
+ esc_attr( $name ),
803
+ $input_html
804
+ );
805
+
806
+ break;
807
+ case 'rule_list':
808
+ $users = count_users();
809
+ $form = new Form_Generator();
810
+ $output = '<p class="description">' . esc_html( $description ) . '</p>';
811
+
812
+ $actions_top = sprintf( '<input type="button" class="button" id="%1$s_new_rule" value="&#43; %2$s" />', esc_attr( $section . '_' . $name ), esc_html__( 'Add New Rule', 'mainwp-child-reports' ) );
813
+ $actions_bottom = sprintf( '<input type="button" class="button" id="%1$s_remove_rules" value="%2$s" />', esc_attr( $section . '_' . $name ), esc_html__( 'Delete Selected Rules', 'mainwp-child-reports' ) );
814
+
815
+ $output .= sprintf( '<div class="tablenav top">%1$s</div>', $actions_top );
816
+ $output .= '<table class="wp-list-table widefat fixed mainwp-stream-exclude-list">';
817
+
818
+ unset( $description );
819
+
820
+ $heading_row = sprintf(
821
+ '<tr>
822
+ <td scope="col" class="manage-column column-cb check-column">%1$s</td>
823
+ <th scope="col" class="manage-column">%2$s</th>
824
+ <th scope="col" class="manage-column">%3$s</th>
825
+ <th scope="col" class="manage-column">%4$s</th>
826
+ <th scope="col" class="manage-column">%5$s</th>
827
+ <th scope="col" class="actions-column manage-column"><span class="hidden">%6$s</span></th>
828
+ </tr>',
829
+ '<input class="cb-select" type="checkbox" />',
830
+ esc_html__( 'Author or Role', 'mainwp-child-reports' ),
831
+ esc_html__( 'Context', 'mainwp-child-reports' ),
832
+ esc_html__( 'Action', 'mainwp-child-reports' ),
833
+ esc_html__( 'IP Address', 'mainwp-child-reports' ),
834
+ esc_html__( 'Filters', 'mainwp-child-reports' )
835
+ );
836
+
837
+ $exclude_rows = array();
838
+
839
+ // Prepend an empty row.
840
+ $current_value['exclude_row'] = array( 'helper' => '' ) + ( isset( $current_value['exclude_row'] ) ? $current_value['exclude_row'] : array() );
841
+
842
+ foreach ( $current_value['exclude_row'] as $key => $value ) {
843
+ // Prepare values.
844
+ $author_or_role = isset( $current_value['author_or_role'][ $key ] ) ? $current_value['author_or_role'][ $key ] : '';
845
+ $connector = isset( $current_value['connector'][ $key ] ) ? $current_value['connector'][ $key ] : '';
846
+ $context = isset( $current_value['context'][ $key ] ) ? $current_value['context'][ $key ] : '';
847
+ $action = isset( $current_value['action'][ $key ] ) ? $current_value['action'][ $key ] : '';
848
+ $ip_address = isset( $current_value['ip_address'][ $key ] ) ? $current_value['ip_address'][ $key ] : '';
849
+
850
+ // Author or Role dropdown menu
851
+ $author_or_role_values = array();
852
+ $author_or_role_selected = array();
853
+
854
+ foreach ( $this->get_roles() as $role_id => $role ) {
855
+ $args = array(
856
+ 'value' => $role_id,
857
+ 'text' => $role,
858
+ );
859
+ $count = isset( $users['avail_roles'][ $role_id ] ) ? $users['avail_roles'][ $role_id ] : 0;
860
+
861
+ if ( ! empty( $count ) ) {
862
+ // translators: Placeholder refers to a number of users (e.g. "42")
863
+ $args['user_count'] = sprintf( _n( '%d user', '%d users', absint( $count ), 'mainwp-child-reports' ), absint( $count ) );
864
+ }
865
+
866
+ if ( $role_id === $author_or_role ) {
867
+ $author_or_role_selected['value'] = $role_id;
868
+ $author_or_role_selected['text'] = $role;
869
+ }
870
+
871
+ $author_or_role_values[] = $args;
872
+ }
873
+
874
+ if ( empty( $author_or_role_selected ) && is_numeric( $author_or_role ) ) {
875
+ $user = new WP_User( $author_or_role );
876
+ $display_name = ( 0 === $user->ID ) ? esc_html__( 'N/A', 'mainwp-child-reports' ) : $user->display_name;
877
+ $author_or_role_selected = array(
878
+ 'value' => $user->ID,
879
+ 'text' => $display_name,
880
+ );
881
+ $author_or_role_values[] = $author_or_role_selected;
882
+ }
883
+
884
+ $author_or_role_input = $form->render_field(
885
+ 'select2', array(
886
+ 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'author_or_role' ) ),
887
+ 'options' => $author_or_role_values,
888
+ 'classes' => 'author_or_role',
889
+ 'data' => array(
890
+ 'placeholder' => esc_html__( 'Any Author or Role', 'mainwp-child-reports' ),
891
+ 'nonce' => esc_attr( wp_create_nonce( 'stream_get_users' ) ),
892
+ 'selected-id' => isset( $author_or_role_selected['value'] ) ? esc_attr( $author_or_role_selected['value'] ) : '',
893
+ 'selected-text' => isset( $author_or_role_selected['text'] ) ? esc_attr( $author_or_role_selected['text'] ) : '',
894
+ ),
895
+ )
896
+ );
897
+
898
+ // Context dropdown menu
899
+ $context_values = array();
900
+
901
+ foreach ( $this->get_terms_labels( 'context' ) as $context_id => $context_data ) {
902
+ if ( is_array( $context_data ) ) {
903
+ $child_values = array();
904
+ if ( isset( $context_data['children'] ) ) {
905
+ $child_values = array();
906
+ foreach ( $context_data['children'] as $child_id => $child_value ) {
907
+ $child_values[] = array(
908
+ 'value' => $context_id . '-' . $child_id,
909
+ 'text' => $child_value,
910
+ 'parent' => $context_id,
911
+ );
912
+ }
913
+ }
914
+ if ( isset( $context_data['label'] ) ) {
915
+ $context_values[] = array(
916
+ 'value' => $context_id,
917
+ 'text' => $context_data['label'],
918
+ 'children' => $child_values,
919
+ );
920
+ }
921
+ } else {
922
+ $context_values[] = array(
923
+ 'value' => $context_id,
924
+ 'text' => $context_data,
925
+ );
926
+ }
927
+ }
928
+
929
+ $connector_or_context_input = $form->render_field(
930
+ 'select2', array(
931
+ 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector_or_context' ) ),
932
+ 'options' => $context_values,
933
+ 'classes' => 'connector_or_context',
934
+ 'data' => array(
935
+ 'group' => 'connector',
936
+ 'placeholder' => __( 'Any Context', 'mainwp-child-reports' ),
937
+ ),
938
+ )
939
+ );
940
+
941
+ $connector_input = $form->render_field(
942
+ 'hidden', array(
943
+ 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'connector' ) ),
944
+ 'value' => $connector,
945
+ 'classes' => 'connector',
946
+ )
947
+ );
948
+
949
+ $context_input = $form->render_field(
950
+ 'hidden', array(
951
+ 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'context' ) ),
952
+ 'value' => $context,
953
+ 'classes' => 'context',
954
+ )
955
+ );
956
+
957
+ // Action dropdown menu
958
+ $action_values = array();
959
+
960
+ foreach ( $this->get_terms_labels( 'action' ) as $action_id => $action_data ) {
961
+ $action_values[] = array(
962
+ 'value' => $action_id,
963
+ 'text' => $action_data,
964
+ );
965
+ }
966
+
967
+ $action_input = $form->render_field(
968
+ 'select2', array(
969
+ 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'action' ) ),
970
+ 'value' => $action,
971
+ 'options' => $action_values,
972
+ 'classes' => 'action',
973
+ 'data' => array(
974
+ 'placeholder' => __( 'Any Action', 'mainwp-child-reports' ),
975
+ ),
976
+ )
977
+ );
978
+
979
+ // IP Address input
980
+ $ip_address_input = $form->render_field(
981
+ 'select2', array(
982
+ 'name' => esc_attr( sprintf( '%1$s[%2$s_%3$s][%4$s][]', $option_key, $section, $name, 'ip_address' ) ),
983
+ 'value' => $ip_address,
984
+ 'classes' => 'ip_address',
985
+ 'data' => array(
986
+ 'placeholder' => esc_attr__( 'Any IP Address', 'mainwp-child-reports' ),
987
+ 'nonce' => esc_attr( wp_create_nonce( 'stream_get_ips' ) ),
988
+ ),
989
+ 'multiple' => true,
990
+ )
991
+ );
992
+
993
+ // Hidden helper input
994
+ $helper_input = sprintf(
995
+ '<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" value="" />',
996
+ esc_attr( $option_key ),
997
+ esc_attr( $section ),
998
+ esc_attr( $name ),
999
+ 'exclude_row'
1000
+ );
1001
+
1002
+ $exclude_rows[] = sprintf(
1003
+ '<tr class="%1$s %2$s">
1004
+ <th scope="row" class="check-column">%3$s %4$s</th>
1005
+ <td>%5$s</td>
1006
+ <td>%6$s %7$s %8$s</td>
1007
+ <td>%9$s</td>
1008
+ <td>%10$s</td>
1009
+ <th scope="row" class="actions-column">%11$s</th>
1010
+ </tr>',
1011
+ ( 0 !== (int) $key % 2 ) ? 'alternate' : '',
1012
+ ( 'helper' === (string) $key ) ? 'hidden helper' : '',
1013
+ '<input class="cb-select" type="checkbox" />',
1014
+ $helper_input,
1015
+ $author_or_role_input,
1016
+ $connector_or_context_input,
1017
+ $connector_input,
1018
+ $context_input,
1019
+ $action_input,
1020
+ $ip_address_input,
1021
+ '<a href="#" class="exclude_rules_remove_rule_row">Delete</a>'
1022
+ );
1023
+ }
1024
+
1025
+ $no_rules_found_row = sprintf(
1026
+ '<tr class="no-items hidden"><td class="colspanchange" colspan="5">%1$s</td></tr>',
1027
+ esc_html__( 'No rules found.', 'mainwp-child-reports' )
1028
+ );
1029
+
1030
+ $output .= '<thead>' . $heading_row . '</thead>';
1031
+ $output .= '<tfoot>' . $heading_row . '</tfoot>';
1032
+ $output .= '<tbody>' . $no_rules_found_row . implode( '', $exclude_rows ) . '</tbody>';
1033
+
1034
+ $output .= '</table>';
1035
+
1036
+ $output .= sprintf( '<div class="tablenav bottom">%1$s</div>', $actions_bottom );
1037
+
1038
+ break;
1039
+ }
1040
+ $output .= ! empty( $description ) ? wp_kses_post( sprintf( '<p class="description">%s</p>', $description ) ) : null;
1041
+
1042
+ return $output;
1043
+ }
1044
+
1045
+ /**
1046
+ * Render Callback for post_types field
1047
+ *
1048
+ * @param array $field
1049
+ *
1050
+ * @return string
1051
+ */
1052
+ public function output_field( $field ) {
1053
+ $method = 'output_' . $field['name'];
1054
+
1055
+ if ( method_exists( $this, $method ) ) {
1056
+ return call_user_func( array( $this, $method ), $field );
1057
+ }
1058
+
1059
+ $output = $this->render_field( $field );
1060
+
1061
+ echo $output; // xss ok
1062
+ }
1063
+
1064
+ /**
1065
+ * Get an array of user roles
1066
+ *
1067
+ * @return array
1068
+ */
1069
+ public function get_roles() {
1070
+ $wp_roles = new WP_Roles();
1071
+ $roles = array();
1072
+
1073
+ foreach ( $wp_roles->get_names() as $role => $label ) {
1074
+ $roles[ $role ] = translate_user_role( $label );
1075
+ }
1076
+
1077
+ return $roles;
1078
+ }
1079
+
1080
+ /**
1081
+ * Function will return all terms labels of given column
1082
+ *
1083
+ * @param string $column string Name of the column
1084
+ *
1085
+ * @return array
1086
+ */
1087
+ public function get_terms_labels( $column ) {
1088
+ $return_labels = array();
1089
+
1090
+ if ( isset( $this->plugin->connectors->term_labels[ 'stream_' . $column ] ) ) {
1091
+ if ( 'context' === $column && isset( $this->plugin->connectors->term_labels['stream_connector'] ) ) {
1092
+ $connectors = $this->plugin->connectors->term_labels['stream_connector'];
1093
+ $contexts = $this->plugin->connectors->term_labels['stream_context'];
1094
+
1095
+ foreach ( $connectors as $connector => $connector_label ) {
1096
+ $return_labels[ $connector ]['label'] = $connector_label;
1097
+ foreach ( $contexts as $context => $context_label ) {
1098
+ if ( isset( $this->plugin->connectors->contexts[ $connector ] ) && array_key_exists( $context, $this->plugin->connectors->contexts[ $connector ] ) ) {
1099
+ $return_labels[ $connector ]['children'][ $context ] = $context_label;
1100
+ }
1101
+ }
1102
+ }
1103
+ } else {
1104
+ $return_labels = $this->plugin->connectors->term_labels[ 'stream_' . $column ];
1105
+ }
1106
+
1107
+ ksort( $return_labels );
1108
+ }
1109
+
1110
+ return $return_labels;
1111
+ }
1112
+
1113
+ /**
1114
+ * Remove records when records TTL is shortened
1115
+ *
1116
+ * @action update_option_wp_stream
1117
+ *
1118
+ * @param array $old_value
1119
+ * @param array $new_value
1120
+ */
1121
+ public function updated_option_ttl_remove_records( $old_value, $new_value ) {
1122
+ $ttl_before = isset( $old_value['general_records_ttl'] ) ? (int) $old_value['general_records_ttl'] : - 1;
1123
+ $ttl_after = isset( $new_value['general_records_ttl'] ) ? (int) $new_value['general_records_ttl'] : - 1;
1124
+
1125
+ if ( $ttl_after < $ttl_before ) {
1126
+ /**
1127
+ * Action assists in purging when TTL is shortened
1128
+ */
1129
+ do_action( 'wp_mainwp_stream_auto_purge' );
1130
+ }
1131
+ }
1132
+
1133
+ /**
1134
+ * Get translations of serialized Stream settings
1135
+ *
1136
+ * @filter wp_mainwp_stream_serialized_labels
1137
+ *
1138
+ * @return array Multidimensional array of fields
1139
+ */
1140
+ public function get_settings_translations( $labels ) {
1141
+ if ( ! isset( $labels[ $this->option_key ] ) ) {
1142
+ $labels[ $this->option_key ] = array();
1143
+ }
1144
+
1145
+ foreach ( $this->get_fields() as $section_slug => $section ) {
1146
+ foreach ( $section['fields'] as $field ) {
1147
+ $labels[ $this->option_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
1148
+ }
1149
+ }
1150
+
1151
+ return $labels;
1152
+ }
1153
+ }
classes/class-uninstall.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Uninstall {
5
+ /**
6
+ * Hold Plugin class
7
+ * @var Plugin
8
+ */
9
+ public $plugin;
10
+
11
+ /**
12
+ * Hold the array of option keys to uninstall
13
+ *
14
+ * @var array
15
+ */
16
+ public $options;
17
+
18
+ /**
19
+ * Hold the array of user meta keys to uninstall
20
+ *
21
+ * @var array
22
+ */
23
+ public $user_meta;
24
+
25
+ public function __construct( $plugin ) {
26
+ $this->plugin = $plugin;
27
+
28
+ $this->user_meta = array(
29
+ 'edit_mainwp_stream_per_page',
30
+ 'stream_last_read', // Deprecated
31
+ 'stream_unread_count', // Deprecated
32
+ 'stream_user_feed_key', // Deprecated
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Uninstall Stream by deleting its data
38
+ */
39
+ public function uninstall() {
40
+ //check_ajax_referer( 'stream_nonce', 'wp_mainwp_stream_nonce' );
41
+
42
+ $this->options = array(
43
+ $this->plugin->install->option_key,
44
+ $this->plugin->settings->option_key,
45
+ $this->plugin->settings->network_options_key,
46
+ );
47
+
48
+ // Verify current user's permissions before proceeding
49
+ if ( ! current_user_can( $this->plugin->admin->settings_cap ) ) {
50
+ wp_die(
51
+ esc_html__( "You don't have sufficient privileges to do this action.", 'mainwp-child-reports' )
52
+ );
53
+ }
54
+
55
+ // Prevent this action from firing
56
+ remove_action( 'deactivate_plugin', array( 'Connector_Installer', 'callback' ), null );
57
+
58
+ // Just in case
59
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
60
+ require_once ABSPATH . '/wp-admin/includes/plugin.php';
61
+ }
62
+
63
+ // Drop everything on single site installs or when network activated
64
+ // Otherwise only delete data relative to the current blog
65
+ if ( ! is_multisite() || is_plugin_active_for_network( $this->plugin->locations['plugin'] ) ) {
66
+ $this->delete_all_records();
67
+ $this->delete_all_options();
68
+ $this->delete_all_user_meta();
69
+ } else {
70
+ $blog_id = get_current_blog_id();
71
+
72
+ $this->delete_blog_records( $blog_id );
73
+ $this->delete_blog_options( $blog_id );
74
+ $this->delete_blog_user_meta( $blog_id );
75
+ }
76
+
77
+ $this->delete_all_cron_events();
78
+
79
+ $this->deactivate();
80
+ }
81
+
82
+ /**
83
+ * Delete the Stream database tables
84
+ */
85
+ private function delete_all_records() {
86
+ global $wpdb;
87
+
88
+ $wpdb->query( "DROP TABLE {$wpdb->mainwp_stream}" );
89
+ $wpdb->query( "DROP TABLE {$wpdb->mainwp_streammeta}" );
90
+ }
91
+
92
+ /**
93
+ * Delete records and record meta from a specific blog
94
+ *
95
+ * @param int $blog_id (optional)
96
+ */
97
+ private function delete_blog_records( $blog_id = 1 ) {
98
+ if ( empty( $blog_id ) || ! is_int( $blog_id ) ) {
99
+ return;
100
+ }
101
+
102
+ global $wpdb;
103
+
104
+ $wpdb->query(
105
+ $wpdb->prepare(
106
+ "DELETE `records`, `meta`
107
+ FROM {$wpdb->mainwp_stream} AS `records`
108
+ LEFT JOIN {$wpdb->mainwp_streammeta} AS `meta`
109
+ ON `meta`.`record_id` = `records`.`ID`
110
+ WHERE blog_id = %d;",
111
+ $blog_id
112
+ )
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Delete all options
118
+ */
119
+ private function delete_all_options() {
120
+ global $wpdb;
121
+
122
+ // Wildcard matches
123
+ $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '%wp_mainwp_stream%';" );
124
+
125
+ // Specific options
126
+ foreach ( $this->options as $option ) {
127
+ delete_site_option( $option ); // Supports both multisite and single site installs
128
+ }
129
+
130
+ // Single site installs can stop here
131
+ if ( ! is_multisite() ) {
132
+ return;
133
+ }
134
+
135
+ // Wildcard matches on network options
136
+ $wpdb->query( "DELETE FROM {$wpdb->sitemeta} WHERE meta_key LIKE '%wp_mainwp_stream%';" );
137
+
138
+ // Delete options from each blog on network
139
+ foreach ( wp_mainwp_stream_get_sites() as $blog ) {
140
+ $this->delete_blog_options( absint( $blog->blog_id ) );
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Delete options from a specific blog
146
+ *
147
+ * @param int $blog_id (optional)
148
+ */
149
+ private function delete_blog_options( $blog_id = 1 ) {
150
+ if ( empty( $blog_id ) || ! is_int( $blog_id ) ) {
151
+ return;
152
+ }
153
+
154
+ global $wpdb;
155
+
156
+ // Wildcard matches
157
+ $wpdb->query( "DELETE FROM {$wpdb->prefix}options WHERE option_name LIKE '%wp_mainwp_stream%';" );
158
+
159
+ // Specific options
160
+ foreach ( $this->options as $option ) {
161
+ delete_blog_option( $blog_id, $option );
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Delete all user meta
167
+ */
168
+ private function delete_all_user_meta() {
169
+ global $wpdb;
170
+
171
+ // Wildcard matches
172
+ $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE '%wp_mainwp_stream%';" );
173
+
174
+ // Specific user meta
175
+ foreach ( $this->user_meta as $meta_key ) {
176
+ $wpdb->query(
177
+ $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = %s;", $meta_key )
178
+ );
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Delete user meta from a specific blog
184
+ *
185
+ * @param int $blog_id (optional)
186
+ */
187
+ private function delete_blog_user_meta( $blog_id = 1 ) {
188
+ if ( empty( $blog_id ) || ! is_int( $blog_id ) ) {
189
+ return;
190
+ }
191
+
192
+ global $wpdb;
193
+
194
+ // Wildcard matches
195
+ $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE '{$wpdb->prefix}%wp_mainwp_stream%';" );
196
+
197
+ // Specific user meta
198
+ foreach ( $this->user_meta as $meta_key ) {
199
+ $wpdb->query(
200
+ $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = {$wpdb->prefix}%s;", $meta_key )
201
+ );
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Delete scheduled cron event hooks
207
+ */
208
+ private function delete_all_cron_events() {
209
+ wp_clear_scheduled_hook( 'wp_mainwp_stream_auto_purge' );
210
+ }
211
+
212
+ /**
213
+ * Deactivate the plugin and redirect to the plugins screen
214
+ */
215
+ private function deactivate() {
216
+ deactivate_plugins( $this->plugin->locations['plugin'] );
217
+
218
+ wp_safe_redirect(
219
+ add_query_arg(
220
+ array(
221
+ 'deactivate' => true,
222
+ ),
223
+ self_admin_url( 'plugins.php' )
224
+ )
225
+ );
226
+
227
+ exit;
228
+ }
229
+ }
classes/debug.log ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [08-Nov-2019 11:30:04 UTC] =========000000000=========1573212604
2
+ [08-Nov-2019 11:30:04 UTC] =========3333333333=========Array
3
+ (
4
+ [0] => stdClass Object
5
+ (
6
+ [ID] => 508
7
+ [site_id] => 1
8
+ [blog_id] => 1
9
+ [object_id] =>
10
+ [user_id] => 1
11
+ [user_role] => administrator
12
+ [summary] => Sucuri scan successful!
13
+ [created] => 2019-11-05 15:05:55
14
+ [connector] => mainwp_sucuri
15
+ [context] => sucuri_scan
16
+ [action] => sucuri_scan
17
+ [ip] => 112.213.89.26
18
+ )
19
+
20
+ )
21
+
22
+ [08-Nov-2019 11:30:05 UTC] =========000000000=========1573212593
23
+ [08-Nov-2019 11:30:05 UTC] =========3333333333=========Array
24
+ (
25
+ [0] => stdClass Object
26
+ (
27
+ [ID] => 508
28
+ [site_id] => 1
29
+ [blog_id] => 1
30
+ [object_id] =>
31
+ [user_id] => 1
32
+ [user_role] => administrator
33
+ [summary] => Sucuri scan successful!
34
+ [created] => 2019-11-05 15:05:55
35
+ [connector] => mainwp_sucuri
36
+ [context] => sucuri_scan
37
+ [action] => sucuri_scan
38
+ [ip] => 112.213.89.26
39
+ )
40
+
41
+ )
42
+
connectors/backupbuddy.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Backupbuddy extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'backupbuddy_backups';
6
-
7
- public static $actions = array(
8
- 'mainwp_reports_backupbuddy_backup',
9
- );
10
-
11
- public static function get_label() {
12
- return __( 'BackupBuddy', 'default' );
13
- }
14
-
15
- public static function get_action_labels() {
16
- return array(
17
- 'mainwp_reports_backupbuddy_backup' => __( 'BackupBuddy Backup', 'default' ),
18
- );
19
- }
20
-
21
- public static function get_context_labels() {
22
- return array(
23
- 'backupbuddy_backups' => __( 'BackupBuddy Backups', 'mainwp-child-reports' ),
24
- );
25
- }
26
-
27
- public static function action_links( $links, $record ) {
28
- if (isset($record->object_id)) {
29
- }
30
- return $links;
31
- }
32
-
33
- public static function callback_mainwp_reports_backupbuddy_backup( $message, $type , $backup_time = 0) {
34
- self::log(
35
- $message,
36
- compact('type', 'backup_time'),
37
- 0,
38
- array( 'backupbuddy_backups' => 'mainwp_reports_backupbuddy_backup' )
39
- );
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/backupwordpress.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Backupwordpress extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'backupwordpress_backups';
6
-
7
- public static $actions = array(
8
- 'backupwordpress_backup',
9
- );
10
-
11
- public static function get_label() {
12
- return __( 'BackupWordPress', 'default' );
13
- }
14
-
15
- public static function get_action_labels() {
16
- return array(
17
- 'backupwordpress_backup' => __( 'BackupWordPress Backup', 'default' ),
18
- );
19
- }
20
-
21
- public static function get_context_labels() {
22
- return array(
23
- 'backupwordpress_backups' => __( 'BackupWordPress Backups', 'mainwp-child-reports' ),
24
- );
25
- }
26
-
27
- public static function action_links( $links, $record ) {
28
- if (isset($record->object_id)) {
29
- }
30
- return $links;
31
- }
32
-
33
- public static function callback_backupwordpress_backup($destination, $message, $status, $type, $backup_time = 0) {
34
- self::log(
35
- $message,
36
- compact('destination', 'status', 'type', 'backup_time'),
37
- 0,
38
- array( 'backupwordpress_backups' => 'backupwordpress_backup' )
39
- );
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/backwpup.php DELETED
@@ -1,76 +0,0 @@
1
- <?php
2
- if ( class_exists( 'MainWP_WP_Stream_Connector' ) ) {
3
- class MainWP_WP_Stream_Connector_Backwpup extends MainWP_WP_Stream_Connector {
4
-
5
- /**
6
- * Connector slug
7
- *
8
- * @var string
9
- */
10
- public static $name = 'backwpup_backups';
11
-
12
- /**
13
- * Actions registered for this connector
14
- *
15
- * @var array
16
- */
17
- public static $actions = array(
18
- 'mainwp_backwpup_backup',
19
- );
20
-
21
- /**
22
- * Return translated connector label
23
- *
24
- * @return string Translated connector label
25
- */
26
- public static function get_label() {
27
- return __( 'BackWPup', 'mainwp-child' );
28
- }
29
-
30
- /**
31
- * Return translated action labels
32
- *
33
- * @return array Action label translations
34
- */
35
- public static function get_action_labels() {
36
- return array(
37
- 'mainwp_backwpup_backup' => __( 'BackWPup Backup', 'mainwp-child' ),
38
- );
39
- }
40
-
41
- /**
42
- * Return translated context labels
43
- *
44
- * @return array Context label translations
45
- */
46
- public static function get_context_labels() {
47
- return array(
48
- 'backwpup_backups' => __( 'BackWPup Backups', 'mainwp-child' ),
49
- );
50
- }
51
-
52
- /**
53
- * Add action links to Stream drop row in admin list screen
54
- *
55
- * @filter wp_stream_action_links_{connector}
56
- *
57
- * @param array $links Previous links registered
58
- * @param int $record Stream record
59
- *
60
- * @return array Action links
61
- */
62
- public static function action_links( $links, $record ) {
63
- return $links;
64
- }
65
-
66
- public static function callback_mainwp_backwpup_backup( $message, $type, $backup_time ) {
67
- self::log(
68
- $message,
69
- compact( 'type', 'backup_time' ),
70
- 0,
71
- array( 'backwpup_backups' => 'mainwp_backwpup_backup' )
72
- );
73
- }
74
- }
75
- }
76
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/class-connector-acf.php ADDED
@@ -0,0 +1,567 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_ACF extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'acf';
11
+
12
+ /**
13
+ * Holds tracked plugin minimum version required
14
+ *
15
+ * @const string
16
+ */
17
+ const PLUGIN_MIN_VERSION = '4.3.8';
18
+
19
+ /**
20
+ * Actions registered for this connector
21
+ *
22
+ * @var array
23
+ */
24
+ public $actions = array(
25
+ 'added_post_meta',
26
+ 'updated_post_meta',
27
+ 'delete_post_meta',
28
+ 'added_user_meta',
29
+ 'updated_user_meta',
30
+ 'delete_user_meta',
31
+ 'added_option',
32
+ 'updated_option',
33
+ 'deleted_option',
34
+ 'pre_post_update',
35
+ );
36
+
37
+ /**
38
+ * Cached location rules, used in shutdown callback to verify changes in meta
39
+ *
40
+ * @var array
41
+ */
42
+ public $cached_location_rules = array();
43
+
44
+ /**
45
+ * Cached field values updates, used by shutdown callback to verify actual changes
46
+ *
47
+ * @var array
48
+ */
49
+ public $cached_field_values_updates = array();
50
+
51
+
52
+ /**
53
+ * Check if plugin dependencies are satisfied and add an admin notice if not
54
+ *
55
+ * @return bool
56
+ */
57
+ public function is_dependency_satisfied() {
58
+ if ( class_exists( 'acf' ) ) { //TODO: Should this be function_exists?
59
+ $acf = \acf();
60
+ if ( version_compare( $acf->settings['version'], self::PLUGIN_MIN_VERSION, '>=' ) ) {
61
+ return true;
62
+ }
63
+ }
64
+
65
+ return false;
66
+ }
67
+
68
+ /**
69
+ * Return translated connector label
70
+ *
71
+ * @return string Translated connector label
72
+ */
73
+ public function get_label() {
74
+ return esc_html_x( 'ACF', 'acf', 'mainwp-child-reports' );
75
+ }
76
+
77
+ /**
78
+ * Return translated action labels
79
+ *
80
+ * @return array Action label translations
81
+ */
82
+ public function get_action_labels() {
83
+ return array(
84
+ 'created' => esc_html_x( 'Created', 'acf', 'mainwp-child-reports' ),
85
+ 'updated' => esc_html_x( 'Updated', 'acf', 'mainwp-child-reports' ),
86
+ 'added' => esc_html_x( 'Added', 'acf', 'mainwp-child-reports' ),
87
+ 'deleted' => esc_html_x( 'Deleted', 'acf', 'mainwp-child-reports' ),
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Return translated context labels
93
+ *
94
+ * @return array Context label translations
95
+ */
96
+ public function get_context_labels() {
97
+ return array(
98
+ 'field_groups' => esc_html_x( 'Field Groups', 'acf', 'mainwp-child-reports' ),
99
+ 'fields' => esc_html_x( 'Fields', 'acf', 'mainwp-child-reports' ),
100
+ 'rules' => esc_html_x( 'Rules', 'acf', 'mainwp-child-reports' ),
101
+ 'options' => esc_html_x( 'Options', 'acf', 'mainwp-child-reports' ),
102
+ 'values' => esc_html_x( 'Values', 'acf', 'mainwp-child-reports' ),
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Register the connector
108
+ */
109
+ public function register() {
110
+ add_filter( 'wp_mainwp_stream_log_data', array( $this, 'log_override' ) );
111
+
112
+ /**
113
+ * Allow devs to disable logging values of rendered forms
114
+ *
115
+ * @return bool
116
+ */
117
+ if ( apply_filters( 'wp_mainwp_stream_acf_enable_value_logging', true ) ) {
118
+ $this->actions[] = 'acf/update_value';
119
+ }
120
+
121
+ parent::register();
122
+ }
123
+
124
+ /**
125
+ * Add action links to Stream drop row in admin list screen
126
+ *
127
+ * @filter wp_mainwp_stream_action_links_{connector}
128
+ *
129
+ * @param array $links Previous links registered
130
+ * @param object $record Stream record
131
+ *
132
+ * @return array Action links
133
+ */
134
+ public function action_links( $links, $record ) {
135
+ $posts_connector = new Connector_Posts();
136
+ $links = $posts_connector->action_links( $links, $record );
137
+
138
+ return $links;
139
+ }
140
+
141
+ /**
142
+ * Track addition of post meta
143
+ *
144
+ * @action added_post_meta
145
+ */
146
+ public function callback_added_post_meta() {
147
+ call_user_func_array( array( $this, 'check_meta' ), array_merge( array( 'post', 'added' ), func_get_args() ) );
148
+ }
149
+
150
+ /**
151
+ * Track updating post meta
152
+ *
153
+ * @action updated_post_meta
154
+ */
155
+ public function callback_updated_post_meta() {
156
+ call_user_func_array( array( $this, 'check_meta' ), array_merge( array( 'post', 'updated' ), func_get_args() ) );
157
+ }
158
+
159
+ /**
160
+ * Track deletion of post meta
161
+ *
162
+ * Note: Using delete_post_meta instead of deleted_post_meta to be able to
163
+ * capture old field value
164
+ *
165
+ * @action delete_post_meta
166
+ */
167
+ public function callback_delete_post_meta() {
168
+ call_user_func_array( array( $this, 'check_meta' ), array_merge( array( 'post', 'deleted' ), func_get_args() ) );
169
+ }
170
+
171
+ /**
172
+ * Track addition of user meta
173
+ *
174
+ * @action added_user_meta
175
+ */
176
+ public function callback_added_user_meta() {
177
+ call_user_func_array( array( $this, 'check_meta' ), array_merge( array( 'user', 'added' ), func_get_args() ) );
178
+ }
179
+
180
+ /**
181
+ * Track updating user meta
182
+ *
183
+ * @action updated_user_meta
184
+ */
185
+ public function callback_updated_user_meta() {
186
+ call_user_func_array( array( $this, 'check_meta' ), array_merge( array( 'user', 'updated' ), func_get_args() ) );
187
+ }
188
+
189
+ /**
190
+ * Track deletion of user meta
191
+ *
192
+ * Note: Using delete_user_meta instead of deleted_user_meta to be able to
193
+ * capture old field value
194
+ *
195
+ * @action delete_user_meta
196
+ */
197
+ public function callback_delete_user_meta() {
198
+ call_user_func_array( array( $this, 'check_meta' ), array_merge( array( 'user', 'deleted' ), func_get_args() ) );
199
+ }
200
+
201
+ /**
202
+ * Track addition of post/user meta
203
+ *
204
+ * @param string $type Type of object, post or user
205
+ * @param string $action Added, updated, deleted
206
+ * @param integer $meta_id
207
+ * @param integer $object_id
208
+ * @param string $meta_key
209
+ * @param mixed|null $meta_value
210
+ */
211
+ public function check_meta( $type, $action, $meta_id, $object_id, $meta_key, $meta_value = null ) {
212
+ $post = get_post( $object_id );
213
+ if ( 'post' !== $type || ! $post || 'acf' !== $post->post_type ) {
214
+ $this->check_meta_values( $type, $action, $meta_id, $object_id, $meta_key, $meta_value );
215
+ return;
216
+ }
217
+
218
+ $action_labels = $this->get_action_labels();
219
+
220
+ // Fields
221
+ if ( 0 === strpos( $meta_key, 'field_' ) ) {
222
+ if ( 'deleted' === $action ) {
223
+ $meta_value = get_post_meta( $object_id, $meta_key, true );
224
+ }
225
+
226
+ $this->log(
227
+ // translators: Placeholders refer to a field label, a form title, and an action (e.g. "Message", "Contact", "Created")
228
+ esc_html_x( '"%1$s" field in "%2$s" %3$s', 'acf', 'mainwp-child-reports' ),
229
+ array(
230
+ 'label' => $meta_value['label'],
231
+ 'title' => $post->post_title,
232
+ 'action' => strtolower( $action_labels[ $action ] ),
233
+ 'key' => $meta_value['key'],
234
+ 'name' => $meta_value['name'],
235
+ ),
236
+ $object_id,
237
+ 'fields',
238
+ $action
239
+ );
240
+ } elseif ( 'rule' === $meta_key ) {
241
+ if ( 'deleted' === $action ) {
242
+ $this->cached_location_rules[ $object_id ] = get_post_meta( $object_id, 'rule' );
243
+
244
+ add_action( 'shutdown', array( $this, 'check_location_rules' ), 9 );
245
+ }
246
+ } elseif ( 'position' === $meta_key ) {
247
+ if ( 'deleted' === $action ) {
248
+ return;
249
+ }
250
+
251
+ $options = array(
252
+ 'acf_after_title' => esc_html_x( 'High (after title)', 'acf', 'mainwp-child-reports' ),
253
+ 'normal' => esc_html_x( 'Normal (after content)', 'acf', 'mainwp-child-reports' ),
254
+ 'side' => esc_html_x( 'Side', 'acf', 'mainwp-child-reports' ),
255
+ );
256
+
257
+ $this->log(
258
+ // translators: Placeholders refer to a form title, and a position (e.g. "Contact", "Side")
259
+ esc_html_x( 'Position of "%1$s" updated to "%2$s"', 'acf', 'mainwp-child-reports' ),
260
+ array(
261
+ 'title' => $post->post_title,
262
+ 'option_label' => $options[ $meta_value ],
263
+ 'option' => $meta_key,
264
+ 'option_value' => $meta_value,
265
+ ),
266
+ $object_id,
267
+ 'options',
268
+ 'updated'
269
+ );
270
+ } elseif ( 'layout' === $meta_key ) {
271
+ if ( 'deleted' === $action ) {
272
+ return;
273
+ }
274
+
275
+ $options = array(
276
+ 'no_box' => esc_html_x( 'Seamless (no metabox)', 'acf', 'mainwp-child-reports' ),
277
+ 'default' => esc_html_x( 'Standard (WP metabox)', 'acf', 'mainwp-child-reports' ),
278
+ );
279
+
280
+ $this->log(
281
+ // translators: Placeholders refer to a form title, and a layout (e.g. "Contact", "Seamless")
282
+ esc_html_x( 'Style of "%1$s" updated to "%2$s"', 'acf', 'mainwp-child-reports' ),
283
+ array(
284
+ 'title' => $post->post_title,
285
+ 'option_label' => $options[ $meta_value ],
286
+ 'option' => $meta_key,
287
+ 'option_value' => $meta_value,
288
+ ),
289
+ $object_id,
290
+ 'options',
291
+ 'updated'
292
+ );
293
+ } elseif ( 'hide_on_screen' === $meta_key ) {
294
+ if ( 'deleted' === $action ) {
295
+ return;
296
+ }
297
+
298
+ $options = array(
299
+ 'permalink' => esc_html_x( 'Permalink', 'acf', 'mainwp-child-reports' ),
300
+ 'the_content' => esc_html_x( 'Content Editor', 'acf', 'mainwp-child-reports' ),
301
+ 'excerpt' => esc_html_x( 'Excerpt', 'acf', 'mainwp-child-reports' ),
302
+ 'custom_fields' => esc_html_x( 'Custom Fields', 'acf', 'mainwp-child-reports' ),
303
+ 'discussion' => esc_html_x( 'Discussion', 'acf', 'mainwp-child-reports' ),
304
+ 'comments' => esc_html_x( 'Comments', 'acf', 'mainwp-child-reports' ),
305
+ 'revisions' => esc_html_x( 'Revisions', 'acf', 'mainwp-child-reports' ),
306
+ 'slug' => esc_html_x( 'Slug', 'acf', 'mainwp-child-reports' ),
307
+ 'author' => esc_html_x( 'Author', 'acf', 'mainwp-child-reports' ),
308
+ 'format' => esc_html_x( 'Format', 'acf', 'mainwp-child-reports' ),
309
+ 'featured_image' => esc_html_x( 'Featured Image', 'acf', 'mainwp-child-reports' ),
310
+ 'categories' => esc_html_x( 'Categories', 'acf', 'mainwp-child-reports' ),
311
+ 'tags' => esc_html_x( 'Tags', 'acf', 'mainwp-child-reports' ),
312
+ 'send-trackbacks' => esc_html_x( 'Send Trackbacks', 'acf', 'mainwp-child-reports' ),
313
+ );
314
+
315
+ if ( count( $options ) === count( $meta_value ) ) {
316
+ $options_label = esc_html_x( 'All screens', 'acf', 'mainwp-child-reports' );
317
+ } elseif ( empty( $meta_value ) ) {
318
+ $options_label = esc_html_x( 'No screens', 'acf', 'mainwp-child-reports' );
319
+ } else {
320
+ $options_label = implode( ', ', array_intersect_key( $options, array_flip( $meta_value ) ) );
321
+ }
322
+
323
+ $this->log(
324
+ // translators: Placeholders refer to a form title, and a display option (e.g. "Contact", "All screens")
325
+ esc_html_x( '"%1$s" set to display on "%2$s"', 'acf', 'mainwp-child-reports' ),
326
+ array(
327
+ 'title' => $post->post_title,
328
+ 'option_label' => $options_label,
329
+ 'option' => $meta_key,
330
+ 'option_value' => $meta_value,
331
+ ),
332
+ $object_id,
333
+ 'options',
334
+ 'updated'
335
+ );
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Track changes to ACF values within rendered post meta forms
341
+ *
342
+ * @param string $type Type of object, post or user
343
+ * @param string $action Added, updated, deleted
344
+ * @param integer $meta_id
345
+ * @param integer $object_id
346
+ * @param string $key
347
+ * @param mixed|null $value
348
+ *
349
+ * @return bool
350
+ */
351
+ public function check_meta_values( $type, $action, $meta_id, $object_id, $key, $value = null ) {
352
+ unset( $action );
353
+ unset( $meta_id );
354
+
355
+ if ( empty( $this->cached_field_values_updates ) ) {
356
+ return false;
357
+ }
358
+
359
+ $object_key = $object_id;
360
+
361
+ if ( 'user' === $type ) {
362
+ $object_key = 'user_' . $object_id;
363
+ } elseif ( 'taxonomy' === $type ) {
364
+ if ( 0 === strpos( $key, '_' ) ) { // Ignore the 'revision' stuff!
365
+ return false;
366
+ }
367
+
368
+ if ( 1 !== preg_match( '#([a-z0-9_-]+)_([\d]+)_([a-z0-9_-]+)#', $key, $matches ) ) {
369
+ return false;
370
+ }
371
+
372
+ list( , $taxonomy, $term_id, $key ) = $matches; // Skips 0 index
373
+
374
+ $object_key = $taxonomy . '_' . $term_id;
375
+ } elseif ( 'option' === $type ) {
376
+ $object_key = 'options';
377
+ $key = preg_replace( '/^options_/', '', $key );
378
+ }
379
+
380
+ if ( isset( $this->cached_field_values_updates[ $object_key ][ $key ] ) ) {
381
+ if ( 'post' === $type ) {
382
+ $posts_connector = new Connector_Posts();
383
+
384
+ $post = get_post( $object_id );
385
+ $title = $post->post_title;
386
+ $type_name = strtolower( $posts_connector->get_post_type_name( $post->post_type ) );
387
+ } elseif ( 'user' === $type ) {
388
+ $user = new \WP_User( $object_id );
389
+ $title = $user->get( 'display_name' );
390
+ $type_name = esc_html__( 'user', 'mainwp-child-reports' );
391
+ } elseif ( 'taxonomy' === $type && isset( $term_id ) && isset( $taxonomy ) ) {
392
+ $term = get_term( $term_id, $taxonomy );
393
+ $title = $term->name;
394
+ $tax_obj = get_taxonomy( $taxonomy );
395
+ $type_name = strtolower( get_taxonomy_labels( $tax_obj )->singular_name );
396
+ } elseif ( 'option' === $type ) {
397
+ $title = 'settings page';
398
+ $type_name = 'option';
399
+ } else {
400
+ return false;
401
+ }
402
+
403
+ $cache = $this->cached_field_values_updates[ $object_key ][ $key ];
404
+
405
+ $this->log(
406
+ // translators: Placeholders refer to a field label, an object title, and an object type (e.g. "Message", "Hello World", "post")
407
+ esc_html_x( '"%1$s" of "%2$s" %3$s updated', 'acf', 'mainwp-child-reports' ),
408
+ array(
409
+ 'field_label' => $cache['field']['label'],
410
+ 'title' => $title,
411
+ 'singular_name' => $type_name,
412
+ 'meta_value' => $value,
413
+ 'meta_key' => $key,
414
+ 'meta_type' => $type,
415
+ ),
416
+ $object_id,
417
+ 'values',
418
+ 'updated'
419
+ );
420
+ }
421
+
422
+ return true;
423
+ }
424
+
425
+ /**
426
+ * Track changes to rules, complements post-meta updates
427
+ *
428
+ * @action shutdown
429
+ */
430
+ public function check_location_rules() {
431
+ foreach ( $this->cached_location_rules as $post_id => $old ) {
432
+ $new = get_post_meta( $post_id, 'rule' );
433
+ $post = get_post( $post_id );
434
+
435
+ if ( $old === $new ) {
436
+ continue;
437
+ }
438
+
439
+ $new = array_map( 'wp_mainwp_stream_json_encode', $new );
440
+ $old = array_map( 'wp_mainwp_stream_json_encode', $old );
441
+ $added = array_diff( $new, $old );
442
+ $deleted = array_diff( $old, $new );
443
+
444
+ $this->log(
445
+ // translators: Placeholders refer to a form title, the number of rules added, and the number of rules deleted (e.g. "Contact", "42", "7")
446
+ esc_html_x( 'Updated rules of "%1$s" (%2$d added, %3$d deleted)', 'acf', 'mainwp-child-reports' ),
447
+ array(
448
+ 'title' => $post->post_title,
449
+ 'no_added' => count( $added ),
450
+ 'no_deleted' => count( $deleted ),
451
+ 'added' => $added,
452
+ 'deleted' => $deleted,
453
+ ),
454
+ $post_id,
455
+ 'rules',
456
+ 'updated'
457
+ );
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Override connector log for our own Settings / Actions
463
+ *
464
+ * @param array $data
465
+ *
466
+ * @return array|bool
467
+ */
468
+ public function log_override( $data ) {
469
+ if ( ! is_array( $data ) ) {
470
+ return $data;
471
+ }
472
+
473
+ if ( 'posts' === $data['connector'] && 'acf' === $data['context'] ) {
474
+ $data['context'] = 'field_groups';
475
+ $data['connector'] = $this->name;
476
+ $data['args']['singular_name'] = esc_html__( 'field group', 'mainwp-child-reports' );
477
+ }
478
+
479
+ return $data;
480
+ }
481
+
482
+ /**
483
+ * Track changes to custom field values updates, saves filtered values to be
484
+ * processed by callback_updated_post_meta
485
+ *
486
+ * @param string $value
487
+ * @param int $post_id
488
+ * @param string $field
489
+ *
490
+ * @return string
491
+ */
492
+ public function callback_acf_update_value( $value, $post_id, $field ) {
493
+ $this->cached_field_values_updates[ $post_id ][ $field['name'] ] = compact( 'field', 'value', 'post_id' );
494
+ return $value;
495
+ }
496
+
497
+ /**
498
+ * Track changes to post main attributes, ie: Order No.
499
+ *
500
+ * @param int $post_id
501
+ * @param array $data Array with the updated post data
502
+ */
503
+ public function callback_pre_post_update( $post_id, $data ) {
504
+ $post = get_post( $post_id );
505
+
506
+ if ( 'acf' !== $post->post_type ) {
507
+ return;
508
+ }
509
+
510
+ // menu_order, aka Order No.
511
+ if ( $data['menu_order'] !== $post->menu_order ) {
512
+ $this->log(
513
+ // translators: Placeholders refer to a form title, a numeric position, and another numeric position (e.g. "Contact", "42", "7")
514
+ esc_html_x( '"%1$s" reordered from %2$d to %3$d', 'acf', 'mainwp-child-reports' ),
515
+ array(
516
+ 'title' => $post->post_title,
517
+ 'old_menu_order' => $post->menu_order,
518
+ 'menu_order' => $data['menu_order'],
519
+ ),
520
+ $post_id,
521
+ 'field_groups',
522
+ 'updated'
523
+ );
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Track addition of new options
529
+ *
530
+ * @param string $key Option name
531
+ * @param string $value Option value
532
+ */
533
+ public function callback_added_option( $key, $value ) {
534
+ $this->check_meta_values( self::get_saved_option_type( $key ), 'added', null, null, $key, $value );
535
+ }
536
+
537
+ /**
538
+ * Track addition of new options
539
+ *
540
+ * @param $key
541
+ * @param $old
542
+ * @param $value
543
+ */
544
+ public function callback_updated_option( $key, $old, $value ) {
545
+ unset( $old );
546
+ $this->check_meta_values( self::get_saved_option_type( $key ), 'updated', null, null, $key, $value );
547
+ }
548
+
549
+ /**
550
+ * Track addition of new options
551
+ *
552
+ * @param $key
553
+ */
554
+ public function callback_deleted_option( $key ) {
555
+ $this->check_meta_values( self::get_saved_option_type( $key ), 'deleted', null, null, $key, null );
556
+ }
557
+
558
+ /**
559
+ * Determines the type of option that is saved
560
+ *
561
+ * @param $key
562
+ * @return string
563
+ */
564
+ private function get_saved_option_type( $key ) {
565
+ return substr( $key, 0, 8 ) === 'options_' ? 'option' : 'taxonomy';
566
+ }
567
+ }
connectors/class-connector-bbpress.php ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_BbPress extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'bbpress';
11
+
12
+ /**
13
+ * Holds tracked plugin minimum version required
14
+ *
15
+ * @const string
16
+ */
17
+ const PLUGIN_MIN_VERSION = '2.5.4';
18
+
19
+ /**
20
+ * Actions registered for this connector
21
+ *
22
+ * @var array
23
+ */
24
+ public $actions = array(
25
+ 'bbp_toggle_topic_admin',
26
+ );
27
+
28
+ /**
29
+ * Tracked option keys
30
+ *
31
+ * @var array
32
+ */
33
+ public $options = array(
34
+ 'bbpress' => null,
35
+ );
36
+
37
+ /**
38
+ * Flag to stop logging update logic twice
39
+ *
40
+ * @var bool
41
+ */
42
+ public $is_update = false;
43
+
44
+ /**
45
+ * @var bool
46
+ */
47
+ public $_deleted_activity = false;
48
+
49
+ /**
50
+ * @var array
51
+ */
52
+ public $_delete_activity_args = array();
53
+
54
+ /**
55
+ * @var bool
56
+ */
57
+ public $ignore_activity_bulk_deletion = false;
58
+
59
+ /**
60
+ * Check if plugin dependencies are satisfied and add an admin notice if not
61
+ *
62
+ * @return bool
63
+ */
64
+ public function is_dependency_satisfied() {
65
+ if ( class_exists( 'bbPress' ) && function_exists( 'bbp_get_version' ) && version_compare( bbp_get_version(), self::PLUGIN_MIN_VERSION, '>=' ) ) {
66
+ return true;
67
+ }
68
+
69
+ return false;
70
+ }
71
+
72
+ /**
73
+ * Return translated connector label
74
+ *
75
+ * @return string Translated connector label
76
+ */
77
+ public function get_label() {
78
+ return esc_html_x( 'bbPress', 'bbpress', 'mainwp-child-reports' );
79
+ }
80
+
81
+ /**
82
+ * Return translated action labels
83
+ *
84
+ * @return array Action label translations
85
+ */
86
+ public function get_action_labels() {
87
+ return array(
88
+ 'created' => esc_html_x( 'Created', 'bbpress', 'mainwp-child-reports' ),
89
+ 'updated' => esc_html_x( 'Updated', 'bbpress', 'mainwp-child-reports' ),
90
+ 'activated' => esc_html_x( 'Activated', 'bbpress', 'mainwp-child-reports' ),
91
+ 'deactivated' => esc_html_x( 'Deactivated', 'bbpress', 'mainwp-child-reports' ),
92
+ 'deleted' => esc_html_x( 'Deleted', 'bbpress', 'mainwp-child-reports' ),
93
+ 'trashed' => esc_html_x( 'Trashed', 'bbpress', 'mainwp-child-reports' ),
94
+ 'untrashed' => esc_html_x( 'Restored', 'bbpress', 'mainwp-child-reports' ),
95
+ 'generated' => esc_html_x( 'Generated', 'bbpress', 'mainwp-child-reports' ),
96
+ 'imported' => esc_html_x( 'Imported', 'bbpress', 'mainwp-child-reports' ),
97
+ 'exported' => esc_html_x( 'Exported', 'bbpress', 'mainwp-child-reports' ),
98
+ 'closed' => esc_html_x( 'Closed', 'bbpress', 'mainwp-child-reports' ),
99
+ 'opened' => esc_html_x( 'Opened', 'bbpress', 'mainwp-child-reports' ),
100
+ 'sticked' => esc_html_x( 'Sticked', 'bbpress', 'mainwp-child-reports' ),
101
+ 'unsticked' => esc_html_x( 'Unsticked', 'bbpress', 'mainwp-child-reports' ),
102
+ 'spammed' => esc_html_x( 'Marked as spam', 'bbpress', 'mainwp-child-reports' ),
103
+ 'unspammed' => esc_html_x( 'Unmarked as spam', 'bbpress', 'mainwp-child-reports' ),
104
+ );
105
+ }
106
+
107
+ /**
108
+ * Return translated context labels
109
+ *
110
+ * @return array Context label translations
111
+ */
112
+ public function get_context_labels() {
113
+ return array(
114
+ 'settings' => esc_html_x( 'Settings', 'bbpress', 'mainwp-child-reports' ),
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Add action links to Stream drop row in admin list screen
120
+ *
121
+ * @filter wp_mainwp_stream_action_links_{connector}
122
+ *
123
+ * @param array $links Previous links registered
124
+ * @param object $record Stream record
125
+ *
126
+ * @return array Action links
127
+ */
128
+ public function action_links( $links, $record ) {
129
+ if ( 'settings' === $record->context ) {
130
+ $option = $record->get_meta( 'option', true );
131
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = esc_url(
132
+ add_query_arg(
133
+ array(
134
+ 'page' => 'bbpress',
135
+ ),
136
+ admin_url( 'options-general.php' )
137
+ ) . esc_url_raw( '#' . $option )
138
+ );
139
+ }
140
+ return $links;
141
+ }
142
+
143
+ public function register() {
144
+ parent::register();
145
+
146
+ add_filter( 'wp_mainwp_stream_log_data', array( $this, 'log_override' ) );
147
+ }
148
+
149
+ /**
150
+ * Override connector log for our own Settings / Actions
151
+ *
152
+ * @param array $data
153
+ *
154
+ * @return array|bool
155
+ */
156
+ public function log_override( $data ) {
157
+ if ( ! is_array( $data ) ) {
158
+ return $data;
159
+ }
160
+
161
+ if ( 'settings' === $data['connector'] && 'bbpress' === $data['args']['context'] ) {
162
+ $settings = \bbp_admin_get_settings_fields();
163
+
164
+ /* fix for missing title for this single field */
165
+ $settings['bbp_settings_features']['_bbp_allow_threaded_replies']['title'] = esc_html__( 'Reply Threading', 'mainwp-child-reports' );
166
+
167
+ $option = $data['args']['option'];
168
+
169
+ foreach ( $settings as $section => $fields ) {
170
+ if ( isset( $fields[ $option ] ) ) {
171
+ $field = $fields[ $option ];
172
+ break;
173
+ }
174
+ }
175
+
176
+ if ( ! isset( $field ) ) {
177
+ return $data;
178
+ }
179
+
180
+ $data['args']['label'] = $field['title'];
181
+ $data['connector'] = $this->name;
182
+ $data['context'] = 'settings';
183
+ $data['action'] = 'updated';
184
+ } elseif ( 'posts' === $data['connector'] && in_array( $data['context'], array( 'forum', 'topic', 'reply' ), true ) ) {
185
+ if ( 'reply' === $data['context'] ) {
186
+ if ( 'updated' === $data['action'] ) {
187
+ // translators: Placeholder refers to a post title (e.g. "Hello World")
188
+ $data['message'] = esc_html__( 'Replied on "%1$s"', 'mainwp-child-reports' );
189
+ $data['args']['post_title'] = get_post( wp_get_post_parent_id( $data['object_id'] ) )->post_title;
190
+ }
191
+ $data['args']['post_title'] = sprintf(
192
+ // translators: Placeholder refers to a post title (e.g. "Hello World")
193
+ __( 'Reply to: %s', 'mainwp-child-reports' ),
194
+ get_post( wp_get_post_parent_id( $data['object_id'] ) )->post_title
195
+ );
196
+ }
197
+
198
+ $data['connector'] = $this->name;
199
+ } elseif ( 'taxonomies' === $data['connector'] && in_array( $data['context'], array( 'topic-tag' ), true ) ) {
200
+ $data['connector'] = $this->name;
201
+ }
202
+
203
+ return $data;
204
+ }
205
+
206
+ /**
207
+ * Tracks togging the forum topics
208
+ *
209
+ * @param bool $success
210
+ * @param \WP_Post $post_data
211
+ * @param string $action
212
+ * @param string $message
213
+ *
214
+ * @return array|bool
215
+ */
216
+ public function callback_bbp_toggle_topic_admin( $success, $post_data, $action, $message ) {
217
+ unset( $success );
218
+ unset( $post_data );
219
+ unset( $action );
220
+
221
+ if ( ! empty( $message['failed'] ) ) {
222
+ return;
223
+ }
224
+
225
+ $action = $message['bbp_topic_toggle_notice'];
226
+ $actions = $this->get_action_labels();
227
+
228
+ if ( ! isset( $actions[ $action ] ) ) {
229
+ return;
230
+ }
231
+
232
+ $topic = get_post( $message['topic_id'] );
233
+
234
+ $this->log(
235
+ // translators: Placeholders refer to an action, and a topic title (e.g. "Created", "Read this first")
236
+ _x( '%1$s "%2$s" topic', '1: Action, 2: Topic title', 'mainwp-child-reports' ),
237
+ array(
238
+ 'action_title' => $actions[ $action ],
239
+ 'topic_title' => $topic->post_title,
240
+ 'action' => $action,
241
+ ),
242
+ $topic->ID,
243
+ 'topic',
244
+ $action
245
+ );
246
+ }
247
+ }
connectors/class-connector-blogs.php ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Blogs extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'blogs';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'wpmu_new_blog',
19
+ 'wpmu_activate_blog',
20
+ 'wpmu_new_user',
21
+ 'add_user_to_blog',
22
+ 'remove_user_from_blog',
23
+ 'make_spam_blog',
24
+ 'make_ham_blog',
25
+ 'mature_blog',
26
+ 'unmature_blog',
27
+ 'archive_blog',
28
+ 'unarchive_blog',
29
+ 'make_delete_blog',
30
+ 'make_undelete_blog',
31
+ 'update_blog_public',
32
+ );
33
+
34
+ /**
35
+ * Register connector in the WP Frontend
36
+ *
37
+ * @var bool
38
+ */
39
+ public $register_frontend = false;
40
+
41
+ /**
42
+ * Return translated connector label
43
+ *
44
+ * @return string
45
+ */
46
+ public function get_label() {
47
+ return esc_html__( 'Sites' );
48
+ }
49
+
50
+ /**
51
+ * Return translated action labels
52
+ *
53
+ * @return array
54
+ */
55
+ public function get_action_labels() {
56
+ return array(
57
+ 'archive_blog' => esc_html__( 'Archived', 'mainwp-child-reports' ),
58
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
59
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
60
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Return translated context labels
66
+ *
67
+ * @return array
68
+ */
69
+ public function get_context_labels() {
70
+ $labels = array();
71
+
72
+ if ( is_multisite() && ! wp_is_large_network() ) {
73
+ $blogs = wp_mainwp_stream_get_sites();
74
+
75
+ foreach ( $blogs as $blog ) {
76
+ $blog_details = get_blog_details( $blog->blog_id );
77
+ $key = sanitize_key( $blog_details->blogname );
78
+ $labels[ $key ] = $blog_details->blogname;
79
+ }
80
+ }
81
+
82
+ return $labels;
83
+ }
84
+
85
+ /**
86
+ * Add action links to Stream drop row in admin list screen
87
+ *
88
+ * @filter wp_mainwp_stream_action_links_{connector}
89
+ *
90
+ * @param array $links
91
+ * @param Record $record
92
+ *
93
+ * @return array
94
+ */
95
+ public function action_links( $links, $record ) {
96
+ $links [ esc_html__( 'Site Admin' ) ] = get_admin_url( $record->object_id );
97
+
98
+ if ( $record->object_id ) {
99
+ $site_admin_link = get_admin_url( $record->object_id );
100
+
101
+ if ( $site_admin_link ) {
102
+ $links [ esc_html__( 'Site Admin' ) ] = $site_admin_link;
103
+ }
104
+
105
+ $site_settings_link = add_query_arg(
106
+ array(
107
+ 'id' => $record->object_id,
108
+ ),
109
+ network_admin_url( 'site-info.php' )
110
+ );
111
+
112
+ if ( $site_settings_link ) {
113
+ $links [ esc_html__( 'Site Settings', 'mainwp-child-reports' ) ] = $site_settings_link;
114
+ }
115
+ }
116
+
117
+ return $links;
118
+ }
119
+
120
+ /**
121
+ * Blog created
122
+ *
123
+ * @action wpmu_new_blog
124
+ *
125
+ * @param int $blog_id
126
+ */
127
+ public function callback_wpmu_new_blog( $blog_id ) {
128
+ $blog = get_blog_details( $blog_id );
129
+
130
+ $this->log(
131
+ // translators: Placeholder refers to site name (e.g. "FooBar Blog")
132
+ _x(
133
+ '"%1$s" site was created',
134
+ '1. Site name',
135
+ 'mainwp-child-reports'
136
+ ),
137
+ array(
138
+ 'site_name' => $blog->blogname,
139
+ ),
140
+ $blog_id,
141
+ sanitize_key( $blog->blogname ),
142
+ 'created'
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Blog registered
148
+ *
149
+ * @action wpmu_activate_blog
150
+ *
151
+ * @param int $blog_id
152
+ * @param int $user_id
153
+ */
154
+ public function callback_wpmu_activate_blog( $blog_id, $user_id ) {
155
+ $blog = get_blog_details( $blog_id );
156
+
157
+ $this->log(
158
+ // translators: Placeholder refers to site name (e.g. "FooBar Blog")
159
+ _x(
160
+ '"%1$s" site was registered',
161
+ '1. Site name',
162
+ 'mainwp-child-reports'
163
+ ),
164
+ array(
165
+ 'site_name' => $blog->blogname,
166
+ ),
167
+ $blog_id,
168
+ sanitize_key( $blog->blogname ),
169
+ 'created',
170
+ $user_id
171
+ );
172
+ }
173
+
174
+ /**
175
+ * User added to a blog
176
+ *
177
+ * @action add_user_to_blog
178
+ *
179
+ * @param int $user_id
180
+ * @param string $role
181
+ * @param int $blog_id
182
+ */
183
+ public function callback_add_user_to_blog( $user_id, $role, $blog_id ) {
184
+ $blog = get_blog_details( $blog_id );
185
+ $user = get_user_by( 'id', $user_id );
186
+
187
+ if ( ! is_a( $user, 'WP_User' ) ) {
188
+ return;
189
+ }
190
+
191
+ $this->log(
192
+ // translators: Placeholders refer to a user's display name, a site name, and a user role (e.g. "Jane Doe", "FooBar Blog", "subscriber")
193
+ _x(
194
+ '%1$s was added to the "%2$s" site with %3$s capabilities',
195
+ '1. User\'s name, 2. Site name, 3. Role',
196
+ 'mainwp-child-reports'
197
+ ),
198
+ array(
199
+ 'user_name' => $user->display_name,
200
+ 'site_name' => $blog->blogname,
201
+ 'role_name' => $role,
202
+ ),
203
+ $blog_id,
204
+ sanitize_key( $blog->blogname ),
205
+ 'updated'
206
+ );
207
+ }
208
+
209
+ /**
210
+ * User removed from a blog
211
+ *
212
+ * @action remove_user_from_blog
213
+ *
214
+ * @param int $user_id
215
+ * @param int $blog_id
216
+ */
217
+ public function callback_remove_user_from_blog( $user_id, $blog_id ) {
218
+ $blog = get_blog_details( $blog_id );
219
+ $user = get_user_by( 'id', $user_id );
220
+
221
+ if ( ! is_a( $user, 'WP_User' ) ) {
222
+ return;
223
+ }
224
+
225
+ $this->log(
226
+ // translators: Placeholders refer to a user's display name, and a site name (e.g. "Jane Doe", "FooBar Blog")
227
+ _x(
228
+ '%1$s was removed from the "%2$s" site',
229
+ '1. User\'s name, 2. Site name',
230
+ 'mainwp-child-reports'
231
+ ),
232
+ array(
233
+ 'user_name' => $user->display_name,
234
+ 'site_name' => $blog->blogname,
235
+ ),
236
+ $blog_id,
237
+ sanitize_key( $blog->blogname ),
238
+ 'updated'
239
+ );
240
+ }
241
+
242
+ /**
243
+ * Blog marked as spam
244
+ *
245
+ * @action make_spam_blog
246
+ *
247
+ * @param int $blog_id
248
+ */
249
+ public function callback_make_spam_blog( $blog_id ) {
250
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'marked as spam', 'mainwp-child-reports' ), 'updated' );
251
+ }
252
+
253
+ /**
254
+ * Blog not marked as spam
255
+ *
256
+ * @action make_ham_blog
257
+ *
258
+ * @param int $blog_id
259
+ */
260
+ public function callback_make_ham_blog( $blog_id ) {
261
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'marked as not spam', 'mainwp-child-reports' ), 'updated' );
262
+ }
263
+
264
+ /**
265
+ * Blog marked as mature
266
+ *
267
+ * @action mature_blog
268
+ *
269
+ * @param int $blog_id
270
+ */
271
+ public function callback_mature_blog( $blog_id ) {
272
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'marked as mature', 'mainwp-child-reports' ), 'updated' );
273
+ }
274
+
275
+ /**
276
+ * Blog not marked as mature
277
+ *
278
+ * @action unmature_blog
279
+ *
280
+ * @param int $blog_id
281
+ */
282
+ public function callback_unmature_blog( $blog_id ) {
283
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'marked as not mature', 'mainwp-child-reports' ), 'updated' );
284
+ }
285
+
286
+ /**
287
+ * Blog marked as archived
288
+ *
289
+ * @action archive_blog
290
+ *
291
+ * @param int $blog_id
292
+ */
293
+ public function callback_archive_blog( $blog_id ) {
294
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'archived', 'mainwp-child-reports' ), 'archive_blog' );
295
+ }
296
+
297
+ /**
298
+ * Blog not marked as archived
299
+ *
300
+ * @action unarchive_blog
301
+ *
302
+ * @param int $blog_id
303
+ */
304
+ public function callback_unarchive_blog( $blog_id ) {
305
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'restored from archive', 'mainwp-child-reports' ), 'updated' );
306
+ }
307
+
308
+ /**
309
+ * Blog marked as deleted
310
+ *
311
+ * @action make_delete_blog
312
+ *
313
+ * @param int $blog_id
314
+ */
315
+ public function callback_make_delete_blog( $blog_id ) {
316
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'deleted', 'mainwp-child-reports' ), 'deleted' );
317
+ }
318
+
319
+ /**
320
+ * Blog not marked as deleted
321
+ *
322
+ * @action undelete_blog
323
+ *
324
+ * @param int $blog_id
325
+ */
326
+ public function callback_make_undelete_blog( $blog_id ) {
327
+ $this->callback_update_blog_status( $blog_id, esc_html__( 'restored', 'mainwp-child-reports' ), 'updated' );
328
+ }
329
+
330
+ /**
331
+ * Blog marked as public or private
332
+ *
333
+ * @action update_blog_public
334
+ *
335
+ * @param int $blog_id
336
+ * @param string $value
337
+ */
338
+ public function callback_update_blog_public( $blog_id, $value ) {
339
+ if ( $value ) {
340
+ $status = esc_html__( 'marked as public', 'mainwp-child-reports' );
341
+ } else {
342
+ $status = esc_html__( 'marked as private', 'mainwp-child-reports' );
343
+ }
344
+
345
+ $this->callback_update_blog_status( $blog_id, $status, 'updated' );
346
+ }
347
+
348
+ /**
349
+ * Blog updated
350
+ *
351
+ * @action update_blog_status
352
+ *
353
+ * @param int $blog_id
354
+ * @param string $status
355
+ * @param string $action
356
+ */
357
+ public function callback_update_blog_status( $blog_id, $status, $action ) {
358
+ $blog = get_blog_details( $blog_id );
359
+
360
+ $this->log(
361
+ // translators: Placeholders refer to a site name, and a blog status (e.g. "FooBar Blog", "archived")
362
+ _x(
363
+ '"%1$s" site was %2$s',
364
+ '1. Site name, 2. Status',
365
+ 'mainwp-child-reports'
366
+ ),
367
+ array(
368
+ 'site_name' => $blog->blogname,
369
+ 'status' => $status,
370
+ ),
371
+ $blog_id,
372
+ sanitize_key( $blog->blogname ),
373
+ $action
374
+ );
375
+ }
376
+ }
connectors/class-connector-buddypress.php ADDED
@@ -0,0 +1,868 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ class Connector_BuddyPress extends Connector {
6
+
7
+ /**
8
+ * Connector slug
9
+ *
10
+ * @var string
11
+ */
12
+ public $name = 'buddypress';
13
+
14
+ /**
15
+ * Holds tracked plugin minimum version required
16
+ *
17
+ * @const string
18
+ */
19
+ const PLUGIN_MIN_VERSION = '2.0.1';
20
+
21
+ /**
22
+ * Actions registered for this connector
23
+ *
24
+ * @var array
25
+ */
26
+ public $actions = array(
27
+ 'update_option',
28
+ 'add_option',
29
+ 'delete_option',
30
+ 'update_site_option',
31
+ 'add_site_option',
32
+ 'delete_site_option',
33
+
34
+ 'bp_before_activity_delete',
35
+ 'bp_activity_deleted_activities',
36
+
37
+ 'bp_activity_mark_as_spam',
38
+ 'bp_activity_mark_as_ham',
39
+ 'bp_activity_admin_edit_after',
40
+
41
+ 'groups_create_group',
42
+ 'groups_update_group',
43
+ 'groups_before_delete_group',
44
+ 'groups_details_updated',
45
+ 'groups_settings_updated',
46
+
47
+ 'groups_leave_group',
48
+ 'groups_join_group',
49
+
50
+ 'groups_promote_member',
51
+ 'groups_demote_member',
52
+ 'groups_ban_member',
53
+ 'groups_unban_member',
54
+ 'groups_remove_member',
55
+
56
+ 'xprofile_field_after_save',
57
+ 'xprofile_fields_deleted_field',
58
+
59
+ 'xprofile_group_after_save',
60
+ 'xprofile_groups_deleted_group',
61
+ );
62
+
63
+ /**
64
+ * Tracked option keys
65
+ *
66
+ * @var array
67
+ */
68
+ public $options = array(
69
+ 'bp-active-components' => null,
70
+ 'bp-pages' => null,
71
+ 'buddypress' => null,
72
+ );
73
+
74
+ /**
75
+ * Flag to stop logging update logic twice
76
+ *
77
+ * @var bool
78
+ */
79
+ public $is_update = false;
80
+
81
+ /**
82
+ * @var bool
83
+ */
84
+ public $_deleted_activity = false;
85
+
86
+ /**
87
+ * @var array
88
+ */
89
+ public $_delete_activity_args = array();
90
+
91
+ /**
92
+ * @var bool
93
+ */
94
+ public $ignore_activity_bulk_deletion = false;
95
+
96
+ /**
97
+ * Check if plugin dependencies are satisfied and add an admin notice if not
98
+ *
99
+ * @return bool
100
+ */
101
+ public function is_dependency_satisfied() {
102
+ if ( class_exists( 'BuddyPress' ) && version_compare( \BuddyPress::instance()->version, self::PLUGIN_MIN_VERSION, '>=' ) ) {
103
+ return true;
104
+ }
105
+
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Return translated connector label
111
+ *
112
+ * @return string Translated connector label
113
+ */
114
+ public function get_label() {
115
+ return esc_html_x( 'BuddyPress', 'buddypress', 'mainwp-child-reports' );
116
+ }
117
+
118
+ /**
119
+ * Return translated action labels
120
+ *
121
+ * @return array Action label translations
122
+ */
123
+ public function get_action_labels() {
124
+ return array(
125
+ 'created' => esc_html_x( 'Created', 'buddypress', 'mainwp-child-reports' ),
126
+ 'updated' => esc_html_x( 'Updated', 'buddypress', 'mainwp-child-reports' ),
127
+ 'activated' => esc_html_x( 'Activated', 'buddypress', 'mainwp-child-reports' ),
128
+ 'deactivated' => esc_html_x( 'Deactivated', 'buddypress', 'mainwp-child-reports' ),
129
+ 'deleted' => esc_html_x( 'Deleted', 'buddypress', 'mainwp-child-reports' ),
130
+ 'spammed' => esc_html_x( 'Marked as spam', 'buddypress', 'mainwp-child-reports' ),
131
+ 'unspammed' => esc_html_x( 'Unmarked as spam', 'buddypress', 'mainwp-child-reports' ),
132
+ 'promoted' => esc_html_x( 'Promoted', 'buddypress', 'mainwp-child-reports' ),
133
+ 'demoted' => esc_html_x( 'Demoted', 'buddypress', 'mainwp-child-reports' ),
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Return translated context labels
139
+ *
140
+ * @return array Context label translations
141
+ */
142
+ public function get_context_labels() {
143
+ return array(
144
+ 'components' => esc_html_x( 'Components', 'buddypress', 'mainwp-child-reports' ),
145
+ 'groups' => esc_html_x( 'Groups', 'buddypress', 'mainwp-child-reports' ),
146
+ 'activity' => esc_html_x( 'Activity', 'buddypress', 'mainwp-child-reports' ),
147
+ 'profile_fields' => esc_html_x( 'Profile fields', 'buddypress', 'mainwp-child-reports' ),
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Add action links to Stream drop row in admin list screen
153
+ *
154
+ * @filter wp_mainwp_stream_action_links_{connector}
155
+ *
156
+ * @param array $links Previous links registered
157
+ * @param object $record Stream record
158
+ *
159
+ * @return array Action links
160
+ */
161
+ public function action_links( $links, $record ) {
162
+ if ( in_array( $record->context, array( 'components' ), true ) ) {
163
+ $option_key = $record->get_meta( 'option_key', true );
164
+
165
+ if ( 'bp-active-components' === $option_key ) {
166
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = add_query_arg(
167
+ array(
168
+ 'page' => 'bp-components',
169
+ ),
170
+ admin_url( 'admin.php' )
171
+ );
172
+ } elseif ( 'bp-pages' === $option_key ) {
173
+ $page_id = $record->get_meta( 'page_id', true );
174
+
175
+ $links[ esc_html__( 'Edit setting', 'mainwp-child-reports' ) ] = add_query_arg(
176
+ array(
177
+ 'page' => 'bp-page-settings',
178
+ ),
179
+ admin_url( 'admin.php' )
180
+ );
181
+
182
+ if ( $page_id ) {
183
+ $links[ esc_html__( 'Edit Page', 'mainwp-child-reports' ) ] = get_edit_post_link( $page_id );
184
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = get_permalink( $page_id );
185
+ }
186
+ }
187
+ } elseif ( in_array( $record->context, array( 'settings' ), true ) ) {
188
+ $links[ esc_html__( 'Edit setting', 'mainwp-child-reports' ) ] = add_query_arg(
189
+ array(
190
+ 'page' => $record->get_meta( 'page', true ),
191
+ ),
192
+ admin_url( 'admin.php' )
193
+ );
194
+ } elseif ( in_array( $record->context, array( 'groups' ), true ) ) {
195
+ $group_id = $record->get_meta( 'id', true );
196
+ $group = \groups_get_group(
197
+ array(
198
+ 'group_id' => $group_id,
199
+ )
200
+ );
201
+
202
+ if ( $group ) {
203
+ // Build actions URLs
204
+ $base_url = \bp_get_admin_url( 'admin.php?page=bp-groups&amp;gid=' . $group_id );
205
+ $delete_url = wp_nonce_url( $base_url . '&amp;action=delete', 'bp-groups-delete' );
206
+ $edit_url = $base_url . '&amp;action=edit';
207
+ $visit_url = \bp_get_group_permalink( $group );
208
+
209
+ $links[ esc_html__( 'Edit group', 'mainwp-child-reports' ) ] = $edit_url;
210
+ $links[ esc_html__( 'View group', 'mainwp-child-reports' ) ] = $visit_url;
211
+ $links[ esc_html__( 'Delete group', 'mainwp-child-reports' ) ] = $delete_url;
212
+ }
213
+ } elseif ( in_array( $record->context, array( 'activity' ), true ) ) {
214
+ $activity_id = $record->get_meta( 'id', true );
215
+ $activities = \bp_activity_get(
216
+ array(
217
+ 'in' => $activity_id,
218
+ 'spam' => 'all',
219
+ )
220
+ );
221
+ if ( ! empty( $activities['activities'] ) ) {
222
+ $activity = reset( $activities['activities'] );
223
+
224
+ $base_url = \bp_get_admin_url( 'admin.php?page=bp-activity&amp;aid=' . $activity->id );
225
+ $spam_nonce = esc_html( '_wpnonce=' . wp_create_nonce( 'spam-activity_' . $activity->id ) );
226
+ $delete_url = $base_url . "&amp;action=delete&amp;$spam_nonce";
227
+ $edit_url = $base_url . '&amp;action=edit';
228
+ $ham_url = $base_url . "&amp;action=ham&amp;$spam_nonce";
229
+ $spam_url = $base_url . "&amp;action=spam&amp;$spam_nonce";
230
+
231
+ if ( $activity->is_spam ) {
232
+ $links[ esc_html__( 'Ham', 'mainwp-child-reports' ) ] = $ham_url;
233
+ } else {
234
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = $edit_url;
235
+ $links[ esc_html__( 'Spam', 'mainwp-child-reports' ) ] = $spam_url;
236
+ }
237
+ $links[ esc_html__( 'Delete', 'mainwp-child-reports' ) ] = $delete_url;
238
+ }
239
+ } elseif ( in_array( $record->context, array( 'profile_fields' ), true ) ) {
240
+ $field_id = $record->get_meta( 'field_id', true );
241
+ $group_id = $record->get_meta( 'group_id', true );
242
+
243
+ if ( empty( $field_id ) ) { // is a group action
244
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = add_query_arg(
245
+ array(
246
+ 'page' => 'bp-profile-setup',
247
+ 'mode' => 'edit_group',
248
+ 'group_id' => $group_id,
249
+ ),
250
+ admin_url( 'users.php' )
251
+ );
252
+ $links[ esc_html__( 'Delete', 'mainwp-child-reports' ) ] = add_query_arg(
253
+ array(
254
+ 'page' => 'bp-profile-setup',
255
+ 'mode' => 'delete_group',
256
+ 'group_id' => $group_id,
257
+ ),
258
+ admin_url( 'users.php' )
259
+ );
260
+ } else {
261
+ $field = new \BP_XProfile_Field( $field_id );
262
+ if ( empty( $field->type ) ) {
263
+ return $links;
264
+ }
265
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = add_query_arg(
266
+ array(
267
+ 'page' => 'bp-profile-setup',
268
+ 'mode' => 'edit_field',
269
+ 'group_id' => $group_id,
270
+ 'field_id' => $field_id,
271
+ ),
272
+ admin_url( 'users.php' )
273
+ );
274
+ $links[ esc_html__( 'Delete', 'mainwp-child-reports' ) ] = add_query_arg(
275
+ array(
276
+ 'page' => 'bp-profile-setup',
277
+ 'mode' => 'delete_field',
278
+ 'field_id' => $field_id,
279
+ ),
280
+ admin_url( 'users.php' )
281
+ );
282
+ }
283
+ }
284
+
285
+ return $links;
286
+ }
287
+
288
+ public function register() {
289
+ parent::register();
290
+
291
+ $this->options = array_merge(
292
+ $this->options,
293
+ array(
294
+ 'hide-loggedout-adminbar' => array(
295
+ 'label' => esc_html_x( 'Toolbar', 'buddypress', 'mainwp-child-reports' ),
296
+ 'page' => 'bp-settings',
297
+ ),
298
+ '_bp_force_buddybar' => array(
299
+ 'label' => esc_html_x( 'Toolbar', 'buddypress', 'mainwp-child-reports' ),
300
+ 'page' => 'bp-settings',
301
+ ),
302
+ 'bp-disable-account-deletion' => array(
303
+ 'label' => esc_html_x( 'Account Deletion', 'buddypress', 'mainwp-child-reports' ),
304
+ 'page' => 'bp-settings',
305
+ ),
306
+ 'bp-disable-profile-sync' => array(
307
+ 'label' => esc_html_x( 'Profile Syncing', 'buddypress', 'mainwp-child-reports' ),
308
+ 'page' => 'bp-settings',
309
+ ),
310
+ 'bp_restrict_group_creation' => array(
311
+ 'label' => esc_html_x( 'Group Creation', 'buddypress', 'mainwp-child-reports' ),
312
+ 'page' => 'bp-settings',
313
+ ),
314
+ 'bb-config-location' => array(
315
+ 'label' => esc_html_x( 'bbPress Configuration', 'buddypress', 'mainwp-child-reports' ),
316
+ 'page' => 'bp-settings',
317
+ ),
318
+ 'bp-disable-blogforum-comments' => array(
319
+ 'label' => _x( 'Blog &amp; Forum Comments', 'buddypress', 'mainwp-child-reports' ),
320
+ 'page' => 'bp-settings',
321
+ ),
322
+ '_bp_enable_heartbeat_refresh' => array(
323
+ 'label' => esc_html_x( 'Activity auto-refresh', 'buddypress', 'mainwp-child-reports' ),
324
+ 'page' => 'bp-settings',
325
+ ),
326
+ '_bp_enable_akismet' => array(
327
+ 'label' => esc_html_x( 'Akismet', 'buddypress', 'mainwp-child-reports' ),
328
+ 'page' => 'bp-settings',
329
+ ),
330
+ 'bp-disable-avatar-uploads' => array(
331
+ 'label' => esc_html_x( 'Avatar Uploads', 'buddypress', 'mainwp-child-reports' ),
332
+ 'page' => 'bp-settings',
333
+ ),
334
+ )
335
+ );
336
+ }
337
+
338
+ public function callback_update_option( $option, $old, $new ) {
339
+ $this->check( $option, $old, $new );
340
+ }
341
+
342
+ public function callback_add_option( $option, $val ) {
343
+ $this->check( $option, null, $val );
344
+ }
345
+
346
+ public function callback_delete_option( $option ) {
347
+ $this->check( $option, null, null );
348
+ }
349
+
350
+ public function callback_update_site_option( $option, $old, $new ) {
351
+ $this->check( $option, $old, $new );
352
+ }
353
+
354
+ public function callback_add_site_option( $option, $val ) {
355
+ $this->check( $option, null, $val );
356
+ }
357
+
358
+ public function callback_delete_site_option( $option ) {
359
+ $this->check( $option, null, null );
360
+ }
361
+
362
+ public function check( $option, $old_value, $new_value ) {
363
+ if ( ! array_key_exists( $option, $this->options ) ) {
364
+ return;
365
+ }
366
+
367
+ $replacement = str_replace( '-', '_', $option );
368
+
369
+ if ( method_exists( $this, 'check_' . $replacement ) ) {
370
+ call_user_func( array(
371
+ $this,
372
+ 'check_' . $replacement,
373
+ ), $old_value, $new_value );
374
+ } else {
375
+ $data = $this->options[ $option ];
376
+ $option_title = $data['label'];
377
+ $context = isset( $data['context'] ) ? $data['context'] : 'settings';
378
+ $page = isset( $data['page'] ) ? $data['page'] : null;
379
+
380
+ $this->log(
381
+ // translators: Placeholder refers to setting name (e.g. "Group Creation")
382
+ __( '"%s" setting updated', 'mainwp-child-reports' ),
383
+ compact( 'option_title', 'option', 'old_value', 'new_value', 'page' ),
384
+ null,
385
+ $context,
386
+ isset( $data['action'] ) ? $data['action'] : 'updated'
387
+ );
388
+ }
389
+ }
390
+
391
+ public function check_bp_active_components( $old_value, $new_value ) {
392
+ $options = array();
393
+
394
+ if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
395
+ return;
396
+ }
397
+
398
+ foreach ( $this->get_changed_keys( $old_value, $new_value, 0 ) as $field_key => $field_value ) {
399
+ $options[ $field_key ] = $field_value;
400
+ }
401
+
402
+ $components = \bp_core_admin_get_components();
403
+
404
+ $actions = array(
405
+ true => esc_html__( 'activated', 'mainwp-child-reports' ),
406
+ false => esc_html__( 'deactivated', 'mainwp-child-reports' ),
407
+ );
408
+
409
+ foreach ( $options as $option => $option_value ) {
410
+ if ( ! isset( $components[ $option ], $actions[ $option_value ] ) ) {
411
+ continue;
412
+ }
413
+
414
+ $this->log(
415
+ sprintf(
416
+ // translators: Placeholder refers to component title (e.g. "Members")
417
+ __( '"%1$s" component %2$s', 'mainwp-child-reports' ),
418
+ $components[ $option ]['title'],
419
+ $actions[ $option_value ]
420
+ ),
421
+ array(
422
+ 'option' => $option,
423
+ 'option_key' => 'bp-active-components',
424
+ 'old_value' => $old_value,
425
+ 'value' => $new_value,
426
+ ),
427
+ null,
428
+ 'components',
429
+ $option_value ? 'activated' : 'deactivated'
430
+ );
431
+ }
432
+ }
433
+
434
+ public function check_bp_pages( $old_value, $new_value ) {
435
+ $options = array();
436
+
437
+ if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
438
+ return;
439
+ }
440
+
441
+ foreach ( $this->get_changed_keys( $old_value, $new_value, 0 ) as $field_key => $field_value ) {
442
+ $options[ $field_key ] = $field_value;
443
+ }
444
+
445
+ $pages = array_merge(
446
+ $this->bp_get_directory_pages(),
447
+ array(
448
+ 'register' => esc_html_x( 'Register', 'buddypress', 'mainwp-child-reports' ),
449
+ 'activate' => esc_html_x( 'Activate', 'buddypress', 'mainwp-child-reports' ),
450
+ )
451
+ );
452
+
453
+ foreach ( $options as $option => $option_value ) {
454
+ if ( ! isset( $pages[ $option ] ) ) {
455
+ continue;
456
+ }
457
+
458
+ $page = ! empty( $new_value[ $option ] ) ? get_post( $new_value[ $option ] )->post_title : esc_html__( 'No page', 'mainwp-child-reports' );
459
+
460
+ $this->log(
461
+ sprintf(
462
+ // translators: Placeholders refer to a directory page, and a page title (e.g. "Register", "Registration" )
463
+ __( '"%1$s" page set to "%2$s"', 'mainwp-child-reports' ),
464
+ $pages[ $option ],
465
+ $page
466
+ ),
467
+ array(
468
+ 'option' => $option,
469
+ 'option_key' => 'bp-pages',
470
+ 'old_value' => $old_value,
471
+ 'value' => $new_value,
472
+ 'page_id' => empty( $new_value[ $option ] ) ? 0 : $new_value[ $option ],
473
+ ),
474
+ null,
475
+ 'components',
476
+ 'updated'
477
+ );
478
+ }
479
+ }
480
+
481
+ public function callback_bp_before_activity_delete( $args ) {
482
+ if ( empty( $args['id'] ) ) { // Bail if we're deleting in bulk
483
+ $this->_delete_activity_args = $args;
484
+
485
+ return;
486
+ }
487
+
488
+ $activity = new \BP_Activity_Activity( $args['id'] );
489
+
490
+ $this->_deleted_activity = $activity;
491
+ }
492
+
493
+ public function callback_bp_activity_deleted_activities( $activities_ids ) {
494
+ if ( 1 === count( $activities_ids ) && isset( $this->_deleted_activity ) ) { // Single activity deletion
495
+ $activity = $this->_deleted_activity;
496
+ $this->log(
497
+ sprintf(
498
+ // translators: Placeholder refers to an activity title (e.g. "Update")
499
+ __( '"%s" activity deleted', 'mainwp-child-reports' ),
500
+ strip_tags( $activity->action )
501
+ ),
502
+ array(
503
+ 'id' => $activity->id,
504
+ 'item_id' => $activity->item_id,
505
+ 'type' => $activity->type,
506
+ 'author' => $activity->user_id,
507
+ ),
508
+ $activity->id,
509
+ $activity->component,
510
+ 'deleted'
511
+ );
512
+ } else { // Bulk deletion
513
+ // Sometimes some objects removal are followed by deleting relevant
514
+ // activities, so we probably don't need to track those
515
+ if ( $this->ignore_activity_bulk_deletion ) {
516
+ $this->ignore_activity_bulk_deletion = false;
517
+
518
+ return;
519
+ }
520
+ $this->log(
521
+ sprintf(
522
+ // translators: Placeholder refers to an activity title (e.g. "Update")
523
+ __( '"%s" activities were deleted', 'mainwp-child-reports' ),
524
+ count( $activities_ids )
525
+ ),
526
+ array(
527
+ 'count' => count( $activities_ids ),
528
+ 'args' => $this->_delete_activity_args,
529
+ 'ids' => $activities_ids,
530
+ ),
531
+ null,
532
+ 'activity',
533
+ 'deleted'
534
+ );
535
+ }
536
+ }
537
+
538
+ public function callback_bp_activity_mark_as_spam( $activity, $by ) {
539
+ unset( $by );
540
+
541
+ $this->log(
542
+ sprintf(
543
+ // translators: Placeholder refers to an activity title (e.g. "Update")
544
+ __( 'Marked activity "%s" as spam', 'mainwp-child-reports' ),
545
+ strip_tags( $activity->action )
546
+ ),
547
+ array(
548
+ 'id' => $activity->id,
549
+ 'item_id' => $activity->item_id,
550
+ 'type' => $activity->type,
551
+ 'author' => $activity->user_id,
552
+ ),
553
+ $activity->id,
554
+ $activity->component,
555
+ 'spammed'
556
+ );
557
+ }
558
+
559
+ public function callback_bp_activity_mark_as_ham( $activity, $by ) {
560
+ unset( $by );
561
+
562
+ $this->log(
563
+ sprintf(
564
+ // translators: Placeholder refers to an activity title (e.g. "Update")
565
+ __( 'Unmarked activity "%s" as spam', 'mainwp-child-reports' ),
566
+ strip_tags( $activity->action )
567
+ ),
568
+ array(
569
+ 'id' => $activity->id,
570
+ 'item_id' => $activity->item_id,
571
+ 'type' => $activity->type,
572
+ 'author' => $activity->user_id,
573
+ ),
574
+ $activity->id,
575
+ $activity->component,
576
+ 'unspammed'
577
+ );
578
+ }
579
+
580
+ public function callback_bp_activity_admin_edit_after( $activity, $error ) {
581
+ unset( $error );
582
+
583
+ $this->log(
584
+ sprintf(
585
+ // translators: Placeholder refers to an activity title (e.g. "Update")
586
+ __( '"%s" activity updated', 'mainwp-child-reports' ),
587
+ strip_tags( $activity->action )
588
+ ),
589
+ array(
590
+ 'id' => $activity->id,
591
+ 'item_id' => $activity->item_id,
592
+ 'type' => $activity->type,
593
+ 'author' => $activity->user_id,
594
+ ),
595
+ $activity->id,
596
+ 'activity',
597
+ 'updated'
598
+ );
599
+ }
600
+
601
+ public function group_action( $group, $action, $meta = array(), $message = null ) {
602
+ if ( is_numeric( $group ) ) {
603
+ $group = \groups_get_group(
604
+ array(
605
+ 'group_id' => $group,
606
+ )
607
+ );
608
+ }
609
+
610
+ $replacements = array(
611
+ $group->name,
612
+ );
613
+
614
+ if ( ! $message ) {
615
+ if ( 'created' === $action ) {
616
+ // translators: Placeholder refers to a group name (e.g. "Favourites")
617
+ $message = esc_html__( '"%s" group created', 'mainwp-child-reports' );
618
+ } elseif ( 'updated' === $action ) {
619
+ // translators: Placeholder refers to a group name (e.g. "Favourites")
620
+ $message = esc_html__( '"%s" group updated', 'mainwp-child-reports' );
621
+ } elseif ( 'deleted' === $action ) {
622
+ // translators: Placeholder refers to a group name (e.g. "Favourites")
623
+ $message = esc_html__( '"%s" group deleted', 'mainwp-child-reports' );
624
+ } elseif ( 'joined' === $action ) {
625
+ // translators: Placeholder refers to a group name (e.g. "Favourites")
626
+ $message = esc_html__( 'Joined group "%s"', 'mainwp-child-reports' );
627
+ } elseif ( 'left' === $action ) {
628
+ // translators: Placeholder refers to a group name (e.g. "Favourites")
629
+ $message = esc_html__( 'Left group "%s"', 'mainwp-child-reports' );
630
+ } elseif ( 'banned' === $action ) {
631
+ // translators: Placeholders refer to a user display name, and a group name (e.g. "Jane Doe", "Favourites")
632
+ $message = esc_html__( 'Banned "%2$s" from "%1$s"', 'mainwp-child-reports' );
633
+ $replacements[] = get_user_by( 'id', $meta['user_id'] )->display_name;
634
+ } elseif ( 'unbanned' === $action ) {
635
+ // translators: Placeholders refer to a user display name, and a group name (e.g. "Jane Doe", "Favourites")
636
+ $message = esc_html__( 'Unbanned "%2$s" from "%1$s"', 'mainwp-child-reports' );
637
+ $replacements[] = get_user_by( 'id', $meta['user_id'] )->display_name;
638
+ } elseif ( 'removed' === $action ) {
639
+ // translators: Placeholders refer to a user display name, and a group name (e.g. "Jane Doe", "Favourites")
640
+ $message = esc_html__( 'Removed "%2$s" from "%1$s"', 'mainwp-child-reports' );
641
+ $replacements[] = get_user_by( 'id', $meta['user_id'] )->display_name;
642
+ } else {
643
+ return;
644
+ }
645
+ }
646
+
647
+ $this->log(
648
+ vsprintf(
649
+ $message,
650
+ $replacements
651
+ ),
652
+ array_merge(
653
+ array(
654
+ 'id' => $group->id,
655
+ 'name' => $group->name,
656
+ 'slug' => $group->slug,
657
+ ),
658
+ $meta
659
+ ),
660
+ $group->id,
661
+ 'groups',
662
+ $action
663
+ );
664
+ }
665
+
666
+ public function callback_groups_create_group( $group_id, $member, $group ) {
667
+ unset( $group_id );
668
+ unset( $member );
669
+
670
+ $this->group_action( $group, 'created' );
671
+ }
672
+
673
+ public function callback_groups_update_group( $group_id, $group ) {
674
+ unset( $group_id );
675
+
676
+ $this->group_action( $group, 'updated' );
677
+ }
678
+
679
+ public function callback_groups_before_delete_group( $group_id ) {
680
+ $this->ignore_activity_bulk_deletion = true;
681
+ $this->group_action( $group_id, 'deleted' );
682
+ }
683
+
684
+ public function callback_groups_details_updated( $group_id ) {
685
+ $this->is_update = true;
686
+ $this->group_action( $group_id, 'updated' );
687
+ }
688
+
689
+ public function callback_groups_settings_updated( $group_id ) {
690
+ if ( $this->is_update ) {
691
+ return;
692
+ }
693
+ $this->group_action( $group_id, 'updated' );
694
+ }
695
+
696
+ public function callback_groups_leave_group( $group_id, $user_id ) {
697
+ $this->group_action( $group_id, 'left', compact( 'user_id' ) );
698
+ }
699
+
700
+ public function callback_groups_join_group( $group_id, $user_id ) {
701
+ $this->group_action( $group_id, 'joined', compact( 'user_id' ) );
702
+ }
703
+
704
+ public function callback_groups_promote_member( $group_id, $user_id, $status ) {
705
+ $group = \groups_get_group(
706
+ array(
707
+ 'group_id' => $group_id,
708
+ )
709
+ );
710
+ $user = new \WP_User( $user_id );
711
+ $roles = array(
712
+ 'admin' => esc_html_x( 'Administrator', 'buddypress', 'mainwp-child-reports' ),
713
+ 'mod' => esc_html_x( 'Moderator', 'buddypress', 'mainwp-child-reports' ),
714
+ );
715
+ $message = sprintf(
716
+ // translators: Placeholders refer to a user's display name, a user role, and a group name (e.g. "Jane Doe", "subscriber", "Favourites")
717
+ __( 'Promoted "%1$s" to "%2$s" in "%3$s"', 'mainwp-child-reports' ),
718
+ $user->display_name,
719
+ $roles[ $status ],
720
+ $group->name
721
+ );
722
+ $this->group_action( $group_id, 'promoted', compact( 'user_id', 'status' ), $message );
723
+ }
724
+
725
+ public function callback_groups_demote_member( $group_id, $user_id ) {
726
+ $group = \groups_get_group(
727
+ array(
728
+ 'group_id' => $group_id,
729
+ )
730
+ );
731
+ $user = new \WP_User( $user_id );
732
+ $message = sprintf(
733
+ // translators: Placeholders refer to a user's display name, a user role, and a group name (e.g. "Jane Doe", "Member", "Favourites")
734
+ __( 'Demoted "%1$s" to "%2$s" in "%3$s"', 'mainwp-child-reports' ),
735
+ $user->display_name,
736
+ _x( 'Member', 'buddypress', 'mainwp-child-reports' ),
737
+ $group->name
738
+ );
739
+ $this->group_action( $group_id, 'demoted', compact( 'user_id' ), $message );
740
+ }
741
+
742
+ public function callback_groups_ban_member( $group_id, $user_id ) {
743
+ $this->group_action( $group_id, 'banned', compact( 'user_id' ) );
744
+ }
745
+
746
+ public function callback_groups_unban_member( $group_id, $user_id ) {
747
+ $this->group_action( $group_id, 'unbanned', compact( 'user_id' ) );
748
+ }
749
+
750
+ public function callback_groups_remove_member( $group_id, $user_id ) {
751
+ $this->group_action( $group_id, 'removed', compact( 'user_id' ) );
752
+ }
753
+
754
+ public function field_action( $field, $action, $meta = array(), $message = null ) {
755
+ $replacements = array(
756
+ $field->name,
757
+ );
758
+
759
+ if ( ! $message ) {
760
+ if ( 'created' === $action ) {
761
+ // translators: Placeholder refers to a user profile field (e.g. "Job Title")
762
+ $message = esc_html__( 'Created profile field "%s"', 'mainwp-child-reports' );
763
+ } elseif ( 'updated' === $action ) {
764
+ // translators: Placeholder refers to a user profile field (e.g. "Job Title")
765
+ $message = esc_html__( 'Updated profile field "%s"', 'mainwp-child-reports' );
766
+ } elseif ( 'deleted' === $action ) {
767
+ // translators: Placeholder refers to a user profile field (e.g. "Job Title")
768
+ $message = esc_html__( 'Deleted profile field "%s"', 'mainwp-child-reports' );
769
+ } else {
770
+ return;
771
+ }
772
+ }
773
+
774
+ $this->log(
775
+ vsprintf(
776
+ $message,
777
+ $replacements
778
+ ),
779
+ array_merge(
780
+ array(
781
+ 'field_id' => $field->id,
782
+ 'field_name' => $field->name,
783
+ 'group_id' => $field->group_id,
784
+ ),
785
+ $meta
786
+ ),
787
+ $field->id,
788
+ 'profile_fields',
789
+ $action
790
+ );
791
+ }
792
+
793
+ public function callback_xprofile_field_after_save( $field ) {
794
+ $action = isset( $field->id ) ? 'updated' : 'created';
795
+ $this->field_action( $field, $action );
796
+ }
797
+
798
+ public function callback_xprofile_fields_deleted_field( $field ) {
799
+ $this->field_action( $field, 'deleted' );
800
+ }
801
+
802
+ public function field_group_action( $group, $action, $meta = array(), $message = null ) {
803
+ $replacements = array(
804
+ $group->name,
805
+ );
806
+
807
+ if ( ! $message ) {
808
+ if ( 'created' === $action ) {
809
+ // translators: Placeholder refers to a user profile field group (e.g. "Appearance")
810
+ $message = esc_html__( 'Created profile field group "%s"', 'mainwp-child-reports' );
811
+ } elseif ( 'updated' === $action ) {
812
+ // translators: Placeholder refers to a user profile field group (e.g. "Appearance")
813
+ $message = esc_html__( 'Updated profile field group "%s"', 'mainwp-child-reports' );
814
+ } elseif ( 'deleted' === $action ) {
815
+ // translators: Placeholder refers to a user profile field group (e.g. "Appearance")
816
+ $message = esc_html__( 'Deleted profile field group "%s"', 'mainwp-child-reports' );
817
+ } else {
818
+ return;
819
+ }
820
+ }
821
+
822
+ $this->log(
823
+ vsprintf(
824
+ $message,
825
+ $replacements
826
+ ),
827
+ array_merge(
828
+ array(
829
+ 'group_id' => $group->id,
830
+ 'group_name' => $group->name,
831
+ ),
832
+ $meta
833
+ ),
834
+ $group->id,
835
+ 'profile_fields',
836
+ $action
837
+ );
838
+ }
839
+
840
+ public function callback_xprofile_group_after_save( $group ) {
841
+ global $wpdb;
842
+ // a bit hacky, due to inconsistency with BP action scheme, see callback_xprofile_field_after_save for correct behavior
843
+ $action = ( $group->id === $wpdb->insert_id ) ? 'created' : 'updated';
844
+ $this->field_group_action( $group, $action );
845
+ }
846
+
847
+ public function callback_xprofile_groups_deleted_group( $group ) {
848
+ $this->field_group_action( $group, 'deleted' );
849
+ }
850
+
851
+ private function bp_get_directory_pages() {
852
+ $bp = \buddypress();
853
+ $directory_pages = array();
854
+
855
+ // Loop through loaded components and collect directories
856
+ if ( is_array( $bp->loaded_components ) ) {
857
+ foreach ( $bp->loaded_components as $component_slug => $component_id ) {
858
+ // Only components that need directories should be listed here
859
+ if ( isset( $bp->{$component_id} ) && ! empty( $bp->{$component_id}->has_directory ) ) {
860
+ // component->name was introduced in BP 1.5, so we must provide a fallback
861
+ $directory_pages[ $component_id ] = ! empty( $bp->{$component_id}->name ) ? $bp->{$component_id}->name : ucwords( $component_id );
862
+ }
863
+ }
864
+ }
865
+
866
+ return $directory_pages;
867
+ }
868
+ }
connectors/class-connector-comments.php ADDED
@@ -0,0 +1,649 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Comments extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'comments';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'comment_flood_trigger',
19
+ 'wp_insert_comment',
20
+ 'edit_comment',
21
+ 'before_delete_post',
22
+ 'deleted_post',
23
+ 'delete_comment',
24
+ 'trash_comment',
25
+ 'untrash_comment',
26
+ 'spam_comment',
27
+ 'unspam_comment',
28
+ 'transition_comment_status',
29
+ 'comment_duplicate_trigger',
30
+ );
31
+
32
+ /**
33
+ * Catch and store the post ID during post deletion
34
+ *
35
+ * @var int
36
+ */
37
+ protected $delete_post = 0;
38
+
39
+ /**
40
+ * Return translated connector label
41
+ *
42
+ * @return string Translated connector label
43
+ */
44
+ public function get_label() {
45
+ return esc_html__( 'Comments', 'mainwp-child-reports' );
46
+ }
47
+
48
+ /**
49
+ * Return translated action labels
50
+ *
51
+ * @return array Action label translations
52
+ */
53
+ public function get_action_labels() {
54
+ return array(
55
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
56
+ 'edited' => esc_html__( 'Edited', 'mainwp-child-reports' ),
57
+ 'replied' => esc_html__( 'Replied', 'mainwp-child-reports' ),
58
+ 'approved' => esc_html__( 'Approved', 'mainwp-child-reports' ),
59
+ 'unapproved' => esc_html__( 'Unapproved', 'mainwp-child-reports' ),
60
+ 'trashed' => esc_html__( 'Trashed', 'mainwp-child-reports' ),
61
+ 'untrashed' => esc_html__( 'Restored', 'mainwp-child-reports' ),
62
+ 'spammed' => esc_html__( 'Marked as Spam', 'mainwp-child-reports' ),
63
+ 'unspammed' => esc_html__( 'Unmarked as Spam', 'mainwp-child-reports' ),
64
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
65
+ 'duplicate' => esc_html__( 'Duplicate', 'mainwp-child-reports' ),
66
+ 'flood' => esc_html__( 'Throttled', 'mainwp-child-reports' ),
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Return translated context labels
72
+ *
73
+ * @return array Context label translations
74
+ */
75
+ public function get_context_labels() {
76
+ return array(
77
+ 'comments' => esc_html__( 'Comments', 'mainwp-child-reports' ),
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Return translated comment type labels
83
+ *
84
+ * @return array Comment type label translations
85
+ */
86
+ public function get_comment_type_labels() {
87
+ return apply_filters(
88
+ 'wp_mainwp_stream_comments_comment_type_labels',
89
+ array(
90
+ 'comment' => esc_html__( 'Comment', 'mainwp-child-reports' ),
91
+ 'trackback' => esc_html__( 'Trackback', 'mainwp-child-reports' ),
92
+ 'pingback' => esc_html__( 'Pingback', 'mainwp-child-reports' ),
93
+ )
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Return the comment type label for a given comment ID
99
+ *
100
+ * @param int $comment_id ID of the comment
101
+ *
102
+ * @return string The comment type label
103
+ */
104
+ public function get_comment_type_label( $comment_id ) {
105
+ $comment_type = get_comment_type( $comment_id );
106
+
107
+ if ( empty( $comment_type ) ) {
108
+ $comment_type = 'comment';
109
+ }
110
+
111
+ $comment_type_labels = $this->get_comment_type_labels();
112
+
113
+ $label = isset( $comment_type_labels[ $comment_type ] ) ? $comment_type_labels[ $comment_type ] : $comment_type;
114
+
115
+ return $label;
116
+ }
117
+
118
+ /**
119
+ * Add action links to Stream drop row in admin list screen
120
+ *
121
+ * @filter wp_mainwp_stream_action_links_{connector}
122
+ *
123
+ * @param array $links Previous links registered
124
+ * @param object $record Stream record
125
+ *
126
+ * @return array Action links
127
+ */
128
+ public function action_links( $links, $record ) {
129
+ if ( $record->object_id ) {
130
+ $comment = get_comment( $record->object_id );
131
+ if ( $comment ) {
132
+ $approve_nonce = wp_create_nonce( "approve-comment_$comment->comment_ID" );
133
+
134
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = admin_url( "comment.php?action=editcomment&c=$comment->comment_ID" );
135
+
136
+ if ( 1 === $comment->comment_approved ) {
137
+ $links[ esc_html__( 'Unapprove', 'mainwp-child-reports' ) ] = admin_url(
138
+ sprintf(
139
+ 'comment.php?action=unapprovecomment&c=%s&_wpnonce=%s',
140
+ $record->object_id,
141
+ $approve_nonce
142
+ )
143
+ );
144
+ } elseif ( empty( $comment->comment_approved ) ) {
145
+ $links[ esc_html__( 'Approve', 'mainwp-child-reports' ) ] = admin_url(
146
+ sprintf(
147
+ 'comment.php?action=approvecomment&c=%s&_wpnonce=%s',
148
+ $record->object_id,
149
+ $approve_nonce
150
+ )
151
+ );
152
+ }
153
+ }
154
+ }
155
+
156
+ return $links;
157
+ }
158
+
159
+ /**
160
+ * Fetches the comment author and returns the specified field.
161
+ *
162
+ * This also takes into consideration whether or not the blog requires only
163
+ * name and e-mail or that users be logged in to comment. In either case it
164
+ * will try to see if the e-mail provided does belong to a registered user.
165
+ *
166
+ * @param object|int $comment A comment object or comment ID
167
+ * @param string $field What field you want to return
168
+ *
169
+ * @return int|string $output User ID or user display name
170
+ */
171
+ public function get_comment_author( $comment, $field = 'id' ) {
172
+ $comment = is_object( $comment ) ? $comment : get_comment( absint( $comment ) );
173
+
174
+ $req_name_email = get_option( 'require_name_email' );
175
+ $req_user_login = get_option( 'comment_registration' );
176
+
177
+ $user_id = 0;
178
+ $user_name = esc_html__( 'Guest', 'mainwp-child-reports' );
179
+
180
+ $output = '';
181
+
182
+ if ( $req_name_email && isset( $comment->comment_author_email ) && isset( $comment->comment_author ) ) {
183
+ $user = get_user_by( 'email', $comment->comment_author_email );
184
+ $user_id = isset( $user->ID ) ? $user->ID : 0;
185
+ $user_name = isset( $user->display_name ) ? $user->display_name : $comment->comment_author;
186
+ }
187
+
188
+ if ( $req_user_login ) {
189
+ $user = wp_get_current_user();
190
+ $user_id = $user->ID;
191
+ $user_name = $user->display_name;
192
+ }
193
+
194
+ if ( 'id' === $field ) {
195
+ $output = $user_id;
196
+ } elseif ( 'name' === $field ) {
197
+ $output = $user_name;
198
+ }
199
+
200
+ return $output;
201
+ }
202
+
203
+ /**
204
+ * Tracks comment flood blocks
205
+ *
206
+ * @action comment_flood_trigger
207
+ *
208
+ * @param string $time_lastcomment
209
+ * @param string $time_newcomment
210
+ */
211
+ public function callback_comment_flood_trigger( $time_lastcomment, $time_newcomment ) {
212
+ $options = wp_mainwp_stream_get_instance()->settings->options;
213
+ $flood_tracking = isset( $options['advanced_comment_flood_tracking'] ) ? $options['advanced_comment_flood_tracking'] : false;
214
+
215
+ if ( ! $flood_tracking ) {
216
+ return;
217
+ }
218
+
219
+ $req_user_login = get_option( 'comment_registration' );
220
+
221
+ if ( $req_user_login ) {
222
+ $user = wp_get_current_user();
223
+ $user_id = $user->ID;
224
+ $user_name = $user->display_name;
225
+ } else {
226
+ $user_name = esc_html__( 'a logged out user', 'mainwp-child-reports' );
227
+ }
228
+
229
+ $this->log(
230
+ // translators: Placeholder refers to a username (e.g. "administrator")
231
+ __( 'Comment flooding by %s detected and prevented', 'mainwp-child-reports' ),
232
+ compact( 'user_name', 'user_id', 'time_lastcomment', 'time_newcomment' ),
233
+ null,
234
+ 'comments',
235
+ 'flood'
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Tracks comment creation
241
+ *
242
+ * @action wp_insert_comment
243
+ *
244
+ * @param int $comment_id
245
+ * @param object $comment
246
+ */
247
+ public function callback_wp_insert_comment( $comment_id, $comment ) {
248
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
249
+ return;
250
+ }
251
+
252
+ $user_id = $this->get_comment_author( $comment, 'id' );
253
+ $user_name = $this->get_comment_author( $comment, 'name' );
254
+ $post_id = $comment->comment_post_ID;
255
+ $post_type = get_post_type( $post_id );
256
+ $post = get_post( $post_id );
257
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
258
+ $comment_status = ( 1 === $comment->comment_approved ) ? esc_html__( 'approved automatically', 'mainwp-child-reports' ) : esc_html__( 'pending approval', 'mainwp-child-reports' );
259
+ $is_spam = false;
260
+
261
+ // Auto-marked spam comments
262
+ $options = wp_mainwp_stream_get_instance()->settings->options;
263
+ $ak_tracking = isset( $options['advanced_akismet_tracking'] ) ? $options['advanced_akismet_tracking'] : false;
264
+
265
+ if ( class_exists( 'Akismet' ) && $ak_tracking && \Akismet::matches_last_comment( $comment ) ) {
266
+ $ak_last_comment = \Akismet::get_last_comment();
267
+ if ( 'true' === $ak_last_comment['akismet_result'] ) {
268
+ $is_spam = true;
269
+ $comment_status = esc_html__( 'automatically marked as spam by Akismet', 'mainwp-child-reports' );
270
+ }
271
+ }
272
+
273
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
274
+
275
+ if ( $comment->comment_parent ) {
276
+ $parent_user_name = get_comment_author( $comment->comment_parent );
277
+
278
+ $this->log(
279
+ // translators: Placeholders refer to a parent comment's author, a comment author, a post title, a comment status, and a comment type
280
+ _x(
281
+ 'Reply to %1$s\'s %5$s by %2$s on %3$s %4$s',
282
+ "1: Parent comment's author, 2: Comment author, 3: Post title, 4: Comment status, 5: Comment type",
283
+ 'mainwp-child-reports'
284
+ ),
285
+ compact( 'parent_user_name', 'user_name', 'post_title', 'comment_status', 'comment_type', 'post_id' ),
286
+ $comment_id,
287
+ $post_type,
288
+ 'replied',
289
+ $user_id
290
+ );
291
+ } else {
292
+ $this->log(
293
+ // translators: Placeholders refer to a comment author, a post title, a comment status, and a comment type
294
+ _x(
295
+ 'New %4$s by %1$s on %2$s %3$s',
296
+ '1: Comment author, 2: Post title 3: Comment status, 4: Comment type',
297
+ 'mainwp-child-reports'
298
+ ),
299
+ compact( 'user_name', 'post_title', 'comment_status', 'comment_type', 'post_id', 'is_spam' ),
300
+ $comment_id,
301
+ $post_type,
302
+ $is_spam ? 'spammed' : 'created',
303
+ $user_id
304
+ );
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Tracks comment updates
310
+ *
311
+ * @action edit_comment
312
+ *
313
+ * @param int $comment_id
314
+ */
315
+ public function callback_edit_comment( $comment_id ) {
316
+ $comment = get_comment( $comment_id );
317
+
318
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
319
+ return;
320
+ }
321
+
322
+ $user_id = $this->get_comment_author( $comment, 'id' );
323
+ $user_name = $this->get_comment_author( $comment, 'name' );
324
+ $post_id = $comment->comment_post_ID;
325
+ $post_type = get_post_type( $post_id );
326
+ $post = get_post( $post_id );
327
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
328
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
329
+
330
+ $this->log(
331
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
332
+ _x(
333
+ '%1$s\'s %3$s on %2$s edited',
334
+ '1: Comment author, 2: Post title, 3: Comment type',
335
+ 'mainwp-child-reports'
336
+ ),
337
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
338
+ $comment_id,
339
+ $post_type,
340
+ 'edited'
341
+ );
342
+ }
343
+
344
+ /**
345
+ * Catch the post ID during deletion
346
+ *
347
+ * @action before_delete_post
348
+ *
349
+ * @param int $post_id
350
+ */
351
+ public function callback_before_delete_post( $post_id ) {
352
+ if ( wp_is_post_revision( $post_id ) ) {
353
+ return;
354
+ }
355
+
356
+ $this->delete_post = $post_id;
357
+ }
358
+
359
+ /**
360
+ * Reset the post ID after deletion
361
+ *
362
+ * @action deleted_post
363
+ *
364
+ * @param int $post_id
365
+ */
366
+ public function callback_deleted_post( $post_id ) {
367
+ if ( wp_is_post_revision( $post_id ) ) {
368
+ return;
369
+ }
370
+
371
+ $this->delete_post = 0;
372
+ }
373
+
374
+ /**
375
+ * Tracks comment delete
376
+ *
377
+ * @action delete_comment
378
+ *
379
+ * @param int $comment_id
380
+ */
381
+ public function callback_delete_comment( $comment_id ) {
382
+ $comment = get_comment( $comment_id );
383
+
384
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
385
+ return;
386
+ }
387
+
388
+ $user_id = $this->get_comment_author( $comment, 'id' );
389
+ $user_name = $this->get_comment_author( $comment, 'name' );
390
+ $post_id = absint( $comment->comment_post_ID );
391
+ $post_type = get_post_type( $post_id );
392
+ $post = get_post( $post_id );
393
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
394
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
395
+
396
+ if ( $this->delete_post === $post_id ) {
397
+ return;
398
+ }
399
+
400
+ $this->log(
401
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
402
+ _x(
403
+ '%1$s\'s %3$s on %2$s deleted permanently',
404
+ '1: Comment author, 2: Post title, 3: Comment type',
405
+ 'mainwp-child-reports'
406
+ ),
407
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
408
+ $comment_id,
409
+ $post_type,
410
+ 'deleted'
411
+ );
412
+ }
413
+
414
+ /**
415
+ * Tracks comment trashing
416
+ *
417
+ * @action trash_comment
418
+ *
419
+ * @param int $comment_id
420
+ */
421
+ public function callback_trash_comment( $comment_id ) {
422
+ $comment = get_comment( $comment_id );
423
+
424
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
425
+ return;
426
+ }
427
+
428
+ $user_id = $this->get_comment_author( $comment, 'id' );
429
+ $user_name = $this->get_comment_author( $comment, 'name' );
430
+ $post_id = $comment->comment_post_ID;
431
+ $post_type = get_post_type( $post_id );
432
+ $post = get_post( $post_id );
433
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
434
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
435
+
436
+ $this->log(
437
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
438
+ _x(
439
+ '%1$s\'s %3$s on %2$s trashed',
440
+ '1: Comment author, 2: Post title, 3: Comment type',
441
+ 'mainwp-child-reports'
442
+ ),
443
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
444
+ $comment_id,
445
+ $post_type,
446
+ 'trashed'
447
+ );
448
+ }
449
+
450
+ /**
451
+ * Tracks comment trashing
452
+ *
453
+ * @action untrash_comment
454
+ *
455
+ * @param int $comment_id
456
+ */
457
+ public function callback_untrash_comment( $comment_id ) {
458
+ $comment = get_comment( $comment_id );
459
+
460
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
461
+ return;
462
+ }
463
+
464
+ $user_id = $this->get_comment_author( $comment, 'id' );
465
+ $user_name = $this->get_comment_author( $comment, 'name' );
466
+ $post_id = $comment->comment_post_ID;
467
+ $post_type = get_post_type( $post_id );
468
+ $post = get_post( $post_id );
469
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
470
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
471
+
472
+ $this->log(
473
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
474
+ _x(
475
+ '%1$s\'s %3$s on %2$s restored',
476
+ '1: Comment author, 2: Post title, 3: Comment type',
477
+ 'mainwp-child-reports'
478
+ ),
479
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
480
+ $comment_id,
481
+ $post_type,
482
+ 'untrashed'
483
+ );
484
+ }
485
+
486
+ /**
487
+ * Tracks comment marking as spam
488
+ *
489
+ * @action spam_comment
490
+ *
491
+ * @param int $comment_id
492
+ */
493
+ public function callback_spam_comment( $comment_id ) {
494
+ $comment = get_comment( $comment_id );
495
+
496
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
497
+ return;
498
+ }
499
+
500
+ $user_id = $this->get_comment_author( $comment, 'id' );
501
+ $user_name = $this->get_comment_author( $comment, 'name' );
502
+ $post_id = $comment->comment_post_ID;
503
+ $post_type = get_post_type( $post_id );
504
+ $post = get_post( $post_id );
505
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
506
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
507
+
508
+ $this->log(
509
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
510
+ _x(
511
+ '%1$s\'s %3$s on %2$s marked as spam',
512
+ '1: Comment author, 2: Post title, 3: Comment type',
513
+ 'mainwp-child-reports'
514
+ ),
515
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
516
+ $comment_id,
517
+ $post_type,
518
+ 'spammed'
519
+ );
520
+ }
521
+
522
+ /**
523
+ * Tracks comment unmarking as spam
524
+ *
525
+ * @action unspam_comment
526
+ *
527
+ * @param int $comment_id
528
+ */
529
+ public function callback_unspam_comment( $comment_id ) {
530
+ $comment = get_comment( $comment_id );
531
+
532
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
533
+ return;
534
+ }
535
+
536
+ $user_id = $this->get_comment_author( $comment, 'id' );
537
+ $user_name = $this->get_comment_author( $comment, 'name' );
538
+ $post_id = $comment->comment_post_ID;
539
+ $post_type = get_post_type( $post_id );
540
+ $post = get_post( $post_id );
541
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
542
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
543
+
544
+ $this->log(
545
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
546
+ _x(
547
+ '%1$s\'s %3$s on %2$s unmarked as spam',
548
+ '1: Comment author, 2: Post title, 3: Comment type',
549
+ 'mainwp-child-reports'
550
+ ),
551
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
552
+ $comment_id,
553
+ $post_type,
554
+ 'unspammed'
555
+ );
556
+ }
557
+
558
+ /**
559
+ * Track comment status transition
560
+ *
561
+ * @action transition_comment_status
562
+ *
563
+ * @param string $new_status
564
+ * @param string $old_status
565
+ * @param object $comment
566
+ */
567
+ public function callback_transition_comment_status( $new_status, $old_status, $comment ) {
568
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
569
+ return;
570
+ }
571
+
572
+ if ( 'approved' !== $new_status && 'unapproved' !== $new_status || 'trash' === $old_status || 'spam' === $old_status ) {
573
+ return;
574
+ }
575
+
576
+ $user_id = $this->get_comment_author( $comment, 'id' );
577
+ $user_name = $this->get_comment_author( $comment, 'name' );
578
+ $post_id = $comment->comment_post_ID;
579
+ $post_type = get_post_type( $post_id );
580
+ $post = get_post( $post_id );
581
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
582
+ $comment_type = get_comment_type( $comment->comment_ID );
583
+
584
+ $this->log(
585
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
586
+ _x(
587
+ '%1$s\'s %3$s %2$s',
588
+ 'Comment status transition. 1: Comment author, 2: Post title, 3: Comment type',
589
+ 'mainwp-child-reports'
590
+ ),
591
+ compact( 'user_name', 'new_status', 'comment_type', 'old_status', 'post_title', 'post_id', 'user_id' ),
592
+ $comment->comment_ID,
593
+ $post_type,
594
+ $new_status
595
+ );
596
+ }
597
+
598
+ /**
599
+ * Track attempts to add duplicate comments
600
+ *
601
+ * @action comment_duplicate_trigger
602
+ *
603
+ * @param array $comment_data
604
+ */
605
+ public function callback_comment_duplicate_trigger( $comment_data ) {
606
+ global $wpdb;
607
+ unset( $comment_data );
608
+
609
+ $comment_id = $wpdb->last_result[0]->comment_ID;
610
+ $comment = get_comment( $comment_id );
611
+
612
+ if ( in_array( $comment->comment_type, $this->get_ignored_comment_types(), true ) ) {
613
+ return;
614
+ }
615
+
616
+ $user_id = $this->get_comment_author( $comment, 'id' );
617
+ $user_name = $this->get_comment_author( $comment, 'name' );
618
+ $post_id = $comment->comment_post_ID;
619
+ $post_type = get_post_type( $post_id );
620
+ $post = get_post( $post_id );
621
+ $post_title = $post ? "\"$post->post_title\"" : esc_html__( 'a post', 'mainwp-child-reports' );
622
+ $comment_type = mb_strtolower( $this->get_comment_type_label( $comment_id ) );
623
+
624
+ $this->log(
625
+ // translators: Placeholders refer to a comment author, a post title, and a comment type
626
+ _x(
627
+ 'Duplicate %3$s by %1$s prevented on %2$s',
628
+ '1: Comment author, 2: Post title, 3: Comment type',
629
+ 'mainwp-child-reports'
630
+ ),
631
+ compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
632
+ $comment_id,
633
+ $post_type,
634
+ 'duplicate'
635
+ );
636
+ }
637
+
638
+ /**
639
+ * Constructs list of ignored comment types for the comments connector
640
+ *
641
+ * @return array List of ignored comment types
642
+ */
643
+ public function get_ignored_comment_types() {
644
+ return apply_filters(
645
+ 'wp_mainwp_stream_comments_exclude_comment_types',
646
+ array()
647
+ );
648
+ }
649
+ }
connectors/class-connector-edd.php ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ class Connector_EDD extends Connector {
6
+
7
+ /**
8
+ * Connector slug
9
+ *
10
+ * @var string
11
+ */
12
+ public $name = 'edd';
13
+
14
+ /**
15
+ * Holds tracked plugin minimum version required
16
+ *
17
+ * @const string
18
+ */
19
+ const PLUGIN_MIN_VERSION = '1.8.8';
20
+
21
+ /**
22
+ * Actions registered for this connector
23
+ *
24
+ * @var array
25
+ */
26
+ public $actions = array(
27
+ 'update_option',
28
+ 'add_option',
29
+ 'delete_option',
30
+ 'update_site_option',
31
+ 'add_site_option',
32
+ 'delete_site_option',
33
+ 'edd_pre_update_discount_status',
34
+ 'edd_generate_pdf',
35
+ 'edd_earnings_export',
36
+ 'edd_payment_export',
37
+ 'edd_email_export',
38
+ 'edd_downloads_history_export',
39
+ 'edd_import_settings',
40
+ 'edd_export_settings',
41
+ 'add_user_meta',
42
+ 'update_user_meta',
43
+ 'delete_user_meta',
44
+ );
45
+
46
+ /**
47
+ * Tracked option keys
48
+ *
49
+ * @var array
50
+ */
51
+ public $options = array();
52
+
53
+ /**
54
+ * Tracking registered Settings, with overridden data
55
+ *
56
+ * @var array
57
+ */
58
+ public $options_override = array();
59
+
60
+ /**
61
+ * Tracking user meta updates related to this connector
62
+ *
63
+ * @var array
64
+ */
65
+ public $user_meta = array(
66
+ 'edd_user_public_key',
67
+ );
68
+
69
+ /**
70
+ * Flag status changes to not create duplicate entries
71
+ * @var bool
72
+ */
73
+ public $is_discount_status_change = false;
74
+
75
+ /**
76
+ * Flag status changes to not create duplicate entries
77
+ * @var bool
78
+ */
79
+ public $is_payment_status_change = false;
80
+
81
+ /**
82
+ * Check if plugin dependencies are satisfied and add an admin notice if not
83
+ *
84
+ * @return bool
85
+ */
86
+ public function is_dependency_satisfied() {
87
+ if ( class_exists( 'Easy_Digital_Downloads' ) && defined( 'EDD_VERSION' ) && version_compare( EDD_VERSION, self::PLUGIN_MIN_VERSION, '>=' ) ) {
88
+ return true;
89
+ }
90
+
91
+ return false;
92
+ }
93
+
94
+ /**
95
+ * Return translated connector label
96
+ *
97
+ * @return string Translated connector label
98
+ */
99
+ public function get_label() {
100
+ return esc_html_x( 'Easy Digital Downloads', 'edd', 'mainwp-child-reports' );
101
+ }
102
+
103
+ /**
104
+ * Return translated action labels
105
+ *
106
+ * @return array Action label translations
107
+ */
108
+ public function get_action_labels() {
109
+ return array(
110
+ 'created' => esc_html_x( 'Created', 'edd', 'mainwp-child-reports' ),
111
+ 'updated' => esc_html_x( 'Updated', 'edd', 'mainwp-child-reports' ),
112
+ 'added' => esc_html_x( 'Added', 'edd', 'mainwp-child-reports' ),
113
+ 'deleted' => esc_html_x( 'Deleted', 'edd', 'mainwp-child-reports' ),
114
+ 'trashed' => esc_html_x( 'Trashed', 'edd', 'mainwp-child-reports' ),
115
+ 'untrashed' => esc_html_x( 'Restored', 'edd', 'mainwp-child-reports' ),
116
+ 'generated' => esc_html_x( 'Generated', 'edd', 'mainwp-child-reports' ),
117
+ 'imported' => esc_html_x( 'Imported', 'edd', 'mainwp-child-reports' ),
118
+ 'exported' => esc_html_x( 'Exported', 'edd', 'mainwp-child-reports' ),
119
+ 'revoked' => esc_html_x( 'Revoked', 'edd', 'mainwp-child-reports' ),
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Return translated context labels
125
+ *
126
+ * @return array Context label translations
127
+ */
128
+ public function get_context_labels() {
129
+ return array(
130
+ 'downloads' => esc_html_x( 'Downloads', 'edd', 'mainwp-child-reports' ),
131
+ 'download_category' => esc_html_x( 'Categories', 'edd', 'mainwp-child-reports' ),
132
+ 'download_tag' => esc_html_x( 'Tags', 'edd', 'mainwp-child-reports' ),
133
+ 'discounts' => esc_html_x( 'Discounts', 'edd', 'mainwp-child-reports' ),
134
+ 'reports' => esc_html_x( 'Reports', 'edd', 'mainwp-child-reports' ),
135
+ 'api_keys' => esc_html_x( 'API Keys', 'edd', 'mainwp-child-reports' ),
136
+ //'payments' => esc_html_x( 'Payments', 'edd', 'mainwp-child-reports' ),
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Add action links to Stream drop row in admin list screen
142
+ *
143
+ * @filter wp_mainwp_stream_action_links_{connector}
144
+ *
145
+ * @param array $links Previous links registered
146
+ * @param object $record Stream record
147
+ *
148
+ * @return array Action links
149
+ */
150
+ public function action_links( $links, $record ) {
151
+ if ( in_array( $record->context, array( 'downloads' ), true ) ) {
152
+ $posts_connector = new Connector_Posts();
153
+ $links = $posts_connector->action_links( $links, $record );
154
+ } elseif ( in_array( $record->context, array( 'discounts' ), true ) ) {
155
+ $post_type_label = get_post_type_labels( get_post_type_object( 'edd_discount' ) )->singular_name;
156
+ $base = admin_url( 'edit.php?post_type=download&page=edd-discounts' );
157
+
158
+ // translators: Placeholder refers to a post type (e.g. "Post")
159
+ $links[ sprintf( esc_html__( 'Edit %s', 'mainwp-child-reports' ), $post_type_label ) ] = add_query_arg(
160
+ array(
161
+ 'edd-action' => 'edit_discount',
162
+ 'discount' => $record->object_id,
163
+ ),
164
+ $base
165
+ );
166
+
167
+ if ( 'active' === get_post( $record->object_id )->post_status ) {
168
+ // translators: Placeholder refers to a post type (e.g. "Post")
169
+ $links[ sprintf( esc_html__( 'Deactivate %s', 'mainwp-child-reports' ), $post_type_label ) ] = add_query_arg(
170
+ array(
171
+ 'edd-action' => 'deactivate_discount',
172
+ 'discount' => $record->object_id,
173
+ ),
174
+ $base
175
+ );
176
+ } else {
177
+ // translators: Placeholder refers to a post type (e.g. "Post")
178
+ $links[ sprintf( esc_html__( 'Activate %s', 'mainwp-child-reports' ), $post_type_label ) ] = add_query_arg(
179
+ array(
180
+ 'edd-action' => 'activate_discount',
181
+ 'discount' => $record->object_id,
182
+ ),
183
+ $base
184
+ );
185
+ }
186
+ } elseif ( in_array( $record->context, array(
187
+ 'download_category',
188
+ 'download_tag',
189
+ ), true ) ) {
190
+ $tax_label = get_taxonomy_labels( get_taxonomy( $record->context ) )->singular_name;
191
+ // translators: Placeholder refers to a taxonomy (e.g. "Category")
192
+ $links[ sprintf( esc_html__( 'Edit %s', 'mainwp-child-reports' ), $tax_label ) ] = get_edit_term_link( $record->object_id, $record->get_meta( 'taxonomy', true ) );
193
+ } elseif ( 'api_keys' === $record->context ) {
194
+ $user = new \WP_User( $record->object_id );
195
+
196
+ if ( apply_filters( 'edd_api_log_requests', true ) ) {
197
+ $links[ esc_html__( 'View API Log', 'mainwp-child-reports' ) ] = add_query_arg(
198
+ array(
199
+ 'view' => 'api_requests',
200
+ 'post_type' => 'download',
201
+ 'page' => 'edd-reports',
202
+ 'tab' => 'logs',
203
+ 's' => $user->user_email,
204
+ ), 'edit.php'
205
+ );
206
+ }
207
+
208
+ $links[ esc_html__( 'Revoke', 'mainwp-child-reports' ) ] = add_query_arg(
209
+ array(
210
+ 'post_type' => 'download',
211
+ 'user_id' => $record->object_id,
212
+ 'edd_action' => 'process_api_key',
213
+ 'edd_api_process' => 'revoke',
214
+ ), 'edit.php'
215
+ );
216
+ $links[ esc_html__( 'Reissue', 'mainwp-child-reports' ) ] = add_query_arg(
217
+ array(
218
+ 'post_type' => 'download',
219
+ 'user_id' => $record->object_id,
220
+ 'edd_action' => 'process_api_key',
221
+ 'edd_api_process' => 'regenerate',
222
+ ), 'edit.php'
223
+ );
224
+ }
225
+
226
+ return $links;
227
+ }
228
+
229
+ public function register() {
230
+ parent::register();
231
+
232
+ add_filter( 'wp_mainwp_stream_log_data', array( $this, 'log_override' ) );
233
+
234
+ $this->options = array(
235
+ 'edd_settings' => null,
236
+ );
237
+ }
238
+
239
+ public function callback_update_option( $option, $old, $new ) {
240
+ $this->check( $option, $old, $new );
241
+ }
242
+
243
+ public function callback_add_option( $option, $val ) {
244
+ $this->check( $option, null, $val );
245
+ }
246
+
247
+ public function callback_delete_option( $option ) {
248
+ $this->check( $option, null, null );
249
+ }
250
+
251
+ public function callback_update_site_option( $option, $old, $new ) {
252
+ $this->check( $option, $old, $new );
253
+ }
254
+
255
+ public function callback_add_site_option( $option, $val ) {
256
+ $this->check( $option, null, $val );
257
+ }
258
+
259
+ public function callback_delete_site_option( $option ) {
260
+ $this->check( $option, null, null );
261
+ }
262
+
263
+ public function check( $option, $old_value, $new_value ) {
264
+ if ( ! array_key_exists( $option, $this->options ) ) {
265
+ return;
266
+ }
267
+
268
+ $replacement = str_replace( '-', '_', $option );
269
+
270
+ if ( method_exists( $this, 'check_' . $replacement ) ) {
271
+ call_user_func( array(
272
+ $this,
273
+ 'check_' . $replacement,
274
+ ), $old_value, $new_value );
275
+ } else {
276
+ $data = $this->options[ $option ];
277
+ $option_title = $data['label'];
278
+ $context = isset( $data['context'] ) ? $data['context'] : 'settings';
279
+
280
+ $this->log(
281
+ // translators: Placeholder refers to a setting title (e.g. "Language")
282
+ __( '"%s" setting updated', 'mainwp-child-reports' ),
283
+ compact( 'option_title', 'option', 'old_value', 'new_value' ),
284
+ null,
285
+ $context,
286
+ isset( $data['action'] ) ? $data['action'] : 'updated'
287
+ );
288
+ }
289
+ }
290
+
291
+ public function check_edd_settings( $old_value, $new_value ) {
292
+ $options = array();
293
+
294
+ if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
295
+ return;
296
+ }
297
+
298
+ foreach ( $this->get_changed_keys( $old_value, $new_value, 0 ) as $field_key => $field_value ) {
299
+ $options[ $field_key ] = $field_value;
300
+ }
301
+
302
+ //TODO: Check this exists first
303
+ $settings = \edd_get_registered_settings();
304
+
305
+ foreach ( $options as $option => $option_value ) {
306
+ $field = null;
307
+
308
+ if ( 'banned_email' === $option ) {
309
+ $field = array(
310
+ 'name' => esc_html_x( 'Banned emails', 'edd', 'mainwp-child-reports' ),
311
+ );
312
+ $tab = 'general';
313
+ } else {
314
+ foreach ( $settings as $tab => $fields ) {
315
+ if ( isset( $fields[ $option ] ) ) {
316
+ $field = $fields[ $option ];
317
+ break;
318
+ }
319
+ }
320
+ }
321
+
322
+ if ( empty( $field ) ) {
323
+ continue;
324
+ }
325
+
326
+ $this->log(
327
+ // translators: Placeholder refers to a setting title (e.g. "Language")
328
+ __( '"%s" setting updated', 'mainwp-child-reports' ),
329
+ array(
330
+ 'option_title' => $field['name'],
331
+ 'option' => $option,
332
+ 'old_value' => $old_value,
333
+ 'value' => $new_value,
334
+ 'tab' => $tab,
335
+ ),
336
+ null,
337
+ 'settings',
338
+ 'updated'
339
+ );
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Override connector log for our own Settings / Actions
345
+ *
346
+ * @param array $data
347
+ *
348
+ * @return array|bool
349
+ */
350
+ public function log_override( $data ) {
351
+ if ( ! is_array( $data ) ) {
352
+ return $data;
353
+ }
354
+
355
+ if ( 'posts' === $data['connector'] && 'download' === $data['context'] ) {
356
+ // Download posts operations
357
+ $data['context'] = 'downloads';
358
+ $data['connector'] = $this->name;
359
+ } elseif ( 'posts' === $data['connector'] && 'edd_discount' === $data['context'] ) {
360
+ // Discount posts operations
361
+ if ( $this->is_discount_status_change ) {
362
+ return false;
363
+ }
364
+
365
+ if ( 'deleted' === $data['action'] ) {
366
+ // translators: Placeholder refers to a discount title (e.g. "Mother's Day")
367
+ $data['message'] = esc_html__( '"%1s" discount deleted', 'mainwp-child-reports' );
368
+ }
369
+
370
+ $data['context'] = 'discounts';
371
+ $data['connector'] = $this->name;
372
+ } elseif ( 'posts' === $data['connector'] && 'edd_payment' === $data['context'] ) {
373
+ // Payment posts operations
374
+ return false; // Do not track payments, they're well logged!
375
+ } elseif ( 'posts' === $data['connector'] && 'edd_log' === $data['context'] ) {
376
+ // Logging operations
377
+ return false; // Do not track notes, because they're basically logs
378
+ } elseif ( 'comments' === $data['connector'] && 'edd_payment' === $data['context'] ) {
379
+ // Payment notes ( comments ) operations
380
+ return false; // Do not track notes, because they're basically logs
381
+ } elseif ( 'taxonomies' === $data['connector'] && 'download_category' === $data['context'] ) {
382
+ $data['connector'] = $this->name;
383
+ } elseif ( 'taxonomies' === $data['connector'] && 'download_tag' === $data['context'] ) {
384
+ $data['connector'] = $this->name;
385
+ } elseif ( 'taxonomies' === $data['connector'] && 'edd_log_type' === $data['context'] ) {
386
+ return false;
387
+ } elseif ( 'settings' === $data['connector'] && 'edd_settings' === $data['args']['option'] ) {
388
+ return false;
389
+ }
390
+
391
+ return $data;
392
+ }
393
+
394
+ public function callback_edd_pre_update_discount_status( $code_id, $new_status ) {
395
+ $this->is_discount_status_change = true;
396
+
397
+ $this->log(
398
+ sprintf(
399
+ // translators: Placeholders refer to a discount title, and a status (e.g. "Mother's Day", "activated")
400
+ __( '"%1$s" discount %2$s', 'mainwp-child-reports' ),
401
+ get_post( $code_id )->post_title,
402
+ 'active' === $new_status ? esc_html__( 'activated', 'mainwp-child-reports' ) : esc_html__( 'deactivated', 'mainwp-child-reports' )
403
+ ),
404
+ array(
405
+ 'post_id' => $code_id,
406
+ 'status' => $new_status,
407
+ ),
408
+ $code_id,
409
+ 'discounts',
410
+ 'updated'
411
+ );
412
+ }
413
+
414
+ private function callback_edd_generate_pdf() {
415
+ $this->report_generated( 'pdf' );
416
+ }
417
+
418
+ public function callback_edd_earnings_export() {
419
+ $this->report_generated( 'earnings' );
420
+ }
421
+
422
+ public function callback_edd_payment_export() {
423
+ $this->report_generated( 'payments' );
424
+ }
425
+
426
+ public function callback_edd_email_export() {
427
+ $this->report_generated( 'emails' );
428
+ }
429
+
430
+ public function callback_edd_downloads_history_export() {
431
+ $this->report_generated( 'download-history' );
432
+ }
433
+
434
+ private function report_generated( $type ) {
435
+ $label = '';
436
+
437
+ if ( 'pdf' === $type ) {
438
+ $label = esc_html__( 'Sales and Earnings', 'mainwp-child-reports' );
439
+ } elseif ( 'earnings' ) {
440
+ $label = esc_html__( 'Earnings', 'mainwp-child-reports' );
441
+ } elseif ( 'payments' ) {
442
+ $label = esc_html__( 'Payments', 'mainwp-child-reports' );
443
+ } elseif ( 'emails' ) {
444
+ $label = esc_html__( 'Emails', 'mainwp-child-reports' );
445
+ } elseif ( 'download-history' ) {
446
+ $label = esc_html__( 'Download History', 'mainwp-child-reports' );
447
+ }
448
+
449
+ $this->log(
450
+ sprintf(
451
+ // translators: Placeholder refers to a report title (e.g. "Sales and Earnings")
452
+ __( 'Generated %s report', 'mainwp-child-reports' ),
453
+ $label
454
+ ),
455
+ array(
456
+ 'type' => $type,
457
+ ),
458
+ null,
459
+ 'reports',
460
+ 'generated'
461
+ );
462
+ }
463
+
464
+ public function callback_edd_export_settings() {
465
+ $this->log(
466
+ __( 'Exported Settings', 'mainwp-child-reports' ),
467
+ array(),
468
+ null,
469
+ 'settings',
470
+ 'exported'
471
+ );
472
+ }
473
+
474
+ public function callback_edd_import_settings() {
475
+ $this->log(
476
+ __( 'Imported Settings', 'mainwp-child-reports' ),
477
+ array(),
478
+ null,
479
+ 'settings',
480
+ 'imported'
481
+ );
482
+ }
483
+
484
+ public function callback_update_user_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
485
+ unset( $meta_id );
486
+ $this->meta( $object_id, $meta_key, $_meta_value );
487
+ }
488
+
489
+ public function callback_add_user_meta( $object_id, $meta_key, $_meta_value ) {
490
+ $this->meta( $object_id, $meta_key, $_meta_value, true );
491
+ }
492
+
493
+ public function callback_delete_user_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
494
+ $this->meta( $object_id, $meta_key, null );
495
+ }
496
+
497
+ public function meta( $object_id, $key, $value, $is_add = false ) {
498
+ if ( ! in_array( $key, $this->user_meta, true ) ) {
499
+ return false;
500
+ }
501
+
502
+ $key = str_replace( '-', '_', $key );
503
+
504
+ if ( ! method_exists( $this, 'meta_' . $key ) ) {
505
+ return false;
506
+ }
507
+
508
+ return call_user_func( array(
509
+ $this,
510
+ 'meta_' . $key,
511
+ ), $object_id, $value, $is_add );
512
+ }
513
+
514
+ private function meta_edd_user_public_key( $user_id, $value, $is_add = false ) {
515
+ if ( is_null( $value ) ) {
516
+ $action = 'revoked';
517
+ $action_title = esc_html__( 'revoked', 'mainwp-child-reports' );
518
+ } elseif ( $is_add ) {
519
+ $action = 'created';
520
+ $action_title = esc_html__( 'created', 'mainwp-child-reports' );
521
+ } else {
522
+ $action = 'updated';
523
+ $action_title = esc_html__( 'updated', 'mainwp-child-reports' );
524
+ }
525
+
526
+ $this->log(
527
+ sprintf(
528
+ // translators: Placeholder refers to a status (e.g. "revoked")
529
+ __( 'User API Key %s', 'mainwp-child-reports' ),
530
+ $action_title
531
+ ),
532
+ array(
533
+ 'meta_value' => $value,
534
+ ),
535
+ $user_id,
536
+ 'api_keys',
537
+ $action
538
+ );
539
+ }
540
+ }
connectors/class-connector-editor.php ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Editor extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'editor';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array();
18
+
19
+ /**
20
+ * Actions registered for this connector
21
+ *
22
+ * @var array
23
+ */
24
+ private $edited_file = array();
25
+
26
+ /**
27
+ * Register connector in the WP Frontend
28
+ *
29
+ * @var bool
30
+ */
31
+ public $register_frontend = false;
32
+
33
+ /**
34
+ * Register all context hooks
35
+ *
36
+ * @return void
37
+ */
38
+ public function register() {
39
+ parent::register();
40
+ add_action( 'load-theme-editor.php', array( $this, 'get_edition_data' ) );
41
+ add_action( 'load-plugin-editor.php', array( $this, 'get_edition_data' ) );
42
+ add_filter( 'wp_redirect', array( $this, 'log_changes' ) );
43
+ }
44
+
45
+ /**
46
+ * Return translated connector label
47
+ *
48
+ * @return string Translated connector label
49
+ */
50
+ public function get_label() {
51
+ return esc_html__( 'Editor', 'mainwp-child-reports' );
52
+ }
53
+
54
+ /**
55
+ * Return translated action labels
56
+ *
57
+ * @return array Action label translations
58
+ */
59
+ public function get_action_labels() {
60
+ return array(
61
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Return translated context labels
67
+ *
68
+ * @return array Context label translations
69
+ */
70
+ public function get_context_labels() {
71
+ /**
72
+ * Filter available context labels for the Editor connector
73
+ *
74
+ * @return array Array of context slugs and their translated labels
75
+ */
76
+ return apply_filters(
77
+ 'wp_mainwp_stream_editor_context_labels',
78
+ array(
79
+ 'themes' => esc_html__( 'Themes', 'mainwp-child-reports' ),
80
+ 'plugins' => esc_html__( 'Plugins', 'mainwp-child-reports' ),
81
+ )
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Get the context based on wp_redirect location
87
+ *
88
+ * @param string $location The URL of the redirect
89
+ * @return string Context slug
90
+ */
91
+ public function get_context( $location ) {
92
+ $context = null;
93
+
94
+ if ( false !== strpos( $location, 'theme-editor.php' ) ) {
95
+ $context = 'themes';
96
+ }
97
+
98
+ if ( false !== strpos( $location, 'plugin-editor.php' ) ) {
99
+ $context = 'plugins';
100
+ }
101
+
102
+ /**
103
+ * Filter available contexts for the Editor connector
104
+ *
105
+ * @param string $context Context slug
106
+ * @param string $location The URL of the redirect
107
+ * @return string Context slug
108
+ */
109
+ return apply_filters( 'wp_mainwp_stream_editor_context', $context, $location );
110
+ }
111
+
112
+ /**
113
+ * Get the message format for file updates
114
+ *
115
+ * @return string Translated string
116
+ */
117
+ public function get_message() {
118
+ // translators: Placeholders refer to a file name, and a theme / plugin name (e.g. "index.php", "Stream")
119
+ return _x(
120
+ '"%1$s" in "%2$s" updated',
121
+ '1: File name, 2: Theme/plugin name',
122
+ 'mainwp-child-reports'
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Add action links to Stream drop row in admin list screen
128
+ *
129
+ * @filter wp_mainwp_stream_action_links_{connector}
130
+ *
131
+ * @param array $links Previous links registered
132
+ * @param object $record Stream record
133
+ *
134
+ * @return array Action links
135
+ */
136
+ public function action_links( $links, $record ) {
137
+ if ( current_user_can( 'edit_theme_options' ) ) {
138
+ $file_name = $record->get_meta( 'file', true );
139
+ $file_path = $record->get_meta( 'file_path', true );
140
+
141
+ if ( ! empty( $file_name ) && ! empty( $file_path ) ) {
142
+ $theme_slug = $record->get_meta( 'theme_slug', true );
143
+ $plugin_slug = $record->get_meta( 'plugin_slug', true );
144
+ $theme_exists = ( ! empty( $theme_slug ) && file_exists( $file_path ) );
145
+ $plugin_exists = ( ! empty( $plugin_slug ) && file_exists( $file_path ) );
146
+
147
+ if ( $theme_exists ) {
148
+ $links[ esc_html__( 'Edit File', 'mainwp-child-reports' ) ] = add_query_arg(
149
+ array(
150
+ 'theme' => rawurlencode( $theme_slug ),
151
+ 'file' => rawurlencode( $file_name ),
152
+ ),
153
+ self_admin_url( 'theme-editor.php' )
154
+ );
155
+
156
+ $links[ esc_html__( 'Theme Details', 'mainwp-child-reports' ) ] = add_query_arg(
157
+ array(
158
+ 'theme' => rawurlencode( $theme_slug ),
159
+ ),
160
+ self_admin_url( 'themes.php' )
161
+ );
162
+ }
163
+
164
+ if ( $plugin_exists ) {
165
+ $links[ esc_html__( 'Edit File', 'mainwp-child-reports' ) ] = add_query_arg(
166
+ array(
167
+ 'plugin' => rawurlencode( $plugin_slug ),
168
+ 'file' => rawurlencode( str_ireplace( trailingslashit( WP_PLUGIN_DIR ), '', $file_path ) ),
169
+ ),
170
+ self_admin_url( 'plugin-editor.php' )
171
+ );
172
+ }
173
+ }
174
+ }
175
+
176
+ return $links;
177
+ }
178
+
179
+ /**
180
+ * Retrieves data submitted on the screen, and prepares it for the appropriate context type
181
+ *
182
+ * @action load-theme-editor.php
183
+ * @action load-plugin-editor.php
184
+ */
185
+ public function get_edition_data() {
186
+ if (
187
+ (
188
+ isset( $_SERVER['REQUEST_METHOD'] )
189
+ &&
190
+ 'POST' !== esc_attr( $_SERVER['REQUEST_METHOD'] )
191
+ )
192
+ ||
193
+ 'update' !== wp_mainwp_stream_filter_input( INPUT_POST, 'action' )
194
+ ) {
195
+ return;
196
+ }
197
+
198
+ $theme_slug = wp_mainwp_stream_filter_input( INPUT_POST, 'theme' );
199
+ if ( $theme_slug ) {
200
+ $this->edited_file = $this->get_theme_data( $theme_slug );
201
+ }
202
+
203
+ $plugin_slug = wp_mainwp_stream_filter_input( INPUT_POST, 'plugin' );
204
+ if ( $plugin_slug ) {
205
+ $this->edited_file = $this->get_plugin_data( $plugin_slug );
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Retrieve theme data needed for the log message
211
+ *
212
+ * @param string $slug The theme slug (e.g. twentyfourteen)
213
+ *
214
+ * @return mixed $output Compacted variables
215
+ */
216
+ public function get_theme_data( $slug ) {
217
+ $theme = wp_get_theme( $slug );
218
+
219
+ if ( ! $theme->exists() || ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) ) {
220
+ return false;
221
+ }
222
+
223
+ $allowed_files = $theme->get_files( 'php', 1 );
224
+ $style_files = $theme->get_files( 'css' );
225
+ $file = wp_mainwp_stream_filter_input( INPUT_POST, 'file' );
226
+
227
+ $allowed_files['style.css'] = $style_files['style.css'];
228
+
229
+ if ( empty( $file ) ) {
230
+ $file_name = 'style.css';
231
+ $file_path = $allowed_files['style.css'];
232
+ } else {
233
+ $file_name = $file;
234
+ $file_path = sprintf( '%s/%s', $theme->get_stylesheet_directory(), $file_name );
235
+ }
236
+
237
+ $file_md5 = md5_file( $file_path );
238
+ $name = $theme->get( 'Name' );
239
+
240
+ $output = compact(
241
+ 'file_name',
242
+ 'file_path',
243
+ 'file_md5',
244
+ 'slug',
245
+ 'name'
246
+ );
247
+
248
+ return $output;
249
+ }
250
+
251
+ /**
252
+ * Retrieve plugin data needed for the log message
253
+ *
254
+ * @param string $slug The plugin file base name (e.g. akismet/akismet.php)
255
+ * @return mixed $output Compacted variables
256
+ */
257
+ public function get_plugin_data( $slug ) {
258
+ $base = null;
259
+ $name = null;
260
+ $slug = current( explode( '/', $slug ) );
261
+ $file_name = wp_mainwp_stream_filter_input( INPUT_POST, 'file' );
262
+ $file_path = WP_PLUGIN_DIR . '/' . $file_name;
263
+ $file_md5 = md5_file( $file_path );
264
+ $plugins = get_plugins();
265
+
266
+ foreach ( $plugins as $key => $plugin_data ) {
267
+ if ( 0 === strpos( $key, $slug ) ) {
268
+ $base = $key;
269
+ $name = $plugin_data['Name'];
270
+ break;
271
+ }
272
+ }
273
+
274
+ $file_name = str_ireplace( trailingslashit( $slug ), '', $file_name );
275
+ $slug = ! empty( $base ) ? $base : $slug;
276
+
277
+ $output = compact(
278
+ 'file_name',
279
+ 'file_path',
280
+ 'file_md5',
281
+ 'slug',
282
+ 'name'
283
+ );
284
+
285
+ return $output;
286
+ }
287
+
288
+ /**
289
+ * @filter wp_redirect
290
+ */
291
+ public function log_changes( $location ) {
292
+ if ( ! empty( $this->edited_file ) ) {
293
+ // TODO: phpcs fix
294
+ if ( md5_file( $this->edited_file['file_path'] ) !== $this->edited_file['file_md5'] ) {
295
+ $context = $this->get_context( $location );
296
+
297
+ switch ( $context ) {
298
+ case 'themes':
299
+ $name_key = 'theme_name';
300
+ $slug_key = 'theme_slug';
301
+ break;
302
+ case 'plugins':
303
+ $name_key = 'plugin_name';
304
+ $slug_key = 'plugin_slug';
305
+ break;
306
+ default:
307
+ $name_key = 'name';
308
+ $slug_key = 'slug';
309
+ }
310
+
311
+ $this->log(
312
+ $this->get_message(),
313
+ array(
314
+ 'file' => (string) $this->edited_file['file_name'],
315
+ $name_key => (string) $this->edited_file['name'],
316
+ $slug_key => (string) $this->edited_file['slug'],
317
+ 'file_path' => (string) $this->edited_file['file_path'],
318
+ ),
319
+ null,
320
+ $context,
321
+ 'updated'
322
+ );
323
+ }
324
+ }
325
+
326
+ return $location;
327
+ }
328
+ }
connectors/class-connector-gravityforms.php ADDED
@@ -0,0 +1,844 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_GravityForms extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'gravityforms';
11
+
12
+ /**
13
+ * Holds tracked plugin minimum version required
14
+ *
15
+ * @const string
16
+ */
17
+ const PLUGIN_MIN_VERSION = '1.9.14';
18
+
19
+ /**
20
+ * Actions registered for this connector
21
+ *
22
+ * @var array
23
+ */
24
+ public $actions = array(
25
+ 'gform_after_save_form',
26
+ 'gform_pre_confirmation_save',
27
+ 'gform_pre_notification_save',
28
+ 'gform_pre_notification_deleted',
29
+ 'gform_pre_confirmation_deleted',
30
+ 'gform_before_delete_form',
31
+ 'gform_post_form_trashed',
32
+ 'gform_post_form_restored',
33
+ 'gform_post_form_activated',
34
+ 'gform_post_form_deactivated',
35
+ 'gform_post_form_duplicated',
36
+ 'gform_post_form_views_deleted',
37
+ 'gform_post_export_entries',
38
+ 'gform_forms_post_import',
39
+ 'gform_delete_lead',
40
+ 'gform_post_note_added',
41
+ 'gform_pre_note_deleted',
42
+ 'gform_update_status',
43
+ 'gform_update_is_read',
44
+ 'gform_update_is_starred',
45
+ 'update_option',
46
+ 'add_option',
47
+ 'delete_option',
48
+ 'update_site_option',
49
+ 'add_site_option',
50
+ 'delete_site_option',
51
+ );
52
+
53
+ /**
54
+ * Tracked option keys
55
+ *
56
+ * @var array
57
+ */
58
+ public $options = array();
59
+
60
+ /**
61
+ * Tracking registered Settings, with overridden data
62
+ *
63
+ * @var array
64
+ */
65
+ public $options_override = array();
66
+
67
+ /**
68
+ * Check if plugin dependencies are satisfied and add an admin notice if not
69
+ *
70
+ * @return bool
71
+ */
72
+ public function is_dependency_satisfied() {
73
+ if ( class_exists( 'GFForms' ) && version_compare( \GFCommon::$version, self::PLUGIN_MIN_VERSION, '>=' ) ) {
74
+ return true;
75
+ }
76
+
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * Return translated connector label
82
+ *
83
+ * @return string Translated connector label
84
+ */
85
+ public function get_label() {
86
+ return esc_html_x( 'Gravity Forms', 'gravityforms', 'mainwp-child-reports' );
87
+ }
88
+
89
+ /**
90
+ * Return translated action labels
91
+ *
92
+ * @return array Action label translations
93
+ */
94
+ public function get_action_labels() {
95
+ return array(
96
+ 'created' => esc_html_x( 'Created', 'gravityforms', 'mainwp-child-reports' ),
97
+ 'updated' => esc_html_x( 'Updated', 'gravityforms', 'mainwp-child-reports' ),
98
+ 'exported' => esc_html_x( 'Exported', 'gravityforms', 'mainwp-child-reports' ),
99
+ 'imported' => esc_html_x( 'Imported', 'gravityforms', 'mainwp-child-reports' ),
100
+ 'added' => esc_html_x( 'Added', 'gravityforms', 'mainwp-child-reports' ),
101
+ 'deleted' => esc_html_x( 'Deleted', 'gravityforms', 'mainwp-child-reports' ),
102
+ 'trashed' => esc_html_x( 'Trashed', 'gravityforms', 'mainwp-child-reports' ),
103
+ 'untrashed' => esc_html_x( 'Restored', 'gravityforms', 'mainwp-child-reports' ),
104
+ 'duplicated' => esc_html_x( 'Duplicated', 'gravityforms', 'mainwp-child-reports' ),
105
+ 'activated' => esc_html_x( 'Activated', 'gravityforms', 'mainwp-child-reports' ),
106
+ 'deactivated' => esc_html_x( 'Deactivated', 'gravityforms', 'mainwp-child-reports' ),
107
+ 'views_deleted' => esc_html_x( 'Views Reset', 'gravityforms', 'mainwp-child-reports' ),
108
+ 'starred' => esc_html_x( 'Starred', 'gravityforms', 'mainwp-child-reports' ),
109
+ 'unstarred' => esc_html_x( 'Unstarred', 'gravityforms', 'mainwp-child-reports' ),
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Return translated context labels
115
+ *
116
+ * @return array Context label translations
117
+ */
118
+ public function get_context_labels() {
119
+ return array(
120
+ 'forms' => esc_html_x( 'Forms', 'gravityforms', 'mainwp-child-reports' ),
121
+ 'settings' => esc_html_x( 'Settings', 'gravityforms', 'mainwp-child-reports' ),
122
+ 'export' => esc_html_x( 'Import/Export', 'gravityforms', 'mainwp-child-reports' ),
123
+ 'entries' => esc_html_x( 'Entries', 'gravityforms', 'mainwp-child-reports' ),
124
+ 'notes' => esc_html_x( 'Notes', 'gravityforms', 'mainwp-child-reports' ),
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Add action links to Stream drop row in admin list screen
130
+ *
131
+ * @filter wp_mainwp_stream_action_links_{connector}
132
+ *
133
+ * @param array $links Previous links registered
134
+ * @param object $record Stream record
135
+ *
136
+ * @return array Action links
137
+ */
138
+ public function action_links( $links, $record ) {
139
+ if ( 'forms' === $record->context ) {
140
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = add_query_arg(
141
+ array(
142
+ 'page' => 'gf_edit_forms',
143
+ 'id' => $record->object_id,
144
+ ),
145
+ admin_url( 'admin.php' )
146
+ );
147
+ } elseif ( 'entries' === $record->context ) {
148
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = add_query_arg(
149
+ array(
150
+ 'page' => 'gf_entries',
151
+ 'view' => 'entry',
152
+ 'lid' => $record->object_id,
153
+ 'id' => $record->get_meta( 'form_id', true ),
154
+ ),
155
+ admin_url( 'admin.php' )
156
+ );
157
+ } elseif ( 'notes' === $record->context ) {
158
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = add_query_arg(
159
+ array(
160
+ 'page' => 'gf_entries',
161
+ 'view' => 'entry',
162
+ 'lid' => $record->get_meta( 'lead_id', true ),
163
+ 'id' => $record->get_meta( 'form_id', true ),
164
+ ),
165
+ admin_url( 'admin.php' )
166
+ );
167
+ } elseif ( 'settings' === $record->context ) {
168
+ $links[ esc_html__( 'Edit Settings', 'mainwp-child-reports' ) ] = add_query_arg(
169
+ array(
170
+ 'page' => 'gf_settings',
171
+ ),
172
+ admin_url( 'admin.php' )
173
+ );
174
+ }
175
+
176
+ return $links;
177
+ }
178
+
179
+ public function register() {
180
+ parent::register();
181
+
182
+ $this->options = array(
183
+ 'rg_gforms_disable_css' => array(
184
+ 'label' => esc_html_x( 'Output CSS', 'gravityforms', 'mainwp-child-reports' ),
185
+ ),
186
+ 'rg_gforms_enable_html5' => array(
187
+ 'label' => esc_html_x( 'Output HTML5', 'gravityforms', 'mainwp-child-reports' ),
188
+ ),
189
+ 'gform_enable_noconflict' => array(
190
+ 'label' => esc_html_x( 'No-Conflict Mode', 'gravityforms', 'mainwp-child-reports' ),
191
+ ),
192
+ 'rg_gforms_currency' => array(
193
+ 'label' => esc_html_x( 'Currency', 'gravityforms', 'mainwp-child-reports' ),
194
+ ),
195
+ 'rg_gforms_captcha_public_key' => array(
196
+ 'label' => esc_html_x( 'reCAPTCHA Public Key', 'gravityforms', 'mainwp-child-reports' ),
197
+ ),
198
+ 'rg_gforms_captcha_private_key' => array(
199
+ 'label' => esc_html_x( 'reCAPTCHA Private Key', 'gravityforms', 'mainwp-child-reports' ),
200
+ ),
201
+ 'rg_gforms_key' => null,
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Track Create/Update actions on Forms
207
+ *
208
+ * @param array $form
209
+ * @param bool $is_new
210
+ * @return void
211
+ */
212
+ public function callback_gform_after_save_form( $form, $is_new ) {
213
+ $title = $form['title'];
214
+ $id = $form['id'];
215
+
216
+ $this->log(
217
+ sprintf(
218
+ // translators: Placeholders refer to a form title, and a status (e.g. "Contact Form", "created")
219
+ __( '"%1$s" form %2$s', 'mainwp-child-reports' ),
220
+ $title,
221
+ $is_new ? esc_html__( 'created', 'mainwp-child-reports' ) : esc_html__( 'updated', 'mainwp-child-reports' )
222
+ ),
223
+ array(
224
+ 'action' => $is_new,
225
+ 'id' => $id,
226
+ 'title' => $title,
227
+ ),
228
+ $id,
229
+ 'forms',
230
+ $is_new ? 'created' : 'updated'
231
+ );
232
+ }
233
+
234
+ /**
235
+ * Track saving form confirmations
236
+ *
237
+ * @param array $confirmation
238
+ * @param array $form
239
+ * @param bool $is_new
240
+ * @return array
241
+ */
242
+ public function callback_gform_pre_confirmation_save( $confirmation, $form, $is_new = true ) {
243
+ if ( ! isset( $is_new ) ) {
244
+ $is_new = false;
245
+ }
246
+
247
+ $this->log(
248
+ sprintf(
249
+ // translators: Placeholders refer to a confirmation name, a status, and a form title (e.g. "Email", "created", "Contact Form")
250
+ __( '"%1$s" confirmation %2$s for "%3$s"', 'mainwp-child-reports' ),
251
+ $confirmation['name'],
252
+ $is_new ? esc_html__( 'created', 'mainwp-child-reports' ) : esc_html__( 'updated', 'mainwp-child-reports' ),
253
+ $form['title']
254
+ ),
255
+ array(
256
+ 'is_new' => $is_new,
257
+ 'form_id' => $form['id'],
258
+ ),
259
+ $form['id'],
260
+ 'forms',
261
+ 'updated'
262
+ );
263
+
264
+ return $confirmation;
265
+ }
266
+
267
+ /**
268
+ * Track saving form notifications
269
+ *
270
+ * @param array $notification
271
+ * @param array $form
272
+ * @param bool $is_new
273
+ * @return array
274
+ */
275
+ public function callback_gform_pre_notification_save( $notification, $form, $is_new = true ) {
276
+ if ( ! isset( $is_new ) ) {
277
+ $is_new = false;
278
+ }
279
+
280
+ $this->log(
281
+ sprintf(
282
+ // translators: Placeholders refer to a notification name, a status, and a form title (e.g. "Email", "created", "Contact Form")
283
+ __( '"%1$s" notification %2$s for "%3$s"', 'mainwp-child-reports' ),
284
+ $notification['name'],
285
+ $is_new ? esc_html__( 'created', 'mainwp-child-reports' ) : esc_html__( 'updated', 'mainwp-child-reports' ),
286
+ $form['title']
287
+ ),
288
+ array(
289
+ 'is_update' => $is_new,
290
+ 'form_id' => $form['id'],
291
+ ),
292
+ $form['id'],
293
+ 'forms',
294
+ 'updated'
295
+ );
296
+
297
+ return $notification;
298
+ }
299
+
300
+ /**
301
+ * Track deletion of notifications
302
+ *
303
+ * @param array $notification
304
+ * @param array $form
305
+ * @return void
306
+ */
307
+ public function callback_gform_pre_notification_deleted( $notification, $form ) {
308
+ $this->log(
309
+ sprintf(
310
+ // translators: Placeholders refer to a notification name, and a form title (e.g. "Email", "Contact Form")
311
+ __( '"%1$s" notification deleted from "%2$s"', 'mainwp-child-reports' ),
312
+ $notification['name'],
313
+ $form['title']
314
+ ),
315
+ array(
316
+ 'form_id' => $form['id'],
317
+ 'notification' => $notification,
318
+ ),
319
+ $form['id'],
320
+ 'forms',
321
+ 'updated'
322
+ );
323
+ }
324
+
325
+ /**
326
+ * Track deletion of confirmations
327
+ *
328
+ * @param array $confirmation
329
+ * @param array $form
330
+ * @return void
331
+ */
332
+ public function callback_gform_pre_confirmation_deleted( $confirmation, $form ) {
333
+ $this->log(
334
+ sprintf(
335
+ // translators: Placeholders refer to a confirmation name, and a form title (e.g. "Email", "Contact Form")
336
+ __( '"%1$s" confirmation deleted from "%2$s"', 'mainwp-child-reports' ),
337
+ $confirmation['name'],
338
+ $form['title']
339
+ ),
340
+ array(
341
+ 'form_id' => $form['id'],
342
+ 'confirmation' => $confirmation,
343
+ ),
344
+ $form['id'],
345
+ 'forms',
346
+ 'updated'
347
+ );
348
+ }
349
+
350
+ /**
351
+ * Track status change of confirmations
352
+ *
353
+ * @param array $confirmation
354
+ * @param array $form
355
+ * @param bool $is_active
356
+ * @return void
357
+ */
358
+ public function callback_gform_confirmation_status( $confirmation, $form, $is_active ) {
359
+ $this->log(
360
+ sprintf(
361
+ // translators: Placeholders refer to a confirmation name, a status, and a form title (e.g. "Email", "activated", "Contact Form")
362
+ __( '"%1$s" confirmation %2$s from "%3$s"', 'mainwp-child-reports' ),
363
+ $confirmation['name'],
364
+ $is_active ? esc_html__( 'activated', 'mainwp-child-reports' ) : esc_html__( 'deactivated', 'mainwp-child-reports' ),
365
+ $form['title']
366
+ ),
367
+ array(
368
+ 'form_id' => $form['id'],
369
+ 'confirmation' => $confirmation,
370
+ 'is_active' => $is_active,
371
+ ),
372
+ null,
373
+ 'forms',
374
+ 'updated'
375
+ );
376
+ }
377
+
378
+ /**
379
+ * Track status change of notifications
380
+ *
381
+ * @param array $notification
382
+ * @param array $form
383
+ * @param bool $is_active
384
+ * @return void
385
+ */
386
+ public function callback_gform_notification_status( $notification, $form, $is_active ) {
387
+ $this->log(
388
+ sprintf(
389
+ // translators: Placeholders refer to a notification name, a status, and a form title (e.g. "Email", "activated", "Contact Form")
390
+ __( '"%1$s" notification %2$s from "%3$s"', 'mainwp-child-reports' ),
391
+ $notification['name'],
392
+ $is_active ? esc_html__( 'activated', 'mainwp-child-reports' ) : esc_html__( 'deactivated', 'mainwp-child-reports' ),
393
+ $form['title']
394
+ ),
395
+ array(
396
+ 'form_id' => $form['id'],
397
+ 'notification' => $notification,
398
+ 'is_active' => $is_active,
399
+ ),
400
+ $form['id'],
401
+ 'forms',
402
+ 'updated'
403
+ );
404
+ }
405
+
406
+ public function callback_update_option( $option, $old, $new ) {
407
+ $this->check( $option, $old, $new );
408
+ }
409
+
410
+ public function callback_add_option( $option, $val ) {
411
+ $this->check( $option, null, $val );
412
+ }
413
+
414
+ public function callback_delete_option( $option ) {
415
+ $this->check( $option, null, null );
416
+ }
417
+
418
+ public function callback_update_site_option( $option, $old, $new ) {
419
+ $this->check( $option, $old, $new );
420
+ }
421
+
422
+ public function callback_add_site_option( $option, $val ) {
423
+ $this->check( $option, null, $val );
424
+ }
425
+
426
+ public function callback_delete_site_option( $option ) {
427
+ $this->check( $option, null, null );
428
+ }
429
+
430
+ public function check( $option, $old_value, $new_value ) {
431
+ if ( ! array_key_exists( $option, $this->options ) ) {
432
+ return;
433
+ }
434
+
435
+ if ( is_null( $this->options[ $option ] ) ) {
436
+ call_user_func( array( $this, 'check_' . str_replace( '-', '_', $option ) ), $old_value, $new_value );
437
+ } else {
438
+ $data = $this->options[ $option ];
439
+ $option_title = $data['label'];
440
+ $context = isset( $data['context'] ) ? $data['context'] : 'settings';
441
+
442
+ $this->log(
443
+ // translators: Placeholder refers to a setting title (e.g. "Language")
444
+ __( '"%s" setting updated', 'mainwp-child-reports' ),
445
+ compact( 'option_title', 'option', 'old_value', 'new_value' ),
446
+ null,
447
+ $context,
448
+ isset( $data['action'] ) ? $data['action'] : 'updated'
449
+ );
450
+ }
451
+ }
452
+
453
+ public function check_rg_gforms_key( $old_value, $new_value ) {
454
+ $is_update = ( $new_value && strlen( $new_value ) );
455
+ $option = 'rg_gforms_key';
456
+
457
+ $this->log(
458
+ sprintf(
459
+ // translators: Placeholder refers to a status (e.g. "updated")
460
+ __( 'Gravity Forms license key %s', 'mainwp-child-reports' ),
461
+ $is_update ? esc_html__( 'updated', 'mainwp-child-reports' ) : esc_html__( 'deleted', 'mainwp-child-reports' )
462
+ ),
463
+ compact( 'option', 'old_value', 'new_value' ),
464
+ null,
465
+ 'settings',
466
+ $is_update ? 'updated' : 'deleted'
467
+ );
468
+ }
469
+
470
+ public function callback_gform_post_export_entries( $form, $start_date, $end_date, $fields ) {
471
+ unset( $fields );
472
+ $this->log(
473
+ // translators: Placeholder refers to a form title (e.g. "Contact Form")
474
+ __( '"%s" form entries exported', 'mainwp-child-reports' ),
475
+ array(
476
+ 'form_title' => $form['title'],
477
+ 'form_id' => $form['id'],
478
+ 'start_date' => empty( $start_date ) ? null : $start_date,
479
+ 'end_date' => empty( $end_date ) ? null : $end_date,
480
+ ),
481
+ $form['id'],
482
+ 'export',
483
+ 'exported'
484
+ );
485
+ }
486
+
487
+ public function callback_gform_forms_post_import( $forms ) {
488
+ $forms_total = count( $forms );
489
+ $forms_ids = wp_list_pluck( $forms, 'id' );
490
+ $forms_titles = wp_list_pluck( $forms, 'title' );
491
+
492
+ $this->log(
493
+ // translators: Placeholder refers to a number of forms (e.g. "42")
494
+ _n( '%d form imported', '%d forms imported', $forms_total, 'mainwp-child-reports' ),
495
+ array(
496
+ 'count' => $forms_total,
497
+ 'ids' => $forms_ids,
498
+ 'titles' => $forms_titles,
499
+ ),
500
+ null,
501
+ 'export',
502
+ 'imported'
503
+ );
504
+ }
505
+
506
+ public function callback_gform_export_separator( $dummy, $form_id ) {
507
+ $form = $this->get_form( $form_id );
508
+
509
+ $this->log(
510
+ // translators: Placeholder refers to a form title (e.g. "Contact Form")
511
+ __( '"%s" form exported', 'mainwp-child-reports' ),
512
+ array(
513
+ 'form_title' => $form['title'],
514
+ 'form_id' => $form_id,
515
+ ),
516
+ $form_id,
517
+ 'export',
518
+ 'exported'
519
+ );
520
+
521
+ return $dummy;
522
+ }
523
+
524
+ public function callback_gform_export_options( $dummy, $forms ) {
525
+ $ids = wp_list_pluck( $forms, 'id' );
526
+ $titles = wp_list_pluck( $forms, 'title' );
527
+
528
+ $this->log(
529
+ // translators: Placeholder refers to a number of forms (e.g. "42")
530
+ _n( 'Export process started for %d form', 'Export process started for %d forms', count( $forms ), 'mainwp-child-reports' ),
531
+ array(
532
+ 'count' => count( $forms ),
533
+ 'ids' => $ids,
534
+ 'titles' => $titles,
535
+ ),
536
+ null,
537
+ 'export',
538
+ 'imported'
539
+ );
540
+
541
+ return $dummy;
542
+ }
543
+
544
+ public function callback_gform_delete_lead( $lead_id ) {
545
+ $lead = $this->get_lead( $lead_id );
546
+ $form = $this->get_form( $lead['form_id'] );
547
+
548
+ $this->log(
549
+ // translators: Placeholders refer to an ID, and a form title (e.g. "42", "Contact Form")
550
+ __( 'Lead #%1$d from "%2$s" deleted', 'mainwp-child-reports' ),
551
+ array(
552
+ 'lead_id' => $lead_id,
553
+ 'form_title' => $form['title'],
554
+ 'form_id' => $form['id'],
555
+ ),
556
+ $lead_id,
557
+ 'entries',
558
+ 'deleted'
559
+ );
560
+ }
561
+
562
+ public function callback_gform_post_note_added( $note_id, $lead_id, $user_id, $user_name, $note, $note_type ) {
563
+ unset( $user_id );
564
+ unset( $user_name );
565
+ unset( $note );
566
+ unset( $note_type );
567
+
568
+ $lead = \GFFormsModel::get_lead( $lead_id );
569
+ $form = $this->get_form( $lead['form_id'] );
570
+
571
+ $this->log(
572
+ // translators: Placeholders refer to an ID, another ID, and a form title (e.g. "42", "7", "Contact Form")
573
+ __( 'Note #%1$d added to lead #%2$d on "%3$s" form', 'mainwp-child-reports' ),
574
+ array(
575
+ 'note_id' => $note_id,
576
+ 'lead_id' => $lead_id,
577
+ 'form_title' => $form['title'],
578
+ 'form_id' => $form['id'],
579
+ ),
580
+ $note_id,
581
+ 'notes',
582
+ 'added'
583
+ );
584
+ }
585
+
586
+ public function callback_gform_pre_note_deleted( $note_id, $lead_id ) {
587
+ $lead = $this->get_lead( $lead_id );
588
+ $form = $this->get_form( $lead['form_id'] );
589
+
590
+ $this->log(
591
+ // translators: Placeholders refer to an ID, another ID, and a form title (e.g. "42", "7", "Contact Form")
592
+ __( 'Note #%1$d deleted from lead #%2$d on "%3$s" form', 'mainwp-child-reports' ),
593
+ array(
594
+ 'note_id' => $note_id,
595
+ 'lead_id' => $lead_id,
596
+ 'form_title' => $form['title'],
597
+ 'form_id' => $form['id'],
598
+ ),
599
+ $note_id,
600
+ 'notes',
601
+ 'deleted'
602
+ );
603
+ }
604
+
605
+ public function callback_gform_update_status( $lead_id, $status, $prev = '' ) {
606
+ $lead = $this->get_lead( $lead_id );
607
+ $form = $this->get_form( $lead['form_id'] );
608
+
609
+ if ( 'active' === $status && 'trash' === $prev ) {
610
+ $status = 'restore';
611
+ }
612
+
613
+ $actions = array(
614
+ 'active' => esc_html__( 'activated', 'mainwp-child-reports' ),
615
+ 'spam' => esc_html__( 'marked as spam', 'mainwp-child-reports' ),
616
+ 'trash' => esc_html__( 'trashed', 'mainwp-child-reports' ),
617
+ 'restore' => esc_html__( 'restored', 'mainwp-child-reports' ),
618
+ );
619
+
620
+ if ( ! isset( $actions[ $status ] ) ) {
621
+ return;
622
+ }
623
+
624
+ $this->log(
625
+ sprintf(
626
+ // translators: Placeholders refer to an ID, a status, and a form title (e.g. "42", "activated", "Contact Form")
627
+ __( 'Lead #%1$d %2$s on "%3$s" form', 'mainwp-child-reports' ),
628
+ $lead_id,
629
+ $actions[ $status ],
630
+ $form['title']
631
+ ),
632
+ array(
633
+ 'lead_id' => $lead_id,
634
+ 'form_title' => $form['title'],
635
+ 'form_id' => $form['id'],
636
+ 'status' => $status,
637
+ 'prev' => $prev,
638
+ ),
639
+ $lead_id,
640
+ 'entries',
641
+ $status
642
+ );
643
+ }
644
+
645
+ /**
646
+ * Callback fired when an entry is read/unread
647
+ *
648
+ * @param int $lead_id
649
+ * @param int $status
650
+ * @return void
651
+ */
652
+ public function callback_gform_update_is_read( $lead_id, $status ) {
653
+ $lead = $this->get_lead( $lead_id );
654
+ $form = $this->get_form( $lead['form_id'] );
655
+ $status = ( ! empty( $status ) ) ? esc_html__( 'read', 'mainwp-child-reports' ) : esc_html__( 'unread', 'mainwp-child-reports' );
656
+
657
+ $this->log(
658
+ sprintf(
659
+ // translators: Placeholders refer to an ID, a status, and a form title (e.g. "42", "unread", "Contact Form")
660
+ __( 'Entry #%1$d marked as %2$s on form #%3$d ("%4$s")', 'mainwp-child-reports' ),
661
+ $lead_id,
662
+ $status,
663
+ $form['id'],
664
+ $form['title']
665
+ ),
666
+ array(
667
+ 'lead_id' => $lead_id,
668
+ 'lead_status' => $status,
669
+ 'form_id' => $form['id'],
670
+ 'form_title' => $form['title'],
671
+ ),
672
+ $lead_id,
673
+ 'entries',
674
+ 'updated'
675
+ );
676
+ }
677
+
678
+ /**
679
+ * Callback fired when an entry is starred/unstarred
680
+ *
681
+ * @param int $lead_id
682
+ * @param int $status
683
+ * @return void
684
+ */
685
+ public function callback_gform_update_is_starred( $lead_id, $status ) {
686
+ $lead = $this->get_lead( $lead_id );
687
+ $form = $this->get_form( $lead['form_id'] );
688
+ $status = ( ! empty( $status ) ) ? esc_html__( 'starred', 'mainwp-child-reports' ) : esc_html__( 'unstarred', 'mainwp-child-reports' );
689
+ $action = $status;
690
+
691
+ $this->log(
692
+ sprintf(
693
+ // translators: Placeholders refer to an ID, a status, and a form title (e.g. "42", "starred", "Contact Form")
694
+ __( 'Entry #%1$d %2$s on form #%3$d ("%4$s")', 'mainwp-child-reports' ),
695
+ $lead_id,
696
+ $status,
697
+ $form['id'],
698
+ $form['title']
699
+ ),
700
+ array(
701
+ 'lead_id' => $lead_id,
702
+ 'lead_status' => $status,
703
+ 'form_id' => $form['id'],
704
+ 'form_title' => $form['title'],
705
+ ),
706
+ $lead_id,
707
+ 'entries',
708
+ $action
709
+ );
710
+ }
711
+
712
+ /**
713
+ * Callback fired when a form is deleted
714
+ *
715
+ * @param int $form_id Form ID
716
+ * @return void
717
+ */
718
+ public function callback_gform_before_delete_form( $form_id ) {
719
+ $this->log_form_action( $form_id, 'deleted' );
720
+ }
721
+
722
+ /**
723
+ * Callback fired when a form is trashed
724
+ *
725
+ * @param int $form_id Form ID
726
+ * @return void
727
+ */
728
+ public function callback_gform_post_form_trashed( $form_id ) {
729
+ $this->log_form_action( $form_id, 'trashed' );
730
+ }
731
+
732
+ /**
733
+ * Callback fired when a form is restored
734
+ *
735
+ * @param int $form_id Form ID
736
+ * @return void
737
+ */
738
+ public function callback_gform_post_form_restored( $form_id ) {
739
+ $this->log_form_action( $form_id, 'untrashed' );
740
+ }
741
+
742
+ /**
743
+ * Callback fired when a form is activated
744
+ *
745
+ * @param int $form_id Form ID
746
+ * @return void
747
+ */
748
+ public function callback_gform_post_form_activated( $form_id ) {
749
+ $this->log_form_action( $form_id, 'activated' );
750
+ }
751
+
752
+ /**
753
+ * Callback fired when a form is deactivated
754
+ *
755
+ * @param int $form_id Form ID
756
+ * @return void
757
+ */
758
+ public function callback_gform_post_form_deactivated( $form_id ) {
759
+ $this->log_form_action( $form_id, 'deactivated' );
760
+ }
761
+
762
+ /**
763
+ * Callback fired when a form is duplicated
764
+ *
765
+ * @param int $form_id Form ID
766
+ * @return void
767
+ */
768
+ public function callback_gform_post_form_duplicated( $form_id ) {
769
+ $this->log_form_action( $form_id, 'duplicated' );
770
+ }
771
+
772
+ /**
773
+ * Callback fired when a form's views are reset
774
+ *
775
+ * @param int $form_id Form ID
776
+ * @return void
777
+ */
778
+ public function callback_gform_post_form_views_deleted( $form_id ) {
779
+ $this->log_form_action( $form_id, 'views_deleted' );
780
+ }
781
+
782
+ /**
783
+ * Track status change of forms
784
+ *
785
+ * @param int $form_id
786
+ * @param string $action
787
+ * @return void
788
+ */
789
+ public function log_form_action( $form_id, $action ) {
790
+ $form = $this->get_form( $form_id );
791
+
792
+ if ( empty( $form ) ) {
793
+ return;
794
+ }
795
+
796
+ $actions = array(
797
+ 'activated' => esc_html__( 'Activated', 'mainwp-child-reports' ),
798
+ 'deactivated' => esc_html__( 'Deactivated', 'mainwp-child-reports' ),
799
+ 'trashed' => esc_html__( 'Trashed', 'mainwp-child-reports' ),
800
+ 'untrashed' => esc_html__( 'Restored', 'mainwp-child-reports' ),
801
+ 'duplicated' => esc_html__( 'Duplicated', 'mainwp-child-reports' ),
802
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
803
+ 'views_deleted' => esc_html__( 'Views Reset', 'mainwp-child-reports' ),
804
+ );
805
+
806
+ $this->log(
807
+ sprintf(
808
+ // translators: Placeholders refer to an ID, a form title, and a status (e.g. "42", "Contact Form", "Activated")
809
+ __( 'Form #%1$d ("%2$s") %3$s', 'mainwp-child-reports' ),
810
+ $form_id,
811
+ $form['title'],
812
+ strtolower( $actions[ $action ] )
813
+ ),
814
+ array(
815
+ 'form_id' => $form_id,
816
+ 'form_title' => $form['title'],
817
+ 'form_status' => strtolower( $action ),
818
+ ),
819
+ $form['id'],
820
+ 'forms',
821
+ $action
822
+ );
823
+ }
824
+
825
+ /**
826
+ * Helper function to get a single entry
827
+ *
828
+ * @param int $lead_id Lead ID
829
+ * @return array
830
+ */
831
+ private function get_lead( $lead_id ) {
832
+ return \GFFormsModel::get_lead( $lead_id );
833
+ }
834
+
835
+ /**
836
+ * Helper function to get a single form
837
+ *
838
+ * @param int $form_id Form ID
839
+ * @return array
840
+ */
841
+ private function get_form( $form_id ) {
842
+ return \GFFormsModel::get_form_meta( $form_id );
843
+ }
844
+ }
connectors/class-connector-installer.php ADDED
@@ -0,0 +1,606 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Installer extends Connector {
5
+
6
+ /**
7
+ * Connector slug
8
+ *
9
+ * @var string
10
+ */
11
+ public $name = 'installer';
12
+
13
+ /**
14
+ * Actions registered for this connector
15
+ *
16
+ * @var array
17
+ */
18
+ public $actions = array(
19
+ 'upgrader_pre_install', // use to the current version of all plugins, before they are upgraded ( Net-Concept - Xavier NUEL )
20
+ 'upgrader_process_complete', // plugins::installed | themes::installed
21
+ 'activate_plugin', // plugins::activated
22
+ 'deactivate_plugin', // plugins::deactivated
23
+ 'switch_theme', // themes::activated
24
+ 'delete_site_transient_update_themes', // themes::deleted
25
+ 'pre_option_uninstall_plugins', // plugins::deleted
26
+ 'pre_set_site_transient_update_plugins',
27
+ '_core_updated_successfully',
28
+ 'mainwp_child_installPluginTheme',
29
+ 'mainwp_child_plugin_action',
30
+ 'mainwp_child_theme_action',
31
+ 'mainwp_child_upgradePluginTheme'
32
+ );
33
+
34
+ public $old_plugins = array();
35
+
36
+ /**
37
+ * Register connector in the WP Frontend
38
+ *
39
+ * @var bool
40
+ */
41
+ public $register_frontend = false;
42
+
43
+ /**
44
+ * Return translated connector label
45
+ *
46
+ * @return string Translated connector label
47
+ */
48
+ public function get_label() {
49
+ return esc_html__( 'Installer', 'mainwp-child-reports' );
50
+ }
51
+
52
+ /**
53
+ * Return translated action labels
54
+ *
55
+ * @return array Action label translations
56
+ */
57
+ public function get_action_labels() {
58
+ return array(
59
+ 'installed' => esc_html__( 'Installed', 'mainwp-child-reports' ),
60
+ 'activated' => esc_html__( 'Activated', 'mainwp-child-reports' ),
61
+ 'deactivated' => esc_html__( 'Deactivated', 'mainwp-child-reports' ),
62
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
63
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Return translated context labels
69
+ *
70
+ * @return array Context label translations
71
+ */
72
+ public function get_context_labels() {
73
+ return array(
74
+ 'plugins' => esc_html__( 'Plugins', 'mainwp-child-reports' ),
75
+ 'themes' => esc_html__( 'Themes', 'mainwp-child-reports' ),
76
+ 'wordpress' => esc_html__( 'WordPress', 'mainwp-child-reports' ),
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Add action links to Stream drop row in admin list screen
82
+ *
83
+ * @filter wp_mainwp_stream_action_links_{connector}
84
+ *
85
+ * @param array $links Previous links registered
86
+ * @param object $record Stream record
87
+ *
88
+ * @return array Action links
89
+ */
90
+ public function action_links( $links, $record ) {
91
+ if ( 'WordPress' === $record->context && 'updated' === $record->action ) {
92
+ global $wp_version;
93
+
94
+ $version = $record->get_meta( 'new_version', true );
95
+
96
+ if ( $version === $wp_version ) {
97
+ $links[ esc_html__( 'About', 'mainwp-child-reports' ) ] = admin_url( 'about.php?updated' );
98
+ }
99
+
100
+ $links[ esc_html__( 'View Release Notes', 'mainwp-child-reports' ) ] = esc_url( sprintf( 'http://codex.wordpress.org/Version_%s', $version ) );
101
+ }
102
+
103
+ return $links;
104
+ }
105
+
106
+ /**
107
+ * Wrapper method for calling get_plugins()
108
+ *
109
+ * @return array
110
+ */
111
+ public function get_plugins() {
112
+ if ( ! function_exists( 'get_plugins' ) ) {
113
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
114
+ }
115
+
116
+ return get_plugins();
117
+ }
118
+
119
+ /**
120
+ * Log plugin installations
121
+ *
122
+ * @action transition_post_status
123
+ *
124
+ * @param \WP_Upgrader $upgrader
125
+ * @param array $extra
126
+ *
127
+ * @return bool
128
+ */
129
+ public function callback_upgrader_process_complete( $upgrader, $extra ) {
130
+ $logs = array();
131
+ $success = ! is_wp_error( $upgrader->skin->result );
132
+ $error = null;
133
+
134
+ if ( ! $success ) {
135
+ $errors = $upgrader->skin->result->errors;
136
+
137
+ list( $error ) = reset( $errors );
138
+ }
139
+
140
+ // This would have failed down the road anyway
141
+ if ( ! isset( $extra['type'] ) ) {
142
+ return false;
143
+ }
144
+
145
+ $type = $extra['type'];
146
+ $action = $extra['action'];
147
+
148
+ if ( ! in_array( $type, array( 'plugin', 'theme' ), true ) ) {
149
+ return false;
150
+ }
151
+
152
+ if ( 'install' === $action ) {
153
+ if ( 'plugin' === $type ) {
154
+ $path = $upgrader->plugin_info();
155
+
156
+ if ( ! $path ) {
157
+ return false;
158
+ }
159
+
160
+ $data = get_plugin_data( $upgrader->skin->result['local_destination'] . '/' . $path );
161
+ $slug = $upgrader->result['destination_name'];
162
+ $name = $data['Name'];
163
+ $version = $data['Version'];
164
+ } else { // theme
165
+ $slug = $upgrader->theme_info();
166
+
167
+ if ( ! $slug ) {
168
+ return false;
169
+ }
170
+
171
+ wp_clean_themes_cache();
172
+
173
+ $theme = wp_get_theme( $slug );
174
+ $name = $theme->name;
175
+ $version = $theme->version;
176
+ }
177
+
178
+ $action = 'installed';
179
+ // translators: Placeholders refer to a plugin/theme type, a plugin/theme name, and a plugin/theme version (e.g. "plugin", "Stream", "4.2")
180
+ $message = _x(
181
+ 'Installed %1$s: %2$s %3$s',
182
+ 'Plugin/theme installation. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
183
+ 'mainwp-child-reports'
184
+ );
185
+
186
+ $logs[] = compact( 'slug', 'name', 'version', 'message', 'action' );
187
+ } elseif ( 'update' === $action ) {
188
+ $action = 'updated';
189
+ // translators: Placeholders refer to a plugin/theme type, a plugin/theme name, and a plugin/theme version (e.g. "plugin", "Stream", "4.2")
190
+ $message = _x(
191
+ 'Updated %1$s: %2$s %3$s',
192
+ 'Plugin/theme update. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
193
+ 'mainwp-child-reports'
194
+ );
195
+
196
+ if ( 'plugin' === $type ) {
197
+ if ( isset( $extra['bulk'] ) && true === $extra['bulk'] ) {
198
+ $slugs = $extra['plugins'];
199
+ } else {
200
+ $slugs = array( $upgrader->skin->plugin );
201
+ }
202
+
203
+ //$_plugins = $this->get_plugins();
204
+
205
+ foreach ( $slugs as $slug ) {
206
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug );
207
+ $name = $plugin_data['Name'];
208
+ $version = $plugin_data['Version'];
209
+ //$old_version = $_plugins[ $slug ]['Version'];
210
+
211
+ //( Net-Concept - Xavier NUEL ) : get old versions
212
+ if (isset($this->old_plugins[$slug])) {
213
+ $old_version = $this->old_plugins[$slug]['Version'];
214
+ } else {
215
+ //$old_version = ''; // Hummm... will this happen ?
216
+ $old_version = $upgrader->skin->plugin_info['Version']; // to fix old version
217
+ }
218
+
219
+ if (version_compare($version, $old_version, '>')) {
220
+ $logs[] = compact('slug', 'name', 'old_version', 'version', 'message', 'action');
221
+ }
222
+
223
+
224
+ //$logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
225
+ }
226
+ } else { // theme
227
+ if ( isset( $extra['bulk'] ) && true === $extra['bulk'] ) {
228
+ $slugs = $extra['themes'];
229
+ } else {
230
+ $slugs = array( $upgrader->skin->theme );
231
+ }
232
+
233
+ foreach ( $slugs as $slug ) {
234
+ $theme = wp_get_theme( $slug );
235
+ $stylesheet = $theme['Stylesheet Dir'] . '/style.css';
236
+ $theme_data = get_file_data(
237
+ $stylesheet, array(
238
+ 'Version' => 'Version',
239
+ )
240
+ );
241
+ $name = $theme['Name'];
242
+ $old_version = $theme['Version'];
243
+ $version = $theme_data['Version'];
244
+
245
+ $logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
246
+ }
247
+ }
248
+ } else {
249
+ return false;
250
+ }
251
+
252
+ $context = $type . 's';
253
+
254
+ foreach ( $logs as $log ) {
255
+ $name = isset( $log['name'] ) ? $log['name'] : null;
256
+ $version = isset( $log['version'] ) ? $log['version'] : null;
257
+ $slug = isset( $log['slug'] ) ? $log['slug'] : null;
258
+ $old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
259
+ $message = isset( $log['message'] ) ? $log['message'] : null;
260
+ $action = isset( $log['action'] ) ? $log['action'] : null;
261
+
262
+ $this->log(
263
+ $message,
264
+ compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
265
+ null,
266
+ $context,
267
+ $action
268
+ );
269
+ }
270
+
271
+ return true;
272
+ }
273
+
274
+ public function callback_activate_plugin( $slug, $network_wide ) {
275
+ $_plugins = $this->get_plugins();
276
+ $name = $_plugins[ $slug ]['Name'];
277
+ $network_wide = $network_wide ? esc_html__( 'network wide', 'mainwp-child-reports' ) : null;
278
+
279
+ $this->log(
280
+ // translators: Placeholders refer to a plugin name, and whether it is on a single site or network wide (e.g. "Stream", "network wide") (a single site results in a blank string)
281
+ _x(
282
+ '"%1$s" plugin activated %2$s',
283
+ '1: Plugin name, 2: Single site or network wide',
284
+ 'mainwp-child-reports'
285
+ ),
286
+ compact( 'name', 'network_wide', 'slug' ),
287
+ null,
288
+ 'plugins',
289
+ 'activated'
290
+ );
291
+ }
292
+
293
+ public function callback_deactivate_plugin( $slug, $network_wide ) {
294
+ $_plugins = $this->get_plugins();
295
+ $name = $_plugins[ $slug ]['Name'];
296
+ $network_wide = $network_wide ? esc_html__( 'network wide', 'mainwp-child-reports' ) : null;
297
+
298
+ $this->log(
299
+ // translators: Placeholders refer to a plugin name, and whether it is on a single site or network wide (e.g. "Stream", "network wide") (a single site results in a blank string)
300
+ _x(
301
+ '"%1$s" plugin deactivated %2$s',
302
+ '1: Plugin name, 2: Single site or network wide',
303
+ 'mainwp-child-reports'
304
+ ),
305
+ compact( 'name', 'network_wide', 'slug' ),
306
+ null,
307
+ 'plugins',
308
+ 'deactivated'
309
+ );
310
+ }
311
+
312
+ public function callback_switch_theme( $name, $theme ) {
313
+ unset( $theme );
314
+ $this->log(
315
+ // translators: Placeholder refers to a theme name (e.g. "Twenty Seventeen")
316
+ __( '"%s" theme activated', 'mainwp-child-reports' ),
317
+ compact( 'name' ),
318
+ null,
319
+ 'themes',
320
+ 'activated'
321
+ );
322
+ }
323
+
324
+ /**
325
+ * @todo Core needs a delete_theme hook
326
+ */
327
+ public function callback_delete_site_transient_update_themes() {
328
+ $backtrace = debug_backtrace(); // @codingStandardsIgnoreLine This is used as a hack to determine a theme was deleted.
329
+ $delete_theme_call = null;
330
+
331
+ foreach ( $backtrace as $call ) {
332
+ if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
333
+ $delete_theme_call = $call;
334
+ break;
335
+ }
336
+ }
337
+
338
+ if ( empty( $delete_theme_call ) ) {
339
+ return;
340
+ }
341
+
342
+ $name = $delete_theme_call['args'][0];
343
+ // @todo Can we get the name of the theme? Or has it already been eliminated
344
+
345
+ $this->log(
346
+ // translators: Placeholder refers to a theme name (e.g. "Twenty Seventeen")
347
+ __( '"%s" theme deleted', 'mainwp-child-reports' ),
348
+ compact( 'name' ),
349
+ null,
350
+ 'themes',
351
+ 'deleted'
352
+ );
353
+ }
354
+
355
+ /**
356
+ * @todo Core needs an uninstall_plugin hook
357
+ * @todo This does not work in WP-CLI
358
+ */
359
+ public function callback_pre_option_uninstall_plugins() {
360
+ if (
361
+ 'delete-selected' !== wp_mainwp_stream_filter_input( INPUT_GET, 'action' )
362
+ &&
363
+ 'delete-selected' !== wp_mainwp_stream_filter_input( INPUT_POST, 'action2' )
364
+ ) {
365
+ return false;
366
+ }
367
+
368
+ // @codingStandardsIgnoreStart
369
+ $type = isset( $_POST['action2'] ) ? INPUT_POST : INPUT_GET;
370
+ // @codingStandardsIgnoreEnd
371
+
372
+ $plugins = wp_mainwp_stream_filter_input( $type, 'checked' );
373
+ $_plugins = $this->get_plugins();
374
+
375
+ $plugins_to_delete = array();
376
+
377
+ foreach ( (array) $plugins as $plugin ) {
378
+ $plugins_to_delete[ $plugin ] = $_plugins[ $plugin ];
379
+ }
380
+
381
+ update_option( 'wp_mainwp_stream_plugins_to_delete', $plugins_to_delete );
382
+
383
+ return false;
384
+ }
385
+
386
+ /**
387
+ * @param mixed $value
388
+ *
389
+ * @return mixed
390
+ * @todo Core needs a delete_plugin hook
391
+ * @todo This does not work in WP-CLI
392
+ */
393
+ public function callback_pre_set_site_transient_update_plugins( $value ) {
394
+ $plugins_to_delete = get_option( 'wp_mainwp_stream_plugins_to_delete' );
395
+ if ( ! wp_mainwp_stream_filter_input( INPUT_POST, 'verify-delete' ) || ! $plugins_to_delete ) {
396
+ return $value;
397
+ }
398
+
399
+ foreach ( $plugins_to_delete as $plugin => $data ) {
400
+ $name = $data['Name'];
401
+ $network_wide = $data['Network'] ? esc_html__( 'network wide', 'mainwp-child-reports' ) : '';
402
+
403
+ $this->log(
404
+ // translators: Placeholder refers to a plugin name (e.g. "Stream")
405
+ __( '"%s" plugin deleted', 'mainwp-child-reports' ),
406
+ compact( 'name', 'plugin', 'network_wide' ),
407
+ null,
408
+ 'plugins',
409
+ 'deleted'
410
+ );
411
+ }
412
+
413
+ delete_option( 'wp_mainwp_stream_plugins_to_delete' );
414
+
415
+ return $value;
416
+ }
417
+
418
+ public function callback__core_updated_successfully( $new_version ) {
419
+ global $pagenow, $wp_version;
420
+
421
+ $old_version = $wp_version;
422
+ $auto_updated = ( 'update-core.php' !== $pagenow );
423
+
424
+ if ( $auto_updated ) {
425
+ // translators: Placeholder refers to a version number (e.g. "4.2")
426
+ $message = esc_html__( 'WordPress auto-updated to %s', 'mainwp-child-reports' );
427
+ } else {
428
+ // translators: Placeholder refers to a version number (e.g. "4.2")
429
+ $message = esc_html__( 'WordPress updated to %s', 'mainwp-child-reports' );
430
+ }
431
+
432
+ $this->log(
433
+ $message,
434
+ compact( 'new_version', 'old_version', 'auto_updated' ),
435
+ null,
436
+ 'WordPress',
437
+ 'updated'
438
+ );
439
+ }
440
+
441
+ public function callback_mainwp_child_installPluginTheme( $args ) {
442
+
443
+ $logs = array();
444
+ $success = isset($args['success']) ? $args['success'] : 0;
445
+ $error = null;
446
+
447
+ if ( ! $success ) {
448
+ $errors = $args['errors'];;
449
+ }
450
+
451
+ // This would have failed down the road anyway
452
+ if ( ! isset( $args['type'] ) ) {
453
+ return false;
454
+ }
455
+
456
+ $type = $args['type'];
457
+ $action = $args['action'];
458
+
459
+ if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
460
+ return;
461
+ }
462
+
463
+ if ( 'install' === $action ) {
464
+ if ( 'plugin' === $type) {
465
+ if ( !isset($args['Name']) || empty($args['Name']))
466
+ return;
467
+ $slug = $args['slug'];
468
+ $name = $args['Name'];
469
+ $version = $args['Version'];
470
+ } else { // theme
471
+ $slug = $args['slug'];
472
+ if ( ! $slug ) {
473
+ return;
474
+ }
475
+ wp_clean_themes_cache();
476
+ $theme = wp_get_theme( $slug );
477
+ $name = $theme->name;
478
+ $version = $theme->version;
479
+ }
480
+ $action = 'installed';
481
+ $message = _x(
482
+ 'Installed %1$s: %2$s %3$s',
483
+ 'Plugin/theme installation. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
484
+ 'mainwp_child_reports'
485
+ );
486
+ $logs[] = compact( 'slug', 'name', 'version', 'message', 'action' );
487
+ } else {
488
+ return false;
489
+ }
490
+
491
+ $context = $type . 's';
492
+
493
+ foreach ( $logs as $log ) {
494
+ $name = isset( $log['name'] ) ? $log['name'] : null;
495
+ $version = isset( $log['version'] ) ? $log['version'] : null;
496
+ $slug = isset( $log['slug'] ) ? $log['slug'] : null;
497
+ $old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
498
+ $message = isset( $log['message'] ) ? $log['message'] : null;
499
+ $action = isset( $log['action'] ) ? $log['action'] : null;
500
+ $this->log(
501
+ $message,
502
+ compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
503
+ null,
504
+ $context,
505
+ $action
506
+ );
507
+ }
508
+ }
509
+
510
+
511
+ public function callback_mainwp_child_plugin_action( $args ) {
512
+ if (!is_array($args) || !isset($args['action']))
513
+ return;
514
+ $action = $args['action'];
515
+ if ($action == 'delete') {
516
+ $name = $args['Name'];
517
+ $network_wide = '';
518
+ $this->log(
519
+ __( '"%s" plugin deleted', 'mainwp-child-reports' ),
520
+ compact( 'name', 'plugin' ),
521
+ null,
522
+ 'plugins',
523
+ 'deleted'
524
+ );
525
+ }
526
+ }
527
+
528
+ public function callback_mainwp_child_theme_action($args) {
529
+ if (!is_array($args) || !isset($args['action']))
530
+ return;
531
+ $action = $args['action'];
532
+ $name = $args['Name'];
533
+ if ($action == 'delete') {
534
+ $this->log(
535
+ __( '"%s" theme deleted', 'mainwp-child-reports' ),
536
+ compact( 'name' ),
537
+ null,
538
+ 'themes',
539
+ 'deleted'
540
+ );
541
+ }
542
+ }
543
+
544
+ // ( Net-Concept - Xavier NUEL ) : save all plugins versions before upgrade
545
+ public function callback_upgrader_pre_install() {
546
+ $this->old_plugins = $this->get_plugins();
547
+ }
548
+
549
+ public function callback_mainwp_child_upgradePluginTheme( $extra ) {
550
+ $logs = array();
551
+
552
+ if ( ! isset( $extra['type'] ) ) {
553
+ return false;
554
+ }
555
+
556
+ $type = $extra['type'];
557
+ $action = $extra['action'];
558
+
559
+ if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
560
+ return;
561
+ }
562
+
563
+ if ( 'update' === $action ) {
564
+ if ( 'plugin' === $type ) {
565
+ $slug = $extra['slug'];
566
+ $name = $extra['name'];
567
+ $version = $extra['version'];
568
+ $old_version = $extra['old_version'];
569
+ } else { // theme
570
+ $name = $extra['name'];
571
+ $version = $extra['version'];
572
+ $old_version = $extra['old_version'];
573
+ }
574
+
575
+ $action = 'updated';
576
+ $message = _x(
577
+ 'Updated %1$s: %2$s %3$s',
578
+ 'Plugin/theme update. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
579
+ 'mainwp_child_reports'
580
+ );
581
+ $logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
582
+ } else {
583
+ return false;
584
+ }
585
+
586
+ $context = $type . 's';
587
+
588
+ foreach ( $logs as $log ) {
589
+ $name = isset( $log['name'] ) ? $log['name'] : null;
590
+ $version = isset( $log['version'] ) ? $log['version'] : null;
591
+ $slug = isset( $log['slug'] ) ? $log['slug'] : null;
592
+ $old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
593
+ $message = isset( $log['message'] ) ? $log['message'] : null;
594
+ $action = isset( $log['action'] ) ? $log['action'] : null;
595
+ $this->log(
596
+ $message,
597
+ compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
598
+ null,
599
+ $context,
600
+ $action
601
+ );
602
+ }
603
+ }
604
+
605
+
606
+ }
connectors/class-connector-jetpack.php ADDED
@@ -0,0 +1,713 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Jetpack extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'jetpack';
11
+
12
+ /**
13
+ * Holds tracked plugin minimum version required
14
+ *
15
+ * @const string
16
+ */
17
+ const PLUGIN_MIN_VERSION = '3.0.2';
18
+
19
+ /**
20
+ * Actions registered for this connector
21
+ *
22
+ * @var array
23
+ */
24
+ public $actions = array(
25
+ 'jetpack_log_entry',
26
+ 'sharing_get_services_state',
27
+ 'update_option',
28
+ 'add_option',
29
+ 'delete_option',
30
+ 'jetpack_module_configuration_load_monitor',
31
+ 'wp_ajax_jetpack_post_by_email_enable', // @todo These three actions do not verify whether the action has been done or if an error has been raised
32
+ 'wp_ajax_jetpack_post_by_email_regenerate',
33
+ 'wp_ajax_jetpack_post_by_email_disable',
34
+ );
35
+
36
+ /**
37
+ * Register connector in the WP Frontend
38
+ *
39
+ * @var bool
40
+ */
41
+ public $register_frontend = false;
42
+
43
+ /**
44
+ * Tracked option keys
45
+ *
46
+ * @var array
47
+ */
48
+ public $options = array();
49
+
50
+ /**
51
+ * Tracking registered Settings, with overridden data
52
+ *
53
+ * @var array
54
+ */
55
+ public $options_override = array();
56
+
57
+ /**
58
+ * Check if plugin dependencies are satisfied and add an admin notice if not
59
+ *
60
+ * @return bool
61
+ */
62
+ public function is_dependency_satisfied() {
63
+ if ( class_exists( 'Jetpack' ) && defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, self::PLUGIN_MIN_VERSION, '>=' ) ) {
64
+ return true;
65
+ }
66
+
67
+ return false;
68
+ }
69
+
70
+ /**
71
+ * Return translated connector label
72
+ *
73
+ * @return string Translated connector label
74
+ */
75
+ public function get_label() {
76
+ return esc_html_x( 'Jetpack', 'jetpack', 'mainwp-child-reports' );
77
+ }
78
+
79
+ /**
80
+ * Return translated action labels
81
+ *
82
+ * @return array Action label translations
83
+ */
84
+ public function get_action_labels() {
85
+ return array(
86
+ 'activated' => esc_html_x( 'Activated', 'jetpack', 'mainwp-child-reports' ),
87
+ 'deactivated' => esc_html_x( 'Deactivated', 'jetpack', 'mainwp-child-reports' ),
88
+ 'register' => esc_html_x( 'Connected', 'jetpack', 'mainwp-child-reports' ),
89
+ 'disconnect' => esc_html_x( 'Disconnected', 'jetpack', 'mainwp-child-reports' ),
90
+ 'authorize' => esc_html_x( 'Link', 'jetpack', 'mainwp-child-reports' ),
91
+ 'unlink' => esc_html_x( 'Unlink', 'jetpack', 'mainwp-child-reports' ),
92
+ 'updated' => esc_html_x( 'Updated', 'jetpack', 'mainwp-child-reports' ),
93
+ 'added' => esc_html_x( 'Added', 'jetpack', 'mainwp-child-reports' ),
94
+ 'removed' => esc_html_x( 'Removed', 'jetpack', 'mainwp-child-reports' ),
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Return translated context labels
100
+ *
101
+ * @return array Context label translations
102
+ */
103
+ public function get_context_labels() {
104
+ return array(
105
+ 'blogs' => esc_html_x( 'Blogs', 'jetpack', 'mainwp-child-reports' ),
106
+ 'carousel' => esc_html_x( 'Carousel', 'jetpack', 'mainwp-child-reports' ),
107
+ 'custom-css' => esc_html_x( 'Custom CSS', 'jetpack', 'mainwp-child-reports' ),
108
+ 'gplus-authorship' => esc_html_x( 'Google+ Profile', 'jetpack', 'mainwp-child-reports' ),
109
+ 'infinite-scroll' => esc_html_x( 'Infinite Scroll', 'jetpack', 'mainwp-child-reports' ),
110
+ 'jetpack-comments' => esc_html_x( 'Comments', 'jetpack', 'mainwp-child-reports' ),
111
+ 'likes' => esc_html_x( 'Likes', 'jetpack', 'mainwp-child-reports' ),
112
+ 'minileven' => esc_html_x( 'Mobile', 'jetpack', 'mainwp-child-reports' ),
113
+ 'modules' => esc_html_x( 'Modules', 'jetpack', 'mainwp-child-reports' ),
114
+ 'monitor' => esc_html_x( 'Monitor', 'jetpack', 'mainwp-child-reports' ),
115
+ 'options' => esc_html_x( 'Options', 'jetpack', 'mainwp-child-reports' ),
116
+ 'post-by-email' => esc_html_x( 'Post by Email', 'jetpack', 'mainwp-child-reports' ),
117
+ 'protect' => esc_html_x( 'Protect', 'jetpack', 'mainwp-child-reports' ),
118
+ 'publicize' => esc_html_x( 'Publicize', 'jetpack', 'mainwp-child-reports' ),
119
+ 'related-posts' => esc_html_x( 'Related Posts', 'jetpack', 'mainwp-child-reports' ),
120
+ 'sharedaddy' => esc_html_x( 'Sharing', 'jetpack', 'mainwp-child-reports' ),
121
+ 'subscriptions' => esc_html_x( 'Subscriptions', 'jetpack', 'mainwp-child-reports' ),
122
+ 'sso' => esc_html_x( 'SSO', 'jetpack', 'mainwp-child-reports' ),
123
+ 'stats' => esc_html_x( 'WordPress.com Stats', 'jetpack', 'mainwp-child-reports' ),
124
+ 'tiled-gallery' => esc_html_x( 'Tiled Galleries', 'jetpack', 'mainwp-child-reports' ),
125
+ 'users' => esc_html_x( 'Users', 'jetpack', 'mainwp-child-reports' ),
126
+ 'verification-tools' => esc_html_x( 'Site Verification', 'jetpack', 'mainwp-child-reports' ),
127
+ 'videopress' => esc_html_x( 'VideoPress', 'jetpack', 'mainwp-child-reports' ),
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Add action links to Stream drop row in admin list screen
133
+ *
134
+ * @filter wp_mainwp_stream_action_links_{connector}
135
+ *
136
+ * @param array $links Previous links registered
137
+ * @param object $record Stream record
138
+ *
139
+ * @return array Action links
140
+ */
141
+ public function action_links( $links, $record ) {
142
+ // @todo provide proper action links
143
+ if ( 'jetpack' === $record->connector ) {
144
+ if ( 'modules' === $record->context ) {
145
+ $slug = $record->get_meta( 'module_slug', true );
146
+
147
+ if ( is_array( $slug ) ) {
148
+ $slug = current( $slug );
149
+ }
150
+
151
+ if ( \Jetpack::is_module_active( $slug ) ) {
152
+ if ( apply_filters( 'jetpack_module_configurable_' . $slug, false ) ) {
153
+ $links[ esc_html__( 'Configure', 'mainwp-child-reports' ) ] = \Jetpack::module_configuration_url( $slug );
154
+ }
155
+
156
+ $links[ esc_html__( 'Deactivate', 'mainwp-child-reports' ) ] = wp_nonce_url(
157
+ add_query_arg(
158
+ array(
159
+ 'action' => 'deactivate',
160
+ 'module' => $slug,
161
+ ),
162
+ \Jetpack::admin_url()
163
+ ),
164
+ 'jetpack_deactivate-' . sanitize_title( $slug )
165
+ );
166
+ } else {
167
+ $links[ esc_html__( 'Activate', 'mainwp-child-reports' ) ] = wp_nonce_url(
168
+ add_query_arg(
169
+ array(
170
+ 'action' => 'activate',
171
+ 'module' => $slug,
172
+ ),
173
+ \Jetpack::admin_url()
174
+ ),
175
+ 'jetpack_activate-' . sanitize_title( $slug )
176
+ );
177
+ }
178
+ } elseif ( \Jetpack::is_module_active( str_replace( 'jetpack-', '', $record->context ) ) ) {
179
+ $slug = str_replace( 'jetpack-', '', $record->context ); // handling jetpack-comment anomaly
180
+
181
+ if ( apply_filters( 'jetpack_module_configurable_' . $slug, false ) ) {
182
+ $links[ esc_html__( 'Configure module', 'mainwp-child-reports' ) ] = \Jetpack::module_configuration_url( $slug );
183
+ }
184
+ }
185
+ }
186
+
187
+ return $links;
188
+ }
189
+
190
+ public function register() {
191
+ parent::register();
192
+
193
+ add_filter( 'wp_mainwp_stream_log_data', array( $this, 'log_override' ) );
194
+
195
+ $this->options = array(
196
+ 'jetpack_options' => null,
197
+ // Sharing module
198
+ 'hide_gplus' => null,
199
+ 'gplus_authors' => null,
200
+ 'sharing-options' => array(
201
+ 'label' => esc_html__( 'Sharing options', 'mainwp-child-reports' ),
202
+ 'context' => 'sharedaddy',
203
+ ),
204
+ 'sharedaddy_disable_resources' => null,
205
+ 'jetpack-twitter-cards-site-tag' => array(
206
+ 'label' => esc_html__( 'Twitter site tag', 'mainwp-child-reports' ),
207
+ 'context' => 'sharedaddy',
208
+ ),
209
+ // Stats module
210
+ 'stats_options' => array(
211
+ 'label' => esc_html__( 'WordPress.com Stats', 'mainwp-child-reports' ),
212
+ 'context' => 'stats',
213
+ ),
214
+ // Comments
215
+ 'jetpack_comment_form_color_scheme' => array(
216
+ 'label' => esc_html__( 'Color Scheme', 'mainwp-child-reports' ),
217
+ 'context' => 'jetpack-comments',
218
+ ),
219
+ // Likes
220
+ 'disabled_likes' => array(
221
+ 'label' => esc_html__( 'WP.com Site-wide Likes', 'mainwp-child-reports' ),
222
+ 'context' => 'likes',
223
+ ),
224
+ // Mobile
225
+ 'wp_mobile_excerpt' => array(
226
+ 'label' => esc_html__( 'Excerpts appearance', 'mainwp-child-reports' ),
227
+ 'context' => 'minileven',
228
+ ),
229
+ 'wp_mobile_app_promos' => array(
230
+ 'label' => esc_html__( 'App promos', 'mainwp-child-reports' ),
231
+ 'context' => 'minileven',
232
+ ),
233
+ );
234
+
235
+ $this->options_override = array(
236
+ // Carousel Module
237
+ 'carousel_background_color' => array(
238
+ 'label' => esc_html__( 'Background color', 'mainwp-child-reports' ),
239
+ 'context' => 'carousel',
240
+ ),
241
+ 'carousel_display_exif' => array(
242
+ 'label' => esc_html__( 'Metadata', 'mainwp-child-reports' ),
243
+ 'context' => 'carousel',
244
+ ),
245
+ // Subscriptions
246
+ 'stb_enabled' => array(
247
+ 'label' => esc_html__( 'Follow blog comment form button', 'mainwp-child-reports' ),
248
+ 'context' => 'subscriptions',
249
+ ),
250
+ 'stc_enabled' => array(
251
+ 'label' => esc_html__( 'Follow comments form button', 'mainwp-child-reports' ),
252
+ 'context' => 'subscriptions',
253
+ ),
254
+ // Jetpack comments
255
+ 'highlander_comment_form_prompt' => array(
256
+ 'label' => esc_html__( 'Greeting Text', 'mainwp-child-reports' ),
257
+ 'context' => 'jetpack-comments',
258
+ ),
259
+ // Infinite Scroll
260
+ 'infinite_scroll_google_analytics' => array(
261
+ 'label' => esc_html__( 'Infinite Scroll Google Analytics', 'mainwp-child-reports' ),
262
+ 'context' => 'infinite-scroll',
263
+ ),
264
+ // Protect
265
+ 'jetpack_protect_blocked_attempts' => array(
266
+ 'label' => esc_html__( 'Blocked Attempts', 'mainwp-child-reports' ),
267
+ 'context' => 'protect',
268
+ ),
269
+ // SSO
270
+ 'jetpack_sso_require_two_step' => array(
271
+ 'label' => esc_html__( 'Require Two-Step Authentication', 'mainwp-child-reports' ),
272
+ 'context' => 'sso',
273
+ ),
274
+ 'jetpack_sso_match_by_email' => array(
275
+ 'label' => esc_html__( 'Match by Email', 'mainwp-child-reports' ),
276
+ 'context' => 'sso',
277
+ ),
278
+ // Related posts
279
+ 'jetpack_relatedposts' => array(
280
+ 'show_headline' => array(
281
+ 'label' => esc_html__( 'Show Related Posts Headline', 'mainwp-child-reports' ),
282
+ 'context' => 'related-posts',
283
+ ),
284
+ 'show_thumbnails' => array(
285
+ 'label' => esc_html__( 'Show Related Posts Thumbnails', 'mainwp-child-reports' ),
286
+ 'context' => 'related-posts',
287
+ ),
288
+ ),
289
+ // Site verification
290
+ 'verification_services_codes' => array(
291
+ 'google' => array(
292
+ 'label' => esc_html__( 'Google Webmaster Tools Token', 'mainwp-child-reports' ),
293
+ 'context' => 'verification-tools',
294
+ ),
295
+ 'bing' => array(
296
+ 'label' => esc_html__( 'Bing Webmaster Center Token', 'mainwp-child-reports' ),
297
+ 'context' => 'verification-tools',
298
+ ),
299
+ 'pinterest' => array(
300
+ 'label' => esc_html__( 'Pinterest Site Verification Token', 'mainwp-child-reports' ),
301
+ 'context' => 'verification-tools',
302
+ ),
303
+ ),
304
+ // Tiled galleries
305
+ 'tiled_galleries' => array(
306
+ 'label' => esc_html__( 'Tiled Galleries', 'mainwp-child-reports' ),
307
+ 'context' => 'tiled-gallery',
308
+ ),
309
+ );
310
+ }
311
+
312
+ /**
313
+ * Track Jetpack log entries
314
+ * Includes:
315
+ * - Activation/Deactivation of modules
316
+ * - Registration/Disconnection of blogs
317
+ * - Authorization/unlinking of users
318
+ *
319
+ * @param array $entry
320
+ */
321
+ public function callback_jetpack_log_entry( array $entry ) {
322
+ if ( isset( $entry['code'] ) ) {
323
+ $method = $entry['code'];
324
+ } else {
325
+ return;
326
+ }
327
+
328
+ if ( isset( $entry['data'] ) ) {
329
+ $data = $entry['data'];
330
+ } else {
331
+ $data = null;
332
+ }
333
+
334
+ $context = null;
335
+ $action = null;
336
+ $meta = array();
337
+
338
+ if ( in_array( $method, array( 'activate', 'deactivate' ), true ) && ! is_null( $data ) ) {
339
+ $module_slug = $data;
340
+ $module = \Jetpack::get_module( $module_slug );
341
+ $module_name = $module['name'];
342
+ $context = 'modules';
343
+ $action = $method . 'd';
344
+ $meta = compact( 'module_slug' );
345
+ $message = sprintf(
346
+ // translators: Placeholders refer to a module name, and a status (e.g. "Photon", "activated")
347
+ __( '%1$s module %2$s', 'mainwp-child-reports' ),
348
+ $module_name,
349
+ ( 'activated' === $action ) ? esc_html__( 'activated', 'mainwp-child-reports' ) : esc_html__( 'deactivated', 'mainwp-child-reports' )
350
+ );
351
+ } elseif ( in_array( $method, array( 'authorize', 'unlink' ), true ) && ! is_null( $data ) ) {
352
+ $user_id = intval( $data );
353
+
354
+ if ( empty( $user_id ) ) {
355
+ $user_id = get_current_user_id();
356
+ }
357
+
358
+ $user = new \WP_User( $user_id );
359
+ $user_email = $user->user_email;
360
+ $user_login = $user->user_login;
361
+ $context = 'users';
362
+ $action = $method;
363
+ $meta = compact( 'user_id', 'user_email', 'user_login' );
364
+ $message = sprintf(
365
+ // translators: Placeholders refer to a user display name, a status, and the connection either "from" or "to" (e.g. "Jane Doe", "unlinked", "from")
366
+ __( '%1$s\'s account %2$s %3$s Jetpack', 'mainwp-child-reports' ),
367
+ $user->display_name,
368
+ ( 'unlink' === $action ) ? esc_html__( 'unlinked', 'mainwp-child-reports' ) : esc_html__( 'linked', 'mainwp-child-reports' ),
369
+ ( 'unlink' === $action ) ? esc_html__( 'from', 'mainwp-child-reports' ) : esc_html__( 'to', 'mainwp-child-reports' )
370
+ );
371
+ } elseif ( in_array( $method, array( 'register', 'disconnect', 'subsiteregister', 'subsitedisconnect' ), true ) ) {
372
+ $context = 'blogs';
373
+ $action = str_replace( 'subsite', '', $method );
374
+ $is_multisite = ( 0 === strpos( $method, 'subsite' ) );
375
+ $blog_id = $is_multisite ? ( isset( $_GET['site_id'] ) ? intval( wp_unslash( $_GET['site_id'] ) ) : null ) : get_current_blog_id(); // phpcs: input var okay, CSRF okay
376
+
377
+ if ( empty( $blog_id ) ) {
378
+ return;
379
+ }
380
+
381
+ if ( ! $is_multisite ) {
382
+ $message = sprintf(
383
+ // translators: Placeholder refers to a connection status. Either "connected to" or "disconnected from".
384
+ __( 'Site %s Jetpack', 'mainwp-child-reports' ),
385
+ ( 'register' === $action ) ? esc_html__( 'connected to', 'mainwp-child-reports' ) : esc_html__( 'disconnected from', 'mainwp-child-reports' )
386
+ );
387
+ } else {
388
+ $blog_details = get_blog_details(
389
+ array(
390
+ 'blog_id' => $blog_id,
391
+ )
392
+ );
393
+ $blog_name = $blog_details->blogname;
394
+ $meta += compact( 'blog_id', 'blog_name' );
395
+
396
+ $message = sprintf(
397
+ // translators: Placeholder refers to a connection status. Either "connected to" or "disconnected from".
398
+ __( '"%1$s" blog %2$s Jetpack', 'mainwp-child-reports' ),
399
+ $blog_name,
400
+ ( 'register' === $action ) ? esc_html__( 'connected to', 'mainwp-child-reports' ) : esc_html__( 'disconnected from', 'mainwp-child-reports' )
401
+ );
402
+ }
403
+ }
404
+
405
+ if ( empty( $message ) ) {
406
+ return;
407
+ }
408
+
409
+ $this->log(
410
+ $message,
411
+ $meta,
412
+ null,
413
+ $context,
414
+ $action
415
+ );
416
+ }
417
+
418
+ /**
419
+ * Track visible/enabled sharing services ( buttons )
420
+ *
421
+ * @param string $state
422
+ */
423
+ public function callback_sharing_get_services_state( $state ) {
424
+ $this->log(
425
+ __( 'Sharing services updated', 'mainwp-child-reports' ),
426
+ $state,
427
+ null,
428
+ 'sharedaddy',
429
+ 'updated'
430
+ );
431
+ }
432
+
433
+ public function callback_update_option( $option, $old, $new ) {
434
+ $this->check( $option, $old, $new );
435
+ }
436
+
437
+ public function callback_add_option( $option, $val ) {
438
+ $this->check( $option, null, $val );
439
+ }
440
+
441
+ public function callback_delete_option( $option ) {
442
+ $this->check( $option, null, null );
443
+ }
444
+
445
+ /**
446
+ * Track Monitor module notification status
447
+ */
448
+ public function callback_jetpack_module_configuration_load_monitor() {
449
+ $active = wp_mainwp_stream_filter_input( INPUT_POST, 'receive_jetpack_monitor_notification' );
450
+
451
+ if ( ! $active ) {
452
+ return;
453
+ }
454
+
455
+ $this->log(
456
+ // translators: Placeholder refers to a status (e.g. "activated")
457
+ __( 'Monitor notifications %s', 'mainwp-child-reports' ),
458
+ array(
459
+ 'status' => $active ? esc_html__( 'activated', 'mainwp-child-reports' ) : esc_html__( 'deactivated', 'mainwp-child-reports' ),
460
+ 'option' => 'receive_jetpack_monitor_notification',
461
+ 'old_value' => ! $active,
462
+ 'value' => $active,
463
+ ),
464
+ null,
465
+ 'monitor',
466
+ 'updated'
467
+ );
468
+ }
469
+
470
+ public function callback_wp_ajax_jetpack_post_by_email_enable() {
471
+ $this->track_post_by_email( true );
472
+ }
473
+
474
+ public function callback_wp_ajax_jetpack_post_by_email_regenerate() {
475
+ $this->track_post_by_email( null );
476
+ }
477
+
478
+ public function callback_wp_ajax_jetpack_post_by_email_disable() {
479
+ $this->track_post_by_email( false );
480
+ }
481
+
482
+ public function track_post_by_email( $status ) {
483
+ if ( true === $status ) {
484
+ $action = esc_html__( 'enabled', 'mainwp-child-reports' );
485
+ } elseif ( false === $status ) {
486
+ $action = esc_html__( 'disabled', 'mainwp-child-reports' );
487
+ } elseif ( null === $status ) {
488
+ $action = esc_html__( 'regenerated', 'mainwp-child-reports' );
489
+ }
490
+
491
+ $user = wp_get_current_user();
492
+
493
+ $this->log(
494
+ // translators: Placeholders refer to a user display name, and a status (e.g. "Jane Doe", "enabled")
495
+ __( '%1$s %2$s Post by Email', 'mainwp-child-reports' ),
496
+ array(
497
+ 'user_displayname' => $user->display_name,
498
+ 'action' => $action,
499
+ 'status' => $status,
500
+ ),
501
+ null,
502
+ 'post-by-email',
503
+ 'updated'
504
+ );
505
+ }
506
+
507
+ public function check( $option, $old_value, $new_value ) {
508
+ if ( ! array_key_exists( $option, $this->options ) ) {
509
+ return;
510
+ }
511
+
512
+ if ( is_null( $this->options[ $option ] ) ) {
513
+ call_user_func( array( $this, 'check_' . str_replace( '-', '_', $option ) ), $old_value, $new_value );
514
+ } else {
515
+ $data = $this->options[ $option ];
516
+ $option_title = $data['label'];
517
+
518
+ $this->log(
519
+ // translators: Placeholder refers to a setting name (e.g. "Language")
520
+ __( '"%s" setting updated', 'mainwp-child-reports' ),
521
+ compact( 'option_title', 'option', 'old_value', 'new_value' ),
522
+ null,
523
+ $data['context'],
524
+ isset( $data['action'] ) ? $data['action'] : 'updated'
525
+ );
526
+ }
527
+ }
528
+
529
+ public function check_jetpack_options( $old_value, $new_value ) {
530
+ $options = array();
531
+
532
+ if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
533
+ return;
534
+ }
535
+
536
+ foreach ( $this->get_changed_keys( $old_value, $new_value, 1 ) as $field_key => $field_value ) {
537
+ $options[ $field_key ] = $field_value;
538
+ }
539
+
540
+ foreach ( $options as $option => $option_value ) {
541
+ $settings = $this->get_settings_def( $option, $option_value );
542
+
543
+ if ( ! $settings ) {
544
+ continue;
545
+ }
546
+
547
+ if ( 0 === $option_value ) { // Skip updated array with updated members, we'll be logging those instead
548
+ continue;
549
+ }
550
+
551
+ $settings['meta'] += array(
552
+ 'option' => $option,
553
+ 'old_value' => $old_value,
554
+ 'value' => $new_value,
555
+ );
556
+
557
+ $this->log(
558
+ $settings['message'],
559
+ $settings['meta'],
560
+ isset( $settings['object_id'] ) ? $settings['object_id'] : null,
561
+ $settings['context'],
562
+ $settings['action']
563
+ );
564
+ }
565
+ }
566
+
567
+ public function check_hide_gplus( $old_value, $new_value ) {
568
+ $status = ! is_null( $new_value );
569
+
570
+ if ( $status && $old_value ) {
571
+ return false;
572
+ }
573
+
574
+ $this->log(
575
+ // translators: Placeholder refers to a status (e.g. "enabled")
576
+ __( 'G+ profile display %s', 'mainwp-child-reports' ),
577
+ array(
578
+ 'action' => $status ? esc_html__( 'enabled', 'mainwp-child-reports' ) : esc_html__( 'disabled', 'mainwp-child-reports' ),
579
+ ),
580
+ null,
581
+ 'gplus-authorship',
582
+ 'updated'
583
+ );
584
+ }
585
+
586
+ public function check_gplus_authors( $old_value, $new_value ) {
587
+ unset( $old_value );
588
+
589
+ $user = wp_get_current_user();
590
+ $connected = is_array( $new_value ) && array_key_exists( $user->ID, $new_value );
591
+
592
+ $this->log(
593
+ // translators: Placeholders refer to a user display name, and a status (e.g. "Jane Doe", "connected")
594
+ __( '%1$s\'s Google+ account %2$s', 'mainwp-child-reports' ),
595
+ array(
596
+ 'display_name' => $user->display_name,
597
+ 'action' => $connected ? esc_html__( 'connected', 'mainwp-child-reports' ) : esc_html__( 'disconnected', 'mainwp-child-reports' ),
598
+ 'user_id' => $user->ID,
599
+ ),
600
+ $user->ID,
601
+ 'gplus-authorship',
602
+ 'updated'
603
+ );
604
+ }
605
+
606
+ public function check_sharedaddy_disable_resources( $old_value, $new_value ) {
607
+ if ( $old_value === $new_value ) {
608
+ return;
609
+ }
610
+
611
+ $status = ! $new_value ? 'enabled' : 'disabled'; // disabled = 1
612
+
613
+ $this->log(
614
+ // translators: Placeholder refers to a status (e.g. "enabled")
615
+ __( 'Sharing CSS/JS %s', 'mainwp-child-reports' ),
616
+ compact( 'status', 'old_value', 'new_value' ),
617
+ null,
618
+ 'sharing',
619
+ 'updated'
620
+ );
621
+ }
622
+
623
+ /**
624
+ * Override connector log for our own Settings / Actions
625
+ *
626
+ * @param array $data
627
+ *
628
+ * @return array|bool
629
+ */
630
+ public function log_override( $data ) {
631
+ if ( ! is_array( $data ) ) {
632
+ return $data;
633
+ }
634
+
635
+ // Handling our Settings
636
+ if ( 'settings' === $data['connector'] && isset( $this->options_override[ $data['args']['option'] ] ) ) {
637
+ if ( isset( $data['args']['option_key'] ) ) {
638
+ $overrides = $this->options_override[ $data['args']['option'] ][ $data['args']['option_key'] ];
639
+ } else {
640
+ $overrides = $this->options_override[ $data['args']['option'] ];
641
+ }
642
+
643
+ if ( isset( $overrides ) ) {
644
+ $data['args']['label'] = $overrides['label'];
645
+ $data['args']['context'] = $overrides['context'];
646
+ $data['context'] = $overrides['context'];
647
+ $data['connector'] = $this->name;
648
+ }
649
+ } elseif ( 'posts' === $data['connector'] && 'safecss' === $data['context'] ) {
650
+ $data = array_merge(
651
+ $data,
652
+ array(
653
+ 'connector' => $this->name,
654
+ 'message' => esc_html__( 'Custom CSS updated', 'mainwp-child-reports' ),
655
+ 'args' => array(),
656
+ 'object_id' => null,
657
+ 'context' => 'custom-css',
658
+ 'action' => 'updated',
659
+ )
660
+ );
661
+ }
662
+
663
+ return $data;
664
+ }
665
+
666
+ private function get_settings_def( $key, $value = null ) {
667
+ // Sharing
668
+ if ( 0 === strpos( $key, 'publicize_connections::' ) ) {
669
+ global $publicize_ui;
670
+
671
+ $name = str_replace( 'publicize_connections::', '', $key );
672
+
673
+ return array(
674
+ // translators: Placeholders refer to a service, and a status (e.g. "Facebook", "added")
675
+ 'message' => esc_html__( '%1$s connection %2$s', 'mainwp-child-reports' ),
676
+ 'meta' => array(
677
+ 'connection' => $publicize_ui->publicize->get_service_label( $name ),
678
+ 'action' => $value ? esc_html__( 'added', 'mainwp-child-reports' ) : esc_html__( 'removed', 'mainwp-child-reports' ),
679
+ 'option' => 'jetpack_options',
680
+ 'option_key' => $key,
681
+ ),
682
+ 'action' => $value ? 'added' : 'removed',
683
+ 'context' => 'publicize',
684
+ );
685
+ } elseif ( 0 === strpos( $key, 'videopress::' ) ) {
686
+ $name = str_replace( 'videopress::', '', $key );
687
+ $options = array(
688
+ 'access' => esc_html__( 'Video Library Access', 'mainwp-child-reports' ),
689
+ 'upload' => esc_html__( 'Allow users to upload videos', 'mainwp-child-reports' ),
690
+ 'freedom' => esc_html__( 'Free formats', 'mainwp-child-reports' ),
691
+ 'hd' => esc_html__( 'Default quality', 'mainwp-child-reports' ),
692
+ );
693
+
694
+ if ( ! isset( $options[ $name ] ) ) {
695
+ return false;
696
+ }
697
+
698
+ return array(
699
+ // translators: Placeholder refers to a setting name (e.g. "Language")
700
+ 'message' => esc_html__( '"%s" setting updated', 'mainwp-child-reports' ),
701
+ 'meta' => array(
702
+ 'option_name' => $options[ $name ],
703
+ 'option' => 'jetpack_options',
704
+ 'option_key' => $key,
705
+ ),
706
+ 'action' => 'updated',
707
+ 'context' => 'videopress',
708
+ );
709
+ }
710
+
711
+ return false;
712
+ }
713
+ }
connectors/class-connector-mainwp-backups.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_MainWP_Backups extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'mainwp_backups';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'mainwp_backup',
19
+ 'mainwp_reports_backupbuddy_backup',
20
+ 'mainwp_reports_backupwordpress_backup',
21
+ 'mainwp_reports_backwpup_backup',
22
+ 'updraftplus_backup', // backup action from updraftplus
23
+ 'mainwp_reports_wptimecapsule_backup'
24
+ );
25
+
26
+ /**
27
+ * Return translated connector label
28
+ *
29
+ * @return string Translated connector label
30
+ */
31
+ public function get_label() {
32
+ return __( 'MainWP Backups', 'default' );
33
+ }
34
+
35
+ /**
36
+ * Return translated action labels
37
+ *
38
+ * @return array Action label translations
39
+ */
40
+ public function get_action_labels() {
41
+ return array(
42
+ 'mainwp_backup' => esc_html__( 'MainWP Backup', 'mainwp-child-reports' ),
43
+ 'mainwp_reports_backupbuddy_backup' => esc_html__( 'BackupBuddy Backup', 'mainwp-child-reports' ),
44
+ 'mainwp_reports_backupwordpress_backup' => esc_html__( 'BackupWordPress Backup', 'mainwp-child-reports' ),
45
+ 'mainwp_reports_backwpup_backup' => __( 'BackWPup Backup', 'mainwp-child-reports' ),
46
+ 'updraftplus_backup' => __( 'Updraftplus Backup', 'mainwp-child-reports' ),
47
+ 'mainwp_reports_wptimecapsule_backup' => __( 'WP Time Capsule Backup', 'mainwp-child-reports' ),
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Return translated context labels
53
+ *
54
+ * @return array Context label translations
55
+ */
56
+ public function get_context_labels() {
57
+ return array(
58
+ 'backups' => __( 'Backups', 'mainwp-child-reports' ),
59
+ );
60
+ }
61
+
62
+ public function register() {
63
+ parent::register();
64
+ }
65
+
66
+
67
+ /**
68
+ * Add action links to Stream drop row in admin list screen
69
+ *
70
+ * @filter wp_stream_action_links_{connector}
71
+ *
72
+ * @param array $links Previous links registered
73
+ * @param int $record Stream record
74
+ *
75
+ * @return array Action links
76
+ */
77
+ public function action_links( $links, $record ) {
78
+ return $links;
79
+ }
80
+
81
+ public function callback_mainwp_backup( $destination, $message, $size, $status, $type ) {
82
+ $this->log(
83
+ $message,
84
+ compact( 'destination', 'status', 'type', 'size' ),
85
+ 0,
86
+ 'backups',
87
+ 'mainwp_backup'
88
+ );
89
+ }
90
+
91
+ public function callback_mainwp_reports_backupbuddy_backup( $message, $type , $backup_time = 0) {
92
+ $this->log(
93
+ $message,
94
+ compact('type', 'backup_time'),
95
+ 0,
96
+ 'backups',
97
+ 'backupbuddy_backup'
98
+ );
99
+ }
100
+
101
+ public function callback_mainwp_reports_backupwordpress_backup($destination, $message, $status, $type, $backup_time = 0) {
102
+ $this->log(
103
+ $message,
104
+ compact('destination', 'status', 'type', 'backup_time'),
105
+ 0,
106
+ 'backups',
107
+ 'backupwordpress_backup'
108
+ );
109
+ }
110
+
111
+
112
+ public function callback_mainwp_reports_backwpup_backup( $message, $type, $backup_time ) {
113
+ $this->log(
114
+ $message,
115
+ compact( 'type', 'backup_time' ),
116
+ 0,
117
+ 'backups',
118
+ 'backwpup_backup'
119
+ );
120
+ }
121
+
122
+ public function callback_updraftplus_backup($destination, $message, $status, $type, $backup_time) {
123
+ $this->log(
124
+ $message,
125
+ compact('destination', 'status', 'type', 'backup_time'),
126
+ 0,
127
+ 'backups',
128
+ 'updraftplus_backup'
129
+ );
130
+ }
131
+
132
+ public function callback_mainwp_reports_wptimecapsule_backup( $message, $type, $backup_time ) {
133
+ $this->log(
134
+ $message,
135
+ compact( 'type', 'backup_time' ),
136
+ 0,
137
+ 'backups',
138
+ 'wptimecapsule_backup'
139
+ );
140
+ }
141
+ }
142
+
143
+
connectors/class-connector-mainwp-maintenance.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WP_MainWP_Stream;
4
+
5
+ class Connector_MainWP_Maintenance extends Connector {
6
+
7
+ public $name = 'mainwp_maintenance';
8
+
9
+ public $actions = array(
10
+ 'mainwp_reports_maintenance',
11
+ );
12
+
13
+ public function get_label() {
14
+ return __( 'Maintenance', 'default' );
15
+ }
16
+
17
+ public function get_action_labels() {
18
+ return array(
19
+ 'mainwp_reports_maintenance' => __( 'Maintenance', 'default' ),
20
+ );
21
+ }
22
+
23
+ public function get_context_labels() {
24
+ return array(
25
+ 'mainwp_maintenance' => __( 'Maintenance', 'default' ),
26
+ );
27
+ }
28
+
29
+ public function register() {
30
+ parent::register();
31
+ }
32
+
33
+ public function action_links( $links, $record ) {
34
+ if (isset($record->object_id)) {
35
+ }
36
+ return $links;
37
+ }
38
+
39
+ public function callback_mainwp_reports_maintenance( $message, $log_time, $details, $result = '') {
40
+ $this->log(
41
+ $message,
42
+ compact('log_time', 'details' , 'result'),
43
+ 0,
44
+ 'mainwp_maintenance',
45
+ 'maintenance'
46
+ );
47
+ }
48
+ }
connectors/class-connector-mainwp-sucuri.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_MainWP_Sucuri extends Connector {
5
+
6
+ /**
7
+ * Connector slug
8
+ *
9
+ * @var string
10
+ */
11
+ public $name = 'mainwp_sucuri';
12
+
13
+ /**
14
+ * Actions registered for this connector
15
+ *
16
+ * @var array
17
+ */
18
+ public $actions = array(
19
+ 'mainwp_reports_sucuri_scan',
20
+ );
21
+
22
+ /**
23
+ * Return translated connector label
24
+ *
25
+ * @return string Translated connector label
26
+ */
27
+ public function get_label() {
28
+ return __( 'MainWP Sucuri', 'default' );
29
+ }
30
+
31
+ /**
32
+ * Return translated action labels
33
+ *
34
+ * @return array Action label translations
35
+ */
36
+ public function get_action_labels() {
37
+ return array(
38
+ 'sucuri_scan' => __( 'Sucuri Scan', 'default' ),
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Return translated context labels
44
+ *
45
+ * @return array Context label translations
46
+ */
47
+ public function get_context_labels() {
48
+ return array(
49
+ 'sucuri_scan' => __( 'Sucuri scan', 'default' ),
50
+ );
51
+ }
52
+
53
+ public function register() {
54
+ parent::register();
55
+ }
56
+
57
+ /**
58
+ * Add action links to Stream drop row in admin list screen
59
+ *
60
+ * @filter wp_stream_action_links_{connector}
61
+ *
62
+ * @param array $links Previous links registered
63
+ * @param int $record Stream record
64
+ *
65
+ * @return array Action links
66
+ */
67
+ public function action_links( $links, $record ) {
68
+ return $links;
69
+ }
70
+
71
+ public function callback_mainwp_reports_sucuri_scan( $data, $scan_status, $scan_data, $scan_time = 0) {
72
+
73
+ $message = '';
74
+ if ( 'success' === $scan_status ) {
75
+ $message = __( 'Sucuri scan successful!', 'mainwp-child' );
76
+ $scan_status = 'success';
77
+ } else {
78
+ $message = __( 'Sucuri scan failed!', 'mainwp-child' );
79
+ $scan_status = 'failed';
80
+ }
81
+
82
+ $scan_result = maybe_unserialize( base64_decode( $data ) );
83
+ $status = $webtrust = '';
84
+ if ( is_array( $scan_result ) ) {
85
+ $status = isset( $scan_result['status'] ) ? $scan_result['status'] : '';
86
+ $webtrust = isset( $scan_result['webtrust'] ) ? $scan_result['webtrust'] : '';
87
+ }
88
+
89
+ if ( empty($scan_time))
90
+ $scan_time = time();
91
+
92
+ $this->log(
93
+ $message,
94
+ compact( 'scan_status', 'status', 'webtrust', 'scan_data', 'scan_time' ),
95
+ 0,
96
+ 'sucuri_scan',
97
+ 'sucuri_scan'
98
+ );
99
+ }
100
+ }
101
+
102
+
connectors/class-connector-mainwp-wordfence.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_MainWP_Wordfence extends Connector {
5
+
6
+ public $name = 'mainwp_wordfence';
7
+
8
+ public $actions = array(
9
+ 'mainwp_reports_wordfence_scan',
10
+ );
11
+
12
+ public function get_label() {
13
+ return __( 'Wordfence', 'default' );
14
+ }
15
+
16
+ public function get_action_labels() {
17
+ return array(
18
+ 'wordfence_scan' => __( 'Wordfence scan', 'default' ),
19
+ );
20
+ }
21
+
22
+ public function get_context_labels() {
23
+ return array(
24
+ 'wordfence_scan' => __( 'Wordfence scan', 'mainwp-child-reports' ),
25
+ );
26
+ }
27
+
28
+ public function register() {
29
+ parent::register();
30
+ }
31
+
32
+ public function action_links( $links, $record ) {
33
+ if (isset($record->object_id)) {
34
+ }
35
+ return $links;
36
+ }
37
+
38
+ public function callback_mainwp_reports_wordfence_scan( $message, $scan_time, $details, $result = '') {
39
+ $this->log(
40
+ $message,
41
+ compact('scan_time', 'result', 'details'),
42
+ 0,
43
+ 'wordfence_scan',
44
+ 'wordfence_scan'
45
+ );
46
+ }
47
+ }
connectors/class-connector-media.php ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Media extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'media';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'add_attachment',
19
+ 'edit_attachment',
20
+ 'delete_attachment',
21
+ 'wp_save_image_editor_file',
22
+ 'wp_save_image_file',
23
+ );
24
+
25
+ /**
26
+ * Return translated connector label
27
+ *
28
+ * @return string Translated connector label
29
+ */
30
+ public function get_label() {
31
+ return esc_html__( 'Media', 'mainwp-child-reports' );
32
+ }
33
+
34
+ /**
35
+ * Return translated action labels
36
+ *
37
+ * @return array Action label translations
38
+ */
39
+ public function get_action_labels() {
40
+ return array(
41
+ 'attached' => esc_html__( 'Attached', 'mainwp-child-reports' ),
42
+ 'uploaded' => esc_html__( 'Uploaded', 'mainwp-child-reports' ),
43
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
44
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
45
+ 'assigned' => esc_html__( 'Assigned', 'mainwp-child-reports' ),
46
+ 'unassigned' => esc_html__( 'Unassigned', 'mainwp-child-reports' ),
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Return translated context labels
52
+ *
53
+ * Based on extension types used by wp_ext2type() in wp-includes/functions.php.
54
+ *
55
+ * @return array Context label translations
56
+ */
57
+ public function get_context_labels() {
58
+ return array(
59
+ 'image' => esc_html__( 'Image', 'mainwp-child-reports' ),
60
+ 'audio' => esc_html__( 'Audio', 'mainwp-child-reports' ),
61
+ 'video' => esc_html__( 'Video', 'mainwp-child-reports' ),
62
+ 'document' => esc_html__( 'Document', 'mainwp-child-reports' ),
63
+ 'spreadsheet' => esc_html__( 'Spreadsheet', 'mainwp-child-reports' ),
64
+ 'interactive' => esc_html__( 'Interactive', 'mainwp-child-reports' ),
65
+ 'text' => esc_html__( 'Text', 'mainwp-child-reports' ),
66
+ 'archive' => esc_html__( 'Archive', 'mainwp-child-reports' ),
67
+ 'code' => esc_html__( 'Code', 'mainwp-child-reports' ),
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Return the file type for an attachment which corresponds with a context label
73
+ *
74
+ * @param object $file_uri URI of the attachment
75
+ *
76
+ * @return string A file type which corresponds with a context label
77
+ */
78
+ public function get_attachment_type( $file_uri ) {
79
+ $extension = pathinfo( $file_uri, PATHINFO_EXTENSION );
80
+ $extension_type = wp_ext2type( $extension );
81
+
82
+ if ( empty( $extension_type ) ) {
83
+ $extension_type = 'document';
84
+ }
85
+
86
+ $context_labels = $this->get_context_labels();
87
+
88
+ if ( ! isset( $context_labels[ $extension_type ] ) ) {
89
+ $extension_type = 'document';
90
+ }
91
+
92
+ return $extension_type;
93
+ }
94
+
95
+ /**
96
+ * Add action links to Stream drop row in admin list screen
97
+ *
98
+ * @filter wp_mainwp_stream_action_links_{connector}
99
+ *
100
+ * @param array $links Previous links registered
101
+ * @param object $record Stream record
102
+ *
103
+ * @return array Action links
104
+ */
105
+ public function action_links( $links, $record ) {
106
+ if ( $record->object_id ) {
107
+ $edit_post_link = get_edit_post_link( $record->object_id );
108
+ if ( $edit_post_link ) {
109
+ $links[ esc_html__( 'Edit Media', 'mainwp-child-reports' ) ] = $edit_post_link;
110
+ }
111
+ $permalink = get_permalink( $record->object_id );
112
+ if ( $permalink ) {
113
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = $permalink;
114
+ }
115
+ }
116
+
117
+ return $links;
118
+ }
119
+
120
+ /**
121
+ * Tracks creation of attachments
122
+ *
123
+ * @action add_attachment
124
+ *
125
+ * @param int $post_id
126
+ */
127
+ public function callback_add_attachment( $post_id ) {
128
+ $post = get_post( $post_id );
129
+ if ( $post->post_parent ) {
130
+ // translators: Placeholders refer to an attachment title, and a post title (e.g. "PIC001", "Hello World")
131
+ $message = _x(
132
+ 'Attached "%1$s" to "%2$s"',
133
+ '1: Attachment title, 2: Parent post title',
134
+ 'mainwp-child-reports'
135
+ );
136
+ } else {
137
+ // translators: Placeholder refers to an attachment title (e.g. "PIC001")
138
+ $message = esc_html__( 'Added "%s" to Media library', 'mainwp-child-reports' );
139
+ }
140
+
141
+ $name = $post->post_title;
142
+ $url = $post->guid;
143
+ $parent_id = $post->post_parent;
144
+ $parent = get_post( $parent_id );
145
+ $parent_title = $parent_id ? $parent->post_title : null;
146
+ $attachment_type = $this->get_attachment_type( $post->guid );
147
+
148
+ $this->log(
149
+ $message,
150
+ compact( 'name', 'parent_title', 'parent_id', 'url' ),
151
+ $post_id,
152
+ $attachment_type,
153
+ $post->post_parent ? 'attached' : 'uploaded'
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Tracks editing attachments
159
+ *
160
+ * @action edit_attachment
161
+ *
162
+ * @param int $post_id
163
+ */
164
+ public function callback_edit_attachment( $post_id ) {
165
+ $post = get_post( $post_id );
166
+ $name = $post->post_title;
167
+ $attachment_type = $this->get_attachment_type( $post->guid );
168
+
169
+ // translators: Placeholder refers to an attachment title (e.g. "PIC001")
170
+ $message = esc_html__( 'Updated "%s"', 'mainwp-child-reports' );
171
+
172
+ $this->log(
173
+ $message,
174
+ compact( 'name' ),
175
+ $post_id,
176
+ $attachment_type,
177
+ 'updated'
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Tracks deletion of attachments
183
+ *
184
+ * @action delete_attachment
185
+ *
186
+ * @param int $post_id
187
+ */
188
+ public function callback_delete_attachment( $post_id ) {
189
+ $post = get_post( $post_id );
190
+ $parent = $post->post_parent ? get_post( $post->post_parent ) : null;
191
+ $parent_id = $parent ? $parent->ID : null;
192
+ $name = $post->post_title;
193
+ $url = $post->guid;
194
+ $attachment_type = $this->get_attachment_type( $post->guid );
195
+
196
+ // translators: Placeholder refers to an attachment title (e.g. "PIC001")
197
+ $message = esc_html__( 'Deleted "%s"', 'mainwp-child-reports' );
198
+
199
+ $this->log(
200
+ $message,
201
+ compact( 'name', 'parent_id', 'url' ),
202
+ $post_id,
203
+ $attachment_type,
204
+ 'deleted'
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Tracks changes made in the image editor
210
+ *
211
+ * @action delete_attachment
212
+ *
213
+ * @param string $dummy
214
+ * @param string $filename
215
+ * @param string $image
216
+ * @param string $mime_type
217
+ * @param int $post_id
218
+ */
219
+ public function callback_wp_save_image_editor_file( $dummy, $filename, $image, $mime_type, $post_id ) {
220
+ unset( $dummy );
221
+ unset( $image );
222
+ unset( $mime_type );
223
+
224
+ $name = basename( $filename );
225
+ $post = get_post( $post_id );
226
+
227
+ $attachment_type = $this->get_attachment_type( $post->guid );
228
+
229
+ $this->log(
230
+ // translators: Placeholder refers to an attachment title (e.g. "PIC001")
231
+ __( 'Edited image "%s"', 'mainwp-child-reports' ),
232
+ compact( 'name', 'filename', 'post_id' ),
233
+ $post_id,
234
+ $attachment_type,
235
+ 'edited'
236
+ );
237
+ }
238
+
239
+ public function callback_wp_save_image_file( $dummy, $filename, $image, $mime_type, $post_id ) {
240
+ return $this->callback_wp_save_image_editor_file( $dummy, $filename, $image, $mime_type, $post_id );
241
+ }
242
+ }
connectors/class-connector-menus.php ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Menus extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'menus';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'wp_create_nav_menu',
19
+ 'wp_update_nav_menu',
20
+ 'delete_nav_menu',
21
+ );
22
+
23
+ /**
24
+ * Register connector in the WP Frontend
25
+ *
26
+ * @var bool
27
+ */
28
+ public $register_frontend = false;
29
+
30
+ /**
31
+ * Return translated connector label
32
+ *
33
+ * @return string Translated connector label
34
+ */
35
+ public function get_label() {
36
+ return esc_html__( 'Menus', 'mainwp-child-reports' );
37
+ }
38
+
39
+ /**
40
+ * Return translated action labels
41
+ *
42
+ * @return array Action label translations
43
+ */
44
+ public function get_action_labels() {
45
+ return array(
46
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
47
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
48
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
49
+ 'assigned' => esc_html__( 'Assigned', 'mainwp-child-reports' ),
50
+ 'unassigned' => esc_html__( 'Unassigned', 'mainwp-child-reports' ),
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Return translated context labels
56
+ *
57
+ * @return array Context label translations
58
+ */
59
+ public function get_context_labels() {
60
+ $labels = array();
61
+ $menus = get_terms(
62
+ 'nav_menu', array(
63
+ 'hide_empty' => false,
64
+ )
65
+ );
66
+
67
+ foreach ( $menus as $menu ) {
68
+ $slug = sanitize_title( $menu->name );
69
+ $labels[ $slug ] = $menu->name;
70
+ }
71
+
72
+ return $labels;
73
+ }
74
+
75
+ public function register() {
76
+ parent::register();
77
+
78
+ add_action( 'update_option_theme_mods_' . get_option( 'stylesheet' ), array( $this, 'callback_update_option_theme_mods' ), 10, 2 );
79
+ }
80
+
81
+ /**
82
+ * Add action links to Stream drop row in admin list screen
83
+ *
84
+ * @filter wp_mainwp_stream_action_links_{connector}
85
+ *
86
+ * @param array $links Previous links registered
87
+ * @param object $record Stream record
88
+ *
89
+ * @return array Action links
90
+ */
91
+ public function action_links( $links, $record ) {
92
+ if ( $record->object_id ) {
93
+ $menus = wp_get_nav_menus();
94
+ $menu_ids = wp_list_pluck( $menus, 'term_id' );
95
+
96
+ if ( in_array( $record->object_id, $menu_ids, true ) ) {
97
+ $links[ esc_html__( 'Edit Menu', 'mainwp-child-reports' ) ] = admin_url( 'nav-menus.php?action=edit&menu=' . $record->object_id );
98
+ }
99
+ }
100
+
101
+ return $links;
102
+ }
103
+
104
+ /**
105
+ * Tracks creation of menus
106
+ *
107
+ * @action wp_create_nav_menu
108
+ *
109
+ * @param int $menu_id
110
+ * @param array $menu_data
111
+ */
112
+ public function callback_wp_create_nav_menu( $menu_id, $menu_data ) {
113
+ $name = $menu_data['menu-name'];
114
+
115
+ $this->log(
116
+ // translators: Placeholder refers to a menu name (e.g. "Primary Menu")
117
+ __( 'Created new menu "%s"', 'mainwp-child-reports' ),
118
+ compact( 'name', 'menu_id' ),
119
+ $menu_id,
120
+ sanitize_title( $name ),
121
+ 'created'
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Tracks menu updates
127
+ *
128
+ * @action wp_update_nav_menu
129
+ *
130
+ * @param int $menu_id
131
+ * @param array $menu_data
132
+ */
133
+ public function callback_wp_update_nav_menu( $menu_id, $menu_data = array() ) {
134
+ if ( empty( $menu_data ) ) {
135
+ return;
136
+ }
137
+
138
+ $name = $menu_data['menu-name'];
139
+
140
+ $this->log(
141
+ // translators: Placeholder refers to a menu name (e.g. "Primary Menu")
142
+ _x( 'Updated menu "%s"', 'Menu name', 'mainwp-child-reports' ),
143
+ compact( 'name', 'menu_id', 'menu_data' ),
144
+ $menu_id,
145
+ sanitize_title( $name ),
146
+ 'updated'
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Tracks menu deletion
152
+ *
153
+ * @action delete_nav_menu
154
+ *
155
+ * @param object $term
156
+ * @param int $tt_id
157
+ * @param object $deleted_term
158
+ */
159
+ public function callback_delete_nav_menu( $term, $tt_id, $deleted_term ) {
160
+ unset( $tt_id );
161
+
162
+ $name = $deleted_term->name;
163
+ $menu_id = $term->term_id;
164
+
165
+ $this->log(
166
+ // translators: Placeholder refers to a menu name (e.g. "Primary Menu")
167
+ _x( 'Deleted "%s"', 'Menu name', 'mainwp-child-reports' ),
168
+ compact( 'name', 'menu_id' ),
169
+ $menu_id,
170
+ sanitize_title( $name ),
171
+ 'deleted'
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Track assignment to menu locations
177
+ *
178
+ * @action update_option_theme_mods_{$stylesheet}
179
+ *
180
+ * @param array $old
181
+ * @param array $new
182
+ */
183
+ public function callback_update_option_theme_mods( $old, $new ) {
184
+ // Disable if we're switching themes
185
+ if ( did_action( 'after_switch_theme' ) ) {
186
+ return;
187
+ }
188
+
189
+ $key = 'nav_menu_locations';
190
+
191
+ if ( ! isset( $new[ $key ] ) ) {
192
+ return; // Switching themes ?
193
+ }
194
+
195
+ if ( $old[ $key ] === $new[ $key ] ) {
196
+ return;
197
+ }
198
+
199
+ $locations = get_registered_nav_menus();
200
+ $old_value = (array) $old[ $key ];
201
+ $new_value = (array) $new[ $key ];
202
+ $changed = array_diff_assoc( $old_value, $new_value ) + array_diff_assoc( $new_value, $old_value );
203
+
204
+ if ( ! $changed ) {
205
+ return;
206
+ }
207
+
208
+ foreach ( $changed as $location_id => $menu_id ) {
209
+ $location = $locations[ $location_id ];
210
+
211
+ if ( empty( $new[ $key ][ $location_id ] ) ) {
212
+ $action = 'unassigned';
213
+ $menu_id = isset( $old[ $key ][ $location_id ] ) ? $old[ $key ][ $location_id ] : 0;
214
+ // translators: Placeholders refer to a menu name, and a theme location (e.g. "Primary Menu", "primary_nav")
215
+ $message = _x(
216
+ '"%1$s" has been unassigned from "%2$s"',
217
+ '1: Menu name, 2: Theme location',
218
+ 'mainwp-child-reports'
219
+ );
220
+ } else {
221
+ $action = 'assigned';
222
+ $menu_id = isset( $new[ $key ][ $location_id ] ) ? $new[ $key ][ $location_id ] : 0;
223
+ // translators: Placeholders refer to a menu name, and a theme location (e.g. "Primary Menu", "primary_nav")
224
+ $message = _x(
225
+ '"%1$s" has been assigned to "%2$s"',
226
+ '1: Menu name, 2: Theme location',
227
+ 'mainwp-child-reports'
228
+ );
229
+ }
230
+
231
+ $menu = get_term( $menu_id, 'nav_menu' );
232
+
233
+ if ( ! $menu || is_wp_error( $menu ) ) {
234
+ continue; // This is a deleted menu
235
+ }
236
+
237
+ $name = $menu->name;
238
+
239
+ $this->log(
240
+ $message,
241
+ compact( 'name', 'location', 'location_id', 'menu_id' ),
242
+ $menu_id,
243
+ sanitize_title( $name ),
244
+ $action
245
+ );
246
+ }
247
+ }
248
+ }
connectors/class-connector-posts.php ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Posts extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'posts';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'transition_post_status',
19
+ 'deleted_post',
20
+ );
21
+
22
+ /**
23
+ * Register connector in the WP Frontend
24
+ *
25
+ * @var bool
26
+ */
27
+ public $register_frontend = false;
28
+
29
+ /**
30
+ * Return translated connector label
31
+ *
32
+ * @return string Translated connector label
33
+ */
34
+ public function get_label() {
35
+ return esc_html__( 'Posts', 'mainwp-child-reports' );
36
+ }
37
+
38
+ /**
39
+ * Return translated action labels
40
+ *
41
+ * @return array Action label translations
42
+ */
43
+ public function get_action_labels() {
44
+ return array(
45
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
46
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
47
+ 'trashed' => esc_html__( 'Trashed', 'mainwp-child-reports' ),
48
+ 'untrashed' => esc_html__( 'Restored', 'mainwp-child-reports' ),
49
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Return translated context labels
55
+ *
56
+ * @return array Context label translations
57
+ */
58
+ public function get_context_labels() {
59
+ global $wp_post_types;
60
+
61
+ $post_types = wp_filter_object_list( $wp_post_types, array(), null, 'label' );
62
+ $post_types = array_diff_key( $post_types, array_flip( $this->get_excluded_post_types() ) );
63
+
64
+ add_action( 'registered_post_type', array( $this, 'registered_post_type' ), 10, 2 );
65
+
66
+ return $post_types;
67
+ }
68
+
69
+ /**
70
+ * Add action links to Stream drop row in admin list screen
71
+ *
72
+ * @filter wp_mainwp_stream_action_links_{connector}
73
+ *
74
+ * @param array $links Previous links registered
75
+ * @param Record $record Stream record
76
+ *
77
+ * @return array Action links
78
+ */
79
+ public function action_links( $links, $record ) {
80
+ $post = get_post( $record->object_id );
81
+
82
+ if ( $post && $post->post_status === $record->get_meta( 'new_status', true ) ) {
83
+ $post_type_name = $this->get_post_type_name( get_post_type( $post->ID ) );
84
+
85
+ if ( 'trash' === $post->post_status ) {
86
+ $untrash = wp_nonce_url(
87
+ add_query_arg(
88
+ array(
89
+ 'action' => 'untrash',
90
+ 'post' => $post->ID,
91
+ ),
92
+ admin_url( 'post.php' )
93
+ ),
94
+ sprintf( 'untrash-post_%d', $post->ID )
95
+ );
96
+
97
+ $delete = wp_nonce_url(
98
+ add_query_arg(
99
+ array(
100
+ 'action' => 'delete',
101
+ 'post' => $post->ID,
102
+ ),
103
+ admin_url( 'post.php' )
104
+ ),
105
+ sprintf( 'delete-post_%d', $post->ID )
106
+ );
107
+
108
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
109
+ $links[ sprintf( esc_html_x( 'Restore %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $untrash;
110
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
111
+ $links[ sprintf( esc_html_x( 'Delete %s Permenantly', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $delete;
112
+ } else {
113
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
114
+ $links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = get_edit_post_link( $post->ID );
115
+
116
+ $view_link = get_permalink( $post->ID );
117
+ if ( $view_link ) {
118
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = $view_link;
119
+ }
120
+
121
+ $revision_id = absint( $record->get_meta( 'revision_id', true ) );
122
+ $revision_id = $this->get_adjacent_post_revision( $revision_id, false );
123
+
124
+ if ( $revision_id ) {
125
+ $links[ esc_html__( 'Revision', 'mainwp-child-reports' ) ] = get_edit_post_link( $revision_id );
126
+ }
127
+ }
128
+ }
129
+
130
+ return $links;
131
+ }
132
+
133
+ /**
134
+ * Catch registration of post_types after initial loading, to cache its labels
135
+ *
136
+ * @action registered_post_type
137
+ *
138
+ * @param string $post_type Post type slug
139
+ * @param array $args Arguments used to register the post type
140
+ */
141
+ public function registered_post_type( $post_type, $args ) {
142
+ unset( $args );
143
+
144
+ $post_type_obj = get_post_type_object( $post_type );
145
+ $label = $post_type_obj->label;
146
+
147
+ wp_mainwp_stream_get_instance()->connectors->term_labels['stream_context'][ $post_type ] = $label;
148
+ }
149
+
150
+ /**
151
+ * Log all post status changes ( creating / updating / trashing )
152
+ *
153
+ * @action transition_post_status
154
+ *
155
+ * @param mixed $new
156
+ * @param mixed $old
157
+ * @param \WP_Post $post
158
+ */
159
+ public function callback_transition_post_status( $new, $old, $post ) {
160
+ if ( in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) {
161
+ return;
162
+ }
163
+
164
+ if ( in_array( $new, array( 'auto-draft', 'inherit' ), true ) ) {
165
+ return;
166
+ } elseif ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
167
+ return;
168
+ } elseif ( 'draft' === $new && 'publish' === $old ) {
169
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
170
+ $summary = _x(
171
+ '"%1$s" %2$s unpublished',
172
+ '1: Post title, 2: Post type singular name',
173
+ 'mainwp-child-reports'
174
+ );
175
+ } elseif ( 'trash' === $old && 'trash' !== $new ) {
176
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
177
+ $summary = _x(
178
+ '"%1$s" %2$s restored from trash',
179
+ '1: Post title, 2: Post type singular name',
180
+ 'mainwp-child-reports'
181
+ );
182
+ $action = 'untrashed';
183
+ } elseif ( 'draft' === $new && 'draft' === $old ) {
184
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
185
+ $summary = _x(
186
+ '"%1$s" %2$s draft saved',
187
+ '1: Post title, 2: Post type singular name',
188
+ 'mainwp-child-reports'
189
+ );
190
+ } elseif ( 'publish' === $new && 'draft' === $old ) {
191
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
192
+ $summary = _x(
193
+ '"%1$s" %2$s published',
194
+ '1: Post title, 2: Post type singular name',
195
+ 'mainwp-child-reports'
196
+ );
197
+ } elseif ( 'draft' === $new ) {
198
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
199
+ $summary = _x(
200
+ '"%1$s" %2$s drafted',
201
+ '1: Post title, 2: Post type singular name',
202
+ 'mainwp-child-reports'
203
+ );
204
+ } elseif ( 'pending' === $new ) {
205
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
206
+ $summary = _x(
207
+ '"%1$s" %2$s pending review',
208
+ '1: Post title, 2: Post type singular name',
209
+ 'mainwp-child-reports'
210
+ );
211
+ } elseif ( 'future' === $new ) {
212
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
213
+ $summary = _x(
214
+ '"%1$s" %2$s scheduled for %3$s',
215
+ '1: Post title, 2: Post type singular name, 3: Scheduled post date',
216
+ 'mainwp-child-reports'
217
+ );
218
+ } elseif ( 'future' === $old && 'publish' === $new ) {
219
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
220
+ $summary = _x(
221
+ '"%1$s" scheduled %2$s published',
222
+ '1: Post title, 2: Post type singular name',
223
+ 'mainwp-child-reports'
224
+ );
225
+ } elseif ( 'private' === $new ) {
226
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
227
+ $summary = _x(
228
+ '"%1$s" %2$s privately published',
229
+ '1: Post title, 2: Post type singular name',
230
+ 'mainwp-child-reports'
231
+ );
232
+ } elseif ( 'trash' === $new ) {
233
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
234
+ $summary = _x(
235
+ '"%1$s" %2$s trashed',
236
+ '1: Post title, 2: Post type singular name',
237
+ 'mainwp-child-reports'
238
+ );
239
+ $action = 'trashed';
240
+ } else {
241
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
242
+ $summary = _x(
243
+ '"%1$s" %2$s updated',
244
+ '1: Post title, 2: Post type singular name',
245
+ 'mainwp-child-reports'
246
+ );
247
+ }
248
+
249
+ if ( 'auto-draft' === $old && 'auto-draft' !== $new ) {
250
+ $action = 'created';
251
+ }
252
+
253
+ if ( empty( $action ) ) {
254
+ $action = 'updated';
255
+ }
256
+
257
+ $revision_id = null;
258
+
259
+ if ( wp_revisions_enabled( $post ) ) {
260
+ $revision = get_children(
261
+ array(
262
+ 'post_type' => 'revision',
263
+ 'post_status' => 'inherit',
264
+ 'post_parent' => $post->ID,
265
+ 'posts_per_page' => 1, // VIP safe
266
+ 'orderby' => 'post_date',
267
+ 'order' => 'DESC',
268
+ )
269
+ );
270
+
271
+ if ( $revision ) {
272
+ $revision = array_values( $revision );
273
+ $revision_id = $revision[0]->ID;
274
+ }
275
+ }
276
+
277
+ $post_type_name = strtolower( $this->get_post_type_name( $post->post_type ) );
278
+
279
+ $this->log(
280
+ $summary,
281
+ array(
282
+ 'post_title' => $post->post_title,
283
+ 'singular_name' => $post_type_name,
284
+ 'post_date' => $post->post_date,
285
+ 'post_date_gmt' => $post->post_date_gmt,
286
+ 'new_status' => $new,
287
+ 'old_status' => $old,
288
+ 'revision_id' => $revision_id,
289
+ ),
290
+ $post->ID,
291
+ $post->post_type,
292
+ $action
293
+ );
294
+ }
295
+
296
+ /**
297
+ * Log post deletion
298
+ *
299
+ * @action deleted_post
300
+ *
301
+ * $param integer $post_id
302
+ */
303
+ public function callback_deleted_post( $post_id ) {
304
+ $post = get_post( $post_id );
305
+
306
+ // We check if post is an instance of WP_Post as it doesn't always resolve in unit testing
307
+ if ( ! ( $post instanceof \WP_Post ) || in_array( $post->post_type, $this->get_excluded_post_types(), true ) ) {
308
+ return;
309
+ }
310
+
311
+ // Ignore auto-drafts that are deleted by the system, see issue-293
312
+ if ( 'auto-draft' === $post->post_status ) {
313
+ return;
314
+ }
315
+
316
+ $post_type_name = strtolower( $this->get_post_type_name( $post->post_type ) );
317
+
318
+ $this->log(
319
+ // translators: Placeholders refer to a post title, and a post type singular name (e.g. "Hello World", "Post")
320
+ _x(
321
+ '"%1$s" %2$s deleted from trash',
322
+ '1: Post title, 2: Post type singular name',
323
+ 'mainwp-child-reports'
324
+ ),
325
+ array(
326
+ 'post_title' => $post->post_title,
327
+ 'singular_name' => $post_type_name,
328
+ ),
329
+ $post->ID,
330
+ $post->post_type,
331
+ 'deleted'
332
+ );
333
+ }
334
+
335
+ /**
336
+ * Constructs list of excluded post types for the Posts connector
337
+ *
338
+ * @return array List of excluded post types
339
+ */
340
+ public function get_excluded_post_types() {
341
+ return apply_filters(
342
+ 'wp_mainwp_stream_posts_exclude_post_types',
343
+ array(
344
+ 'nav_menu_item',
345
+ 'attachment',
346
+ 'revision',
347
+ )
348
+ );
349
+ }
350
+
351
+ /**
352
+ * Gets the singular post type label
353
+ *
354
+ * @param string $post_type_slug
355
+ *
356
+ * @return string Post type label
357
+ */
358
+ public function get_post_type_name( $post_type_slug ) {
359
+ $name = esc_html__( 'Post', 'mainwp-child-reports' ); // Default
360
+
361
+ if ( post_type_exists( $post_type_slug ) ) {
362
+ $post_type = get_post_type_object( $post_type_slug );
363
+ $name = $post_type->labels->singular_name;
364
+ }
365
+
366
+ return $name;
367
+ }
368
+
369
+ /**
370
+ * Get an adjacent post revision ID
371
+ *
372
+ * @param int $revision_id
373
+ * @param bool $previous
374
+ *
375
+ * @return int $revision_id
376
+ */
377
+ public function get_adjacent_post_revision( $revision_id, $previous = true ) {
378
+ if ( empty( $revision_id ) || ! wp_is_post_revision( $revision_id ) ) {
379
+ return false;
380
+ }
381
+
382
+ $revision = wp_get_post_revision( $revision_id );
383
+ $operator = ( $previous ) ? '<' : '>';
384
+ $order = ( $previous ) ? 'DESC' : 'ASC';
385
+
386
+ global $wpdb;
387
+ // @codingStandardsIgnoreStart
388
+ $revision_id = $wpdb->get_var( // db call okay
389
+ $wpdb->prepare(
390
+ "SELECT p.ID
391
+ FROM $wpdb->posts AS p
392
+ WHERE p.post_date {$operator} %s
393
+ AND p.post_type = 'revision'
394
+ AND p.post_parent = %d
395
+ ORDER BY p.post_date {$order}
396
+ LIMIT 1",
397
+ $revision->post_date,
398
+ $revision->post_parent
399
+ )
400
+ );
401
+ // @codingStandardsIgnoreEnd
402
+ // prepare okay
403
+
404
+ $revision_id = absint( $revision_id );
405
+
406
+ if ( ! wp_is_post_revision( $revision_id ) ) {
407
+ return false;
408
+ }
409
+
410
+ return $revision_id;
411
+ }
412
+ }
connectors/class-connector-settings.php ADDED
@@ -0,0 +1,848 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Settings extends Connector {
5
+ /**
6
+ * Prefix for the highlight URL hash
7
+ *
8
+ * @const string
9
+ */
10
+ const HIGHLIGHT_FIELD_URL_HASH_PREFIX = 'wp-mainwp-stream-highlight:';
11
+
12
+ /**
13
+ * Connector slug
14
+ *
15
+ * @var string
16
+ */
17
+ public $name = 'settings';
18
+
19
+ /**
20
+ * Actions registered for this connector
21
+ *
22
+ * @var array
23
+ */
24
+ public $actions = array(
25
+ 'whitelist_options',
26
+ 'update_option',
27
+ 'update_site_option',
28
+ 'update_option_permalink_structure',
29
+ 'update_option_category_base',
30
+ 'update_option_tag_base',
31
+ );
32
+
33
+ /**
34
+ * Labels used for WordPress Settings
35
+ *
36
+ * @var array
37
+ */
38
+ public $labels = array();
39
+
40
+ /**
41
+ * Option names used in options-permalink.php
42
+ *
43
+ * @var array
44
+ */
45
+ public $permalink_options = array(
46
+ 'permalink_structure',
47
+ 'category_base',
48
+ 'tag_base',
49
+ );
50
+
51
+ /**
52
+ * Option names used in network/settings.php
53
+ *
54
+ * @var array
55
+ */
56
+ public $network_options = array(
57
+ 'registrationnotification',
58
+ 'registration',
59
+ 'add_new_users',
60
+ 'menu_items',
61
+ 'upload_space_check_disabled',
62
+ 'blog_upload_space',
63
+ 'upload_filetypes',
64
+ 'site_name',
65
+ 'first_post',
66
+ 'first_page',
67
+ 'first_comment',
68
+ 'first_comment_url',
69
+ 'first_comment_author',
70
+ 'welcome_email',
71
+ 'welcome_user_email',
72
+ 'fileupload_maxk',
73
+ 'global_terms_enabled',
74
+ 'illegal_names',
75
+ 'limited_email_domains',
76
+ 'banned_email_domains',
77
+ 'WPLANG',
78
+ 'blog_count',
79
+ 'user_count',
80
+ 'admin_email',
81
+ 'new_admin_email',
82
+ );
83
+
84
+ /**
85
+ * Register connector in the WP Frontend
86
+ *
87
+ * @var bool
88
+ */
89
+ public $register_frontend = false;
90
+
91
+ /**
92
+ * Register all context hooks
93
+ *
94
+ * @return void
95
+ */
96
+ public function register() {
97
+ parent::register();
98
+
99
+ $this->labels = array(
100
+ // General
101
+ 'blogname' => esc_html__( 'Site Title', 'mainwp-child-reports' ),
102
+ 'blogdescription' => esc_html__( 'Tagline', 'mainwp-child-reports' ),
103
+ 'gmt_offset' => esc_html__( 'Timezone', 'mainwp-child-reports' ),
104
+ 'admin_email' => esc_html__( 'E-mail Address', 'mainwp-child-reports' ),
105
+ 'new_admin_email' => esc_html__( 'E-mail Address', 'mainwp-child-reports' ),
106
+ 'siteurl' => esc_html__( 'WordPress Address (URL)', 'mainwp-child-reports' ),
107
+ 'home' => esc_html__( 'Site Address (URL)', 'mainwp-child-reports' ),
108
+ 'users_can_register' => esc_html__( 'Membership', 'mainwp-child-reports' ),
109
+ 'default_role' => esc_html__( 'New User Default Role', 'mainwp-child-reports' ),
110
+ 'timezone_string' => esc_html__( 'Timezone', 'mainwp-child-reports' ),
111
+ 'date_format' => esc_html__( 'Date Format', 'mainwp-child-reports' ),
112
+ 'time_format' => esc_html__( 'Time Format', 'mainwp-child-reports' ),
113
+ 'start_of_week' => esc_html__( 'Week Starts On', 'mainwp-child-reports' ),
114
+ // Writing
115
+ 'use_smilies' => esc_html__( 'Formatting', 'mainwp-child-reports' ),
116
+ 'use_balanceTags' => esc_html__( 'Formatting', 'mainwp-child-reports' ),
117
+ 'default_category' => esc_html__( 'Default Post Category', 'mainwp-child-reports' ),
118
+ 'default_post_format' => esc_html__( 'Default Post Format', 'mainwp-child-reports' ),
119
+ 'mailserver_url' => esc_html__( 'Mail Server', 'mainwp-child-reports' ),
120
+ 'mailserver_login' => esc_html__( 'Login Name', 'mainwp-child-reports' ),
121
+ 'mailserver_pass' => esc_html__( 'Password', 'mainwp-child-reports' ),
122
+ 'default_email_category' => esc_html__( 'Default Mail Category', 'mainwp-child-reports' ),
123
+ 'default_link_category' => esc_html__( 'Default Link Category', 'mainwp-child-reports' ),
124
+ 'ping_sites' => esc_html__( 'Update Services', 'mainwp-child-reports' ),
125
+ // Reading
126
+ 'show_on_front' => esc_html__( 'Front page displays', 'mainwp-child-reports' ),
127
+ 'page_on_front' => esc_html__( 'Front page displays', 'mainwp-child-reports' ),
128
+ 'page_for_posts' => esc_html__( 'Front page displays', 'mainwp-child-reports' ),
129
+ 'posts_per_page' => esc_html__( 'Blog pages show at most', 'mainwp-child-reports' ),
130
+ 'posts_per_rss' => esc_html__( 'Syndication feeds show the most recent', 'mainwp-child-reports' ),
131
+ 'rss_use_excerpt' => esc_html__( 'For each article in a feed, show', 'mainwp-child-reports' ),
132
+ 'blog_public' => esc_html__( 'Search Engine Visibility', 'mainwp-child-reports' ),
133
+ // Discussion
134
+ 'default_pingback_flag' => esc_html__( 'Default article settings', 'mainwp-child-reports' ),
135
+ 'default_ping_status' => esc_html__( 'Default article settings', 'mainwp-child-reports' ),
136
+ 'default_comment_status' => esc_html__( 'Default article settings', 'mainwp-child-reports' ),
137
+ 'require_name_email' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
138
+ 'comment_registration' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
139
+ 'close_comments_for_old_posts' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
140
+ 'close_comments_days_old' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
141
+ 'thread_comments' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
142
+ 'thread_comments_depth' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
143
+ 'page_comments' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
144
+ 'comments_per_page' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
145
+ 'default_comments_page' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
146
+ 'comment_order' => esc_html__( 'Other comment settings', 'mainwp-child-reports' ),
147
+ 'comments_notify' => esc_html__( 'E-mail me whenever', 'mainwp-child-reports' ),
148
+ 'moderation_notify' => esc_html__( 'E-mail me whenever', 'mainwp-child-reports' ),
149
+ 'comment_moderation' => esc_html__( 'Before a comment appears', 'mainwp-child-reports' ),
150
+ 'comment_whitelist' => esc_html__( 'Before a comment appears', 'mainwp-child-reports' ),
151
+ 'comment_max_links' => esc_html__( 'Comment Moderation', 'mainwp-child-reports' ),
152
+ 'moderation_keys' => esc_html__( 'Comment Moderation', 'mainwp-child-reports' ),
153
+ 'blacklist_keys' => esc_html__( 'Comment Blacklist', 'mainwp-child-reports' ),
154
+ 'show_avatars' => esc_html__( 'Show Avatars', 'mainwp-child-reports' ),
155
+ 'avatar_rating' => esc_html__( 'Maximum Rating', 'mainwp-child-reports' ),
156
+ 'avatar_default' => esc_html__( 'Default Avatar', 'mainwp-child-reports' ),
157
+ // Media
158
+ 'thumbnail_size_w' => esc_html__( 'Thumbnail size', 'mainwp-child-reports' ),
159
+ 'thumbnail_size_h' => esc_html__( 'Thumbnail size', 'mainwp-child-reports' ),
160
+ 'thumbnail_crop' => esc_html__( 'Thumbnail size', 'mainwp-child-reports' ),
161
+ 'medium_size_w' => esc_html__( 'Medium size', 'mainwp-child-reports' ),
162
+ 'medium_size_h' => esc_html__( 'Medium size', 'mainwp-child-reports' ),
163
+ 'large_size_w' => esc_html__( 'Large size', 'mainwp-child-reports' ),
164
+ 'large_size_h' => esc_html__( 'Large size', 'mainwp-child-reports' ),
165
+ 'uploads_use_yearmonth_folders' => esc_html__( 'Uploading Files', 'mainwp-child-reports' ),
166
+ // Permalinks
167
+ 'permalink_structure' => esc_html__( 'Permalink Settings', 'mainwp-child-reports' ),
168
+ 'category_base' => esc_html__( 'Category base', 'mainwp-child-reports' ),
169
+ 'tag_base' => esc_html__( 'Tag base', 'mainwp-child-reports' ),
170
+ // Network
171
+ 'registrationnotification' => esc_html__( 'Registration notification', 'mainwp-child-reports' ),
172
+ 'registration' => esc_html__( 'Allow new registrations', 'mainwp-child-reports' ),
173
+ 'add_new_users' => esc_html__( 'Add New Users', 'mainwp-child-reports' ),
174
+ 'menu_items' => esc_html__( 'Enable administration menus', 'mainwp-child-reports' ),
175
+ 'upload_space_check_disabled' => esc_html__( 'Site upload space check', 'mainwp-child-reports' ),
176
+ 'blog_upload_space' => esc_html__( 'Site upload space', 'mainwp-child-reports' ),
177
+ 'upload_filetypes' => esc_html__( 'Upload file types', 'mainwp-child-reports' ),
178
+ 'site_name' => esc_html__( 'Network Title', 'mainwp-child-reports' ),
179
+ 'first_post' => esc_html__( 'First Post', 'mainwp-child-reports' ),
180
+ 'first_page' => esc_html__( 'First Page', 'mainwp-child-reports' ),
181
+ 'first_comment' => esc_html__( 'First Comment', 'mainwp-child-reports' ),
182
+ 'first_comment_url' => esc_html__( 'First Comment URL', 'mainwp-child-reports' ),
183
+ 'first_comment_author' => esc_html__( 'First Comment Author', 'mainwp-child-reports' ),
184
+ 'welcome_email' => esc_html__( 'Welcome Email', 'mainwp-child-reports' ),
185
+ 'welcome_user_email' => esc_html__( 'Welcome User Email', 'mainwp-child-reports' ),
186
+ 'fileupload_maxk' => esc_html__( 'Max upload file size', 'mainwp-child-reports' ),
187
+ 'global_terms_enabled' => esc_html__( 'Terms Enabled', 'mainwp-child-reports' ),
188
+ 'illegal_names' => esc_html__( 'Banned Names', 'mainwp-child-reports' ),
189
+ 'limited_email_domains' => esc_html__( 'Limited Email Registrations', 'mainwp-child-reports' ),
190
+ 'banned_email_domains' => esc_html__( 'Banned Email Domains', 'mainwp-child-reports' ),
191
+ 'WPLANG' => esc_html__( 'Network Language', 'mainwp-child-reports' ),
192
+ 'blog_count' => esc_html__( 'Blog Count', 'mainwp-child-reports' ),
193
+ 'user_count' => esc_html__( 'User Count', 'mainwp-child-reports' ),
194
+ // Other
195
+ 'wp_mainwp_stream_db' => esc_html__( 'Reports Database Version', 'mainwp-child-reports' ),
196
+ );
197
+
198
+ // These option labels are special and need to change based on multisite context
199
+ if ( is_network_admin() ) {
200
+ $this->labels['admin_email'] = esc_html__( 'Network Admin Email', 'mainwp-child-reports' );
201
+ $this->labels['new_admin_email'] = esc_html__( 'Network Admin Email', 'mainwp-child-reports' );
202
+ }
203
+
204
+ add_action( 'admin_head', array( $this, 'highlight_field' ) );
205
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_jquery_color' ) );
206
+ add_action( sprintf( 'update_option_theme_mods_%s', get_option( 'stylesheet' ) ), array( $this, 'log_theme_modification' ), 10, 2 );
207
+ }
208
+
209
+ /**
210
+ * @action update_option_theme_mods_{name}
211
+ *
212
+ * @param mixed $old_value
213
+ * @param mixed $new_value
214
+ */
215
+ public function log_theme_modification( $old_value, $new_value ) {
216
+ $this->callback_updated_option( 'theme_mods', $old_value, $new_value );
217
+ }
218
+
219
+ /**
220
+ * Return translated context label
221
+ *
222
+ * @return string Translated context label
223
+ */
224
+ public function get_label() {
225
+ return esc_html__( 'Settings', 'mainwp-child-reports' );
226
+ }
227
+
228
+ /**
229
+ * Return translated action labels
230
+ *
231
+ * @return array Action label translations
232
+ */
233
+ public function get_action_labels() {
234
+ return array(
235
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Return translated context labels
241
+ *
242
+ * @return array Context label translations
243
+ */
244
+ public function get_context_labels() {
245
+ $context_labels = array(
246
+ 'settings' => esc_html__( 'Settings', 'mainwp-child-reports' ),
247
+ 'general' => esc_html__( 'General', 'mainwp-child-reports' ),
248
+ 'writing' => esc_html__( 'Writing', 'mainwp-child-reports' ),
249
+ 'reading' => esc_html__( 'Reading', 'mainwp-child-reports' ),
250
+ 'discussion' => esc_html__( 'Discussion', 'mainwp-child-reports' ),
251
+ 'media' => esc_html__( 'Media', 'mainwp-child-reports' ),
252
+ 'permalink' => esc_html__( 'Permalinks', 'mainwp-child-reports' ),
253
+ 'network' => esc_html__( 'Network', 'mainwp-child-reports' ),
254
+ 'wp_mainwp_stream' => esc_html__( 'Reports', 'mainwp-child-reports' ),
255
+ 'custom_background' => esc_html__( 'Custom Background', 'mainwp-child-reports' ),
256
+ 'custom_header' => esc_html__( 'Custom Header', 'mainwp-child-reports' ),
257
+ );
258
+
259
+ if ( is_network_admin() ) {
260
+ $context_labels = array_merge(
261
+ $context_labels,
262
+ array(
263
+ 'wp_mainwp_stream_network' => esc_html__( 'Reports Network', 'mainwp-child-reports' ),
264
+ 'wp_mainwp_stream_defaults' => esc_html__( 'Reports Defaults', 'mainwp-child-reports' ),
265
+ )
266
+ );
267
+ }
268
+
269
+ return $context_labels;
270
+ }
271
+
272
+ /**
273
+ * Return context by option name and key
274
+ *
275
+ * @param string $option_name
276
+ * @param string $key
277
+ *
278
+ * @return string Context slug
279
+ */
280
+ public function get_context_by_key( $option_name, $key ) {
281
+ $contexts = array(
282
+ 'theme_mods' => array(
283
+ 'custom_background' => array(
284
+ 'background_image',
285
+ 'background_position_x',
286
+ 'background_repeat',
287
+ 'background_attachment',
288
+ 'background_color',
289
+ ),
290
+ 'custom_header' => array(
291
+ 'header_image',
292
+ 'header_textcolor',
293
+ ),
294
+ ),
295
+ );
296
+
297
+ if ( isset( $contexts[ $option_name ] ) ) {
298
+ foreach ( $contexts[ $option_name ] as $context => $keys ) {
299
+ if ( in_array( $key, $keys, true ) ) {
300
+ return $context;
301
+ }
302
+ }
303
+ }
304
+
305
+ return false;
306
+ }
307
+
308
+ /**
309
+ * Find out if the option key should be ignored and not logged
310
+ *
311
+ * @param string $option_name
312
+ * @param string $key
313
+ *
314
+ * @return bool Whether option key is ignored or not
315
+ */
316
+ public function is_key_ignored( $option_name, $key ) {
317
+ $ignored = array(
318
+ 'theme_mods' => array(
319
+ 'background_image_thumb',
320
+ 'header_image_data',
321
+ ),
322
+ );
323
+
324
+ if ( isset( $ignored[ $option_name ] ) ) {
325
+ return in_array( $key, $ignored[ $option_name ], true );
326
+ }
327
+
328
+ return false;
329
+ }
330
+
331
+ /**
332
+ * Find out if the option should be ignored and not logged
333
+ *
334
+ * @param string $option_name
335
+ *
336
+ * @return bool Whether the option is ignored or not
337
+ */
338
+ public function is_option_ignored( $option_name ) {
339
+ if ( 0 === strpos( $option_name, '_transient_' ) || 0 === strpos( $option_name, '_site_transient_' ) ) {
340
+ return true;
341
+ }
342
+
343
+ if ( '$' === substr( $option_name, -1 ) ) {
344
+ return true;
345
+ }
346
+
347
+ $ignored = array(
348
+ 'image_default_link_type',
349
+ 'medium_large_size_w',
350
+ 'medium_large_size_h',
351
+ );
352
+
353
+ return in_array( $option_name, $ignored, true );
354
+ }
355
+
356
+ /**
357
+ * Find out if array keys in the option should be logged separately
358
+ *
359
+ * @param mixed $value
360
+ *
361
+ * @return bool Whether the option should be treated as a group
362
+ */
363
+ public function is_option_group( $value ) {
364
+ if ( ! is_array( $value ) ) {
365
+ return false;
366
+ }
367
+
368
+ if ( 0 === count( array_filter( array_keys( $value ), 'is_string' ) ) ) {
369
+ return false;
370
+ }
371
+
372
+ return true;
373
+ }
374
+
375
+ /**
376
+ * Return translated labels for all default Settings fields found in WordPress.
377
+ *
378
+ * @param string $field_key
379
+ *
380
+ * @return array Field label translations
381
+ */
382
+ public function get_field_label( $field_key ) {
383
+ if ( isset( $this->labels[ $field_key ] ) ) {
384
+ return $this->labels[ $field_key ];
385
+ }
386
+
387
+ return $field_key;
388
+ }
389
+
390
+ /**
391
+ * Enqueue jQuery Color plugin
392
+ *
393
+ * @action admin_enqueue_scripts
394
+ * @return void
395
+ */
396
+ public function enqueue_jquery_color() {
397
+ wp_enqueue_script( 'jquery-color' );
398
+ }
399
+
400
+ /**
401
+ * Return translated labels for all serialized Settings found in WordPress.
402
+ *
403
+ * @param string $option_name
404
+ * @param string $field_key
405
+ *
406
+ * @return string Field key translation or key itself if not found
407
+ */
408
+ public function get_serialized_field_label( $option_name, $field_key ) {
409
+ $labels = array(
410
+ 'theme_mods' => array(
411
+ // Custom Background
412
+ 'background_image' => esc_html__( 'Background Image', 'mainwp-child-reports' ),
413
+ 'background_position_x' => esc_html__( 'Background Position', 'mainwp-child-reports' ),
414
+ 'background_repeat' => esc_html__( 'Background Repeat', 'mainwp-child-reports' ),
415
+ 'background_attachment' => esc_html__( 'Background Attachment', 'mainwp-child-reports' ),
416
+ 'background_color' => esc_html__( 'Background Color', 'mainwp-child-reports' ),
417
+ // Custom Header
418
+ 'header_image' => esc_html__( 'Header Image', 'mainwp-child-reports' ),
419
+ 'header_textcolor' => esc_html__( 'Text Color', 'mainwp-child-reports' ),
420
+ 'header_background_color' => esc_html__( 'Header and Sidebar Background Color', 'mainwp-child-reports' ),
421
+ // Featured Content
422
+ 'featured_content_layout' => esc_html__( 'Layout', 'mainwp-child-reports' ),
423
+ // Custom Sidebar
424
+ 'sidebar_textcolor' => esc_html__( 'Header and Sidebar Text Color', 'mainwp-child-reports' ),
425
+ // Custom Colors
426
+ 'color_scheme' => esc_html__( 'Color Scheme', 'mainwp-child-reports' ),
427
+ 'main_text_color' => esc_html__( 'Main Text Color', 'mainwp-child-reports' ),
428
+ 'secondary_text_color' => esc_html__( 'Secondary Text Color', 'mainwp-child-reports' ),
429
+ 'link_color' => esc_html__( 'Link Color', 'mainwp-child-reports' ),
430
+ 'page_background_color' => esc_html__( 'Page Background Color', 'mainwp-child-reports' ),
431
+ ),
432
+ );
433
+
434
+ /**
435
+ * Filter allows for insertion of serialized labels
436
+ *
437
+ * @param array $lables Serialized labels
438
+ * @return array Updated array of serialzed labels
439
+ */
440
+ $labels = apply_filters( 'wp_mainwp_stream_serialized_labels', $labels );
441
+
442
+ if ( isset( $labels[ $option_name ] ) && isset( $labels[ $option_name ][ $field_key ] ) ) {
443
+ return $labels[ $option_name ][ $field_key ];
444
+ }
445
+
446
+ return $field_key;
447
+ }
448
+
449
+ /**
450
+ * Add action links to Stream drop row in admin list screen
451
+ *
452
+ * @filter wp_mainwp_stream_action_links_{connector}
453
+ *
454
+ * @param array $links Previous links registered
455
+ * @param Record $record Stream record
456
+ *
457
+ * @return array Action links
458
+ */
459
+ public function action_links( $links, $record ) {
460
+ $context_labels = $this->get_context_labels();
461
+ $plugin = wp_mainwp_stream_get_instance();
462
+
463
+ $rules = array(
464
+ 'mainwp-child-reports' => array(
465
+ 'menu_slug' => 'wp_mainwp_stream',
466
+ 'submenu_slug' => $plugin->admin->settings_page_slug,
467
+ 'url' => function( $rule, $record ) use ( $plugin ) {
468
+ $option_key = $record->get_meta( 'option_key', true );
469
+ $url_tab = null;
470
+
471
+ if ( '' !== $option_key ) {
472
+ foreach ( $plugin->settings->get_fields() as $tab_name => $tab_properties ) {
473
+ foreach ( $tab_properties['fields'] as $field ) {
474
+ $field_key = sprintf( '%s_%s', $tab_name, $field['name'] );
475
+ if ( $field_key === $option_key ) {
476
+ $url_tab = $tab_name;
477
+ break 2;
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ return add_query_arg(
484
+ array(
485
+ 'page' => $rule['submenu_slug'],
486
+ 'tab' => $url_tab,
487
+ ),
488
+ admin_url( 'admin.php' )
489
+ );
490
+ },
491
+ 'applicable' => function( $submenu, $record ) {
492
+ return 'wp_mainwp_stream' === $record->context;
493
+ },
494
+ ),
495
+ 'background_header' => array(
496
+ 'menu_slug' => 'themes.php',
497
+ 'submenu_slug' => function( $record ) {
498
+ return str_replace( '_', '-', $record->context );
499
+ },
500
+ 'url' => function( $rule, $record ) {
501
+ return add_query_arg( 'page', $rule['submenu_slug']( $record ), admin_url( $rule['menu_slug'] ) );
502
+ },
503
+ 'applicable' => function( $submenu, $record ) {
504
+ return in_array( $record->context, array( 'custom_header', 'custom_background' ), true );
505
+ },
506
+ ),
507
+ 'general' => array(
508
+ 'menu_slug' => 'options-general.php',
509
+ 'submenu_slug' => function( $record ) {
510
+ return sprintf( 'options-%s.php', $record->context );
511
+ },
512
+ 'url' => function( $rule, $record ) {
513
+ return admin_url( $rule['submenu_slug']( $record ) );
514
+ },
515
+ 'applicable' => function( $submenu, $record ) {
516
+ return ! empty( $submenu['options-general.php'] );
517
+ },
518
+ ),
519
+ 'network' => array(
520
+ 'menu_slug' => 'settings.php',
521
+ 'submenu_slug' => function( $record ) {
522
+ return 'settings.php';
523
+ },
524
+ 'url' => function( $rule, $record ) {
525
+ return network_admin_url( $rule['menu_slug'] );
526
+ },
527
+ 'applicable' => function( $submenu, $record ) {
528
+ if ( ! $record->blog_id ) {
529
+ return ! empty( $submenu['settings.php'] );
530
+ }
531
+ return false;
532
+ },
533
+ ),
534
+ );
535
+
536
+ if ( 'settings' !== $record->context && in_array( $record->context, array_keys( $context_labels ), true ) ) {
537
+ global $submenu;
538
+
539
+ $applicable_rules = array_filter(
540
+ $rules,
541
+ function( $rule ) use ( $submenu, $record ) {
542
+ return call_user_func( $rule['applicable'], $submenu, $record );
543
+ }
544
+ );
545
+
546
+ if ( ! empty( $applicable_rules ) ) {
547
+ // The first applicable rule wins
548
+ $rule = array_shift( $applicable_rules );
549
+ $menu_slug = $rule['menu_slug'];
550
+ $submenu_slug = ( is_object( $rule['submenu_slug'] ) && $rule['submenu_slug'] instanceof Closure ? $rule['submenu_slug']( $record ) : $rule['submenu_slug'] );
551
+ $url = $rule['url']( $rule, $record );
552
+
553
+ if ( isset( $submenu[ $menu_slug ] ) ) {
554
+ $found_submenus = wp_list_filter(
555
+ $submenu[ $menu_slug ],
556
+ array(
557
+ 2 => $submenu_slug,
558
+ )
559
+ );
560
+ }
561
+
562
+ if ( ! empty( $found_submenus ) ) {
563
+ $target_submenu = array_pop( $found_submenus );
564
+ list( $menu_title, $capability ) = $target_submenu;
565
+
566
+ if ( current_user_can( $capability ) ) {
567
+ $url = apply_filters( 'wp_mainwp_stream_action_link_url', $url, $record );
568
+ $field_name = $record->get_meta( 'option_key', true );
569
+
570
+ // translators: Placeholder refers to a context (e.g. "Editor")
571
+ $text = sprintf( esc_html__( 'Edit %s Settings', 'mainwp-child-reports' ), $context_labels[ $record->context ] );
572
+
573
+ if ( '' === $field_name ) {
574
+ $field_name = $record->get_meta( 'option', true );
575
+ }
576
+
577
+ if ( '' !== $field_name ) {
578
+ $url = sprintf( '%s#%s%s', rtrim( preg_replace( '/#.*/', '', $url ), '/' ), self::HIGHLIGHT_FIELD_URL_HASH_PREFIX, $field_name );
579
+ }
580
+
581
+ $links[ $text ] = $url;
582
+ }
583
+ }
584
+ }
585
+ }
586
+
587
+ return $links;
588
+ }
589
+
590
+ /**
591
+ * Trigger this connector from WP CLI or the Customizer, only for known Settings
592
+ *
593
+ * @action update_option
594
+ *
595
+ * @param string $option
596
+ * @param mixed $old_value
597
+ * @param mixed $value
598
+ */
599
+ public function callback_update_option( $option, $value, $old_value ) {
600
+ if ( ( defined( '\WP_CLI' ) && \WP_CLI || did_action( 'customize_save' ) ) && array_key_exists( $option, $this->labels ) ) {
601
+ $this->callback_updated_option( $option, $value, $old_value );
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Trigger this connector core tracker, only on options.php page
607
+ *
608
+ * @action whitelist_options
609
+ *
610
+ * @param array $options
611
+ *
612
+ * @return array
613
+ */
614
+ public function callback_whitelist_options( $options ) {
615
+ add_action( 'updated_option', array( $this, 'callback' ), 10, 3 );
616
+
617
+ return $options;
618
+ }
619
+
620
+ /**
621
+ * Trigger this connector core tracker, only on options-permalink.php page
622
+ *
623
+ * @action update_option_permalink_structure
624
+ *
625
+ * @param mixed $old_value
626
+ * @param mixed $value
627
+ *
628
+ */
629
+ public function callback_update_option_permalink_structure( $old_value, $value ) {
630
+ $this->callback_updated_option( 'permalink_structure', $old_value, $value );
631
+ }
632
+
633
+ /**
634
+ * Trigger this connector core tracker, only on network/settings.php page
635
+ *
636
+ * @action update_site_option
637
+ *
638
+ * @param string $option
639
+ * @param mixed $old_value
640
+ * @param mixed $value
641
+ */
642
+ public function callback_update_site_option( $option, $value, $old_value ) {
643
+ $this->callback_updated_option( $option, $value, $old_value );
644
+ }
645
+
646
+ /**
647
+ * Trigger this connector core tracker, only on options-permalink.php page
648
+ *
649
+ * @action update_option_category_base
650
+ *
651
+ * @param mixed $old_value
652
+ * @param mixed $value
653
+ */
654
+ public function callback_update_option_category_base( $old_value, $value ) {
655
+ $this->callback_updated_option( 'category_base', $old_value, $value );
656
+ }
657
+
658
+ /**
659
+ * Trigger this connector core tracker, only on options-permalink.php page
660
+ *
661
+ * @action update_option_tag_base
662
+ *
663
+ * @param mixed $old_value
664
+ * @param mixed $value
665
+ */
666
+ public function callback_update_option_tag_base( $old_value, $value ) {
667
+ $this->callback_updated_option( 'tag_base', $old_value, $value );
668
+ }
669
+
670
+ /**
671
+ * Track updated settings
672
+ *
673
+ * @action updated_option
674
+ *
675
+ * @param string $option
676
+ * @param mixed $old_value
677
+ * @param mixed $value
678
+ */
679
+ public function callback_updated_option( $option, $old_value, $value ) {
680
+ global $whitelist_options, $new_whitelist_options;
681
+
682
+ if ( $this->is_option_ignored( $option ) ) {
683
+ return;
684
+ }
685
+
686
+ $options = array_merge(
687
+ (array) $whitelist_options,
688
+ (array) $new_whitelist_options,
689
+ array(
690
+ 'permalink' => $this->permalink_options,
691
+ ),
692
+ array(
693
+ 'network' => $this->network_options,
694
+ )
695
+ );
696
+
697
+ foreach ( $options as $key => $opts ) {
698
+ if ( in_array( $option, $opts, true ) ) {
699
+ $context = $key;
700
+ break;
701
+ }
702
+ }
703
+
704
+ if ( ! isset( $context ) ) {
705
+ $context = 'settings';
706
+ }
707
+
708
+ $changed_options = array();
709
+
710
+ if ( $this->is_option_group( $value ) ) {
711
+ foreach ( $this->get_changed_keys( $old_value, $value ) as $field_key ) {
712
+ if ( ! $this->is_key_ignored( $option, $field_key ) ) {
713
+ $key_context = $this->get_context_by_key( $option, $field_key );
714
+ $changed_options[] = array(
715
+ 'label' => $this->get_serialized_field_label( $option, $field_key ),
716
+ 'option' => $option,
717
+ 'option_key' => $field_key,
718
+ 'context' => ( false !== $key_context ? $key_context : $context ),
719
+ 'old_value' => isset( $old_value[ $field_key ] ) ? $this->sanitize_value( $old_value[ $field_key ] ) : null,
720
+ 'value' => isset( $value[ $field_key ] ) ? $this->sanitize_value( $value[ $field_key ] ) : null,
721
+ );
722
+ }
723
+ }
724
+ } else {
725
+ $changed_options[] = array(
726
+ 'label' => $this->get_field_label( $option ),
727
+ 'option' => $option,
728
+ 'context' => $context,
729
+ 'old_value' => $this->sanitize_value( $old_value ),
730
+ 'value' => $this->sanitize_value( $value ),
731
+ );
732
+ }
733
+
734
+ foreach ( $changed_options as $properties ) {
735
+ $this->log(
736
+ // translators: Placeholder refers to a setting name (e.g. "Language")
737
+ __( '"%s" setting was updated', 'mainwp-child-reports' ),
738
+ $properties,
739
+ null,
740
+ $properties['context'],
741
+ 'updated'
742
+ );
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Add class to highlight field by URL param
748
+ *
749
+ * @action admin_head
750
+ */
751
+ public function highlight_field() {
752
+ ?>
753
+ <script>
754
+ (function ($) {
755
+ $(function () {
756
+ var hashPrefix = <?php echo wp_mainwp_stream_json_encode( self::HIGHLIGHT_FIELD_URL_HASH_PREFIX ); // xss ok ?>,
757
+ hashFieldName = "",
758
+ fieldNames = [],
759
+ $select2Choices = {},
760
+ $field = {};
761
+
762
+ if (location.hash.substr(1, hashPrefix.length) === hashPrefix) {
763
+ hashFieldName = location.hash.substr(hashPrefix.length + 1);
764
+ fieldNames = [hashFieldName];
765
+
766
+ $field = $("input, textarea, select").filter(function () {
767
+ return fieldNames.indexOf( $(this).attr("name") ) > -1;
768
+ });
769
+
770
+ // try to find wp_stream field
771
+ if ( $field.length === 0 ) {
772
+ fieldNames = [
773
+ "wp_mainwp_stream_" + hashFieldName,
774
+ "wp_stream[" + hashFieldName + "]"
775
+ ];
776
+
777
+ $field = $("input, textarea, select, div").filter(function () {
778
+ return fieldNames.indexOf( $(this).attr("id") ) > -1;
779
+ });
780
+
781
+ // if the field has been selectified, the list is the one to be colorized
782
+ $select2Choices = $field.find(".select2-choices");
783
+ if ( $select2Choices.length === 1 ) {
784
+ $field = $select2Choices;
785
+ }
786
+ }
787
+
788
+ $("html, body")
789
+ .animate({
790
+ scrollTop: ($field.closest("tr").length === 1 ? $field.closest("tr") : $field).offset().top - $("#wpadminbar").height()
791
+ }, 1000, function () {
792
+
793
+ $field
794
+ .css("background", $(this).css("background-color"))
795
+ .animate({
796
+ backgroundColor: "#fffedf",
797
+ }, 250);
798
+
799
+ $("label")
800
+ .filter(function () {
801
+ return fieldNames.indexOf( $(this).attr("for") ) > -1;
802
+ })
803
+ .animate({
804
+ color: "#d54e21"
805
+ }, 250);
806
+ }
807
+ );
808
+ }
809
+ });
810
+ }(jQuery));
811
+ </script>
812
+ <?php
813
+ }
814
+
815
+ /**
816
+ * Find out if array keys in the option should be logged separately
817
+ *
818
+ * @deprecated 3.0.6
819
+ * @deprecated Use is_option_group()
820
+ * @see is_option_group()
821
+ *
822
+ * @param string $key
823
+ * @param mixed $old_value
824
+ * @param mixed $value
825
+ *
826
+ * @return bool Whether the option should be treated as a group
827
+ */
828
+ public function is_key_option_group( $key, $old_value, $value ) {
829
+ _deprecated_function( __FUNCTION__, '3.0.6', 'is_option_group' );
830
+ return $this->is_option_group( $value );
831
+ }
832
+
833
+ /**
834
+ * Sanitize values, so that we don't store complex data, such as arrays or objects
835
+ *
836
+ * @param mixed $value
837
+ * @return string
838
+ */
839
+ public function sanitize_value( $value ) {
840
+ if ( is_array( $value ) ) {
841
+ return '';
842
+ } elseif ( is_object( $value ) && ! in_array( '__toString', get_class_methods( $value ), true ) ) {
843
+ return '';
844
+ }
845
+
846
+ return strval( $value );
847
+ }
848
+ }
connectors/class-connector-taxonomies.php ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Taxonomies extends Connector {
5
+ /**
6
+ * Connector slug
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'taxonomies';
11
+
12
+ /**
13
+ * Actions registered for this connector
14
+ *
15
+ * @var array
16
+ */
17
+ public $actions = array(
18
+ 'created_term',
19
+ 'delete_term',
20
+ 'edit_term',
21
+ 'edited_term',
22
+ );
23
+
24
+ /**
25
+ * Cache term values before update, used by callback_edit_term/callback_edited_term
26
+ *
27
+ * @var Object
28
+ */
29
+ public $cached_term_before_update;
30
+
31
+ /**
32
+ * Cache taxonomy labels
33
+ *
34
+ * @var array
35
+ */
36
+ public $context_labels;
37
+
38
+ /**
39
+ * Register connector in the WP Frontend
40
+ *
41
+ * @var bool
42
+ */
43
+ public $register_frontend = false;
44
+
45
+ /**
46
+ * Return translated connector label
47
+ *
48
+ * @return string Translated connector label
49
+ */
50
+ public function get_label() {
51
+ return esc_html__( 'Taxonomies', 'mainwp-child-reports' );
52
+ }
53
+
54
+ /**
55
+ * Return translated action labels
56
+ *
57
+ * @return array Action label translations
58
+ */
59
+ public function get_action_labels() {
60
+ return array(
61
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
62
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
63
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Return translated context labels
69
+ *
70
+ * @return array Context label translations
71
+ */
72
+ public function get_context_labels() {
73
+ global $wp_taxonomies;
74
+
75
+ $labels = wp_list_pluck( $wp_taxonomies, 'labels' );
76
+
77
+ $this->context_labels = wp_list_pluck( $labels, 'singular_name' );
78
+
79
+ add_action( 'registered_taxonomy', array( $this, 'registered_taxonomy' ), 10, 3 );
80
+
81
+ return $this->context_labels;
82
+ }
83
+
84
+ /**
85
+ * Add action links to Stream drop row in admin list screen
86
+ *
87
+ * @filter wp_mainwp_stream_action_links_{connector}
88
+ *
89
+ * @param array $links Previous links registered
90
+ * @param Record $record Stream record
91
+ *
92
+ * @return array Action links
93
+ */
94
+ public function action_links( $links, $record ) {
95
+ $term = get_term_by( 'term_taxonomy_id', $record->object_id, $record->context ); // wpcom_vip_get_term_by() does not indicate support for `term_taxonomy_id`
96
+ if ( $record->object_id && 'deleted' !== $record->action && $term ) {
97
+ if ( ! is_wp_error( $term ) ) {
98
+ $tax_obj = get_taxonomy( $term->taxonomy );
99
+ $tax_label = isset( $tax_obj->labels->singular_name ) ? $tax_obj->labels->singular_name : null;
100
+
101
+ if ( function_exists( 'wp_get_split_term' ) ) {
102
+ $term_id = wp_get_split_term( $term->term_id, $term->taxonomy );
103
+ }
104
+
105
+ $term_id = empty( $term_id ) ? $term->term_id : $term_id;
106
+
107
+ // translators: Placeholder refers to a term singular name (e.g. "Tag")
108
+ $links[ sprintf( _x( 'Edit %s', 'Term singular name', 'mainwp-child-reports' ), $tax_label ) ] = get_edit_term_link( $term_id, $term->taxonomy );
109
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = wp_mainwp_stream_is_vip() ? \wpcom_vip_get_term_link( $term_id, $term->taxonomy ) : get_term_link( $term_id, $term->taxonomy );
110
+ }
111
+ }
112
+
113
+ return $links;
114
+ }
115
+
116
+ /**
117
+ * Catch registration of taxonomies after inital loading, so we can cache its labels
118
+ *
119
+ * @action registered_taxonomy
120
+ *
121
+ * @param string $taxonomy Taxonomy slug
122
+ * @param array|string $object_type Object type or array of object types
123
+ * @param array|string $args Array or string of taxonomy registration arguments
124
+ */
125
+ public function registered_taxonomy( $taxonomy, $object_type, $args ) {
126
+ unset( $object_type );
127
+
128
+ $taxonomy_obj = (object) $args;
129
+ $label = get_taxonomy_labels( $taxonomy_obj )->singular_name;
130
+
131
+ $this->context_labels[ $taxonomy ] = $label;
132
+
133
+ wp_mainwp_stream_get_instance()->connectors->term_labels['stream_context'][ $taxonomy ] = $label;
134
+ }
135
+
136
+ /**
137
+ * Tracks creation of terms
138
+ *
139
+ * @action created_term
140
+ *
141
+ * @param integer $term_id
142
+ * @param integer $tt_id
143
+ * @param string $taxonomy
144
+ */
145
+ public function callback_created_term( $term_id, $tt_id, $taxonomy ) {
146
+ if ( in_array( $taxonomy, $this->get_excluded_taxonomies(), true ) ) {
147
+ return;
148
+ }
149
+
150
+ $term = get_term( $term_id, $taxonomy );
151
+ $term_name = $term->name;
152
+ $taxonomy_label = strtolower( $this->context_labels[ $taxonomy ] );
153
+ $term_parent = $term->parent;
154
+
155
+ $this->log(
156
+ // translators: Placeholders refer to a term name, and a taxonomy singular label (e.g. "Tags", "Genre")
157
+ _x(
158
+ '"%1$s" %2$s created',
159
+ '1: Term name, 2: Taxonomy singular label',
160
+ 'mainwp-child-reports'
161
+ ),
162
+ compact( 'term_name', 'taxonomy_label', 'term_id', 'taxonomy', 'term_parent' ),
163
+ $tt_id,
164
+ $taxonomy,
165
+ 'created'
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Tracks deletion of taxonomy terms
171
+ *
172
+ * @action delete_term
173
+ *
174
+ * @param integer $term_id
175
+ * @param integer $tt_id
176
+ * @param string $taxonomy
177
+ * @param object $deleted_term
178
+ */
179
+ public function callback_delete_term( $term_id, $tt_id, $taxonomy, $deleted_term ) {
180
+ if ( in_array( $taxonomy, $this->get_excluded_taxonomies(), true ) ) {
181
+ return;
182
+ }
183
+
184
+ $term_name = $deleted_term->name;
185
+ $term_parent = $deleted_term->parent;
186
+ $taxonomy_label = strtolower( $this->context_labels[ $taxonomy ] );
187
+
188
+ $this->log(
189
+ // translators: Placeholders refer to a term name, and a taxonomy singular label (e.g. "Tags", "Genre")
190
+ _x(
191
+ '"%1$s" %2$s deleted',
192
+ '1: Term name, 2: Taxonomy singular label',
193
+ 'mainwp-child-reports'
194
+ ),
195
+ compact( 'term_name', 'taxonomy_label', 'term_id', 'taxonomy', 'term_parent' ),
196
+ $tt_id,
197
+ $taxonomy,
198
+ 'deleted'
199
+ );
200
+ }
201
+
202
+ /**
203
+ * Tracks updates of taxonomy terms
204
+ *
205
+ * @action edit_term
206
+ *
207
+ * @param integer $term_id
208
+ * @param integer $tt_id
209
+ * @param string $taxonomy
210
+ */
211
+ public function callback_edit_term( $term_id, $tt_id, $taxonomy ) {
212
+ unset( $tt_id );
213
+ $this->cached_term_before_update = get_term( $term_id, $taxonomy );
214
+ }
215
+
216
+ public function callback_edited_term( $term_id, $tt_id, $taxonomy ) {
217
+ if ( in_array( $taxonomy, $this->get_excluded_taxonomies(), true ) ) {
218
+ return;
219
+ }
220
+
221
+ $term = $this->cached_term_before_update;
222
+
223
+ if ( ! $term ) { // For some reason!
224
+ $term = get_term( $term_id, $taxonomy );
225
+ }
226
+
227
+ $term_name = $term->name;
228
+ $taxonomy_label = strtolower( $this->context_labels[ $taxonomy ] );
229
+ $term_parent = $term->parent;
230
+
231
+ $this->log(
232
+ // translators: Placeholders refer to a term name, and a taxonomy singular label (e.g. "Tags", "Genre")
233
+ _x(
234
+ '"%1$s" %2$s updated',
235
+ '1: Term name, 2: Taxonomy singular label',
236
+ 'mainwp-child-reports'
237
+ ),
238
+ compact( 'term_name', 'taxonomy_label', 'term_id', 'taxonomy', 'term_parent' ),
239
+ $tt_id,
240
+ $taxonomy,
241
+ 'updated'
242
+ );
243
+ }
244
+
245
+ /**
246
+ * Constructs list of excluded taxonomies for the Taxonomies connector
247
+ *
248
+ * @return array List of excluded taxonomies
249
+ */
250
+ public function get_excluded_taxonomies() {
251
+ return apply_filters(
252
+ 'wp_mainwp_stream_taxonomies_exclude_taxonomies',
253
+ array(
254
+ 'nav_menu',
255
+ )
256
+ );
257
+ }
258
+ }
connectors/class-connector-user-switching.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_User_Switching extends Connector {
5
+
6
+ /**
7
+ * Connector slug
8
+ *
9
+ * @var string
10
+ */
11
+ public $name = 'userswitching';
12
+
13
+ /**
14
+ * Actions registered for this connector
15
+ *
16
+ * @var array
17
+ */
18
+ public $actions = array(
19
+ 'wp_mainwp_stream_after_connectors_registration',
20
+ 'switch_to_user',
21
+ 'switch_back_user',
22
+ 'switch_off_user',
23
+ );
24
+
25
+ /**
26
+ * Check if plugin dependencies are satisfied and add an admin notice if not
27
+ *
28
+ * @return bool
29
+ */
30
+ public function is_dependency_satisfied() {
31
+ if ( class_exists( 'user_switching' ) ) {
32
+ return true;
33
+ }
34
+
35
+ return false;
36
+ }
37
+
38
+ /**
39
+ * Return translated connector label
40
+ *
41
+ * @return string Translated connector label
42
+ */
43
+ public function get_label() {
44
+ return esc_html_x( 'User Switching', 'user-switching', 'mainwp-child-reports' );
45
+ }
46
+
47
+ /**
48
+ * Return translated action term labels
49
+ *
50
+ * @return array Action terms label translation
51
+ */
52
+ public function get_action_labels() {
53
+ return array();
54
+ }
55
+
56
+ /**
57
+ * Return translated context labels
58
+ *
59
+ * @return array Context label translations
60
+ */
61
+ public function get_context_labels() {
62
+ return array();
63
+ }
64
+
65
+ /**
66
+ * Register this connector.
67
+ *
68
+ * Overrides the default `Connector::register()` method.
69
+ *
70
+ */
71
+ public function register() {
72
+ parent::register();
73
+
74
+ add_filter( 'wp_mainwp_stream_log_data', array( $this, 'log_override' ) );
75
+ }
76
+
77
+ /**
78
+ * Override connector log for our own actions
79
+ *
80
+ * This changes the connector property to the Users connector if the log entry is from
81
+ * our User_Switching connector.
82
+ *
83
+ * @param array $data The log data.
84
+ * @return array The updated log data.
85
+ */
86
+ public function log_override( $data ) {
87
+ if ( ! is_array( $data ) ) {
88
+ return $data;
89
+ }
90
+
91
+ if ( 'User_Switching' === $data['connector'] ) {
92
+ $data['connector'] = 'Users';
93
+ }
94
+
95
+ return $data;
96
+ }
97
+
98
+ /**
99
+ * Fired after Stream has instantiated all of its connectors.
100
+ *
101
+ * This unhooks the Users connector's login and logout actions so they don't appear when a user switches
102
+ * user with the User Switching plugin.
103
+ *
104
+ * @param array $labels All registered connector labels
105
+ * @param Connectors $connectors The Connectors object instance
106
+ */
107
+ public function callback_wp_mainwp_stream_after_connectors_registration( array $labels, Connectors $connectors ) {
108
+ $action = wp_mainwp_stream_filter_input( INPUT_GET, 'action' ) ?: wp_mainwp_stream_filter_input( INPUT_POST, 'action' );
109
+
110
+ if ( ! $action ) {
111
+ return;
112
+ }
113
+
114
+ switch ( $action ) {
115
+
116
+ case 'switch_to_user':
117
+ $this->unhook_user_action( $connectors, 'clear_auth_cookie' );
118
+ $this->unhook_user_action( $connectors, 'set_logged_in_cookie' );
119
+ break;
120
+
121
+ case 'switch_to_olduser':
122
+ $this->unhook_user_action( $connectors, 'clear_auth_cookie' );
123
+ $this->unhook_user_action( $connectors, 'set_logged_in_cookie' );
124
+ break;
125
+
126
+ case 'switch_off':
127
+ $this->unhook_user_action( $connectors, 'clear_auth_cookie' );
128
+ break;
129
+
130
+ }
131
+
132
+ }
133
+
134
+ /**
135
+ * Fired when a user switches user with the User Switching plugin.
136
+ *
137
+ * @param int $user_id The ID of the user being switched to.
138
+ * @param int $old_user_id The ID of the user being switched from.
139
+ */
140
+ public function callback_switch_to_user( $user_id, $old_user_id ) {
141
+
142
+ $user = get_userdata( $user_id );
143
+ $old_user = get_userdata( $old_user_id );
144
+ // translators: Placeholders refer to a user display name, and a username (e.g. "Jane Doe", "administrator")
145
+ $message = _x(
146
+ 'Switched user to %1$s (%2$s)',
147
+ '1: User display name, 2: User login',
148
+ 'mainwp-child-reports'
149
+ );
150
+
151
+ $this->log(
152
+ $message,
153
+ array(
154
+ 'display_name' => $user->display_name,
155
+ 'user_login' => $user->user_login,
156
+ ),
157
+ $old_user->ID,
158
+ 'sessions',
159
+ 'switched-to',
160
+ $old_user->ID
161
+ );
162
+
163
+ }
164
+
165
+ /**
166
+ * Fired when a user switches back to their previous user account with the User Switching plugin.
167
+ *
168
+ * @param int $user_id The ID of the user being switched back to.
169
+ * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back
170
+ * after having been switched off.
171
+ */
172
+ public function callback_switch_back_user( $user_id, $old_user_id ) {
173
+
174
+ $user = get_userdata( $user_id );
175
+ // translators: Placeholders refer to a user display name, and a username (e.g. "Jane Doe", "administrator")
176
+ $message = _x(
177
+ 'Switched back to %1$s (%2$s)',
178
+ '1: User display name, 2: User login',
179
+ 'mainwp-child-reports'
180
+ );
181
+
182
+ if ( $old_user_id ) {
183
+ $old_user = get_userdata( $old_user_id );
184
+ } else {
185
+ $old_user = $user;
186
+ }
187
+
188
+ $this->log(
189
+ $message,
190
+ array(
191
+ 'display_name' => $user->display_name,
192
+ 'user_login' => $user->user_login,
193
+ ),
194
+ $old_user->ID,
195
+ 'sessions',
196
+ 'switched-back',
197
+ $old_user->ID
198
+ );
199
+
200
+ }
201
+
202
+ /**
203
+ * Fired when a user switches off with the User Switching plugin.
204
+ *
205
+ * @param int $old_user_id The ID of the user switching off.
206
+ */
207
+ public function callback_switch_off_user( $old_user_id ) {
208
+
209
+ $old_user = get_userdata( $old_user_id );
210
+ $message = __(
211
+ 'Switched off',
212
+ 'mainwp-child-reports'
213
+ );
214
+
215
+ $this->log(
216
+ $message,
217
+ array(),
218
+ $old_user->ID,
219
+ 'sessions',
220
+ 'switched-off',
221
+ $old_user->ID
222
+ );
223
+
224
+ }
225
+
226
+ /**
227
+ * Unhook the requested action from the Users connector.
228
+ *
229
+ * @param Connectors $connectors The Connectors instance
230
+ * @param string $action The name of the action to unhook
231
+ */
232
+ protected function unhook_user_action( Connectors $connectors, $action ) {
233
+ foreach ( $connectors->connectors as $connector ) {
234
+ if ( 'users' === $connector->name ) {
235
+ remove_action( $action, array( $connector, 'callback' ) );
236
+ }
237
+ }
238
+ }
239
+ }
connectors/class-connector-users.php ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Users extends Connector {
5
+
6
+ /**
7
+ * Connector slug
8
+ *
9
+ * @var string
10
+ */
11
+ public $name = 'users';
12
+
13
+ /**
14
+ * Stores users object before the user being deleted.
15
+ */
16
+ protected $_users_object_pre_deleted = array();
17
+
18
+ /**
19
+ * Actions registered for this connector
20
+ *
21
+ * @var array
22
+ */
23
+ public $actions = array(
24
+ 'user_register',
25
+ 'profile_update',
26
+ 'password_reset',
27
+ 'retrieve_password',
28
+ // 'set_logged_in_cookie',
29
+ // 'clear_auth_cookie',
30
+ 'delete_user',
31
+ 'deleted_user',
32
+ 'set_user_role',
33
+ );
34
+
35
+ /**
36
+ * Return translated connector label
37
+ *
38
+ * @return string Translated connector label
39
+ */
40
+ public function get_label() {
41
+ return esc_html__( 'Users', 'mainwp-child-reports' );
42
+ }
43
+
44
+ /**
45
+ * Return translated action term labels
46
+ *
47
+ * @return array Action terms label translation
48
+ */
49
+ public function get_action_labels() {
50
+ return array(
51
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
52
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
53
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
54
+ 'password-reset' => esc_html__( 'Password Reset', 'mainwp-child-reports' ),
55
+ 'forgot-password' => esc_html__( 'Lost Password', 'mainwp-child-reports' ),
56
+ 'login' => esc_html__( 'Log In', 'mainwp-child-reports' ),
57
+ 'logout' => esc_html__( 'Log Out', 'mainwp-child-reports' ),
58
+ 'switched-to' => esc_html__( 'Switched To', 'mainwp-child-reports' ),
59
+ 'switched-back' => esc_html__( 'Switched Back', 'mainwp-child-reports' ),
60
+ 'switched-off' => esc_html__( 'Switched Off', 'mainwp-child-reports' ),
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Return translated context labels
66
+ *
67
+ * @return array Context label translations
68
+ */
69
+ public function get_context_labels() {
70
+ return array(
71
+ 'users' => esc_html__( 'Users', 'mainwp-child-reports' ),
72
+ 'sessions' => esc_html__( 'Sessions', 'mainwp-child-reports' ),
73
+ 'profiles' => esc_html__( 'Profiles', 'mainwp-child-reports' ),
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Add action links to Stream drop row in admin list screen
79
+ *
80
+ * @filter wp_mainwp_stream_action_links_{connector}
81
+ *
82
+ * @param array $links Previous links registered
83
+ * @param Record $record Stream record
84
+ *
85
+ * @return array Action links
86
+ */
87
+ public function action_links( $links, $record ) {
88
+ if ( $record->object_id ) {
89
+ $link = get_edit_user_link( $record->object_id );
90
+ if ( $link ) {
91
+ $links [ esc_html__( 'Edit User', 'mainwp-child-reports' ) ] = $link;
92
+ }
93
+ }
94
+
95
+ return $links;
96
+ }
97
+
98
+ /**
99
+ * Get an array of role lables assigned to a specific user.
100
+ *
101
+ * @param object|int $user User object or user ID to get roles for
102
+ *
103
+ * @return array $labels An array of role labels
104
+ */
105
+ public function get_role_labels( $user ) {
106
+ if ( is_int( $user ) ) {
107
+ $user = get_user_by( 'id', $user );
108
+ }
109
+
110
+ if ( ! is_a( $user, 'WP_User' ) ) {
111
+ return array();
112
+ }
113
+
114
+ global $wp_roles;
115
+
116
+ $roles = $wp_roles->get_names();
117
+ $labels = array();
118
+
119
+ foreach ( $roles as $role => $label ) {
120
+ if ( in_array( $role, (array) $user->roles, true ) ) {
121
+ $labels[] = translate_user_role( $label );
122
+ }
123
+ }
124
+
125
+ return $labels;
126
+ }
127
+
128
+ /**
129
+ * Log user registrations
130
+ *
131
+ * @action user_register
132
+ *
133
+ * @param int $user_id Newly registered user ID
134
+ */
135
+ public function callback_user_register( $user_id ) {
136
+ $current_user = wp_get_current_user();
137
+ $registered_user = get_user_by( 'id', $user_id );
138
+
139
+ if ( ! $current_user->ID ) { // Non logged-in user registered themselves
140
+ $message = esc_html__( 'New user registration', 'mainwp-child-reports' );
141
+ $user_to_log = $registered_user->ID;
142
+ } else { // Current logged-in user created a new user
143
+ // translators: Placeholders refer to a user display name, and a user role (e.g. "Jane Doe", "subscriber")
144
+ $message = _x(
145
+ 'New user account created for %1$s (%2$s)',
146
+ '1: User display name, 2: User role',
147
+ 'mainwp-child-reports'
148
+ );
149
+ $user_to_log = $current_user->ID;
150
+ }
151
+
152
+ $this->log(
153
+ $message,
154
+ array(
155
+ 'display_name' => ( $registered_user->display_name ) ? $registered_user->display_name : $registered_user->user_login,
156
+ 'roles' => implode( ', ', $this->get_role_labels( $user_id ) ),
157
+ ),
158
+ $registered_user->ID,
159
+ 'users',
160
+ 'created',
161
+ $user_to_log
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Log profile update
167
+ *
168
+ * @action profile_update
169
+ *
170
+ * @param int $user_id registered user ID
171
+ * @param \WP_User $user registered user object
172
+ */
173
+ public function callback_profile_update( $user_id, $user ) {
174
+ unset( $user_id );
175
+
176
+ $this->log(
177
+ // translators: Placeholder refers to a user display name (e.g. "Jane Doe")
178
+ __( '%s\'s profile was updated', 'mainwp-child-reports' ),
179
+ array(
180
+ 'display_name' => $user->display_name,
181
+ ),
182
+ $user->ID,
183
+ 'profiles',
184
+ 'updated'
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Log role transition
190
+ *
191
+ * @action set_user_role
192
+ *
193
+ * @param int $user_id
194
+ * @param string $new_role
195
+ * @param array $old_roles
196
+ */
197
+ public function callback_set_user_role( $user_id, $new_role, $old_roles ) {
198
+ if ( empty( $old_roles ) ) {
199
+ return;
200
+ }
201
+
202
+ global $wp_roles;
203
+
204
+ $this->log(
205
+ // translators: Placeholders refer to a user display name, a user role, and another user role (e.g. "Jane Doe", "editor", "subscriber")
206
+ _x(
207
+ '%1$s\'s role was changed from %2$s to %3$s',
208
+ '1: User display name, 2: Old role, 3: New role',
209
+ 'mainwp-child-reports'
210
+ ),
211
+ array(
212
+ 'display_name' => get_user_by( 'id', $user_id )->display_name,
213
+ 'old_role' => translate_user_role( $wp_roles->role_names[ current( $old_roles ) ] ),
214
+ 'new_role' => $new_role ? translate_user_role( $wp_roles->role_names[ $new_role ] ) : __( 'N/A', 'mainwp-child-reports' ),
215
+ ),
216
+ $user_id,
217
+ 'profiles',
218
+ 'updated'
219
+ );
220
+ }
221
+
222
+ /**
223
+ * Log password reset
224
+ *
225
+ * @action password_reset
226
+ *
227
+ * @param \WP_User $user
228
+ */
229
+ public function callback_password_reset( $user ) {
230
+ $this->log(
231
+ // translators: Placeholder refers to a user display name (e.g. "Jane Doe")
232
+ __( '%s\'s password was reset', 'mainwp-child-reports' ),
233
+ array(
234
+ 'email' => $user->display_name,
235
+ ),
236
+ $user->ID,
237
+ 'profiles',
238
+ 'password-reset',
239
+ $user->ID
240
+ );
241
+ }
242
+
243
+ /**
244
+ * Log user requests to retrieve passwords
245
+ *
246
+ * @action retrieve_password
247
+ *
248
+ * @param string $user_login
249
+ */
250
+ public function callback_retrieve_password( $user_login ) {
251
+ if ( wp_mainwp_stream_filter_var( $user_login, FILTER_VALIDATE_EMAIL ) ) {
252
+ $user = get_user_by( 'email', $user_login );
253
+ } else {
254
+ $user = get_user_by( 'login', $user_login );
255
+ }
256
+
257
+ $this->log(
258
+ // translators: Placeholder refers to a user display name (e.g. "Jane Doe")
259
+ __( '%s\'s password was requested to be reset', 'mainwp-child-reports' ),
260
+ array(
261
+ 'display_name' => $user->display_name,
262
+ ),
263
+ $user->ID,
264
+ 'sessions',
265
+ 'forgot-password',
266
+ $user->ID
267
+ );
268
+ }
269
+
270
+ /**
271
+ * Log user login
272
+ *
273
+ * @action set_logged_in_cookie
274
+ *
275
+ * @param string $logged_in_cookie
276
+ * @param int $expire
277
+ * @param int $expiration
278
+ * @param int $user_id
279
+ */
280
+ // DISABLED
281
+ public function callback_set_logged_in_cookie( $logged_in_cookie, $expire, $expiration, $user_id ) {
282
+ unset( $logged_in_cookie );
283
+ unset( $expire );
284
+ unset( $expiration );
285
+ $user = get_user_by( 'id', $user_id );
286
+
287
+ $this->log(
288
+ // translators: Placeholder refers to a user display name (e.g. "Jane Doe")
289
+ __( '%s logged in', 'mainwp-child-reports' ),
290
+ array(
291
+ 'display_name' => $user->display_name,
292
+ ),
293
+ $user->ID,
294
+ 'sessions',
295
+ 'login',
296
+ $user->ID
297
+ );
298
+ }
299
+
300
+ /**
301
+ * Log user logout
302
+ *
303
+ * @action clear_auth_cookie
304
+ */
305
+ // DISABLED
306
+ public function callback_clear_auth_cookie() {
307
+ $user = wp_get_current_user();
308
+
309
+ // For some reason, incognito mode calls clear_auth_cookie on failed login attempts
310
+ if ( empty( $user ) || ! $user->exists() ) {
311
+ return;
312
+ }
313
+
314
+ $this->log(
315
+ // translators: Placeholder refers to a user display name (e.g. "Jane Doe")
316
+ __( '%s logged out', 'mainwp-child-reports' ),
317
+ array(
318
+ 'display_name' => $user->display_name,
319
+ ),
320
+ $user->ID,
321
+ 'sessions',
322
+ 'logout',
323
+ $user->ID
324
+ );
325
+ }
326
+
327
+ /**
328
+ * There's no logging in this callback's action, the reason
329
+ * behind this hook is so that we can store user objects before
330
+ * being deleted. During `deleted_user` hook, our callback
331
+ * receives $user_id param but it's useless as the user record
332
+ * was already removed from DB.
333
+ *
334
+ * @action delete_user
335
+ * @param int $user_id User ID that maybe deleted
336
+ */
337
+ public function callback_delete_user( $user_id ) {
338
+ if ( ! isset( $this->_users_object_pre_deleted[ $user_id ] ) ) {
339
+ $this->_users_object_pre_deleted[ $user_id ] = get_user_by( 'id', $user_id );
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Log deleted user.
345
+ *
346
+ * @action deleted_user
347
+ * @param int $user_id Deleted user ID
348
+ */
349
+ public function callback_deleted_user( $user_id ) {
350
+ $user = wp_get_current_user();
351
+
352
+ if ( isset( $this->_users_object_pre_deleted[ $user_id ] ) ) {
353
+ // translators: Placeholders refer to a user display name, and a user role (e.g. "Jane Doe", "subscriber")
354
+ $message = _x(
355
+ '%1$s\'s account was deleted (%2$s)',
356
+ '1: User display name, 2: User roles',
357
+ 'mainwp-child-reports'
358
+ );
359
+ $display_name = $this->_users_object_pre_deleted[ $user_id ]->display_name;
360
+ $deleted_user = $this->_users_object_pre_deleted[ $user_id ];
361
+ unset( $this->_users_object_pre_deleted[ $user_id ] );
362
+ } else {
363
+ // translators: Placeholders refer to a user display name, and a user role (e.g. "Jane Doe", "subscriber")
364
+ $message = esc_html__( 'User account #%d was deleted', 'mainwp-child-reports' );
365
+ $display_name = $user_id;
366
+ $deleted_user = $user_id;
367
+ }
368
+
369
+ $this->log(
370
+ $message,
371
+ array(
372
+ 'display_name' => $display_name,
373
+ 'roles' => implode( ', ', $this->get_role_labels( $deleted_user ) ),
374
+ ),
375
+ $user_id,
376
+ 'users',
377
+ 'deleted',
378
+ $user->ID
379
+ );
380
+ }
381
+ }
connectors/class-connector-widgets.php ADDED
@@ -0,0 +1,849 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Widgets extends Connector {
5
+
6
+ /**
7
+ * Whether or not 'created' and 'deleted' actions should be logged. Normally
8
+ * the sidebar 'added' and 'removed' actions will correspond with these.
9
+ * See note below with usage.
10
+ *
11
+ * @var bool
12
+ */
13
+ public $verbose_widget_created_deleted_actions = false;
14
+
15
+ /**
16
+ * Connector slug
17
+ *
18
+ * @var string
19
+ */
20
+ public $name = 'widgets';
21
+
22
+ /**
23
+ * Actions registered for this connector
24
+ *
25
+ * @var array
26
+ */
27
+ public $actions = array(
28
+ 'update_option_sidebars_widgets',
29
+ 'updated_option',
30
+ );
31
+
32
+ /**
33
+ * Store the initial sidebars_widgets option when the customizer does its
34
+ * multiple rounds of saving to the sidebars_widgets option.
35
+ *
36
+ * @var array
37
+ */
38
+ protected $customizer_initial_sidebars_widgets = null;
39
+
40
+ /**
41
+ * Return translated connector label
42
+ *
43
+ * @return string Translated connector label
44
+ */
45
+ public function get_label() {
46
+ return esc_html__( 'Widgets', 'mainwp-child-reports' );
47
+ }
48
+
49
+ /**
50
+ * Return translated action labels
51
+ *
52
+ * @return array Action label translations
53
+ */
54
+ public function get_action_labels() {
55
+ return array(
56
+ 'added' => esc_html__( 'Added', 'mainwp-child-reports' ),
57
+ 'removed' => esc_html__( 'Removed', 'mainwp-child-reports' ),
58
+ 'moved' => esc_html__( 'Moved', 'mainwp-child-reports' ),
59
+ 'created' => esc_html__( 'Created', 'mainwp-child-reports' ),
60
+ 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
61
+ 'deactivated' => esc_html__( 'Deactivated', 'mainwp-child-reports' ),
62
+ 'reactivated' => esc_html__( 'Reactivated', 'mainwp-child-reports' ),
63
+ 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
64
+ 'sorted' => esc_html__( 'Sorted', 'mainwp-child-reports' ),
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Return translated context labels
70
+ *
71
+ * @return array Context label translations
72
+ */
73
+ public function get_context_labels() {
74
+ global $wp_registered_sidebars;
75
+
76
+ $labels = array();
77
+
78
+ foreach ( $wp_registered_sidebars as $sidebar ) {
79
+ $labels[ $sidebar['id'] ] = $sidebar['name'];
80
+ }
81
+
82
+ $labels['wp_inactive_widgets'] = esc_html__( 'Inactive Widgets', 'mainwp-child-reports' );
83
+ $labels['orphaned_widgets'] = esc_html__( 'Orphaned Widgets', 'mainwp-child-reports' );
84
+
85
+ return $labels;
86
+ }
87
+
88
+ /**
89
+ * Add action links to Stream drop row in admin list screen
90
+ *
91
+ * @filter wp_mainwp_stream_action_links_{connector}
92
+ *
93
+ * @param array $links Previous links registered
94
+ * @param Record $record Stream record
95
+ *
96
+ * @return array Action links
97
+ */
98
+ public function action_links( $links, $record ) {
99
+ $sidebar = $record->get_meta( 'sidebar_id', true );
100
+ if ( $sidebar ) {
101
+ global $wp_registered_sidebars;
102
+
103
+ if ( array_key_exists( $sidebar, $wp_registered_sidebars ) ) {
104
+ $links[ esc_html__( 'Edit Widget Area', 'mainwp-child-reports' ) ] = admin_url( 'widgets.php#' . $sidebar ); // xss ok (@todo fix WPCS rule)
105
+ }
106
+ // @todo Also old_sidebar_id and new_sidebar_id
107
+ // @todo Add Edit Widget link
108
+ }
109
+
110
+ return $links;
111
+ }
112
+
113
+ /**
114
+ * Tracks addition/deletion/reordering/deactivation of widgets from sidebars
115
+ *
116
+ * @action update_option_sidebars_widgets
117
+ *
118
+ * @param array $old Old sidebars widgets
119
+ * @param array $new New sidebars widgets
120
+ *
121
+ * @return void
122
+ */
123
+ public function callback_update_option_sidebars_widgets( $old, $new ) {
124
+ // Disable listener if we're switching themes
125
+ if ( did_action( 'after_switch_theme' ) ) {
126
+ return;
127
+ }
128
+
129
+ if ( did_action( 'customize_save' ) ) {
130
+ if ( is_null( $this->customizer_initial_sidebars_widgets ) ) {
131
+ $this->customizer_initial_sidebars_widgets = $old;
132
+ add_action( 'customize_save_after', array( $this, 'callback_customize_save_after' ) );
133
+ }
134
+ } else {
135
+ $this->handle_sidebars_widgets_changes( $old, $new );
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Since the sidebars_widgets may get updated multiple times when saving
141
+ * changes to Widgets in the Customizer, defer handling the changes until
142
+ * customize_save_after.
143
+ *
144
+ * @see callback_update_option_sidebars_widgets()
145
+ */
146
+ public function callback_customize_save_after() {
147
+ $old_sidebars_widgets = $this->customizer_initial_sidebars_widgets;
148
+ $new_sidebars_widgets = get_option( 'sidebars_widgets' );
149
+
150
+ $this->handle_sidebars_widgets_changes( $old_sidebars_widgets, $new_sidebars_widgets );
151
+ }
152
+
153
+ /**
154
+ * @param array $old
155
+ * @param array $new
156
+ */
157
+ protected function handle_sidebars_widgets_changes( $old, $new ) {
158
+ unset( $old['array_version'] );
159
+ unset( $new['array_version'] );
160
+
161
+ if ( $old === $new ) {
162
+ return;
163
+ }
164
+
165
+ $this->handle_deactivated_widgets( $old, $new );
166
+ $this->handle_reactivated_widgets( $old, $new );
167
+ $this->handle_widget_removal( $old, $new );
168
+ $this->handle_widget_addition( $old, $new );
169
+ $this->handle_widget_reordering( $old, $new );
170
+ $this->handle_widget_moved( $old, $new );
171
+ }
172
+
173
+ /**
174
+ * Track deactivation of widgets from sidebars
175
+ *
176
+ * @param array $old Old sidebars widgets
177
+ * @param array $new New sidebars widgets
178
+ * @return void
179
+ */
180
+ protected function handle_deactivated_widgets( $old, $new ) {
181
+ $new_deactivated_widget_ids = array_diff( $new['wp_inactive_widgets'], $old['wp_inactive_widgets'] );
182
+
183
+ foreach ( $new_deactivated_widget_ids as $widget_id ) {
184
+ $sidebar_id = '';
185
+
186
+ foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
187
+ if ( in_array( $widget_id, $old_widget_ids, true ) ) {
188
+ $sidebar_id = $old_sidebar_id;
189
+ break;
190
+ }
191
+ }
192
+
193
+ $action = 'deactivated';
194
+ $name = $this->get_widget_name( $widget_id );
195
+ $title = $this->get_widget_title( $widget_id );
196
+ $labels = $this->get_context_labels();
197
+ $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
198
+
199
+ if ( $name && $title ) {
200
+ // translators: Placeholders refer to a widget name, a widget title, and a sidebar name (e.g. "Archives", "Browse", "Footer Area 1")
201
+ $message = _x( '%1$s widget named "%2$s" from "%3$s" deactivated', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
202
+ } elseif ( $name ) {
203
+ // Empty title, but we have the name
204
+ // translators: Placeholders refer to a widget name, and a sidebar name (e.g. "Archives", "Footer Area 1")
205
+ $message = _x( '%1$s widget from "%3$s" deactivated', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
206
+ } elseif ( $title ) {
207
+ // Likely a single widget since no name is available
208
+ // translators: Placeholders refer to a widget title, and a sidebar name (e.g. "Browse", "Footer Area 1")
209
+ $message = _x( 'Unknown widget type named "%2$s" from "%3$s" deactivated', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
210
+ } else {
211
+ // Neither a name nor a title are available, so use the widget ID
212
+ // translators: Placeholders refer to a widget ID, and a sidebar name (e.g. "42", "Footer Area 1")
213
+ $message = _x( '%4$s widget from "%3$s" deactivated', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
214
+ }
215
+
216
+ $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
217
+
218
+ $this->log(
219
+ $message,
220
+ compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
221
+ null,
222
+ 'wp_inactive_widgets',
223
+ $action
224
+ );
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Track reactivation of widgets from sidebars
230
+ *
231
+ * @param array $old Old sidebars widgets
232
+ * @param array $new New sidebars widgets
233
+ * @return void
234
+ */
235
+ protected function handle_reactivated_widgets( $old, $new ) {
236
+ $new_reactivated_widget_ids = array_diff( $old['wp_inactive_widgets'], $new['wp_inactive_widgets'] );
237
+
238
+ foreach ( $new_reactivated_widget_ids as $widget_id ) {
239
+ $sidebar_id = '';
240
+
241
+ foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
242
+ if ( in_array( $widget_id, $new_widget_ids, true ) ) {
243
+ $sidebar_id = $new_sidebar_id;
244
+ break;
245
+ }
246
+ }
247
+
248
+ $action = 'reactivated';
249
+ $name = $this->get_widget_name( $widget_id );
250
+ $title = $this->get_widget_title( $widget_id );
251
+
252
+ if ( $name && $title ) {
253
+ // translators: Placeholders refer to a widget name, and a widget title (e.g. "Archives", "Browse")
254
+ $message = _x( '%1$s widget named "%2$s" reactivated', '1: Name, 2: Title', 'mainwp-child-reports' );
255
+ } elseif ( $name ) {
256
+ // Empty title, but we have the name
257
+ // translators: Placeholder refers to a widget name (e.g. "Archives")
258
+ $message = _x( '%1$s widget reactivated', '1: Name', 'mainwp-child-reports' );
259
+ } elseif ( $title ) {
260
+ // Likely a single widget since no name is available
261
+ // translators: Placeholder refers to a widget title (e.g. "Browse")
262
+ $message = _x( 'Unknown widget type named "%2$s" reactivated', '2: Title', 'mainwp-child-reports' );
263
+ } else {
264
+ // Neither a name nor a title are available, so use the widget ID
265
+ // translators: Placeholder refers to a widget ID (e.g. "42")
266
+ $message = _x( '%3$s widget reactivated', '3: Widget ID', 'mainwp-child-reports' );
267
+ }
268
+
269
+ $message = sprintf( $message, $name, $title, $widget_id );
270
+
271
+ $this->log(
272
+ $message,
273
+ compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
274
+ null,
275
+ $sidebar_id,
276
+ $action
277
+ );
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Track deletion of widgets from sidebars
283
+ *
284
+ * @param array $old Old sidebars widgets
285
+ * @param array $new New sidebars widgets
286
+ * @return void
287
+ */
288
+ protected function handle_widget_removal( $old, $new ) {
289
+ $all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
290
+ $all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
291
+ // @todo In the customizer, moving widgets to other sidebars is problematic because each sidebar is registered as a separate setting; so we need to make sure that all $_POST['customized'] are applied?
292
+ // @todo The widget option is getting updated before the sidebars_widgets are updated, so we need to hook into the option update to try to cache any deletions for future lookup
293
+
294
+ $deleted_widget_ids = array_diff( $all_old_widget_ids, $all_new_widget_ids );
295
+
296
+ foreach ( $deleted_widget_ids as $widget_id ) {
297
+ $sidebar_id = '';
298
+
299
+ foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
300
+ if ( in_array( $widget_id, $old_widget_ids, true ) ) {
301
+ $sidebar_id = $old_sidebar_id;
302
+ break;
303
+ }
304
+ }
305
+
306
+ $action = 'removed';
307
+ $name = $this->get_widget_name( $widget_id );
308
+ $title = $this->get_widget_title( $widget_id );
309
+ $labels = $this->get_context_labels();
310
+ $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
311
+
312
+ if ( $name && $title ) {
313
+ // translators: Placeholders refer to a widget name, a widget title, and a sidebar name (e.g. "Archives", "Browse", "Footer Area 1")
314
+ $message = _x( '%1$s widget named "%2$s" removed from "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
315
+ } elseif ( $name ) {
316
+ // Empty title, but we have the name
317
+ // translators: Placeholders refer to a widget name, and a sidebar name (e.g. "Archives", "Footer Area 1")
318
+ $message = _x( '%1$s widget removed from "%3$s"', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
319
+ } elseif ( $title ) {
320
+ // Likely a single widget since no name is available
321
+ // translators: Placeholders refer to a widget title, and a sidebar name (e.g. "Browse", "Footer Area 1")
322
+ $message = _x( 'Unknown widget type named "%2$s" removed from "%3$s"', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
323
+ } else {
324
+ // Neither a name nor a title are available, so use the widget ID
325
+ // translators: Placeholders refer to a widget ID, and a sidebar name (e.g. "42", "Footer Area 1")
326
+ $message = _x( '%4$s widget removed from "%3$s"', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
327
+ }
328
+
329
+ $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
330
+
331
+ $this->log(
332
+ $message,
333
+ compact( 'widget_id', 'sidebar_id' ),
334
+ null,
335
+ $sidebar_id,
336
+ $action
337
+ );
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Track reactivation of widgets from sidebars
343
+ *
344
+ * @param array $old Old sidebars widgets
345
+ * @param array $new New sidebars widgets
346
+ * @return void
347
+ */
348
+ protected function handle_widget_addition( $old, $new ) {
349
+ $all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
350
+ $all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
351
+ $added_widget_ids = array_diff( $all_new_widget_ids, $all_old_widget_ids );
352
+
353
+ foreach ( $added_widget_ids as $widget_id ) {
354
+ $sidebar_id = '';
355
+
356
+ foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
357
+ if ( in_array( $widget_id, $new_widget_ids, true ) ) {
358
+ $sidebar_id = $new_sidebar_id;
359
+ break;
360
+ }
361
+ }
362
+
363
+ $action = 'added';
364
+ $name = $this->get_widget_name( $widget_id );
365
+ $title = $this->get_widget_title( $widget_id );
366
+ $labels = $this->get_context_labels();
367
+ $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
368
+
369
+ if ( $name && $title ) {
370
+ // translators: Placeholders refer to a widget name, a widget title, and a sidebar name (e.g. "Archives", "Browse", "Footer Area 1")
371
+ $message = _x( '%1$s widget named "%2$s" added to "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
372
+ } elseif ( $name ) {
373
+ // Empty title, but we have the name
374
+ // translators: Placeholders refer to a widget name, and a sidebar name (e.g. "Archives", "Footer Area 1")
375
+ $message = _x( '%1$s widget added to "%3$s"', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
376
+ } elseif ( $title ) {
377
+ // Likely a single widget since no name is available
378
+ // translators: Placeholders refer to a widget title, and a sidebar name (e.g. "Browse", "Footer Area 1")
379
+ $message = _x( 'Unknown widget type named "%2$s" added to "%3$s"', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
380
+ } else {
381
+ // Neither a name nor a title are available, so use the widget ID
382
+ // translators: Placeholders refer to a widget ID, and a sidebar name (e.g. "42", "Footer Area 1")
383
+ $message = _x( '%4$s widget added to "%3$s"', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
384
+ }
385
+
386
+ $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
387
+
388
+ $this->log(
389
+ $message,
390
+ compact( 'widget_id', 'sidebar_id' ), // @todo Do we care about sidebar_id in meta if it is already context? But there is no 'context' for what the context signifies
391
+ null,
392
+ $sidebar_id,
393
+ $action
394
+ );
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Track reordering of widgets
400
+ *
401
+ * @param array $old Old sidebars widgets
402
+ * @param array $new New sidebars widgets
403
+ * @return void
404
+ */
405
+ protected function handle_widget_reordering( $old, $new ) {
406
+ $all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
407
+
408
+ foreach ( $all_sidebar_ids as $sidebar_id ) {
409
+ if ( $old[ $sidebar_id ] === $new[ $sidebar_id ] ) {
410
+ continue;
411
+ }
412
+
413
+ // Use intersect to ignore widget additions and removals
414
+ $all_widget_ids = array_unique( array_merge( $old[ $sidebar_id ], $new[ $sidebar_id ] ) );
415
+ $common_widget_ids = array_intersect( $old[ $sidebar_id ], $new[ $sidebar_id ] );
416
+ $uncommon_widget_ids = array_diff( $all_widget_ids, $common_widget_ids );
417
+ $new_widget_ids = array_values( array_diff( $new[ $sidebar_id ], $uncommon_widget_ids ) );
418
+ $old_widget_ids = array_values( array_diff( $old[ $sidebar_id ], $uncommon_widget_ids ) );
419
+ $widget_order_changed = ( $new_widget_ids !== $old_widget_ids );
420
+
421
+ if ( $widget_order_changed ) {
422
+ $labels = $this->get_context_labels();
423
+ $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
424
+ $old_widget_ids = $old[ $sidebar_id ];
425
+
426
+ // translators: Placeholder refers to a sidebar name (e.g. "Footer Area 1")
427
+ $message = _x( 'Widgets reordered in "%s"', 'Sidebar name', 'mainwp-child-reports' );
428
+ $message = sprintf( $message, $sidebar_name );
429
+
430
+ $this->log(
431
+ $message,
432
+ compact( 'sidebar_id', 'old_widget_ids' ),
433
+ null,
434
+ $sidebar_id,
435
+ 'sorted'
436
+ );
437
+ }
438
+ }
439
+
440
+ }
441
+
442
+ /**
443
+ * Track movement of widgets to other sidebars
444
+ *
445
+ * @param array $old Old sidebars widgets
446
+ * @param array $new New sidebars widgets
447
+ * @return void
448
+ */
449
+ protected function handle_widget_moved( $old, $new ) {
450
+ $all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
451
+
452
+ foreach ( $all_sidebar_ids as $new_sidebar_id ) {
453
+ if ( $old[ $new_sidebar_id ] === $new[ $new_sidebar_id ] ) {
454
+ continue;
455
+ }
456
+
457
+ $new_widget_ids = array_diff( $new[ $new_sidebar_id ], $old[ $new_sidebar_id ] );
458
+
459
+ foreach ( $new_widget_ids as $widget_id ) {
460
+ // Now find the sidebar that the widget was originally located in, as long it is not wp_inactive_widgets
461
+ $old_sidebar_id = null;
462
+ foreach ( $old as $sidebar_id => $old_widget_ids ) {
463
+ if ( in_array( $widget_id, $old_widget_ids, true ) ) {
464
+ $old_sidebar_id = $sidebar_id;
465
+ break;
466
+ }
467
+ }
468
+
469
+ if ( ! $old_sidebar_id || 'wp_inactive_widgets' === $old_sidebar_id || 'wp_inactive_widgets' === $new_sidebar_id ) {
470
+ continue;
471
+ }
472
+
473
+ assert( $old_sidebar_id !== $new_sidebar_id );
474
+
475
+ $name = $this->get_widget_name( $widget_id );
476
+ $title = $this->get_widget_title( $widget_id );
477
+ $labels = $this->get_context_labels();
478
+ $old_sidebar_name = isset( $labels[ $old_sidebar_id ] ) ? $labels[ $old_sidebar_id ] : $old_sidebar_id;
479
+ $new_sidebar_name = isset( $labels[ $new_sidebar_id ] ) ? $labels[ $new_sidebar_id ] : $new_sidebar_id;
480
+
481
+ if ( $name && $title ) {
482
+ // translators: Placeholders refer to a widget name, a widget title, a sidebar name, and another sidebar name (e.g. "Archives", "Browse", "Footer Area 1", "Footer Area 2")
483
+ $message = _x( '%1$s widget named "%2$s" moved from "%4$s" to "%5$s"', '1: Name, 2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
484
+ } elseif ( $name ) {
485
+ // Empty title, but we have the name
486
+ // translators: Placeholders refer to a widget name, a sidebar name, and another sidebar name (e.g. "Archives", "Footer Area 1", "Footer Area 2")
487
+ $message = _x( '%1$s widget moved from "%4$s" to "%5$s"', '1: Name, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
488
+ } elseif ( $title ) {
489
+ // Likely a single widget since no name is available
490
+ // translators: Placeholders refer to a widget title, a sidebar name, and another sidebar name (e.g. "Browse", "Footer Area 1", "Footer Area 2")
491
+ $message = _x( 'Unknown widget type named "%2$s" moved from "%4$s" to "%5$s"', '2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
492
+ } else {
493
+ // Neither a name nor a title are available, so use the widget ID
494
+ // translators: Placeholders refer to a widget ID, a sidebar name, and another sidebar name (e.g. "42", "Footer Area 1", "Footer Area 2")
495
+ $message = _x( '%3$s widget moved from "%4$s" to "%5$s"', '3: Widget ID, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
496
+ }
497
+
498
+ $message = sprintf( $message, $name, $title, $widget_id, $old_sidebar_name, $new_sidebar_name );
499
+ $sidebar_id = $new_sidebar_id;
500
+
501
+ $this->log(
502
+ $message,
503
+ compact( 'widget_id', 'sidebar_id', 'old_sidebar_id' ),
504
+ null,
505
+ $sidebar_id,
506
+ 'moved'
507
+ );
508
+ }
509
+ }
510
+
511
+ }
512
+
513
+ /**
514
+ * Track changes to widgets
515
+ *
516
+ * @action updated_option
517
+ *
518
+ * @param string $option_name
519
+ * @param array $old_value
520
+ * @param array $new_value
521
+ */
522
+ public function callback_updated_option( $option_name, $old_value, $new_value ) {
523
+ if ( ! preg_match( '/^widget_(.+)$/', $option_name, $matches ) || ! is_array( $new_value ) ) {
524
+ return;
525
+ }
526
+
527
+ $is_multi = ! empty( $new_value['_multiwidget'] );
528
+ $widget_id_base = $matches[1];
529
+
530
+ $creates = array();
531
+ $updates = array();
532
+ $deletes = array();
533
+
534
+ if ( $is_multi ) {
535
+ $widget_id_format = "$widget_id_base-%d";
536
+
537
+ unset( $new_value['_multiwidget'] );
538
+ unset( $old_value['_multiwidget'] );
539
+
540
+ /**
541
+ * Created widgets
542
+ */
543
+ $created_widget_numbers = array_diff( array_keys( $new_value ), array_keys( $old_value ) );
544
+
545
+ foreach ( $created_widget_numbers as $widget_number ) {
546
+ $instance = $new_value[ $widget_number ];
547
+ $widget_id = sprintf( $widget_id_format, $widget_number );
548
+ $name = $this->get_widget_name( $widget_id );
549
+ $title = ! empty( $instance['title'] ) ? $instance['title'] : null;
550
+ $sidebar_id = $this->get_widget_sidebar_id( $widget_id ); // @todo May not be assigned yet
551
+
552
+ $creates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
553
+ }
554
+
555
+ /**
556
+ * Updated widgets
557
+ */
558
+ $updated_widget_numbers = array_intersect( array_keys( $old_value ), array_keys( $new_value ) );
559
+
560
+ foreach ( $updated_widget_numbers as $widget_number ) {
561
+ $new_instance = $new_value[ $widget_number ];
562
+ $old_instance = $old_value[ $widget_number ];
563
+
564
+ if ( $old_instance !== $new_instance ) {
565
+ $widget_id = sprintf( $widget_id_format, $widget_number );
566
+ $name = $this->get_widget_name( $widget_id );
567
+ $title = ! empty( $new_instance['title'] ) ? $new_instance['title'] : null;
568
+ $sidebar_id = $this->get_widget_sidebar_id( $widget_id );
569
+ $labels = $this->get_context_labels();
570
+ $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
571
+
572
+ $updates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'old_instance', 'sidebar_name' );
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Deleted widgets
578
+ */
579
+ $deleted_widget_numbers = array_diff( array_keys( $old_value ), array_keys( $new_value ) );
580
+
581
+ foreach ( $deleted_widget_numbers as $widget_number ) {
582
+ $instance = $old_value[ $widget_number ];
583
+ $widget_id = sprintf( $widget_id_format, $widget_number );
584
+ $name = $this->get_widget_name( $widget_id );
585
+ $title = ! empty( $instance['title'] ) ? $instance['title'] : null;
586
+ $sidebar_id = $this->get_widget_sidebar_id( $widget_id ); // @todo May not be assigned anymore
587
+
588
+ $deletes[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
589
+ }
590
+ } else {
591
+ // Doing our best guess for tracking changes to old single widgets, assuming their options start with 'widget_'
592
+ $widget_id = $widget_id_base;
593
+ $name = $widget_id; // There aren't names available for single widgets
594
+ $title = ! empty( $new_value['title'] ) ? $new_value['title'] : null;
595
+ $sidebar_id = $this->get_widget_sidebar_id( $widget_id );
596
+ $old_instance = $old_value;
597
+ $labels = $this->get_context_labels();
598
+ $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
599
+
600
+ $updates[] = compact( 'widget_id', 'title', 'name', 'sidebar_id', 'old_instance', 'sidebar_name' );
601
+ }
602
+
603
+ /**
604
+ * Log updated actions
605
+ */
606
+ foreach ( $updates as $update ) {
607
+ if ( $update['name'] && $update['title'] ) {
608
+ // translators: Placeholders refer to a widget name, a widget title, and a sidebar name (e.g. "Archives", "Browse", "Footer Area 1")
609
+ $message = _x( '%1$s widget named "%2$s" in "%3$s" updated', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
610
+ } elseif ( $update['name'] ) {
611
+ // Empty title, but we have the name
612
+ // translators: Placeholders refer to a widget name, and a sidebar name (e.g. "Archives", "Footer Area 1")
613
+ $message = _x( '%1$s widget in "%3$s" updated', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
614
+ } elseif ( $update['title'] ) {
615
+ // Likely a single widget since no name is available
616
+ // translators: Placeholders refer to a widget title, and a sidebar name (e.g. "Browse", "Footer Area 1")
617
+ $message = _x( 'Unknown widget type named "%2$s" in "%3$s" updated', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
618
+ } else {
619
+ // Neither a name nor a title are available, so use the widget ID
620
+ // translators: Placeholders refer to a widget ID, and a sidebar name (e.g. "42", "Footer Area 1")
621
+ $message = _x( '%4$s widget in "%3$s" updated', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
622
+ }
623
+
624
+ $message = sprintf( $message, $update['name'], $update['title'], $update['sidebar_name'], $update['widget_id'] );
625
+
626
+ unset( $update['title'], $update['name'] );
627
+
628
+ $this->log(
629
+ $message,
630
+ $update,
631
+ null,
632
+ $update['sidebar_id'],
633
+ 'updated'
634
+ );
635
+ }
636
+
637
+ /**
638
+ * In the normal case, widgets are never created or deleted in a vacuum.
639
+ * Created widgets are immediately assigned to a sidebar, and deleted
640
+ * widgets are immediately removed from their assigned sidebar. If,
641
+ * however, widget instances get manipulated programmatically, it is
642
+ * possible that they could be orphaned, in which case the following
643
+ * actions would be useful to log.
644
+ */
645
+ if ( $this->verbose_widget_created_deleted_actions ) {
646
+ // We should only do these if not captured by an update to the sidebars_widgets option
647
+ /**
648
+ * Log created actions
649
+ */
650
+ foreach ( $creates as $create ) {
651
+ if ( $create['name'] && $create['title'] ) {
652
+ // translators: Placeholders refer to a widget name, and a widget title (e.g. "Archives", "Browse")
653
+ $message = _x( '%1$s widget named "%2$s" created', '1: Name, 2: Title', 'mainwp-child-reports' );
654
+ } elseif ( $create['name'] ) {
655
+ // Empty title, but we have the name
656
+ // translators: Placeholder refers to a widget name (e.g. "Archives")
657
+ $message = _x( '%1$s widget created', '1: Name', 'mainwp-child-reports' );
658
+ } elseif ( $create['title'] ) {
659
+ // Likely a single widget since no name is available
660
+ // translators: Placeholder refers to a widget title (e.g. "Browse")
661
+ $message = _x( 'Unknown widget type named "%2$s" created', '2: Title', 'mainwp-child-reports' );
662
+ } else {
663
+ // Neither a name nor a title are available, so use the widget ID
664
+ // translators: Placeholder refers to a widget ID (e.g. "42")
665
+ $message = _x( '%3$s widget created', '3: Widget ID', 'mainwp-child-reports' );
666
+ }
667
+
668
+ $message = sprintf( $message, $create['name'], $create['title'], $create['widget_id'] );
669
+
670
+ unset( $create['title'], $create['name'] );
671
+
672
+ $this->log(
673
+ $message,
674
+ $create,
675
+ null,
676
+ $create['sidebar_id'],
677
+ 'created'
678
+ );
679
+ }
680
+
681
+ /**
682
+ * Log deleted actions
683
+ */
684
+ foreach ( $deletes as $delete ) {
685
+ if ( $delete['name'] && $delete['title'] ) {
686
+ // translators: Placeholders refer to a widget name, and a widget title (e.g. "Archives", "Browse")
687
+ $message = _x( '%1$s widget named "%2$s" deleted', '1: Name, 2: Title', 'mainwp-child-reports' );
688
+ } elseif ( $delete['name'] ) {
689
+ // Empty title, but we have the name
690
+ // translators: Placeholder refers to a widget name (e.g. "Archives")
691
+ $message = _x( '%1$s widget deleted', '1: Name', 'mainwp-child-reports' );
692
+ } elseif ( $delete['title'] ) {
693
+ // Likely a single widget since no name is available
694
+ // translators: Placeholder refers to a widget title (e.g. "Browse")
695
+ $message = _x( 'Unknown widget type named "%2$s" deleted', '2: Title', 'mainwp-child-reports' );
696
+ } else {
697
+ // Neither a name nor a title are available, so use the widget ID
698
+ // translators: Placeholder refers to a widget ID (e.g. "42")
699
+ $message = _x( '%3$s widget deleted', '3: Widget ID', 'mainwp-child-reports' );
700
+ }
701
+
702
+ $message = sprintf( $message, $delete['name'], $delete['title'], $delete['widget_id'] );
703
+
704
+ unset( $delete['title'], $delete['name'] );
705
+
706
+ $this->log(
707
+ $message,
708
+ $delete,
709
+ null,
710
+ $delete['sidebar_id'],
711
+ 'deleted'
712
+ );
713
+ }
714
+ }
715
+ }
716
+
717
+ /**
718
+ * @param string $widget_id
719
+ *
720
+ * @return string
721
+ */
722
+ public function get_widget_title( $widget_id ) {
723
+ $instance = $this->get_widget_instance( $widget_id );
724
+ return ! empty( $instance['title'] ) ? $instance['title'] : null;
725
+ }
726
+
727
+ /**
728
+ * @param string $widget_id
729
+ *
730
+ * @return string|null
731
+ */
732
+ public function get_widget_name( $widget_id ) {
733
+ $widget_obj = $this->get_widget_object( $widget_id );
734
+ return $widget_obj ? $widget_obj->name : null;
735
+ }
736
+
737
+ /**
738
+ * @param $widget_id
739
+ *
740
+ * @return array|null
741
+ */
742
+ public function parse_widget_id( $widget_id ) {
743
+ if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
744
+ return array(
745
+ 'id_base' => $matches[1],
746
+ 'widget_number' => intval( $matches[2] ),
747
+ );
748
+ } else {
749
+ return null;
750
+ }
751
+ }
752
+
753
+ /**
754
+ * @param string $widget_id
755
+ *
756
+ * @return \WP_Widget|null
757
+ */
758
+ public function get_widget_object( $widget_id ) {
759
+ global $wp_widget_factory;
760
+
761
+ $parsed_widget_id = $this->parse_widget_id( $widget_id );
762
+
763
+ if ( ! $parsed_widget_id ) {
764
+ return null;
765
+ }
766
+
767
+ $id_base = $parsed_widget_id['id_base'];
768
+
769
+ $id_base_to_widget_class_map = array_combine(
770
+ wp_list_pluck( $wp_widget_factory->widgets, 'id_base' ),
771
+ array_keys( $wp_widget_factory->widgets )
772
+ );
773
+
774
+ if ( ! isset( $id_base_to_widget_class_map[ $id_base ] ) ) {
775
+ return null;
776
+ }
777
+
778
+ return $wp_widget_factory->widgets[ $id_base_to_widget_class_map[ $id_base ] ];
779
+ }
780
+
781
+ /**
782
+ * Returns widget instance settings
783
+ *
784
+ * @param string $widget_id Widget ID, ex: pages-1
785
+ *
786
+ * @return array|null Widget instance
787
+ */
788
+ public function get_widget_instance( $widget_id ) {
789
+ $instance = null;
790
+ $parsed_widget_id = $this->parse_widget_id( $widget_id );
791
+ $widget_obj = $this->get_widget_object( $widget_id );
792
+
793
+ if ( $widget_obj && $parsed_widget_id ) {
794
+ $settings = $widget_obj->get_settings();
795
+ $multi_number = $parsed_widget_id['widget_number'];
796
+
797
+ if ( isset( $settings[ $multi_number ] ) && ! empty( $settings[ $multi_number ]['title'] ) ) {
798
+ $instance = $settings[ $multi_number ];
799
+ }
800
+ } else {
801
+ // Single widgets, try our best guess at the option used
802
+ $potential_instance = get_option( "widget_{$widget_id}" );
803
+
804
+ if ( ! empty( $potential_instance ) && ! empty( $potential_instance['title'] ) ) {
805
+ $instance = $potential_instance;
806
+ }
807
+ }
808
+
809
+ return $instance;
810
+ }
811
+
812
+ /**
813
+ * Get global sidebars widgets
814
+ *
815
+ * @return array
816
+ */
817
+ public function get_sidebars_widgets() {
818
+ /**
819
+ * Filter allows for insertion of sidebar widgets
820
+ * @todo Do we need this filter?
821
+ *
822
+ * @param array Sidebar Widgets in Options table
823
+ * @param array Inserted Sidebar Widgets
824
+ * @return array Array of updated Sidebar Widgets
825
+ */
826
+ return apply_filters( 'sidebars_widgets', get_option( 'sidebars_widgets', array() ) );
827
+ }
828
+
829
+ /**
830
+ * Return the sidebar of a certain widget, based on widget_id
831
+ *
832
+ * @param string $widget_id Widget id, ex: pages-1
833
+ *
834
+ * @return string Sidebar id
835
+ */
836
+ public function get_widget_sidebar_id( $widget_id ) {
837
+ $sidebars_widgets = $this->get_sidebars_widgets();
838
+
839
+ unset( $sidebars_widgets['array_version'] );
840
+
841
+ foreach ( $sidebars_widgets as $sidebar_id => $widget_ids ) {
842
+ if ( in_array( $widget_id, $widget_ids, true ) ) {
843
+ return $sidebar_id;
844
+ }
845
+ }
846
+
847
+ return 'orphaned_widgets';
848
+ }
849
+ }
connectors/class-connector-woocommerce.php ADDED
@@ -0,0 +1,841 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_Woocommerce extends Connector {
5
+ /**
6
+ * Context name
7
+ * @var string
8
+ */
9
+ public $name = 'woocommerce';
10
+
11
+ /**
12
+ * Holds tracked plugin minimum version required
13
+ *
14
+ * @const string
15
+ */
16
+ const PLUGIN_MIN_VERSION = '2.1.10';
17
+
18
+ /**
19
+ * Actions registered for this context
20
+ * @var array
21
+ */
22
+ public $actions = array(
23
+ 'wp_mainwp_stream_record_array',
24
+ 'updated_option',
25
+ 'transition_post_status',
26
+ 'deleted_post',
27
+ 'woocommerce_order_status_changed',
28
+ 'woocommerce_attribute_added',
29
+ 'woocommerce_attribute_updated',
30
+ 'woocommerce_attribute_deleted',
31
+ 'woocommerce_tax_rate_added',
32
+ 'woocommerce_tax_rate_updated',
33
+ 'woocommerce_tax_rate_deleted',
34
+ );
35
+
36
+ public $taxonomies = array(
37
+ 'product_type',
38
+ 'product_cat',
39
+ 'product_tag',
40
+ 'product_shipping_class',
41
+ 'shop_order_status',
42
+ );
43
+
44
+ public $post_types = array(
45
+ 'product',
46
+ 'product_variation',
47
+ 'shop_order',
48
+ 'shop_coupon',
49
+ );
50
+
51
+ private $order_update_logged = false;
52
+
53
+ private $settings_pages = array();
54
+
55
+ private $settings = array();
56
+
57
+ private $plugin_version = null;
58
+
59
+ public function register() {
60
+ parent::register();
61
+
62
+ add_filter( 'wp_mainwp_stream_posts_exclude_post_types', array( $this, 'exclude_order_post_types' ) );
63
+ add_action( 'wp_mainwp_stream_comments_exclude_comment_types', array( $this, 'exclude_order_comment_types' ) );
64
+
65
+ $this->get_woocommerce_settings_fields();
66
+ }
67
+
68
+ /**
69
+ * Check if plugin dependencies are satisfied and add an admin notice if not
70
+ *
71
+ * @return bool
72
+ */
73
+ public function is_dependency_satisfied() {
74
+ global $woocommerce;
75
+
76
+ if ( class_exists( 'WooCommerce' ) && version_compare( $woocommerce->version, self::PLUGIN_MIN_VERSION, '>=' ) ) {
77
+ $this->plugin_version = $woocommerce->version;
78
+ return true;
79
+ }
80
+
81
+ return false;
82
+ }
83
+
84
+ /**
85
+ * Return translated context label
86
+ *
87
+ * @return string Translated context label
88
+ */
89
+ public function get_label() {
90
+ return esc_html_x( 'WooCommerce', 'woocommerce', 'mainwp-child-reports' );
91
+ }
92
+
93
+ /**
94
+ * Return translated action labels
95
+ *
96
+ * @return array Action label translations
97
+ */
98
+ public function get_action_labels() {
99
+ return array(
100
+ 'updated' => esc_html_x( 'Updated', 'woocommerce', 'mainwp-child-reports' ),
101
+ 'created' => esc_html_x( 'Created', 'woocommerce', 'mainwp-child-reports' ),
102
+ 'trashed' => esc_html_x( 'Trashed', 'woocommerce', 'mainwp-child-reports' ),
103
+ 'deleted' => esc_html_x( 'Deleted', 'woocommerce', 'mainwp-child-reports' ),
104
+ );
105
+ }
106
+
107
+ /**
108
+ * Return translated context labels
109
+ *
110
+ * @return array Context label translations
111
+ */
112
+ public function get_context_labels() {
113
+ $context_labels = array();
114
+
115
+ if ( class_exists( 'Connector_Posts' ) ) {
116
+ $posts_connector = new Connector_Posts();
117
+ $context_labels = array_merge(
118
+ $context_labels,
119
+ $posts_connector->get_context_labels()
120
+ );
121
+ }
122
+
123
+ $custom_context_labels = array(
124
+ 'attributes' => esc_html_x( 'Attributes', 'woocommerce', 'mainwp-child-reports' ),
125
+ );
126
+
127
+ $context_labels = array_merge(
128
+ $context_labels,
129
+ $custom_context_labels,
130
+ $this->settings_pages
131
+ );
132
+
133
+ return apply_filters( 'wp_mainwp_stream_woocommerce_contexts', $context_labels );
134
+ }
135
+
136
+ /**
137
+ * Return settings used by WooCommerce that aren't registered
138
+ *
139
+ * @return array Custom settings with translated title and page
140
+ */
141
+ public function get_custom_settings() {
142
+ $custom_settings = array(
143
+ 'woocommerce_frontend_css_colors' => array(
144
+ 'title' => esc_html__( 'Frontend Styles', 'mainwp-child-reports' ),
145
+ 'page' => 'wc-settings',
146
+ 'tab' => 'general',
147
+ 'section' => '',
148
+ 'type' => esc_html__( 'setting', 'mainwp-child-reports' ),
149
+ ),
150
+ 'woocommerce_default_gateway' => array(
151
+ 'title' => esc_html__( 'Gateway Display Default', 'mainwp-child-reports' ),
152
+ 'page' => 'wc-settings',
153
+ 'tab' => 'checkout',
154
+ 'section' => '',
155
+ 'type' => esc_html__( 'setting', 'mainwp-child-reports' ),
156
+ ),
157
+ 'woocommerce_gateway_order' => array(
158
+ 'title' => esc_html__( 'Gateway Display Order', 'mainwp-child-reports' ),
159
+ 'page' => 'wc-settings',
160
+ 'tab' => 'checkout',
161
+ 'section' => '',
162
+ 'type' => esc_html__( 'setting', 'mainwp-child-reports' ),
163
+ ),
164
+ 'woocommerce_default_shipping_method' => array(
165
+ 'title' => esc_html__( 'Shipping Methods Default', 'mainwp-child-reports' ),
166
+ 'page' => 'wc-settings',
167
+ 'tab' => 'shipping',
168
+ 'section' => '',
169
+ 'type' => esc_html__( 'setting', 'mainwp-child-reports' ),
170
+ ),
171
+ 'woocommerce_shipping_method_order' => array(
172
+ 'title' => esc_html__( 'Shipping Methods Order', 'mainwp-child-reports' ),
173
+ 'page' => 'wc-settings',
174
+ 'tab' => 'shipping',
175
+ 'section' => '',
176
+ 'type' => esc_html__( 'setting', 'mainwp-child-reports' ),
177
+ ),
178
+ 'shipping_debug_mode' => array(
179
+ 'title' => esc_html__( 'Shipping Debug Mode', 'mainwp-child-reports' ),
180
+ 'page' => 'wc-status',
181
+ 'tab' => 'tools',
182
+ 'section' => '',
183
+ 'type' => esc_html__( 'tool', 'mainwp-child-reports' ),
184
+ ),
185
+ 'template_debug_mode' => array(
186
+ 'title' => esc_html__( 'Template Debug Mode', 'mainwp-child-reports' ),
187
+ 'page' => 'wc-status',
188
+ 'tab' => 'tools',
189
+ 'section' => '',
190
+ 'type' => esc_html__( 'tool', 'mainwp-child-reports' ),
191
+ ),
192
+ 'uninstall_data' => array(
193
+ 'title' => esc_html__( 'Remove post types on uninstall', 'mainwp-child-reports' ),
194
+ 'page' => 'wc-status',
195
+ 'tab' => 'tools',
196
+ 'section' => '',
197
+ 'type' => esc_html__( 'tool', 'mainwp-child-reports' ),
198
+ ),
199
+ );
200
+
201
+ return apply_filters( 'wp_mainwp_stream_woocommerce_custom_settings', $custom_settings );
202
+ }
203
+
204
+ /**
205
+ * Add action links to Stream drop row in admin list screen
206
+ *
207
+ * @filter wp_mainwp_stream_action_links_{connector}
208
+ *
209
+ * @param array $links Previous links registered
210
+ * @param Record $record Stream record
211
+ *
212
+ * @return array Action links
213
+ */
214
+ public function action_links( $links, $record ) {
215
+ if ( in_array( $record->context, $this->post_types, true ) && get_post( $record->object_id ) ) {
216
+ $edit_post_link = get_edit_post_link( $record->object_id );
217
+ if ( $edit_post_link ) {
218
+ $posts_connector = new Connector_Posts();
219
+ $post_type_name = $posts_connector->get_post_type_name( get_post_type( $record->object_id ) );
220
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
221
+ $links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $edit_post_link;
222
+ }
223
+
224
+ $permalink = get_permalink( $record->object_id );
225
+ if ( post_type_exists( get_post_type( $record->object_id ) ) && $permalink ) {
226
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = $permalink;
227
+ }
228
+ }
229
+
230
+ $context_labels = $this->get_context_labels();
231
+ $option_key = $record->get_meta( 'option', true );
232
+ $option_page = $record->get_meta( 'page', true );
233
+ $option_tab = $record->get_meta( 'tab', true );
234
+ $option_section = $record->get_meta( 'section', true );
235
+
236
+ if ( $option_key && $option_tab ) {
237
+ // translators: Placeholder refers to a context (e.g. "Attribute")
238
+ $text = sprintf( esc_html__( 'Edit WooCommerce %s', 'mainwp-child-reports' ), $context_labels[ $record->context ] );
239
+ $url = add_query_arg(
240
+ array(
241
+ 'page' => $option_page,
242
+ 'tab' => $option_tab,
243
+ 'section' => $option_section,
244
+ ),
245
+ admin_url( 'admin.php' ) // Not self_admin_url here, as WooCommerce doesn't exist in Network Admin
246
+ );
247
+
248
+ $links[ $text ] = $url . '#wp-mainwp-stream-highlight:' . $option_key;
249
+ }
250
+
251
+ return $links;
252
+ }
253
+
254
+ /**
255
+ * Prevent the Stream Posts connector from logging orders
256
+ * so that we can handle them differently here
257
+ *
258
+ * @filter wp_mainwp_stream_posts_exclude_post_types
259
+ *
260
+ * @param array $post_types Ignored post types
261
+ *
262
+ * @return array Filtered post types
263
+ */
264
+ public function exclude_order_post_types( $post_types ) {
265
+ $post_types[] = 'shop_order';
266
+
267
+ return $post_types;
268
+ }
269
+
270
+ /**
271
+ * Prevent the Stream Comments connector from logging status
272
+ * change comments on orders
273
+ *
274
+ * @filter wp_mainwp_stream_commnent_exclude_comment_types
275
+ *
276
+ * @param array $comment_types Ignored post types
277
+ *
278
+ * @return array Filtered post types
279
+ */
280
+ public function exclude_order_comment_types( $comment_types ) {
281
+ $comment_types[] = 'order_note';
282
+
283
+ return $comment_types;
284
+ }
285
+
286
+ /**
287
+ * Log Order major status changes ( creating / updating / trashing )
288
+ *
289
+ * @action transition_post_status
290
+ *
291
+ * @param string $new
292
+ * @param string $old
293
+ * @param \WP_Post $post
294
+ */
295
+ public function callback_transition_post_status( $new, $old, $post ) {
296
+ // Only track orders
297
+ if ( 'shop_order' !== $post->post_type ) {
298
+ return;
299
+ }
300
+
301
+ // Don't track customer actions
302
+ if ( ! is_admin() ) {
303
+ return;
304
+ }
305
+
306
+ // Don't track minor status change actions
307
+ if ( in_array( wp_mainwp_stream_filter_input( INPUT_GET, 'action' ), array( 'mark_processing', 'mark_on-hold', 'mark_completed' ), true ) || defined( 'DOING_AJAX' ) ) {
308
+ return;
309
+ }
310
+
311
+ // Don't log updates when more than one happens at the same time
312
+ if ( $post->ID === $this->order_update_logged ) {
313
+ return;
314
+ }
315
+
316
+ if ( in_array( $new, array( 'auto-draft', 'draft', 'inherit' ), true ) ) {
317
+ return;
318
+ } elseif ( 'auto-draft' === $old && 'publish' === $new ) {
319
+ // translators: Placeholder refers to an order title (e.g. "Order #42")
320
+ $message = esc_html_x(
321
+ '%s created',
322
+ 'Order title',
323
+ 'mainwp-child-reports'
324
+ );
325
+ $action = 'created';
326
+ } elseif ( 'trash' === $new ) {
327
+ // translators: Placeholder refers to an order title (e.g. "Order #42")
328
+ $message = esc_html_x(
329
+ '%s trashed',
330
+ 'Order title',
331
+ 'mainwp-child-reports'
332
+ );
333
+ $action = 'trashed';
334
+ } elseif ( 'trash' === $old && 'publish' === $new ) {
335
+ // translators: Placeholder refers to an order title (e.g. "Order #42")
336
+ $message = esc_html_x(
337
+ '%s restored from the trash',
338
+ 'Order title',
339
+ 'mainwp-child-reports'
340
+ );
341
+ $action = 'untrashed';
342
+ } else {
343
+ // translators: Placeholder refers to an order title (e.g. "Order #42")
344
+ $message = esc_html_x(
345
+ '%s updated',
346
+ 'Order title',
347
+ 'mainwp-child-reports'
348
+ );
349
+ }
350
+
351
+ if ( empty( $action ) ) {
352
+ $action = 'updated';
353
+ }
354
+
355
+ $order = new \WC_Order( $post->ID );
356
+ $order_title = esc_html__( 'Order number', 'mainwp-child-reports' ) . ' ' . esc_html( $order->get_order_number() );
357
+ $order_type_name = esc_html__( 'order', 'mainwp-child-reports' );
358
+
359
+ $this->log(
360
+ $message,
361
+ array(
362
+ 'post_title' => $order_title,
363
+ 'singular_name' => $order_type_name,
364
+ 'new_status' => $new,
365
+ 'old_status' => $old,
366
+ 'revision_id' => null,
367
+ ),
368
+ $post->ID,
369
+ $post->post_type,
370
+ $action
371
+ );
372
+
373
+ $this->order_update_logged = $post->ID;
374
+ }
375
+
376
+ /**
377
+ * Log order deletion
378
+ *
379
+ * @action deleted_post
380
+ *
381
+ * @param int $post_id
382
+ */
383
+ public function callback_deleted_post( $post_id ) {
384
+ $post = get_post( $post_id );
385
+
386
+ // We check if post is an instance of WP_Post as it doesn't always resolve in unit testing
387
+ if ( ! ( $post instanceof \WP_Post ) || 'shop_order' !== $post->post_type ) {
388
+ return;
389
+ }
390
+
391
+ // Ignore auto-drafts that are deleted by the system, see issue-293
392
+ if ( 'auto-draft' === $post->post_status ) {
393
+ return;
394
+ }
395
+
396
+ $order = new \WC_Order( $post->ID );
397
+ $order_title = esc_html__( 'Order number', 'mainwp-child-reports' ) . ' ' . esc_html( $order->get_order_number() );
398
+ $order_type_name = esc_html__( 'order', 'mainwp-child-reports' );
399
+
400
+ $this->log(
401
+ // translators: Placeholder refers to an order title (e.g. "Order #42")
402
+ _x(
403
+ '"%s" deleted from trash',
404
+ 'Order title',
405
+ 'mainwp-child-reports'
406
+ ),
407
+ array(
408
+ 'post_title' => $order_title,
409
+ 'singular_name' => $order_type_name,
410
+ ),
411
+ $post->ID,
412
+ $post->post_type,
413
+ 'deleted'
414
+ );
415
+ }
416
+
417
+ /**
418
+ * Log Order minor status changes ( pending / on-hold / failed / processing / completed / refunded / cancelled )
419
+ *
420
+ * @action woocommerce_order_status_changed
421
+ *
422
+ * @param int $order_id
423
+ * @param string $old
424
+ * @param string $new
425
+ */
426
+ public function callback_woocommerce_order_status_changed( $order_id, $old, $new ) {
427
+ // Don't track customer actions
428
+ if ( ! is_admin() ) {
429
+ return;
430
+ }
431
+
432
+ // Don't track new statuses
433
+ if ( empty( $old ) ) {
434
+ return;
435
+ }
436
+
437
+ if ( version_compare( $this->plugin_version, '2.2', '>=' ) ) {
438
+ $old_status_name = wc_get_order_status_name( $old );
439
+ $new_status_name = wc_get_order_status_name( $new );
440
+ } else {
441
+ $old_status = wp_mainwp_stream_is_vip() ? wpcom_vip_get_term_by( 'slug', $old, 'shop_order_status' ) : get_term_by( 'slug', $old, 'shop_order_status' );
442
+ $new_status = wp_mainwp_stream_is_vip() ? wpcom_vip_get_term_by( 'slug', $new, 'shop_order_status' ) : get_term_by( 'slug', $new, 'shop_order_status' );
443
+ $new_status_name = $new_status->name;
444
+ $old_status_name = $old_status->name;
445
+ }
446
+
447
+ // translators: Placeholders refer to an order title, and order status, and another order status (e.g. "Order #42", "processing", "complete")
448
+ $message = esc_html_x(
449
+ '%1$s status changed from %2$s to %3$s',
450
+ '1. Order title, 2. Old status, 3. New status',
451
+ 'mainwp-child-reports'
452
+ );
453
+
454
+ $order = new \WC_Order( $order_id );
455
+ $order_title = esc_html__( 'Order number', 'mainwp-child-reports' ) . ' ' . esc_html( $order->get_order_number() );
456
+ $order_type_name = esc_html__( 'order', 'mainwp-child-reports' );
457
+
458
+ $this->log(
459
+ $message,
460
+ array(
461
+ 'post_title' => $order_title,
462
+ 'old_status_name' => $old_status_name,
463
+ 'new_status_name' => $new_status_name,
464
+ 'singular_name' => $order_type_name,
465
+ 'new_status' => $new,
466
+ 'old_status' => $old,
467
+ 'revision_id' => null,
468
+ ),
469
+ $order_id,
470
+ 'shop_order',
471
+ 'updated'
472
+ );
473
+ }
474
+
475
+ /**
476
+ * Log adding a product attribute
477
+ *
478
+ * @action woocommerce_attribute_added
479
+ *
480
+ * @param int $attribute_id
481
+ * @param array $attribute
482
+ */
483
+ public function callback_woocommerce_attribute_added( $attribute_id, $attribute ) {
484
+ $this->log(
485
+ // translators: Placeholder refers to a term name (e.g. "color")
486
+ _x(
487
+ '"%s" product attribute created',
488
+ 'Term name',
489
+ 'mainwp-child-reports'
490
+ ),
491
+ $attribute,
492
+ $attribute_id,
493
+ 'attributes',
494
+ 'created'
495
+ );
496
+ }
497
+
498
+ /**
499
+ * Log updating a product attribute
500
+ *
501
+ * @action woocommerce_attribute_updated
502
+ *
503
+ * @param int $attribute_id
504
+ * @param array $attribute
505
+ */
506
+ public function callback_woocommerce_attribute_updated( $attribute_id, $attribute ) {
507
+ $this->log(
508
+ // translators: Placeholder refers to a term name (e.g. "color")
509
+ _x(
510
+ '"%s" product attribute updated',
511
+ 'Term name',
512
+ 'mainwp-child-reports'
513
+ ),
514
+ $attribute,
515
+ $attribute_id,
516
+ 'attributes',
517
+ 'updated'
518
+ );
519
+ }
520
+
521
+ /**
522
+ * Log deleting a product attribute
523
+ *
524
+ * @action woocommerce_attribute_updated
525
+ *
526
+ * @param int $attribute_id
527
+ * @param string $attribute_name
528
+ */
529
+ public function callback_woocommerce_attribute_deleted( $attribute_id, $attribute_name ) {
530
+ $this->log(
531
+ // translators: Placeholder refers to a term name (e.g. "color")
532
+ _x(
533
+ '"%s" product attribute deleted',
534
+ 'Term name',
535
+ 'mainwp-child-reports'
536
+ ),
537
+ array(
538
+ 'attribute_name' => $attribute_name,
539
+ ),
540
+ $attribute_id,
541
+ 'attributes',
542
+ 'deleted'
543
+ );
544
+ }
545
+
546
+ /**
547
+ * Log adding a tax rate
548
+ *
549
+ * @action woocommerce_tax_rate_added
550
+ *
551
+ * @param int $tax_rate_id
552
+ * @param array $tax_rate
553
+ */
554
+ public function callback_woocommerce_tax_rate_added( $tax_rate_id, $tax_rate ) {
555
+ $this->log(
556
+ // translators: Placeholder refers to a tax rate name (e.g. "GST")
557
+ _x(
558
+ '"%4$s" tax rate created',
559
+ 'Tax rate name',
560
+ 'mainwp-child-reports'
561
+ ),
562
+ $tax_rate,
563
+ $tax_rate_id,
564
+ 'tax',
565
+ 'created'
566
+ );
567
+ }
568
+
569
+ /**
570
+ * Log updating a tax rate
571
+ *
572
+ * @action woocommerce_tax_rate_updated
573
+ *
574
+ * @param int $tax_rate_id
575
+ * @param array $tax_rate
576
+ */
577
+ public function callback_woocommerce_tax_rate_updated( $tax_rate_id, $tax_rate ) {
578
+ $this->log(
579
+ // translators: Placeholder refers to a tax rate name (e.g. "GST")
580
+ _x(
581
+ '"%4$s" tax rate updated',
582
+ 'Tax rate name',
583
+ 'mainwp-child-reports'
584
+ ),
585
+ $tax_rate,
586
+ $tax_rate_id,
587
+ 'tax',
588
+ 'updated'
589
+ );
590
+ }
591
+
592
+ /**
593
+ * Log deleting a tax rate
594
+ *
595
+ * @action woocommerce_tax_rate_updated
596
+ *
597
+ * @param int $tax_rate_id
598
+ */
599
+ public function callback_woocommerce_tax_rate_deleted( $tax_rate_id ) {
600
+ global $wpdb;
601
+
602
+ $tax_rate_name = $wpdb->get_var(
603
+ $wpdb->prepare(
604
+ "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates
605
+ WHERE tax_rate_id = %s
606
+ ",
607
+ $tax_rate_id
608
+ )
609
+ );
610
+
611
+ $this->log(
612
+ // translators: Placeholder refers to a tax rate name (e.g. "GST")
613
+ _x(
614
+ '"%s" tax rate deleted',
615
+ 'Tax rate name',
616
+ 'mainwp-child-reports'
617
+ ),
618
+ array(
619
+ 'tax_rate_name' => $tax_rate_name,
620
+ ),
621
+ $tax_rate_id,
622
+ 'tax',
623
+ 'deleted'
624
+ );
625
+ }
626
+
627
+ /**
628
+ * Filter records and take-over our precious data
629
+ *
630
+ * @filter wp_mainwp_stream_record_array
631
+ *
632
+ * @param array $recordarr Record data to be inserted
633
+ *
634
+ * @return array Filtered record data
635
+ */
636
+ public function callback_wp_mainwp_stream_record_array( $recordarr ) {
637
+ foreach ( $recordarr as $key => $record ) {
638
+ if ( ! isset( $record['connector'] ) || ! isset( $record['context'] ) ) {
639
+ continue;
640
+ }
641
+
642
+ // Change connector::posts records
643
+ if ( 'posts' === $record['connector'] && in_array( $record['context'], $this->post_types, true ) ) {
644
+ $recordarr[ $key ]['connector'] = $this->name;
645
+ } elseif ( 'taxonomies' === $record['connector'] && in_array( $record['context'], $this->taxonomies, true ) ) {
646
+ $recordarr[ $key ]['connector'] = $this->name;
647
+ } elseif ( 'settings' === $record['connector'] ) {
648
+ $option = isset( $record['meta']['option_key'] ) ? $record['meta']['option_key'] : false;
649
+
650
+ if ( $option && isset( $this->settings[ $option ] ) ) {
651
+ return false;
652
+ }
653
+ }
654
+ }
655
+
656
+ return $recordarr;
657
+ }
658
+
659
+ public function callback_updated_option( $option_key, $old_value, $value ) {
660
+ $options = array( $option_key );
661
+
662
+ if ( is_array( $old_value ) || is_array( $value ) ) {
663
+ foreach ( $this->get_changed_keys( $old_value, $value ) as $field_key ) {
664
+ $options[] = $field_key;
665
+ }
666
+ }
667
+
668
+ foreach ( $options as $option ) {
669
+ if ( ! array_key_exists( $option, $this->settings ) ) {
670
+ continue;
671
+ }
672
+
673
+ $this->log(
674
+ // translators: Placeholders refer to a setting name and a setting type (e.g. "Direct Deposit", "Payment Method")
675
+ __( '"%1$s" %2$s updated', 'mainwp-child-reports' ),
676
+ array(
677
+ 'label' => $this->settings[ $option ]['title'],
678
+ 'type' => $this->settings[ $option ]['type'],
679
+ 'page' => $this->settings[ $option ]['page'],
680
+ 'tab' => $this->settings[ $option ]['tab'],
681
+ 'section' => $this->settings[ $option ]['section'],
682
+ 'option' => $option,
683
+ 'old_value' => $old_value,
684
+ 'value' => $value,
685
+ ),
686
+ null,
687
+ $this->settings[ $option ]['tab'],
688
+ 'updated'
689
+ );
690
+ }
691
+ }
692
+
693
+ public function get_woocommerce_settings_fields() {
694
+ if ( ! defined( 'WC_VERSION' ) || ! class_exists( 'WC_Admin_Settings' ) ) {
695
+ return false;
696
+ }
697
+
698
+ if ( ! empty( $this->settings ) ) {
699
+ return $this->settings;
700
+ }
701
+
702
+ $settings_cache_key = 'stream_connector_woocommerce_settings_' . sanitize_key( WC_VERSION );
703
+ $settings_transient = get_transient( $settings_cache_key );
704
+
705
+ if ( $settings_transient ) {
706
+ $settings = $settings_transient['settings'];
707
+ $settings_pages = $settings_transient['settings_pages'];
708
+ } else {
709
+ global $woocommerce;
710
+
711
+ $settings = array();
712
+ $settings_pages = array();
713
+
714
+ foreach ( \WC_Admin_Settings::get_settings_pages() as $page ) {
715
+ // Get ID / Label of the page, since they're protected, by hacking into
716
+ // the callback filter for 'woocommerce_settings_tabs_array'
717
+ $info = $page->add_settings_page( array() );
718
+ $page_id = key( $info );
719
+ $page_label = current( $info );
720
+ $sections = $page->get_sections();
721
+
722
+ if ( empty( $sections ) ) {
723
+ $sections[''] = $page_label;
724
+ }
725
+
726
+ $settings_pages[ $page_id ] = $page_label;
727
+
728
+ // Remove non-fields ( sections, titles and whatever )
729
+ $fields = array();
730
+
731
+ foreach ( $sections as $section_key => $section_label ) {
732
+ $_fields = array_filter(
733
+ $page->get_settings( $section_key ),
734
+ function( $item ) {
735
+ return isset( $item['id'] ) && ( ! in_array( $item['type'], array( 'title', 'sectionend' ), true ) );
736
+ }
737
+ );
738
+
739
+ foreach ( $_fields as $field ) {
740
+ $title = isset( $field['title'] ) ? $field['title'] : ( isset( $field['desc'] ) ? $field['desc'] : 'N/A' );
741
+ $fields[ $field['id'] ] = array(
742
+ 'title' => $title,
743
+ 'page' => 'wc-settings',
744
+ 'tab' => $page_id,
745
+ 'section' => $section_key,
746
+ 'type' => esc_html__( 'setting', 'mainwp-child-reports' ),
747
+ );
748
+ }
749
+ }
750
+
751
+ // Store fields in the global array to be searched later
752
+ $settings = array_merge( $settings, $fields );
753
+ }
754
+
755
+ // Provide additional context for each of the settings pages
756
+ array_walk(
757
+ $settings_pages, function( &$value ) {
758
+ $value .= ' ' . esc_html__( 'Settings', 'mainwp-child-reports' );
759
+ }
760
+ );
761
+
762
+ // Load Payment Gateway Settings
763
+ $payment_gateway_settings = array();
764
+ $payment_gateways = $woocommerce->payment_gateways();
765
+
766
+ foreach ( $payment_gateways->payment_gateways as $section_key => $payment_gateway ) {
767
+ $title = $payment_gateway->title;
768
+ $key = $payment_gateway->plugin_id . $payment_gateway->id . '_settings';
769
+
770
+ $payment_gateway_settings[ $key ] = array(
771
+ 'title' => $title,
772
+ 'page' => 'wc-settings',
773
+ 'tab' => 'checkout',
774
+ 'section' => strtolower( $section_key ),
775
+ 'type' => esc_html__( 'payment gateway', 'mainwp-child-reports' ),
776
+ );
777
+ }
778
+
779
+ $settings = array_merge( $settings, $payment_gateway_settings );
780
+
781
+ // Load Shipping Method Settings
782
+ $shipping_method_settings = array();
783
+ $shipping_methods = $woocommerce->shipping();
784
+
785
+ foreach ( (array) $shipping_methods->shipping_methods as $section_key => $shipping_method ) {
786
+ $title = $shipping_method->title;
787
+ $key = $shipping_method->plugin_id . $shipping_method->id . '_settings';
788
+
789
+ $shipping_method_settings[ $key ] = array(
790
+ 'title' => $title,
791
+ 'page' => 'wc-settings',
792
+ 'tab' => 'shipping',
793
+ 'section' => strtolower( $section_key ),
794
+ 'type' => esc_html__( 'shipping method', 'mainwp-child-reports' ),
795
+ );
796
+ }
797
+
798
+ $settings = array_merge( $settings, $shipping_method_settings );
799
+
800
+ // Load Email Settings
801
+ $email_settings = array();
802
+ $emails = $woocommerce->mailer();
803
+
804
+ foreach ( $emails->emails as $section_key => $email ) {
805
+ $title = $email->title;
806
+ $key = $email->plugin_id . $email->id . '_settings';
807
+
808
+ $email_settings[ $key ] = array(
809
+ 'title' => $title,
810
+ 'page' => 'wc-settings',
811
+ 'tab' => 'email',
812
+ 'section' => strtolower( $section_key ),
813
+ 'type' => esc_html__( 'email', 'mainwp-child-reports' ),
814
+ );
815
+ }
816
+
817
+ $settings = array_merge( $settings, $email_settings );
818
+
819
+ // Tools page
820
+ $tools_page = array(
821
+ 'tools' => esc_html__( 'Tools', 'mainwp-child-reports' ),
822
+ );
823
+
824
+ $settings_pages = array_merge( $settings_pages, $tools_page );
825
+
826
+ // Cache the results
827
+ $settings_cache = array(
828
+ 'settings' => $settings,
829
+ 'settings_pages' => $settings_pages,
830
+ );
831
+
832
+ set_transient( $settings_cache_key, $settings_cache, MINUTE_IN_SECONDS * 60 * 6 );
833
+ }
834
+
835
+ $custom_settings = $this->get_custom_settings();
836
+ $this->settings = array_merge( $settings, $custom_settings );
837
+ $this->settings_pages = $settings_pages;
838
+
839
+ return $this->settings;
840
+ }
841
+ }
connectors/class-connector-wordpress-seo.php ADDED
@@ -0,0 +1,527 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Connector_WordPress_SEO extends Connector {
5
+
6
+ /**
7
+ * Connector slug
8
+ *
9
+ * @var string
10
+ */
11
+ public $name = 'wordpressseo';
12
+
13
+ /**
14
+ * Holds tracked plugin minimum version required
15
+ *
16
+ * @const string
17
+ */
18
+ const PLUGIN_MIN_VERSION = '1.5.3.3';
19
+
20
+ /**
21
+ * Actions registered for this connector
22
+ *
23
+ * @var array
24
+ */
25
+ public $actions = array(
26
+ 'wpseo_handle_import',
27
+ 'wpseo_import',
28
+ 'seo_page_wpseo_files',
29
+ 'added_post_meta',
30
+ 'updated_post_meta',
31
+ 'deleted_post_meta',
32
+ );
33
+
34
+ /**
35
+ * Tracking registered Settings, with overridden data
36
+ *
37
+ * @var array
38
+ */
39
+ public $option_groups = array();
40
+
41
+ /**
42
+ * Check if plugin dependencies are satisfied and add an admin notice if not
43
+ *
44
+ * @return bool
45
+ */
46
+ public function is_dependency_satisfied() {
47
+ if ( defined( 'WPSEO_VERSION' ) && version_compare( WPSEO_VERSION, self::PLUGIN_MIN_VERSION, '>=' ) ) {
48
+ return true;
49
+ }
50
+
51
+ return false;
52
+ }
53
+
54
+ /**
55
+ * Return translated connector label
56
+ *
57
+ * @return string Translated connector label
58
+ */
59
+ public function get_label() {
60
+ return esc_html_x( 'WordPress SEO', 'wordpress-seo', 'mainwp-child-reports' );
61
+ }
62
+
63
+ /**
64
+ * Return translated action labels
65
+ *
66
+ * @return array Action label translations
67
+ */
68
+ public function get_action_labels() {
69
+ return array(
70
+ 'created' => esc_html_x( 'Created', 'wordpress-seo', 'mainwp-child-reports' ),
71
+ 'updated' => esc_html_x( 'Updated', 'wordpress-seo', 'mainwp-child-reports' ),
72
+ 'added' => esc_html_x( 'Added', 'wordpress-seo', 'mainwp-child-reports' ),
73
+ 'deleted' => esc_html_x( 'Deleted', 'wordpress-seo', 'mainwp-child-reports' ),
74
+ 'exported' => esc_html_x( 'Exported', 'wordpress-seo', 'mainwp-child-reports' ),
75
+ 'imported' => esc_html_x( 'Imported', 'wordpress-seo', 'mainwp-child-reports' ),
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Return translated context labels
81
+ *
82
+ * @return array Context label translations
83
+ */
84
+ public function get_context_labels() {
85
+ return array(
86
+ 'wpseo_dashboard' => esc_html_x( 'Dashboard', 'wordpress-seo', 'mainwp-child-reports' ),
87
+ 'wpseo_titles' => _x( 'Titles &amp; Metas', 'wordpress-seo', 'mainwp-child-reports' ),
88
+ 'wpseo_social' => esc_html_x( 'Social', 'wordpress-seo', 'mainwp-child-reports' ),
89
+ 'wpseo_xml' => esc_html_x( 'XML Sitemaps', 'wordpress-seo', 'mainwp-child-reports' ),
90
+ 'wpseo_permalinks' => esc_html_x( 'Permalinks', 'wordpress-seo', 'mainwp-child-reports' ),
91
+ 'wpseo_internal-links' => esc_html_x( 'Internal Links', 'wordpress-seo', 'mainwp-child-reports' ),
92
+ 'wpseo_advanced' => esc_html_x( 'Advanced', 'wordpress-seo', 'mainwp-child-reports' ),
93
+ 'wpseo_rss' => esc_html_x( 'RSS', 'wordpress-seo', 'mainwp-child-reports' ),
94
+ 'wpseo_import' => esc_html_x( 'Import & Export', 'wordpress-seo', 'mainwp-child-reports' ),
95
+ 'wpseo_bulk-title-editor' => esc_html_x( 'Bulk Title Editor', 'wordpress-seo', 'mainwp-child-reports' ),
96
+ 'wpseo_bulk-description-editor' => esc_html_x( 'Bulk Description Editor', 'wordpress-seo', 'mainwp-child-reports' ),
97
+ 'wpseo_files' => esc_html_x( 'Files', 'wordpress-seo', 'mainwp-child-reports' ),
98
+ 'wpseo_meta' => esc_html_x( 'Content', 'wordpress-seo', 'mainwp-child-reports' ),
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Add action links to Stream drop row in admin list screen
104
+ *
105
+ * @filter wp_mainwp_stream_action_links_{connector}
106
+ *
107
+ * @param array $links Previous links registered
108
+ * @param Record $record Stream record
109
+ *
110
+ * @return array Action links
111
+ */
112
+ public function action_links( $links, $record ) {
113
+ // Options
114
+ $option = $record->get_meta( 'option', true );
115
+ if ( $option ) {
116
+ $key = $record->get_meta( 'option_key', true );
117
+
118
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = add_query_arg(
119
+ array(
120
+ 'page' => $record->context,
121
+ ),
122
+ admin_url( 'admin.php' )
123
+ ) . '#stream-highlight-' . esc_attr( $key );
124
+ } elseif ( 'wpseo_files' === $record->context ) {
125
+ $links[ esc_html__( 'Edit', 'mainwp-child-reports' ) ] = add_query_arg(
126
+ array(
127
+ 'page' => $record->context,
128
+ ),
129
+ admin_url( 'admin.php' )
130
+ );
131
+ } elseif ( 'wpseo_meta' === $record->context ) {
132
+ $post = get_post( $record->object_id );
133
+
134
+ if ( $post ) {
135
+ $posts_connector = new Connector_Posts();
136
+ $post_type_name = $posts_connector->get_post_type_name( get_post_type( $post->ID ) );
137
+
138
+ if ( 'trash' === $post->post_status ) {
139
+ $untrash = wp_nonce_url(
140
+ add_query_arg(
141
+ array(
142
+ 'action' => 'untrash',
143
+ 'post' => $post->ID,
144
+ ),
145
+ admin_url( 'post.php' )
146
+ ),
147
+ sprintf( 'untrash-post_%d', $post->ID )
148
+ );
149
+
150
+ $delete = wp_nonce_url(
151
+ add_query_arg(
152
+ array(
153
+ 'action' => 'delete',
154
+ 'post' => $post->ID,
155
+ ),
156
+ admin_url( 'post.php' )
157
+ ),
158
+ sprintf( 'delete-post_%d', $post->ID )
159
+ );
160
+
161
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
162
+ $links[ sprintf( esc_html_x( 'Restore %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $untrash;
163
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
164
+ $links[ sprintf( esc_html_x( 'Delete %s Permenantly', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $delete;
165
+ } else {
166
+ // translators: Placeholder refers to a post type singular name (e.g. "Post")
167
+ $links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = get_edit_post_link( $post->ID );
168
+
169
+ $view_link = get_permalink( $post->ID );
170
+ if ( $view_link ) {
171
+ $links[ esc_html__( 'View', 'mainwp-child-reports' ) ] = $view_link;
172
+ }
173
+
174
+ $revision_id = $record->get_meta( 'revision_id', true );
175
+ if ( $revision_id ) {
176
+ $links[ esc_html__( 'Revision', 'mainwp-child-reports' ) ] = get_edit_post_link( $revision_id );
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ return $links;
183
+ }
184
+
185
+ public function register() {
186
+ if ( is_network_admin() && ! is_plugin_active_for_network( 'wordpress-seo/wordpress-seo-main.php' ) ) {
187
+ return;
188
+ }
189
+ parent::register();
190
+
191
+ foreach ( \WPSEO_Options::$options as $class ) {
192
+ /* @var $class WPSEO_Options */
193
+ $this->option_groups[ $class::get_instance()->group_name ] = array(
194
+ 'class' => $class,
195
+ 'name' => $class::get_instance()->option_name,
196
+ );
197
+ }
198
+
199
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
200
+ add_filter( 'wp_mainwp_stream_log_data', array( $this, 'log_override' ) );
201
+ }
202
+
203
+ public function admin_enqueue_scripts( $hook ) {
204
+ if ( 0 === strpos( $hook, 'seo_page_' ) ) {
205
+ $stream = wp_mainwp_stream_get_instance();
206
+ $src = $stream->locations['url'] . '/ui/js/wpseo-admin.js';
207
+ wp_enqueue_script( 'stream-connector-wpseo', $src, array( 'jquery' ), $stream->get_version() );
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Track importing settings from other plugins
213
+ */
214
+ public function callback_wpseo_handle_import() {
215
+ $imports = array(
216
+ 'importheadspace' => esc_html__( 'HeadSpace2', 'mainwp-child-reports' ), # type = checkbox
217
+ 'importaioseo' => esc_html__( 'All-in-One SEO', 'mainwp-child-reports' ), # type = checkbox
218
+ 'importaioseoold' => esc_html__( 'OLD All-in-One SEO', 'mainwp-child-reports' ), # type = checkbox
219
+ 'importwoo' => esc_html__( 'WooThemes SEO framework', 'mainwp-child-reports' ), # type = checkbox
220
+ 'importrobotsmeta' => esc_html__( 'Robots Meta (by Yoast)', 'mainwp-child-reports' ), # type = checkbox
221
+ 'importrssfooter' => esc_html__( 'RSS Footer (by Yoast)', 'mainwp-child-reports' ), # type = checkbox
222
+ 'importbreadcrumbs' => esc_html__( 'Yoast Breadcrumbs', 'mainwp-child-reports' ), # type = checkbox
223
+ );
224
+
225
+ $opts = wp_mainwp_stream_filter_input( INPUT_POST, 'wpseo' );
226
+
227
+ foreach ( $imports as $key => $name ) {
228
+ if ( isset( $opts[ $key ] ) ) {
229
+ $this->log(
230
+ sprintf(
231
+ // translators: Placeholders refer to an import method, and an extra string (sometimes blank) (e.g. "HeadSpace2", ", and deleted old data")
232
+ __( 'Imported settings from %1$s%2$s', 'mainwp-child-reports' ),
233
+ $name,
234
+ isset( $opts['deleteolddata'] ) ? esc_html__( ', and deleted old data', 'mainwp-child-reports' ) : ''
235
+ ),
236
+ array(
237
+ 'key' => $key,
238
+ 'deleteolddata' => isset( $opts['deleteolddata'] ),
239
+ ),
240
+ null,
241
+ 'wpseo_import',
242
+ 'imported'
243
+ );
244
+ }
245
+ }
246
+ }
247
+
248
+ public function callback_wpseo_import() {
249
+ $opts = wp_mainwp_stream_filter_input( INPUT_POST, 'wpseo' );
250
+
251
+ if ( wp_mainwp_stream_filter_input( INPUT_POST, 'wpseo_export' ) ) {
252
+ $this->log(
253
+ sprintf(
254
+ // translators: Placeholder refers to an extra string (sometimes blank) (e.g. ", including taxonomy meta")
255
+ __( 'Exported settings%s', 'mainwp-child-reports' ),
256
+ isset( $opts['include_taxonomy_meta'] ) ? esc_html__( ', including taxonomy meta', 'mainwp-child-reports' ) : ''
257
+ ),
258
+ array(
259
+ 'include_taxonomy_meta' => isset( $opts['include_taxonomy_meta'] ),
260
+ ),
261
+ null,
262
+ 'wpseo_import',
263
+ 'exported'
264
+ );
265
+ } elseif ( isset( $_FILES['settings_import_file']['name'] ) ) { // phpcs: input var okay
266
+ $this->log(
267
+ sprintf(
268
+ // translators: Placeholder refers to a filename (e.g. "test.xml")
269
+ __( 'Tried importing settings from "%s"', 'mainwp-child-reports' ),
270
+ sanitize_text_field( wp_unslash( $_FILES['settings_import_file']['name'] ) ) // phpcs: input var okay
271
+ ),
272
+ array(
273
+ 'file' => sanitize_text_field( wp_unslash( $_FILES['settings_import_file']['name'] ) ), // phpcs: input var okay
274
+ ),
275
+ null,
276
+ 'wpseo_import',
277
+ 'exported'
278
+ );
279
+ }
280
+ }
281
+
282
+ public function callback_seo_page_wpseo_files() {
283
+ if ( wp_mainwp_stream_filter_input( INPUT_POST, 'create_robots' ) ) {
284
+ $message = esc_html__( 'Tried creating robots.txt file', 'mainwp-child-reports' );
285
+ } elseif ( wp_mainwp_stream_filter_input( INPUT_POST, 'submitrobots' ) ) {
286
+ $message = esc_html__( 'Tried updating robots.txt file', 'mainwp-child-reports' );
287
+ } elseif ( wp_mainwp_stream_filter_input( INPUT_POST, 'submithtaccess' ) ) {
288
+ $message = esc_html__( 'Tried updating htaccess file', 'mainwp-child-reports' );
289
+ }
290
+
291
+ if ( isset( $message ) ) {
292
+ $this->log(
293
+ $message,
294
+ array(),
295
+ null,
296
+ 'wpseo_files',
297
+ 'updated'
298
+ );
299
+ }
300
+ }
301
+
302
+ public function callback_added_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
303
+ unset( $meta_id );
304
+ $this->meta( $object_id, $meta_key, $meta_value );
305
+ }
306
+ public function callback_updated_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
307
+ unset( $meta_id );
308
+ $this->meta( $object_id, $meta_key, $meta_value );
309
+ }
310
+ public function callback_deleted_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
311
+ unset( $meta_id );
312
+ $this->meta( $object_id, $meta_key, $meta_value );
313
+ }
314
+
315
+ private function meta( $object_id, $meta_key, $meta_value ) {
316
+ $prefix = \WPSEO_Meta::$meta_prefix;
317
+
318
+ \WPSEO_Metabox::translate_meta_boxes();
319
+
320
+ if ( 0 !== strpos( $meta_key, $prefix ) ) {
321
+ return;
322
+ }
323
+
324
+ $key = str_replace( $prefix, '', $meta_key );
325
+
326
+ foreach ( \WPSEO_Meta::$meta_fields as $tab => $fields ) {
327
+ if ( isset( $fields[ $key ] ) ) {
328
+ $field = $fields[ $key ];
329
+ break;
330
+ }
331
+ }
332
+
333
+ if ( ! isset( $field, $field['title'], $tab ) || '' === $field['title'] ) {
334
+ return;
335
+ }
336
+
337
+ $post = get_post( $object_id );
338
+ $post_type_label = get_post_type_labels( get_post_type_object( $post->post_type ) )->singular_name;
339
+
340
+ $this->log(
341
+ sprintf(
342
+ // translators: Placeholders refer to a meta field title, a post title, and a post type (e.g. "Description", "Hello World", "Post")
343
+ __( 'Updated "%1$s" of "%2$s" %3$s', 'mainwp-child-reports' ),
344
+ $field['title'],
345
+ $post->post_title,
346
+ $post_type_label
347
+ ),
348
+ array(
349
+ 'meta_key' => $meta_key,
350
+ 'meta_value' => $meta_value,
351
+ 'post_type' => $post->post_type,
352
+ ),
353
+ $object_id,
354
+ 'wpseo_meta',
355
+ 'updated'
356
+ );
357
+ }
358
+
359
+ /**
360
+ * Override connector log for our own Settings / Actions
361
+ *
362
+ * @param array $data
363
+ *
364
+ * @return array|bool
365
+ */
366
+ public function log_override( $data ) {
367
+ if ( ! is_array( $data ) ) {
368
+ return $data;
369
+ }
370
+
371
+ global $pagenow;
372
+
373
+ if ( 'options.php' === $pagenow && 'settings' === $data['connector'] && wp_mainwp_stream_filter_input( INPUT_POST, '_wp_http_referer' ) ) {
374
+ if ( ! isset( $data['args']['context'] ) || ! isset( $this->option_groups[ $data['args']['context'] ] ) ) {
375
+ return $data;
376
+ }
377
+
378
+ $page = preg_match( '#page=([^&]*)#', wp_mainwp_stream_filter_input( INPUT_POST, '_wp_http_referer' ), $match ) ? $match[1] : '';
379
+ $labels = $this->get_context_labels();
380
+
381
+ if ( ! isset( $labels[ $page ] ) ) {
382
+ return $data;
383
+ }
384
+
385
+ $label = $this->settings_labels( $data['args']['option_key'] );
386
+ if ( ! $label ) {
387
+ // translators: Placeholder refers to a context (e.g. "Dashboard")
388
+ $data['message'] = esc_html__( '%s settings updated', 'mainwp-child-reports' );
389
+ $label = $labels[ $page ];
390
+ }
391
+
392
+ $data['args']['label'] = $label;
393
+ $data['args']['context'] = $page;
394
+ $data['context'] = $page;
395
+ $data['connector'] = $this->name;
396
+ }
397
+
398
+ return $data;
399
+ }
400
+
401
+ private function settings_labels( $option ) {
402
+ $labels = array(
403
+ // wp-content/plugins/wordpress-seo/admin/pages/dashboard.php:
404
+ 'yoast_tracking' => esc_html_x( "Allow tracking of this WordPress install's anonymous data.", 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
405
+ 'disableadvanced_meta' => esc_html_x( 'Disable the Advanced part of the WordPress SEO meta box', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
406
+ 'alexaverify' => esc_html_x( 'Alexa Verification ID', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
407
+ 'msverify' => esc_html_x( 'Bing Webmaster Tools', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
408
+ 'googleverify' => esc_html_x( 'Google Webmaster Tools', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
409
+ 'pinterestverify' => esc_html_x( 'Pinterest', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
410
+ 'yandexverify' => esc_html_x( 'Yandex Webmaster Tools', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
411
+
412
+ // wp-content/plugins/wordpress-seo/admin/pages/advanced.php:
413
+ 'breadcrumbs-enable' => esc_html_x( 'Enable Breadcrumbs', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
414
+ 'breadcrumbs-sep' => esc_html_x( 'Separator between breadcrumbs', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
415
+ 'breadcrumbs-home' => esc_html_x( 'Anchor text for the Homepage', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
416
+ 'breadcrumbs-prefix' => esc_html_x( 'Prefix for the breadcrumb path', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
417
+ 'breadcrumbs-archiveprefix' => esc_html_x( 'Prefix for Archive breadcrumbs', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
418
+ 'breadcrumbs-searchprefix' => esc_html_x( 'Prefix for Search Page breadcrumbs', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
419
+ 'breadcrumbs-404crumb' => esc_html_x( 'Breadcrumb for 404 Page', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
420
+ 'breadcrumbs-blog-remove' => esc_html_x( 'Remove Blog page from Breadcrumbs', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
421
+ 'breadcrumbs-boldlast' => esc_html_x( 'Bold the last page in the breadcrumb', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
422
+ 'post_types-post-maintax' => esc_html_x( 'Taxonomy to show in breadcrumbs for post types', 'wordpress-seo', 'mainwp-child-reports' ), # type = select
423
+
424
+ // wp-content/plugins/wordpress-seo/admin/pages/metas.php:
425
+ 'forcerewritetitle' => esc_html_x( 'Force rewrite titles', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
426
+ 'noindex-subpages-wpseo' => esc_html_x( 'Noindex subpages of archives', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
427
+ 'usemetakeywords' => _x( 'Use <code>meta</code> keywords tag?', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
428
+ 'noodp' => _x( 'Add <code>noodp</code> meta robots tag sitewide', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
429
+ 'noydir' => _x( 'Add <code>noydir</code> meta robots tag sitewide', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
430
+ 'hide-rsdlink' => esc_html_x( 'Hide RSD Links', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
431
+ 'hide-wlwmanifest' => esc_html_x( 'Hide WLW Manifest Links', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
432
+ 'hide-shortlink' => esc_html_x( 'Hide Shortlink for posts', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
433
+ 'hide-feedlinks' => esc_html_x( 'Hide RSS Links', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
434
+ 'disable-author' => esc_html_x( 'Disable the author archives', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
435
+ 'disable-date' => esc_html_x( 'Disable the date-based archives', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
436
+
437
+ // wp-content/plugins/wordpress-seo/admin/pages/network.php:
438
+ 'access' => esc_html_x( 'Who should have access to the WordPress SEO settings', 'wordpress-seo', 'mainwp-child-reports' ), # type = select
439
+ 'defaultblog' => esc_html_x( 'New blogs get the SEO settings from this blog', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
440
+ 'restoreblog' => esc_html_x( 'Blog ID', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
441
+
442
+ // wp-content/plugins/wordpress-seo/admin/pages/permalinks.php:
443
+ 'stripcategorybase' => _x( 'Strip the category base (usually <code>/category/</code>) from the category URL.', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
444
+ 'trailingslash' => esc_html_x( "Enforce a trailing slash on all category and tag URL's", 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
445
+ 'cleanslugs' => esc_html_x( 'Remove stop words from slugs.', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
446
+ 'redirectattachment' => esc_html_x( "Redirect attachment URL's to parent post URL.", 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
447
+ 'cleanreplytocom' => _x( 'Remove the <code>?replytocom</code> variables.', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
448
+ 'cleanpermalinks' => esc_html_x( "Redirect ugly URL's to clean permalinks. (Not recommended in many cases!)", 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
449
+ 'force_transport' => esc_html_x( 'Force Transport', 'wordpress-seo', 'mainwp-child-reports' ), # type = select
450
+ 'cleanpermalink-googlesitesearch' => esc_html_x( "Prevent cleaning out Google Site Search URL's.", 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
451
+ 'cleanpermalink-googlecampaign' => esc_html_x( 'Prevent cleaning out Google Analytics Campaign & Google AdWords Parameters.', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
452
+ 'cleanpermalink-extravars' => esc_html_x( 'Other variables not to clean', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
453
+
454
+ // wp-content/plugins/wordpress-seo/admin/pages/social.php:
455
+ 'opengraph' => esc_html_x( 'Add Open Graph meta data', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
456
+ 'facebook_site' => esc_html_x( 'Facebook Page URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
457
+ 'instagram_url' => esc_html_x( 'Instagram URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
458
+ 'linkedin_url' => esc_html_x( 'LinkedIn URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
459
+ 'myspace_url' => esc_html_x( 'MySpace URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
460
+ 'pinterest_url' => esc_html_x( 'Pinterest URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
461
+ 'youtube_url' => esc_html_x( 'YouTube URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
462
+ 'google_plus_url' => esc_html_x( 'Google+ URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
463
+ 'og_frontpage_image' => esc_html_x( 'Image URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
464
+ 'og_frontpage_desc' => esc_html_x( 'Description', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
465
+ 'og_frontpage_title' => esc_html_x( 'Title', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
466
+ 'og_default_image' => esc_html_x( 'Image URL', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
467
+ 'twitter' => esc_html_x( 'Add Twitter card meta data', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
468
+ 'twitter_site' => esc_html_x( 'Site Twitter Username', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
469
+ 'twitter_card_type' => esc_html_x( 'The default card type to use', 'wordpress-seo', 'mainwp-child-reports' ), # type = select
470
+ 'googleplus' => esc_html_x( 'Add Google+ specific post meta data (excluding author metadata)', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
471
+ 'plus-publisher' => esc_html_x( 'Google Publisher Page', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
472
+ 'fbadminapp' => esc_html_x( 'Facebook App ID', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
473
+
474
+ // wp-content/plugins/wordpress-seo/admin/pages/xml-sitemaps.php:
475
+ 'enablexmlsitemap' => esc_html_x( 'Check this box to enable XML sitemap functionality.', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
476
+ 'disable_author_sitemap' => esc_html_x( 'Disable author/user sitemap', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
477
+ 'disable_author_noposts' => esc_html_x( 'Users with zero posts', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
478
+ 'user_role-administrator-not_in_sitemap' => esc_html_x( 'Filter specific user roles - Administrator', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
479
+ 'user_role-editor-not_in_sitemap' => esc_html_x( 'Filter specific user roles - Editor', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
480
+ 'user_role-author-not_in_sitemap' => esc_html_x( 'Filter specific user roles - Author', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
481
+ 'user_role-contributor-not_in_sitemap' => esc_html_x( 'Filter specific user roles - Contributor', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
482
+ 'user_role-subscriber-not_in_sitemap' => esc_html_x( 'Filter specific user roles - Subscriber', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
483
+ 'xml_ping_yahoo' => esc_html_x( 'Ping Yahoo!', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
484
+ 'xml_ping_ask' => esc_html_x( 'Ping Ask.com', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
485
+ 'entries-per-page' => esc_html_x( 'Max entries per sitemap page', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
486
+ 'excluded-posts' => esc_html_x( 'Posts to exclude', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
487
+ 'post_types-post-not_in_sitemap' => _x( 'Post Types Posts (<code>post</code>)', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
488
+ 'post_types-page-not_in_sitemap' => _x( 'Post Types Pages (<code>page</code>)', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
489
+ 'post_types-attachment-not_in_sitemap' => _x( 'Post Types Media (<code>attachment</code>)', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
490
+ 'taxonomies-category-not_in_sitemap' => _x( 'Taxonomies Categories (<code>category</code>)', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
491
+ 'taxonomies-post_tag-not_in_sitemap' => _x( 'Taxonomies Tags (<code>post_tag</code>)', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
492
+
493
+ // Added manually
494
+ 'rssbefore' => esc_html_x( 'Content to put before each post in the feed', 'wordpress-seo', 'mainwp-child-reports' ),
495
+ 'rssafter' => esc_html_x( 'Content to put after each post', 'wordpress-seo', 'mainwp-child-reports' ),
496
+ );
497
+
498
+ $ast_labels = array(
499
+ 'title-' => esc_html_x( 'Title template', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
500
+ 'metadesc-' => esc_html_x( 'Meta description template', 'wordpress-seo', 'mainwp-child-reports' ), # type = textarea
501
+ 'metakey-' => esc_html_x( 'Meta keywords template', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
502
+ 'noindex-' => esc_html_x( 'Meta Robots', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
503
+ 'noauthorship-' => esc_html_x( 'Authorship', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
504
+ 'showdate-' => esc_html_x( 'Show date in snippet preview?', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
505
+ 'hideeditbox-' => esc_html_x( 'WordPress SEO Meta Box', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
506
+ 'bctitle-' => esc_html_x( 'Breadcrumbs Title', 'wordpress-seo', 'mainwp-child-reports' ), # type = textinput
507
+ 'post_types-' => esc_html_x( 'Post types', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
508
+ 'taxonomies-' => esc_html_x( 'Taxonomies', 'wordpress-seo', 'mainwp-child-reports' ), # type = checkbox
509
+ );
510
+
511
+ if ( $option ) {
512
+ if ( isset( $labels[ $option ] ) ) {
513
+ return $labels[ $option ];
514
+ } else {
515
+ foreach ( $ast_labels as $key => $trans ) {
516
+ if ( 0 === strpos( $option, $key ) ) {
517
+ return $trans;
518
+ }
519
+ }
520
+
521
+ return false;
522
+ }
523
+ }
524
+
525
+ return $labels;
526
+ }
527
+ }
connectors/comments.php DELETED
@@ -1,320 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Comments extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'comments';
6
-
7
- public static $actions = array(
8
- 'wp_insert_comment',
9
- 'edit_comment',
10
- 'delete_comment',
11
- 'trash_comment',
12
- 'untrash_comment',
13
- 'spam_comment'
14
- );
15
-
16
- public static function get_label() {
17
- return __( 'Comments', 'default' );
18
- }
19
-
20
- public static function get_action_labels() {
21
- return array(
22
- 'created' => __( 'Created', 'mainwp-child-reports' ),
23
- 'edited' => __( 'Edited', 'mainwp-child-reports' ),
24
- 'replied' => __( 'Replied', 'mainwp-child-reports' ),
25
- 'approved' => __( 'Approved', 'mainwp-child-reports' ),
26
- 'unapproved' => __( 'Unapproved', 'mainwp-child-reports' ),
27
- 'trashed' => __( 'Trashed', 'mainwp-child-reports' ),
28
- 'untrashed' => __( 'Restored', 'mainwp-child-reports' ),
29
- 'spammed' => __( 'Marked as Spam', 'mainwp-child-reports' ),
30
- // 'unspammed' => __( 'Unmarked as Spam', 'mainwp-child-reports' ),
31
- 'deleted' => __( 'Deleted', 'mainwp-child-reports' ),
32
- // 'duplicate' => __( 'Duplicate', 'mainwp-child-reports' ),
33
- // 'flood' => __( 'Throttled', 'mainwp-child-reports' ),
34
- );
35
- }
36
-
37
- public static function get_context_labels() {
38
- return array(
39
- 'comments' => __( 'Comments', 'default' ),
40
- );
41
- }
42
-
43
- public static function get_comment_type_labels() {
44
- return apply_filters(
45
- 'mainwp_wp_stream_comment_type_labels',
46
- array(
47
- 'comment' => __( 'Comment', 'default' ),
48
- 'trackback' => __( 'Trackback', 'default' ),
49
- 'pingback' => __( 'Pingback', 'default' ),
50
- )
51
- );
52
- }
53
-
54
- public static function get_comment_type_label( $comment_id ) {
55
- $comment_type = get_comment_type( $comment_id );
56
-
57
- if ( empty( $comment_type ) ) {
58
- $comment_type = 'comment';
59
- }
60
-
61
- $comment_type_labels = self::get_comment_type_labels();
62
-
63
- $label = isset( $comment_type_labels[ $comment_type ] ) ? $comment_type_labels[ $comment_type ] : $comment_type;
64
-
65
- return $label;
66
- }
67
-
68
- public static function action_links( $links, $record ) {
69
- if ( $record->object_id ) {
70
- if ( $comment = get_comment( $record->object_id ) ) {
71
- $del_nonce = wp_create_nonce( "delete-comment_$comment->comment_ID" );
72
- $approve_nonce = wp_create_nonce( "approve-comment_$comment->comment_ID" );
73
-
74
- $links[ __( 'Edit', 'default' ) ] = admin_url( "comment.php?action=editcomment&c=$comment->comment_ID" );
75
-
76
- if ( 1 === $comment->comment_approved ) {
77
- $links[ __( 'Unapprove', 'mainwp-child-reports' ) ] = admin_url(
78
- sprintf(
79
- 'comment.php?action=unapprovecomment&c=%s&_wpnonce=%s',
80
- $record->object_id,
81
- $approve_nonce
82
- )
83
- );
84
- } elseif ( empty( $comment->comment_approved ) ) {
85
- $links[ __( 'Approve', 'mainwp-child-reports' ) ] = admin_url(
86
- sprintf(
87
- 'comment.php?action=approvecomment&c=%s&_wpnonce=%s',
88
- $record->object_id,
89
- $approve_nonce
90
- )
91
- );
92
- }
93
- }
94
- }
95
-
96
- return $links;
97
- }
98
-
99
- public static function get_comment_author( $comment, $field = 'id' ) {
100
- $comment = is_object( $comment ) ? $comment : get_comment( absint( $comment ) );
101
-
102
- $req_name_email = get_option( 'require_name_email' );
103
- $req_user_login = get_option( 'comment_registration' );
104
-
105
- $user_id = 0;
106
- $user_name = __( 'Guest', 'mainwp-child-reports' );
107
-
108
- if ( $req_name_email && isset( $comment->comment_author_email ) && isset( $comment->comment_author ) ) {
109
- $user = get_user_by( 'email', $comment->comment_author_email );
110
- $user_id = isset( $user->ID ) ? $user->ID : 0;
111
- $user_name = isset( $user->display_name ) ? $user->display_name : $comment->comment_author;
112
- }
113
-
114
- if ( $req_user_login ) {
115
- $user = wp_get_current_user();
116
- $user_id = $user->ID;
117
- $user_name = $user->display_name;
118
- }
119
-
120
- if ( 'id' === $field ) {
121
- $output = $user_id;
122
- } elseif ( 'name' === $field ) {
123
- $output = $user_name;
124
- }
125
-
126
- return $output;
127
- }
128
-
129
- public static function callback_wp_insert_comment( $comment_id, $comment ) {
130
- if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
131
- return;
132
- }
133
-
134
- $user_id = self::get_comment_author( $comment, 'id' );
135
- $user_name = self::get_comment_author( $comment, 'name' );
136
- $post_id = $comment->comment_post_ID;
137
- $post_type = get_post_type( $post_id );
138
- $post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'mainwp-child-reports' );
139
- $comment_status = ( 1 == $comment->comment_approved ) ? __( 'approved automatically', 'mainwp-child-reports' ) : __( 'pending approval', 'mainwp-child-reports' );
140
- $is_spam = false;
141
- // Auto-marked spam comments
142
- if ( class_exists( 'Akismet' ) && Akismet::matches_last_comment( $comment ) ) {
143
- $ak_last_comment = Akismet::get_last_comment();
144
- if ( 'true' == $ak_last_comment['akismet_result'] ) {
145
- $is_spam = true;
146
- $comment_status = __( 'automatically marked as spam by Akismet', 'mainwp-child-reports' );
147
- }
148
- }
149
- $comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
150
-
151
- if ( $comment->comment_parent ) {
152
- $parent_user_id = get_comment_author( $comment->comment_parent, 'id' );
153
- $parent_user_name = get_comment_author( $comment->comment_parent, 'name' );
154
-
155
- self::log(
156
- _x(
157
- 'Reply to %1$s\'s %5$s by %2$s on %3$s %4$s',
158
- "1: Parent comment's author, 2: Comment author, 3: Post title, 4: Comment status, 5: Comment type",
159
- 'mainwp_child_reports'
160
- ),
161
- compact( 'parent_user_name', 'user_name', 'post_title', 'comment_status', 'comment_type', 'post_id', 'parent_user_id' ),
162
- $comment_id,
163
- array( $post_type => 'replied' ),
164
- $user_id
165
- );
166
- } else {
167
- self::log(
168
- _x(
169
- 'New %4$s by %1$s on %2$s %3$s',
170
- '1: Comment author, 2: Post title 3: Comment status, 4: Comment type',
171
- 'mainwp_child_reports'
172
- ),
173
- compact( 'user_name', 'post_title', 'comment_status', 'comment_type', 'post_id', 'is_spam' ),
174
- $comment_id,
175
- array( $post_type => $is_spam ? 'spammed' : 'created' ),
176
- $user_id
177
- );
178
- }
179
- }
180
-
181
- public static function callback_edit_comment( $comment_id ) {
182
- $comment = get_comment( $comment_id );
183
-
184
- if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
185
- return;
186
- }
187
-
188
- $user_id = self::get_comment_author( $comment, 'id' );
189
- $user_name = self::get_comment_author( $comment, 'name' );
190
- $post_id = $comment->comment_post_ID;
191
- $post_type = get_post_type( $post_id );
192
- $post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'mainwp-child-reports' );
193
- $comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
194
-
195
- self::log(
196
- _x(
197
- '%1$s\'s %3$s on %2$s edited',
198
- '1: Comment author, 2: Post title, 3: Comment type',
199
- 'mainwp_child_reports'
200
- ),
201
- compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
202
- $comment_id,
203
- array( $post_type => 'edited' )
204
- );
205
- }
206
-
207
- public static function callback_delete_comment( $comment_id ) {
208
- $comment = get_comment( $comment_id );
209
-
210
- if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
211
- return;
212
- }
213
-
214
- $user_id = self::get_comment_author( $comment, 'id' );
215
- $user_name = self::get_comment_author( $comment, 'name' );
216
- $post_id = $comment->comment_post_ID;
217
- $post_type = get_post_type( $post_id );
218
- $post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'mainwp-child-reports' );
219
- $comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
220
-
221
- self::log(
222
- _x(
223
- '%1$s\'s %3$s on %2$s deleted permanently',
224
- '1: Comment author, 2: Post title, 3: Comment type',
225
- 'mainwp_child_reports'
226
- ),
227
- compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
228
- $comment_id,
229
- array( $post_type => 'deleted' )
230
- );
231
- }
232
-
233
- public static function callback_trash_comment( $comment_id ) {
234
- $comment = get_comment( $comment_id );
235
-
236
- if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
237
- return;
238
- }
239
-
240
- $user_id = self::get_comment_author( $comment, 'id' );
241
- $user_name = self::get_comment_author( $comment, 'name' );
242
- $post_id = $comment->comment_post_ID;
243
- $post_type = get_post_type( $post_id );
244
- $post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'mainwp-child-reports' );
245
- $comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
246
-
247
- self::log(
248
- _x(
249
- '%1$s\'s %3$s on %2$s trashed',
250
- '1: Comment author, 2: Post title, 3: Comment type',
251
- 'mainwp_child_reports'
252
- ),
253
- compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
254
- $comment_id,
255
- array( $post_type => 'trashed' )
256
- );
257
- }
258
-
259
- public static function callback_untrash_comment( $comment_id ) {
260
- $comment = get_comment( $comment_id );
261
-
262
- if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
263
- return;
264
- }
265
-
266
- $user_id = self::get_comment_author( $comment, 'id' );
267
- $user_name = self::get_comment_author( $comment, 'name' );
268
- $post_id = $comment->comment_post_ID;
269
- $post_type = get_post_type( $post_id );
270
- $post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'mainwp-child-reports' );
271
- $comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
272
-
273
- self::log(
274
- _x(
275
- '%1$s\'s %3$s on %2$s restored',
276
- '1: Comment author, 2: Post title, 3: Comment type',
277
- 'mainwp_child_reports'
278
- ),
279
- compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
280
- $comment_id,
281
- array( $post_type => 'untrashed' )
282
- );
283
- }
284
-
285
- public static function callback_spam_comment( $comment_id ) {
286
- $comment = get_comment( $comment_id );
287
-
288
- if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
289
- return;
290
- }
291
-
292
- $user_id = self::get_comment_author( $comment, 'id' );
293
- $user_name = self::get_comment_author( $comment, 'name' );
294
- $post_id = $comment->comment_post_ID;
295
- $post_type = get_post_type( $post_id );
296
- $post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'mainwp-child-reports' );
297
- $comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
298
-
299
- self::log(
300
- _x(
301
- '%1$s\'s %3$s on %2$s marked as spam',
302
- '1: Comment author, 2: Post title, 3: Comment type',
303
- 'mainwp_child_reports'
304
- ),
305
- compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
306
- $comment_id,
307
- array( $post_type => 'spammed' )
308
- );
309
- }
310
-
311
- public static function get_ignored_comment_types() {
312
- $comment_types[] = 'order_note';
313
- $comment_types[] = 'action_log';
314
- return apply_filters(
315
- 'mainwp_wp_stream_comment_exclude_comment_types',
316
- $comment_types
317
- );
318
- }
319
-
320
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/editor.php DELETED
@@ -1,140 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Editor extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'editor';
6
-
7
- public static $actions = array();
8
-
9
- private static $edited_file = array();
10
-
11
- public static function register() {
12
- parent::register();
13
- add_action( 'load-theme-editor.php', array( __CLASS__, 'get_edition_data' ) );
14
- add_filter( 'wp_redirect', array( __CLASS__, 'log_changes' ) );
15
- }
16
-
17
- public static function get_label() {
18
- return __( 'Theme Editor', 'mainwp-child-reports' );
19
- }
20
-
21
- public static function get_action_labels() {
22
- return array(
23
- 'updated' => __( 'Updated', 'mainwp-child-reports' ),
24
- );
25
- }
26
-
27
- public static function get_context_labels() {
28
- $themes = wp_get_themes();
29
-
30
- $themes_slugs = array_map(
31
- function( $theme ) {
32
- return $theme->get_template();
33
- },
34
- $themes
35
- );
36
-
37
- $themes_names = array_map(
38
- function( $theme ) {
39
- return (string) $theme;
40
- },
41
- $themes
42
- );
43
-
44
- return array_combine( $themes_slugs, $themes_names );
45
- }
46
-
47
- public static function get_message() {
48
- return __( '"%1$s" in "%2$s" updated', 'mainwp-child-reports' );
49
- }
50
-
51
- public static function action_links( $links, $record ) {
52
- if ( current_user_can( 'edit_theme_options' ) ) {
53
- $file_name = mainwp_wp_stream_get_meta( $record->ID, 'file', true );
54
- $theme_slug = mainwp_wp_stream_get_meta( $record->ID, 'theme_slug', true );
55
-
56
- if ( '' !== $file_name && '' !== $theme_slug ) {
57
- $links[ __( 'Edit File', 'mainwp-child-reports' ) ] = admin_url(
58
- sprintf(
59
- 'theme-editor.php?theme=%s&file=%s',
60
- $theme_slug,
61
- $file_name
62
- )
63
- );
64
-
65
- $links[ __( 'Edit Theme', 'mainwp-child-reports' ) ] = admin_url(
66
- sprintf(
67
- 'themes.php?theme=%s',
68
- $theme_slug
69
- )
70
- );
71
- }
72
- }
73
-
74
- return $links;
75
- }
76
-
77
- public static function get_edition_data() {
78
- if ( 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
79
- return;
80
- }
81
-
82
- if ( 'update' !== mainwp_wp_stream_filter_input( INPUT_POST, 'action' ) ) {
83
- return;
84
- }
85
-
86
- $theme_slug = mainwp_wp_stream_filter_input( INPUT_POST, 'theme' ) ? mainwp_wp_stream_filter_input( INPUT_POST, 'theme' ) : get_stylesheet();
87
- $theme = wp_get_theme( $theme_slug );
88
-
89
- if ( ! $theme->exists() || ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) ) {
90
- return;
91
- }
92
-
93
- $allowed_files = $theme->get_files( 'php', 1 );
94
- $style_files = $theme->get_files( 'css' );
95
- $allowed_files['style.css'] = $style_files['style.css'];
96
- $file = mainwp_wp_stream_filter_input( INPUT_POST, 'file' );
97
-
98
- if ( empty( $file ) ) {
99
- $file_name = 'style.css';
100
- $file_path = $allowed_files['style.css'];
101
- } else {
102
- $file_name = $file;
103
- $file_path = sprintf( '%s/%s', $theme->get_stylesheet_directory(), $file_name );
104
- }
105
-
106
- $file_contents_before = file_get_contents( $file_path );
107
-
108
- self::$edited_file = compact(
109
- 'file_name',
110
- 'file_path',
111
- 'file_contents_before',
112
- 'theme'
113
- );
114
- }
115
-
116
- public static function log_changes( $location ) {
117
- if ( ! empty( self::$edited_file ) ) {
118
- $file_contents_after = file_get_contents( self::$edited_file['file_path'] );
119
-
120
- if ( $file_contents_after !== self::$edited_file['file_contents_before'] ) {
121
- $theme_slug = self::$edited_file['theme']->get_template();
122
- $properties = array(
123
- 'file' => self::$edited_file['file_name'],
124
- 'theme_name' => (string) self::$edited_file['theme'],
125
- 'theme_slug' => $theme_slug,
126
- );
127
-
128
- self::log(
129
- self::get_message(),
130
- $properties,
131
- null,
132
- array( $theme_slug => 'updated' )
133
- );
134
- }
135
- }
136
-
137
- return $location;
138
- }
139
-
140
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/installer.php DELETED
@@ -1,528 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Installer extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'installer';
6
-
7
- public static $actions = array(
8
- 'upgrader_pre_install', // use to the current version of all plugins, before they are upgraded ( Net-Concept - Xavier NUEL )
9
- 'upgrader_process_complete', // plugins::installed | themes::installed
10
- 'activate_plugin', // plugins::activated
11
- 'deactivate_plugin', // plugins::deactivated
12
- 'switch_theme', // themes::activated
13
- 'delete_site_transient_update_themes', // themes::deleted
14
- 'pre_option_uninstall_plugins', // plugins::deleted
15
- 'pre_set_site_transient_update_plugins',
16
- 'wp_redirect',
17
- '_core_updated_successfully',
18
- 'mainwp_child_installPluginTheme',
19
- 'mainwp_child_plugin_action',
20
- 'mainwp_child_theme_action',
21
- 'mainwp_child_upgradePluginTheme'
22
- );
23
- public static $old_plugins = array();
24
-
25
- public static function get_label() {
26
- return __( 'Installer', 'mainwp-child-reports' );
27
- }
28
-
29
- public static function get_action_labels() {
30
- return array(
31
- 'installed' => __( 'Installed', 'mainwp-child-reports' ),
32
- 'activated' => __( 'Activated', 'mainwp-child-reports' ),
33
- 'deactivated' => __( 'Deactivated', 'mainwp-child-reports' ),
34
- 'deleted' => __( 'Deleted', 'mainwp-child-reports' ),
35
- 'edited' => __( 'Edited', 'mainwp-child-reports' ),
36
- 'updated' => __( 'Updated', 'mainwp-child-reports' ),
37
- );
38
- }
39
-
40
- public static function get_context_labels() {
41
- return array(
42
- 'plugins' => __( 'Plugins', 'default' ),
43
- 'themes' => __( 'Themes', 'default' ),
44
- 'wordpress' => __( 'WordPress', 'default' ),
45
- );
46
- }
47
-
48
- public static function action_links( $links, $record ) {
49
- if ( 'wordpress' === $record->context && 'updated' === $record->action ) {
50
- global $wp_version;
51
- $version = mainwp_wp_stream_get_meta( $record->ID, 'new_version', true );
52
- if ( $version === $wp_version ) {
53
- $links[ __( 'About', 'mainwp-child-reports' ) ] = admin_url( 'about.php?updated' );
54
- }
55
- $links[ __( 'View Release Notes', 'mainwp-child-reports' ) ] = esc_url( sprintf( 'http://codex.wordpress.org/Version_%s', $version ) );
56
- }
57
- return $links;
58
- }
59
-
60
-
61
- public static function get_plugins() {
62
- if ( ! function_exists( 'get_plugins' ) ) {
63
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
64
- }
65
-
66
- return get_plugins();
67
- }
68
-
69
- // ( Net-Concept - Xavier NUEL ) : save all plugins versions before upgrade
70
- public static function callback_upgrader_pre_install() {
71
- self::$old_plugins = self::get_plugins();
72
- }
73
-
74
- public static function callback_mainwp_child_installPluginTheme($args) {
75
- $logs = array();
76
- $success = isset($args['success']) ? $args['success'] : 0;
77
- $error = null;
78
-
79
- if ( ! $success ) {
80
- $errors = $args['errors'];;
81
- }
82
-
83
- // This would have failed down the road anyway
84
- if ( ! isset( $args['type'] ) ) {
85
- return false;
86
- }
87
-
88
- $type = $args['type'];
89
- $action = $args['action'];
90
-
91
- if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
92
- return;
93
- }
94
-
95
- if ( 'install' === $action ) {
96
- if ( 'plugin' === $type) {
97
- if ( !isset($args['Name']) || empty($args['Name']))
98
- return;
99
- $slug = $args['slug'];
100
- $name = $args['Name'];
101
- $version = $args['Version'];
102
- } else { // theme
103
- $slug = $args['slug'];
104
- if ( ! $slug ) {
105
- return;
106
- }
107
- wp_clean_themes_cache();
108
- $theme = wp_get_theme( $slug );
109
- $name = $theme->name;
110
- $version = $theme->version;
111
- }
112
- $action = 'installed';
113
- $message = _x(
114
- 'Installed %1$s: %2$s %3$s',
115
- 'Plugin/theme installation. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
116
- 'mainwp_child_reports'
117
- );
118
- $logs[] = compact( 'slug', 'name', 'version', 'message', 'action' );
119
- } else {
120
- return false;
121
- }
122
-
123
- $context = $type . 's';
124
-
125
- foreach ( $logs as $log ) {
126
- $name = isset( $log['name'] ) ? $log['name'] : null;
127
- $version = isset( $log['version'] ) ? $log['version'] : null;
128
- $slug = isset( $log['slug'] ) ? $log['slug'] : null;
129
- $old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
130
- $message = isset( $log['message'] ) ? $log['message'] : null;
131
- $action = isset( $log['action'] ) ? $log['action'] : null;
132
- self::log(
133
- $message,
134
- compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
135
- null,
136
- array( $context => $action )
137
- );
138
- }
139
- }
140
-
141
-
142
- public static function callback_mainwp_child_plugin_action( $args ) {
143
- if (!is_array($args) || !isset($args['action']))
144
- return;
145
- $action = $args['action'];
146
- if ($action == 'delete') {
147
- $name = $args['Name'];
148
- $network_wide = '';
149
- self::log(
150
- __( '"%s" plugin deleted', 'mainwp-child-reports' ),
151
- compact( 'name', 'plugin', 'network_wide' ),
152
- null,
153
- array( 'plugins' => 'deleted' )
154
- );
155
- }
156
- }
157
-
158
- public static function callback_mainwp_child_theme_action($args) {
159
- if (!is_array($args) || !isset($args['action']))
160
- return;
161
- $action = $args['action'];
162
- $name = $args['Name'];
163
- if ($action == 'delete') {
164
- self::log(
165
- __( '"%s" theme deleted', 'mainwp-child-reports' ),
166
- compact( 'name' ),
167
- null,
168
- array( 'themes' => 'deleted' )
169
- );
170
- }
171
- }
172
-
173
- public static function callback_upgrader_process_complete( $upgrader, $extra ) {
174
- // do not logging if update from mainwp dashboard
175
- if (isset($_POST['mainwpsignature']) && isset($_POST['function']) && $_POST['function'] == 'upgradeplugintheme') {
176
- return false;
177
- }
178
- $logs = array();
179
- $success = ! is_wp_error( $upgrader->skin->result );
180
- $error = null;
181
-
182
- if ( ! $success ) {
183
- $errors = $upgrader->skin->result->errors;
184
- list( $error ) = reset( $errors );
185
- }
186
-
187
- // This would have failed down the road anyway
188
- if ( ! isset( $extra['type'] ) ) {
189
- return false;
190
- }
191
-
192
- $type = $extra['type'];
193
- $action = $extra['action'];
194
-
195
- if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
196
- return;
197
- }
198
-
199
- if ( 'install' === $action ) {
200
- if ( 'plugin' === $type ) {
201
- $path = $upgrader->plugin_info();
202
- if ( ! $path ) {
203
- return;
204
- }
205
- $data = get_plugin_data( $upgrader->skin->result['local_destination'] . '/' . $path );
206
- $slug = $upgrader->result['destination_name'];
207
- $name = $data['Name'];
208
- $version = $data['Version'];
209
- } else { // theme
210
- $slug = $upgrader->theme_info();
211
- if ( ! $slug ) {
212
- return;
213
- }
214
- wp_clean_themes_cache();
215
- $theme = wp_get_theme( $slug );
216
- $name = $theme->name;
217
- $version = $theme->version;
218
- }
219
- $action = 'installed';
220
- $message = _x(
221
- 'Installed %1$s: %2$s %3$s',
222
- 'Plugin/theme installation. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
223
- 'mainwp_child_reports'
224
- );
225
- $logs[] = compact( 'slug', 'name', 'version', 'message', 'action' );
226
- } elseif ( 'update' === $action ) {
227
- $action = 'updated';
228
- $message = _x(
229
- 'Updated %1$s: %2$s %3$s',
230
- 'Plugin/theme update. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
231
- 'mainwp_child_reports'
232
- );
233
- if ( 'plugin' === $type ) {
234
- if ( isset( $extra['bulk'] ) && true == $extra['bulk'] ) {
235
- $slugs = $extra['plugins'];
236
- } else {
237
- $slugs = array( $upgrader->skin->plugin );
238
- }
239
- foreach ( $slugs as $slug ) {
240
- $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug );
241
- $name = $plugin_data['Name'];
242
- $version = $plugin_data['Version'];
243
-
244
- // ( Net-Concept - Xavier NUEL ) : get old versions
245
- if (isset(self::$old_plugins[$slug])) {
246
- $old_version = self::$old_plugins[$slug]['Version'];
247
- } else {
248
- //$old_version = ''; // Hummm... will this happen ?
249
- $old_version = $upgrader->skin->plugin_info['Version']; // to fix old version
250
- }
251
-
252
- if (version_compare($version, $old_version, '>')) {
253
- $logs[] = compact('slug', 'name', 'old_version', 'version', 'message', 'action');
254
- }
255
- }
256
- } else { // theme
257
- if ( isset( $extra['bulk'] ) && true == $extra['bulk'] ) {
258
- $slugs = $extra['themes'];
259
- } else {
260
- $slugs = array( $upgrader->skin->theme );
261
- }
262
- foreach ( $slugs as $slug ) {
263
- $theme = wp_get_theme( $slug );
264
- $stylesheet = $theme['Stylesheet Dir'] . '/style.css';
265
- $theme_data = get_file_data( $stylesheet, array( 'Version' => 'Version' ) );
266
- $name = $theme['Name'];
267
- $old_version = $upgrader->skin->theme_info->get('Version'); // to fix old version //$theme['Version'];
268
- $version = $theme_data['Version'];
269
- if (version_compare($version, $old_version, '>')) { // to fix
270
- $logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
271
- }
272
- }
273
- }
274
- } else {
275
- return false;
276
- }
277
-
278
- $context = $type . 's';
279
-
280
- foreach ( $logs as $log ) {
281
- $name = isset( $log['name'] ) ? $log['name'] : null;
282
- $version = isset( $log['version'] ) ? $log['version'] : null;
283
- $slug = isset( $log['slug'] ) ? $log['slug'] : null;
284
- $old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
285
- $message = isset( $log['message'] ) ? $log['message'] : null;
286
- $action = isset( $log['action'] ) ? $log['action'] : null;
287
- self::log(
288
- $message,
289
- compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
290
- null,
291
- array( $context => $action )
292
- );
293
- }
294
- }
295
-
296
- public static function callback_mainwp_child_upgradePluginTheme( $extra ) {
297
- $logs = array();
298
-
299
- if ( ! isset( $extra['type'] ) ) {
300
- return false;
301
- }
302
-
303
- $type = $extra['type'];
304
- $action = $extra['action'];
305
-
306
- if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
307
- return;
308
- }
309
-
310
- if ( 'update' === $action ) {
311
- if ( 'plugin' === $type ) {
312
- $slug = $extra['slug'];
313
- $name = $extra['name'];
314
- $version = $extra['version'];
315
- $old_version = $extra['old_version'];
316
- } else { // theme
317
- $name = $extra['name'];
318
- $version = $extra['version'];
319
- $old_version = $extra['old_version'];
320
- }
321
-
322
- $action = 'updated';
323
- $message = _x(
324
- 'Updated %1$s: %2$s %3$s',
325
- 'Plugin/theme update. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
326
- 'mainwp_child_reports'
327
- );
328
- $logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
329
- } else {
330
- return false;
331
- }
332
-
333
- $context = $type . 's';
334
-
335
- foreach ( $logs as $log ) {
336
- $name = isset( $log['name'] ) ? $log['name'] : null;
337
- $version = isset( $log['version'] ) ? $log['version'] : null;
338
- $slug = isset( $log['slug'] ) ? $log['slug'] : null;
339
- $old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
340
- $message = isset( $log['message'] ) ? $log['message'] : null;
341
- $action = isset( $log['action'] ) ? $log['action'] : null;
342
- self::log(
343
- $message,
344
- compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
345
- null,
346
- array( $context => $action )
347
- );
348
- }
349
- }
350
-
351
-
352
- public static function callback_activate_plugin( $slug, $network_wide = false ) {
353
- $plugins = self::get_plugins();
354
- $name = $plugins[ $slug ]['Name'];
355
- $network_wide = $network_wide ? __( 'network wide', 'mainwp-child-reports' ) : null;
356
- self::log(
357
- _x(
358
- '"%1$s" plugin activated %2$s',
359
- '1: Plugin name, 2: Single site or network wide',
360
- 'mainwp_child_reports'
361
- ),
362
- compact( 'name', 'network_wide', 'slug' ),
363
- null,
364
- array( 'plugins' => 'activated' )
365
- );
366
- }
367
-
368
- public static function callback_deactivate_plugin( $slug, $network_wide = false) {
369
- $plugins = self::get_plugins();
370
- $name = $plugins[ $slug ]['Name'];
371
- $network_wide = $network_wide ? __( 'network wide', 'mainwp-child-reports' ) : null;
372
- self::log(
373
- _x(
374
- '"%1$s" plugin deactivated %2$s',
375
- '1: Plugin name, 2: Single site or network wide',
376
- 'mainwp_child_reports'
377
- ),
378
- compact( 'name', 'network_wide', 'slug' ),
379
- null,
380
- array( 'plugins' => 'deactivated' )
381
- );
382
- }
383
-
384
- public static function callback_switch_theme( $name, $theme ) {
385
- $stylesheet = $theme->get_stylesheet();
386
-
387
- self::log(
388
- __( '"%s" theme activated', 'mainwp-child-reports' ),
389
- compact( 'name' ),
390
- null,
391
- array( 'themes' => 'activated' )
392
- );
393
- }
394
-
395
- public static function callback_delete_site_transient_update_themes() {
396
-
397
- $backtrace = debug_backtrace();
398
- $delete_theme_call = null;
399
- foreach ( $backtrace as $call ) {
400
- if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
401
- $delete_theme_call = $call;
402
- break;
403
- }
404
- }
405
-
406
- if ( empty( $delete_theme_call ) ) {
407
- return;
408
- }
409
-
410
- $name = $delete_theme_call['args'][0];
411
-
412
- self::log(
413
- __( '"%s" theme deleted', 'mainwp-child-reports' ),
414
- compact( 'name' ),
415
- null,
416
- array( 'themes' => 'deleted' )
417
- );
418
- }
419
-
420
- public static function callback_pre_option_uninstall_plugins() {
421
- global $plugins;
422
-
423
- if ( 'delete-selected' !== mainwp_wp_stream_filter_input( INPUT_GET, 'action' ) && 'delete-selected' !== mainwp_wp_stream_filter_input( INPUT_POST, 'action2' ) ) {
424
- return false;
425
- }
426
-
427
- $_plugins = self::get_plugins();
428
-
429
- foreach ( $plugins as $plugin ) {
430
- $plugins_to_delete[ $plugin ] = $_plugins[ $plugin ];
431
- }
432
-
433
- update_option( 'mainwp_wp_stream_plugins_to_delete', $plugins_to_delete );
434
-
435
- return false;
436
- }
437
-
438
- public static function callback_pre_set_site_transient_update_plugins( $value ) {
439
- if ( ! mainwp_wp_stream_filter_input( INPUT_POST, 'verify-delete' ) || ! ( $plugins_to_delete = get_option( 'mainwp_wp_stream_plugins_to_delete' ) ) ) {
440
- return $value;
441
- }
442
-
443
- foreach ( $plugins_to_delete as $plugin => $data ) {
444
- $name = $data['Name'];
445
- $network_wide = $data['Network'] ? __( 'network wide', 'mainwp-child-reports' ) : '';
446
-
447
- self::log(
448
- __( '"%s" plugin deleted', 'mainwp-child-reports' ),
449
- compact( 'name', 'plugin', 'network_wide' ),
450
- null,
451
- array( 'plugins' => 'deleted' )
452
- );
453
- }
454
-
455
- delete_option( 'mainwp_wp_stream_plugins_to_delete' );
456
-
457
- return $value;
458
- }
459
-
460
- public static function callback_wp_redirect( $location ) {
461
- if ( ! preg_match( '#(plugin)-editor.php#', $location, $match ) ) {
462
- return $location;
463
- }
464
-
465
- $type = $match[1];
466
-
467
- list( $url, $query ) = explode( '?', $location );
468
-
469
- $query = wp_parse_args( $query );
470
- $file = $query['file'];
471
-
472
- if ( empty( $query['file'] ) ) {
473
- return $location;
474
- }
475
-
476
- if ( 'theme' === $type ) {
477
- if ( empty( $query['updated'] ) ) {
478
- return $location;
479
- }
480
- $theme = wp_get_theme( $query['theme'] );
481
- $name = $theme['Name'];
482
- }
483
- elseif ( 'plugin' === $type ) {
484
- global $plugin, $plugins;
485
- $plugin_base = current( explode( '/', $plugin ) );
486
- foreach ( $plugins as $key => $plugin_data ) {
487
- if ( $plugin_base === current( explode( '/', $key ) ) ) {
488
- $name = $plugin_data['Name'];
489
- break;
490
- }
491
- }
492
- }
493
-
494
- self::log(
495
- _x(
496
- 'Edited %1$s: %2$s',
497
- 'Plugin/theme editing. 1: Type (plugin/theme), 2: Plugin/theme name',
498
- 'mainwp_child_reports'
499
- ),
500
- compact( 'type', 'name', 'file' ),
501
- null,
502
- array( $type . 's' => 'edited' )
503
- );
504
-
505
- return $location;
506
- }
507
-
508
- public static function callback__core_updated_successfully( $new_version ) {
509
- global $pagenow, $wp_version;
510
-
511
- $old_version = $wp_version;
512
- $auto_updated = ( 'update-core.php' !== $pagenow && !isset($_POST['mainwpsignature']));
513
-
514
- if ( $auto_updated ) {
515
- $message = __( 'WordPress auto-updated to %s', 'mainwp-child-reports' );
516
- } else {
517
- $message = __( 'WordPress updated to %s', 'mainwp-child-reports' );
518
- }
519
-
520
- self::log(
521
- $message,
522
- compact( 'new_version', 'old_version', 'auto_updated' ),
523
- null,
524
- array( 'wordpress' => 'updated' )
525
- );
526
- }
527
-
528
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/maintenance.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Maintenance extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'mainwp_maintenance';
6
-
7
- public static $actions = array(
8
- 'mainwp_reports_maintenance',
9
- );
10
-
11
- public static function get_label() {
12
- return __( 'Maintenance', 'default' );
13
- }
14
-
15
- public static function get_action_labels() {
16
- return array(
17
- 'mainwp_reports_maintenance' => __( 'Maintenance', 'default' ),
18
- );
19
- }
20
-
21
- public static function get_context_labels() {
22
- return array(
23
- 'mainwp_maintenances' => __( 'Maintenance', 'default' ),
24
- );
25
- }
26
-
27
- public static function action_links( $links, $record ) {
28
- if (isset($record->object_id)) {
29
- }
30
- return $links;
31
- }
32
-
33
- public static function callback_mainwp_reports_maintenance( $message, $log_time, $details, $result = '') {
34
- self::log(
35
- $message,
36
- compact('log_time', 'details' , 'result'),
37
- 0,
38
- array( 'mainwp_maintenances' => 'mainwp_reports_maintenance' )
39
- );
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/media.php DELETED
@@ -1,153 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Media extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'media';
6
-
7
- public static $actions = array(
8
- 'add_attachment',
9
- 'edit_attachment',
10
- 'delete_attachment',
11
- 'wp_save_image_editor_file',
12
- 'wp_save_image_file',
13
- );
14
-
15
- public static function get_label() {
16
- return __( 'Media', 'default' );
17
- }
18
-
19
- public static function get_action_labels() {
20
- return array(
21
- 'attached' => esc_html__( 'Attached', 'mainwp-child-reports' ),
22
- 'uploaded' => esc_html__( 'Uploaded', 'mainwp-child-reports' ),
23
- 'updated' => esc_html__( 'Updated', 'mainwp-child-reports' ),
24
- 'deleted' => esc_html__( 'Deleted', 'mainwp-child-reports' ),
25
- // 'assigned' => esc_html__( 'Assigned', 'mainwp-child-reports' ),
26
- // 'unassigned' => esc_html__( 'Unassigned', 'mainwp-child-reports' ),
27
- );
28
- }
29
-
30
- public static function get_context_labels() {
31
- return array(
32
- 'image' => __( 'Image', 'default' ),
33
- 'audio' => __( 'Audio', 'default' ),
34
- 'video' => __( 'Video', 'default' ),
35
- 'document' => __( 'Document', 'mainwp-child-reports' ),
36
- 'spreadsheet' => __( 'Spreadsheet', 'mainwp-child-reports' ),
37
- 'interactive' => __( 'Interactive', 'mainwp-child-reports' ),
38
- 'text' => __( 'Text', 'default' ),
39
- 'archive' => __( 'Archive', 'default' ),
40
- 'code' => __( 'Code', 'default' ),
41
- );
42
- }
43
-
44
- public static function get_attachment_type( $file_uri ) {
45
- $extension = pathinfo( $file_uri, PATHINFO_EXTENSION );
46
- $extension_type = wp_ext2type( $extension );
47
-
48
- if ( empty( $extension_type ) ) {
49
- $extension_type = 'document';
50
- }
51
-
52
- $context_labels = self::get_context_labels();
53
-
54
- if ( ! isset( $context_labels[ $extension_type ] ) ) {
55
- $extension_type = 'document';
56
- }
57
-
58
- return $extension_type;
59
- }
60
-
61
- public static function action_links( $links, $record ) {
62
- if ( $record->object_id ) {
63
- if ( $link = get_edit_post_link( $record->object_id ) ) {
64
- $links[ __( 'Edit Media', 'default' ) ] = $link;
65
- }
66
- if ( $link = get_permalink( $record->object_id ) ) {
67
- $links[ __( 'View', 'default' ) ] = $link;
68
- }
69
- }
70
-
71
- return $links;
72
- }
73
-
74
- public static function callback_add_attachment( $post_id ) {
75
- $post = get_post( $post_id );
76
- if ( $post->post_parent ) {
77
- // translators: Placeholders refer to an attachment title, and a post title (e.g. "PIC001", "Hello World")
78
- $message = _x(
79
- 'Attached "%1$s" to "%2$s"',
80
- '1: Attachment title, 2: Parent post title',
81
- 'stream'
82
- );
83
- } else {
84
- // translators: Placeholder refers to an attachment title (e.g. "PIC001")
85
- $message = esc_html__( 'Added "%s" to Media library', 'stream' );
86
- }
87
-
88
- $name = $post->post_title;
89
- $url = $post->guid;
90
- $parent_id = $post->post_parent;
91
- $parent = get_post( $parent_id );
92
- $parent_title = $parent_id ? $parent->post_title : null;
93
- $attachment_type = self::get_attachment_type( $post->guid );
94
-
95
- self::log(
96
- $message,
97
- compact( 'name', 'parent_title', 'parent_id', 'url' ),
98
- $post_id,
99
- // $attachment_type,
100
- // $post->post_parent ? 'attached' : 'uploaded'
101
- array( $attachment_type => $post->post_parent ? 'attached' : 'uploaded' )
102
- );
103
- }
104
-
105
-
106
- public static function callback_edit_attachment( $post_id ) {
107
- $post = get_post( $post_id );
108
- $message = __( 'Updated "%s"', 'mainwp-child-reports' );
109
- $name = $post->post_title;
110
- $attachment_type = self::get_attachment_type( $post->guid );
111
-
112
- self::log(
113
- $message,
114
- compact( 'name' ),
115
- $post_id,
116
- array( $attachment_type => 'updated' )
117
- );
118
- }
119
-
120
- public static function callback_delete_attachment( $post_id ) {
121
- $post = get_post( $post_id );
122
- $parent = $post->post_parent ? get_post( $post->post_parent ) : null;
123
- $parent_id = $parent ? $parent->ID : null;
124
- $message = __( 'Deleted "%s"', 'mainwp-child-reports' );
125
- $name = $post->post_title;
126
- $url = $post->guid;
127
- $attachment_type = self::get_attachment_type( $post->guid );
128
-
129
- self::log(
130
- $message,
131
- compact( 'name', 'parent_id', 'url' ),
132
- $post_id,
133
- array( $attachment_type => 'deleted' )
134
- );
135
- }
136
-
137
- public static function callback_wp_save_image_editor_file( $dummy, $filename, $image, $mime_type, $post_id ) {
138
- $name = basename( $filename );
139
- $attachment_type = self::get_attachment_type( $post->guid );
140
-
141
- self::log(
142
- __( 'Edited image "%s"', 'mainwp-child-reports' ),
143
- compact( 'name', 'filename', 'post_id' ),
144
- $post_id,
145
- array( $attachment_type => 'edited' )
146
- );
147
- }
148
-
149
- public static function callback_wp_save_image_file( $dummy, $filename, $image, $mime_type, $post_id ) {
150
- return self::callback_wp_save_image_editor_file( $dummy, $filename, $image, $mime_type, $post_id );
151
- }
152
-
153
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/menus.php DELETED
@@ -1,96 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Menus extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'menus';
6
-
7
- public static $actions = array(
8
- 'wp_create_nav_menu',
9
- 'wp_update_nav_menu',
10
- 'delete_nav_menu',
11
- );
12
-
13
- public static function get_label() {
14
- return __( 'Menus', 'default' );
15
- }
16
-
17
- public static function get_action_labels() {
18
- return array(
19
- 'created' => __( 'Created', 'mainwp-child-reports' ),
20
- 'updated' => __( 'Updated', 'mainwp-child-reports' ),
21
- 'deleted' => __( 'Deleted', 'mainwp-child-reports' ),
22
- );
23
- }
24
-
25
- public static function get_context_labels() {
26
- $labels = array();
27
- $menus = get_terms( 'nav_menu', array( 'hide_empty' => false ) );
28
-
29
- foreach ( $menus as $menu ) {
30
- $slug = sanitize_title( $menu->name );
31
- $labels[ $slug ] = $menu->name;
32
- }
33
-
34
- return $labels;
35
- }
36
-
37
- public static function register() {
38
- parent::register();
39
-
40
- //add_action( 'update_option_theme_mods_' . get_option( 'stylesheet' ), array( __CLASS__, 'callback_update_option_theme_mods' ), 10, 2 );
41
- }
42
-
43
- public static function action_links( $links, $record ) {
44
- if ( $record->object_id ) {
45
- $menus = wp_get_nav_menus();
46
- $menu_ids = wp_list_pluck( $menus, 'term_id' );
47
-
48
- if ( in_array( $record->object_id, $menu_ids ) ) {
49
- $links[ __( 'Edit Menu', 'mainwp-child-reports' ) ] = admin_url( 'nav-menus.php?action=edit&menu=' . $record->object_id ); // xss ok (@todo fix WPCS rule)
50
- }
51
- }
52
-
53
- return $links;
54
- }
55
-
56
- public static function callback_wp_create_nav_menu( $menu_id, $menu_data ) {
57
- $name = $menu_data['menu-name'];
58
-
59
- self::log(
60
- __( 'Created new menu "%s"', 'mainwp-child-reports' ),
61
- compact( 'name', 'menu_id' ),
62
- $menu_id,
63
- array( sanitize_title( $name ) => 'created' )
64
- );
65
- }
66
-
67
- public static function callback_wp_update_nav_menu( $menu_id, $menu_data = array() ) {
68
- if ( empty( $menu_data ) ) {
69
- return;
70
- }
71
-
72
- $name = $menu_data['menu-name'];
73
-
74
- self::log(
75
- _x( 'Updated menu "%s"', 'Menu name', 'mainwp-child-reports' ),
76
- compact( 'name', 'menu_id', 'menu_data' ),
77
- $menu_id,
78
- array( sanitize_title( $name ) => 'updated' )
79
- );
80
- }
81
-
82
- public static function callback_delete_nav_menu( $term, $tt_id, $deleted_term ) {
83
- $name = $deleted_term->name;
84
- $menu_id = $term;
85
-
86
- self::log(
87
- _x( 'Deleted "%s"', 'Menu name', 'mainwp-child-reports' ),
88
- compact( 'name', 'menu_id' ),
89
- $menu_id,
90
- array( sanitize_title( $name ) => 'deleted' )
91
- );
92
- }
93
-
94
-
95
-
96
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/posts.php DELETED
@@ -1,257 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Posts extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'posts';
6
-
7
- public static $actions = array(
8
- 'transition_post_status',
9
- 'deleted_post',
10
- );
11
-
12
- public static function get_label() {
13
- return __( 'Posts', 'default' );
14
- }
15
-
16
- public static function get_action_labels() {
17
- return array(
18
- 'updated' => __( 'Updated', 'mainwp-child-reports' ),
19
- 'autosave' => __( 'Auto save', 'mainwp-child-reports' ),
20
- 'created' => __( 'Created', 'mainwp-child-reports' ),
21
- 'trashed' => __( 'Trashed', 'mainwp-child-reports' ),
22
- 'untrashed' => __( 'Restored', 'mainwp-child-reports' ),
23
- 'deleted' => __( 'Deleted', 'mainwp-child-reports' ),
24
- );
25
- }
26
-
27
- public static function get_context_labels() {
28
- global $wp_post_types;
29
- $post_types = wp_filter_object_list( $wp_post_types, array(), null, 'label' );
30
- $post_types = array_diff_key( $post_types, array_flip( self::get_ignored_post_types() ) );
31
-
32
- add_action( 'registered_post_type', array( __CLASS__, '_registered_post_type' ), 10, 2 );
33
-
34
- return $post_types;
35
- }
36
-
37
- public static function action_links( $links, $record ) {
38
- $post = get_post( $record->object_id );
39
-
40
- if ( $post && $post->post_status === mainwp_wp_stream_get_meta( $record->ID, 'new_status', true ) ) {
41
- $post_type_name = self::get_post_type_name( get_post_type( $post->ID ) );
42
-
43
- if ( 'trash' === $post->post_status ) {
44
- $untrash = wp_nonce_url(
45
- add_query_arg(
46
- array(
47
- 'action' => 'untrash',
48
- 'post' => $post->ID,
49
- ),
50
- admin_url( 'post.php' )
51
- ),
52
- sprintf( 'untrash-post_%d', $post->ID )
53
- );
54
-
55
- $delete = wp_nonce_url(
56
- add_query_arg(
57
- array(
58
- 'action' => 'delete',
59
- 'post' => $post->ID,
60
- ),
61
- admin_url( 'post.php' )
62
- ),
63
- sprintf( 'delete-post_%d', $post->ID )
64
- );
65
-
66
- $links[ sprintf( esc_html_x( 'Restore %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $untrash;
67
- $links[ sprintf( esc_html_x( 'Delete %s Permenantly', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = $delete;
68
- } else {
69
- $links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'mainwp-child-reports' ), $post_type_name ) ] = get_edit_post_link( $post->ID );
70
-
71
- if ( $view_link = get_permalink( $post->ID ) ) {
72
- $links[ esc_html__( 'View', 'default' ) ] = $view_link;
73
- }
74
-
75
- if ( $revision_id = mainwp_wp_stream_get_meta( $record->ID, 'revision_id', true ) ) {
76
- $links[ esc_html__( 'Revision', 'default' ) ] = get_edit_post_link( $revision_id );
77
- }
78
- }
79
- }
80
-
81
- return $links;
82
- }
83
-
84
- public static function _registered_post_type( $post_type, $args ) {
85
- $post_type_obj = get_post_type_object( $post_type );
86
- $label = $post_type_obj->label;
87
-
88
- MainWP_WP_Stream_Connectors::$term_labels['stream_context'][ $post_type ] = $label;
89
- }
90
-
91
- public static function callback_transition_post_status( $new, $old, $post ) {
92
- if ( in_array( $post->post_type, self::get_ignored_post_types() ) ) {
93
- return;
94
- }
95
-
96
- if ( in_array( $new, array( 'auto-draft', 'inherit' ) ) ) {
97
- return;
98
- } elseif ( $old == 'auto-draft' && $new == 'draft' ) {
99
- $message = _x(
100
- '"%1$s" %2$s drafted',
101
- '1: Post title, 2: Post type singular name',
102
- 'mainwp_child_reports'
103
- );
104
- $action = 'created';
105
- } elseif ( $old == 'auto-draft' && ( in_array( $new, array( 'publish', 'private' ) ) ) ) {
106
- $message = _x(
107
- '"%1$s" %2$s published',
108
- '1: Post title, 2: Post type singular name',
109
- 'mainwp_child_reports'
110
- );
111
- $action = 'created';
112
- } elseif ( $old == 'draft' && ( in_array( $new, array( 'publish', 'private' ) ) ) ) {
113
- $message = _x(
114
- '"%1$s" %2$s published',
115
- '1: Post title, 2: Post type singular name',
116
- 'mainwp_child_reports'
117
- );
118
- } elseif ( $old == 'publish' && ( in_array( $new, array( 'draft' ) ) ) ) {
119
- $message = _x(
120
- '"%1$s" %2$s unpublished',
121
- '1: Post title, 2: Post type singular name',
122
- 'mainwp_child_reports'
123
- );
124
- } elseif ( $new == 'trash' ) {
125
- $message = _x(
126
- '"%1$s" %2$s trashed',
127
- '1: Post title, 2: Post type singular name',
128
- 'mainwp_child_reports'
129
- );
130
- $action = 'trashed';
131
- } elseif ( $old == 'trash' && $new != 'trash' ) {
132
- $message = _x(
133
- '"%1$s" %2$s restored from trash',
134
- '1: Post title, 2: Post type singular name',
135
- 'mainwp_child_reports'
136
- );
137
- $action = 'untrashed';
138
- } else {
139
- $message = _x(
140
- '"%1$s" %2$s updated',
141
- '1: Post title, 2: Post type singular name',
142
- 'mainwp_child_reports'
143
- );
144
- if (defined( 'DOING_AUTOSAVE' ) )
145
- $action = 'autosave';
146
- }
147
-
148
- if ( empty( $action ) ) {
149
- $action = 'updated';
150
- }
151
-
152
- $revision_id = null;
153
-
154
- if ( wp_revisions_enabled( $post ) ) {
155
- $revision = get_children(
156
- array(
157
- 'post_type' => 'revision',
158
- 'post_status' => 'inherit',
159
- 'post_parent' => $post->ID,
160
- 'posts_per_page' => 1,
161
- 'order' => 'desc',
162
- 'fields' => 'ids',
163
- )
164
- );
165
- if ( $revision ) {
166
- $revision_id = $revision[0];
167
- }
168
- }
169
-
170
- $post_type_name = strtolower( self::get_post_type_name( $post->post_type ) );
171
-
172
- if ($action == 'updated' && ($post->post_type == 'page' || $post->post_type == 'post')) {
173
- $report_settings = get_option('mainwp_wp_stream', array());
174
- $minutes = is_array($report_settings) && isset($report_settings['general_period_of_time']) ? $report_settings['general_period_of_time'] : 30;
175
- if (!empty($minutes) && intval($minutes) > 0) {
176
- $args = array();
177
- $args['object_id'] = $post->ID;
178
- $date_from = time() - $minutes * 60;
179
- $args['datetime_from'] = date( 'Y-m-d H:i:s', $date_from );
180
- $args['context'] = $post->post_type;
181
- $args['action'] = 'updated';
182
- $args['records_per_page'] = 9999;
183
- $args['orderby'] = 'created';
184
- $args['order'] = 'desc';
185
- $items = mainwp_wp_stream_query( $args );
186
- if (count($items) > 0)
187
- return;
188
- }
189
- }
190
-
191
- self::log(
192
- $message,
193
- array(
194
- 'post_title' => $post->post_title,
195
- 'singular_name' => $post_type_name,
196
- 'new_status' => $new,
197
- 'old_status' => $old,
198
- 'revision_id' => $revision_id,
199
- ),
200
- $post->ID,
201
- array( $post->post_type => $action )
202
- );
203
- }
204
-
205
- public static function callback_deleted_post( $post_id ) {
206
- $post = get_post( $post_id );
207
-
208
- // We check if post is an instance of WP_Post as it doesn't always resolve in unit testing
209
- if ( ! ( $post instanceof WP_Post ) || in_array( $post->post_type, self::get_ignored_post_types() ) ) {
210
- return;
211
- }
212
-
213
- // Ignore auto-drafts that are deleted by the system, see issue-293
214
- if ( 'auto-draft' === $post->post_status ) {
215
- return;
216
- }
217
-
218
- $post_type_name = strtolower( self::get_post_type_name( $post->post_type ) );
219
-
220
- self::log(
221
- _x(
222
- '"%1$s" %2$s deleted from trash',
223
- '1: Post title, 2: Post type singular name',
224
- 'mainwp_child_reports'
225
- ),
226
- array(
227
- 'post_title' => $post->post_title,
228
- 'singular_name' => $post_type_name,
229
- ),
230
- $post->ID,
231
- array( $post->post_type => 'deleted' )
232
- );
233
- }
234
-
235
- public static function get_ignored_post_types() {
236
- return apply_filters(
237
- 'mainwp_wp_stream_post_exclude_post_types',
238
- array(
239
- 'nav_menu_item',
240
- 'attachment',
241
- 'revision',
242
- )
243
- );
244
- }
245
-
246
- private static function get_post_type_name( $post_type_slug ) {
247
- $name = __( 'Post', 'default' ); // Default
248
-
249
- if ( post_type_exists( $post_type_slug ) ) {
250
- $post_type = get_post_type_object( $post_type_slug );
251
- $name = $post_type->labels->singular_name;
252
- }
253
-
254
- return $name;
255
- }
256
-
257
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/updraftplus.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Updraftplus extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'updraftplus_backups';
6
-
7
- public static $actions = array(
8
- 'updraftplus_backup',
9
- );
10
-
11
- public static function get_label() {
12
- return __( 'Updraftplus', 'default' );
13
- }
14
-
15
- public static function get_action_labels() {
16
- return array(
17
- 'updraftplus_backup' => __( 'Updraftplus Backup', 'default' ),
18
- );
19
- }
20
-
21
- public static function get_context_labels() {
22
- return array(
23
- 'updraftplus_backups' => __( 'Updraftplus Backups', 'mainwp-child-reports' ),
24
- );
25
- }
26
-
27
- public static function action_links( $links, $record ) {
28
- if (isset($record->object_id)) {
29
- }
30
- return $links;
31
- }
32
-
33
- public static function callback_updraftplus_backup($destination, $message, $status, $type, $backup_time) {
34
- self::log(
35
- $message,
36
- compact('destination', 'status', 'type', 'backup_time'),
37
- 0,
38
- array( 'updraftplus_backups' => 'updraftplus_backup' )
39
- );
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/users.php DELETED
@@ -1,172 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Users extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'users';
6
-
7
- protected static $_users_object_pre_deleted = array();
8
-
9
- public static $actions = array(
10
- 'user_register',
11
- 'profile_update',
12
- 'delete_user',
13
- 'deleted_user',
14
- 'set_user_role',
15
- );
16
-
17
- public static function get_label() {
18
- return __( 'Users', 'default' );
19
- }
20
-
21
- public static function get_action_labels() {
22
- return array(
23
- 'updated' => __( 'Updated', 'mainwp-child-reports' ),
24
- 'created' => __( 'Created', 'mainwp-child-reports' ),
25
- 'deleted' => __( 'Deleted', 'mainwp-child-reports' ),
26
- // 'password-reset' => __( 'Password Reset', 'default' ),
27
- // 'forgot-password' => __( 'Lost Password', 'default' ),
28
- // 'login' => __( 'Log In', 'default' ),
29
- // 'logout' => __( 'Log Out', 'default' ),
30
- );
31
- }
32
-
33
- public static function get_context_labels() {
34
- return array(
35
- 'users' => __( 'Users', 'default' ),
36
- 'sessions' => __( 'Sessions', 'mainwp-child-reports' ),
37
- 'profiles' => __( 'Profiles', 'mainwp-child-reports' ),
38
- );
39
- }
40
-
41
- public static function action_links( $links, $record ) {
42
- if ( $record->object_id ) {
43
- if ( $link = get_edit_user_link( $record->object_id ) ) {
44
- $links [ __( 'Edit User', 'default' ) ] = $link;
45
- }
46
- }
47
-
48
- return $links;
49
- }
50
-
51
- public static function get_role_labels( $user ) {
52
- if ( is_int( $user ) ) {
53
- $user = get_user_by( 'id', $user );
54
- }
55
-
56
- if ( ! is_a( $user, 'WP_User' ) ) {
57
- return array();
58
- }
59
-
60
- global $wp_roles;
61
-
62
- $roles = $wp_roles->get_names();
63
- $labels = array();
64
-
65
- foreach ( $roles as $role => $label ) {
66
- if ( in_array( $role, (array) $user->roles ) ) {
67
- $labels[] = translate_user_role( $label );
68
- }
69
- }
70
-
71
- return $labels;
72
- }
73
-
74
- public static function callback_user_register( $user_id ) {
75
- $current_user = wp_get_current_user();
76
- $registered_user = get_user_by( 'id', $user_id );
77
-
78
- if ( ! $current_user->ID ) { // Non logged-in user registered themselves
79
- $message = __( 'New user registration', 'mainwp-child-reports' );
80
- $user_to_log = $registered_user->ID;
81
- } else { // Current logged-in user created a new user
82
- $message = _x(
83
- 'New user account created for %1$s (%2$s)',
84
- '1: User display name, 2: User role',
85
- 'mainwp_child_reports'
86
- );
87
- $user_to_log = $current_user->ID;
88
- }
89
-
90
- self::log(
91
- $message,
92
- array(
93
- 'display_name' => ( $registered_user->display_name ) ? $registered_user->display_name : $registered_user->user_login,
94
- 'roles' => implode( ', ', self::get_role_labels( $user_id ) ),
95
- ),
96
- $registered_user->ID,
97
- array( 'users' => 'created' ),
98
- $user_to_log
99
- );
100
- }
101
-
102
- public static function callback_profile_update( $user_id, $user ) {
103
- self::log(
104
- __( '%s\'s profile was updated', 'mainwp-child-reports' ),
105
- array(
106
- 'display_name' => $user->display_name,
107
- ),
108
- $user->ID,
109
- array( 'profiles' => 'updated' )
110
- );
111
- }
112
-
113
- public static function callback_set_user_role( $user_id, $new_role, $old_roles ) {
114
- if ( empty( $old_roles ) ) {
115
- return;
116
- }
117
-
118
- global $wp_roles;
119
-
120
- self::log(
121
- _x(
122
- '%1$s\'s role was changed from %2$s to %3$s',
123
- '1: User display name, 2: Old role, 3: New role',
124
- 'mainwp_child_reports'
125
- ),
126
- array(
127
- 'display_name' => get_user_by( 'id', $user_id )->display_name,
128
- 'old_role' => translate_user_role( $wp_roles->role_names[ $old_roles[0] ] ),
129
- 'new_role' => translate_user_role( $wp_roles->role_names[ $new_role ] ),
130
- ),
131
- $user_id,
132
- array( 'profiles' => 'updated' )
133
- );
134
- }
135
-
136
- public static function callback_delete_user( $user_id ) {
137
- if ( ! isset( self::$_users_object_pre_deleted[ $user_id ] ) ) {
138
- self::$_users_object_pre_deleted[ $user_id ] = get_user_by( 'id', $user_id );
139
- }
140
- }
141
-
142
- public static function callback_deleted_user( $user_id ) {
143
- $user = wp_get_current_user();
144
-
145
- if ( isset( self::$_users_object_pre_deleted[ $user_id ] ) ) {
146
- $message = _x(
147
- '%1$s\'s account was deleted (%2$s)',
148
- '1: User display name, 2: User roles',
149
- 'mainwp_child_reports'
150
- );
151
- $display_name = self::$_users_object_pre_deleted[ $user_id ]->display_name;
152
- $deleted_user = self::$_users_object_pre_deleted[ $user_id ];
153
- unset( self::$_users_object_pre_deleted[ $user_id ] );
154
- } else {
155
- $message = __( 'User account #%d was deleted', 'mainwp-child-reports' );
156
- $display_name = $user_id;
157
- $deleted_user = $user_id;
158
- }
159
-
160
- self::log(
161
- $message,
162
- array(
163
- 'display_name' => $display_name,
164
- 'roles' => implode( ', ', self::get_role_labels( $deleted_user ) ),
165
- ),
166
- $user_id,
167
- array( 'users' => 'deleted' ),
168
- $user->ID
169
- );
170
- }
171
-
172
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/widgets.php DELETED
@@ -1,596 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Widgets extends MainWP_WP_Stream_Connector {
4
-
5
- public static $verbose_widget_created_deleted_actions = false;
6
-
7
- public static $name = 'widgets';
8
-
9
- public static $actions = array(
10
- 'update_option_sidebars_widgets',
11
- 'updated_option',
12
- );
13
-
14
- protected static $customizer_initial_sidebars_widgets = null;
15
-
16
- public static function get_label() {
17
- return __( 'Widgets', 'default' );
18
- }
19
-
20
- public static function get_action_labels() {
21
- return array(
22
- 'added' => __( 'Added', 'mainwp-child-reports' ),
23
- 'removed' => __( 'Removed', 'mainwp-child-reports' ),
24
- 'moved' => __( 'Moved', 'mainwp-child-reports' ),
25
- 'created' => __( 'Created', 'mainwp-child-reports' ),
26
- 'deleted' => __( 'Deleted', 'mainwp-child-reports' ),
27
- 'deactivated' => __( 'Deactivated', 'mainwp-child-reports' ),
28
- 'reactivated' => __( 'Reactivated', 'mainwp-child-reports' ),
29
- 'updated' => __( 'Updated', 'mainwp-child-reports' ),
30
- 'sorted' => __( 'Sorted', 'mainwp-child-reports' ),
31
- );
32
- }
33
-
34
- public static function get_context_labels() {
35
- global $wp_registered_sidebars;
36
-
37
- $labels = array();
38
-
39
- foreach ( $wp_registered_sidebars as $sidebar ) {
40
- $labels[ $sidebar['id'] ] = $sidebar['name'];
41
- }
42
-
43
- $labels['wp_inactive_widgets'] = __( 'Inactive Widgets', 'default' );
44
- $labels['orphaned_widgets'] = __( 'Orphaned Widgets', 'mainwp-child-reports' );
45
- $labels[''] = __( 'Unknown', 'mainwp-child-reports' );
46
-
47
- return $labels;
48
- }
49
-
50
- public static function action_links( $links, $record ) {
51
- if ( $sidebar = mainwp_wp_stream_get_meta( $record->ID, 'sidebar_id', true ) ) {
52
- global $wp_registered_sidebars;
53
-
54
- if ( array_key_exists( $sidebar, $wp_registered_sidebars ) ) {
55
- $links[ __( 'Edit Widget Area', 'mainwp-child-reports' ) ] = admin_url( 'widgets.php#' . $sidebar ); // xss ok (@todo fix WPCS rule)
56
- }
57
- }
58
-
59
- return $links;
60
- }
61
-
62
- public static function callback_update_option_sidebars_widgets( $old, $new ) {
63
- // Disable listener if we're switching themes
64
- if ( did_action( 'after_switch_theme' ) ) {
65
- return;
66
- }
67
-
68
- if ( did_action( 'customize_save' ) ) {
69
- if ( is_null( self::$customizer_initial_sidebars_widgets ) ) {
70
- self::$customizer_initial_sidebars_widgets = $old;
71
- add_action( 'customize_save_after', array( __CLASS__, '_callback_customize_save_after' ) );
72
- }
73
- } else {
74
- self::handle_sidebars_widgets_changes( $old, $new );
75
- }
76
- }
77
-
78
- public static function _callback_customize_save_after() {
79
- $old_sidebars_widgets = self::$customizer_initial_sidebars_widgets;
80
- $new_sidebars_widgets = get_option( 'sidebars_widgets' );
81
-
82
- self::handle_sidebars_widgets_changes( $old_sidebars_widgets, $new_sidebars_widgets );
83
- }
84
-
85
- protected static function handle_sidebars_widgets_changes( $old, $new ) {
86
- unset( $old['array_version'] );
87
- unset( $new['array_version'] );
88
-
89
- if ( $old === $new ) {
90
- return;
91
- }
92
-
93
- self::handle_deactivated_widgets( $old, $new );
94
- self::handle_reactivated_widgets( $old, $new );
95
- self::handle_widget_removal( $old, $new );
96
- self::handle_widget_addition( $old, $new );
97
- self::handle_widget_reordering( $old, $new );
98
- self::handle_widget_moved( $old, $new );
99
- }
100
-
101
- static protected function handle_deactivated_widgets( $old, $new ) {
102
- $new_deactivated_widget_ids = array_diff( $new['wp_inactive_widgets'], $old['wp_inactive_widgets'] );
103
-
104
- foreach ( $new_deactivated_widget_ids as $widget_id ) {
105
- $sidebar_id = '';
106
-
107
- foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
108
- if ( in_array( $widget_id, $old_widget_ids ) ) {
109
- $sidebar_id = $old_sidebar_id;
110
- break;
111
- }
112
- }
113
-
114
- $action = 'deactivated';
115
- $name = self::get_widget_name( $widget_id );
116
- $title = self::get_widget_title( $widget_id );
117
- $labels = self::get_context_labels();
118
- $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
119
-
120
- if ( $name && $title ) {
121
- $message = _x( '%1$s widget named "%2$s" from "%3$s" deactivated', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
122
- } elseif ( $name ) {
123
- // Empty title, but we have the name
124
- $message = _x( '%1$s widget from "%3$s" deactivated', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
125
- } elseif ( $title ) {
126
- // Likely a single widget since no name is available
127
- $message = _x( 'Unknown widget type named "%2$s" from "%3$s" deactivated', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
128
- } else {
129
- // Neither a name nor a title are available, so use the widget ID
130
- $message = _x( '%4$s widget from "%3$s" deactivated', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
131
- }
132
-
133
- $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
134
-
135
- self::log(
136
- $message,
137
- compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
138
- null,
139
- array( 'wp_inactive_widgets' => $action )
140
- );
141
- }
142
- }
143
-
144
- static protected function handle_reactivated_widgets( $old, $new ) {
145
- $new_reactivated_widget_ids = array_diff( $old['wp_inactive_widgets'], $new['wp_inactive_widgets'] );
146
-
147
- foreach ( $new_reactivated_widget_ids as $widget_id ) {
148
- $sidebar_id = '';
149
-
150
- foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
151
- if ( in_array( $widget_id, $new_widget_ids ) ) {
152
- $sidebar_id = $new_sidebar_id;
153
- break;
154
- }
155
- }
156
-
157
- $action = 'reactivated';
158
- $name = self::get_widget_name( $widget_id );
159
- $title = self::get_widget_title( $widget_id );
160
-
161
- if ( $name && $title ) {
162
- $message = _x( '%1$s widget named "%2$s" reactivated', '1: Name, 2: Title', 'mainwp-child-reports' );
163
- } elseif ( $name ) {
164
- // Empty title, but we have the name
165
- $message = _x( '%1$s widget reactivated', '1: Name', 'mainwp-child-reports' );
166
- } elseif ( $title ) {
167
- // Likely a single widget since no name is available
168
- $message = _x( 'Unknown widget type named "%2$s" reactivated', '2: Title', 'mainwp-child-reports' );
169
- } else {
170
- // Neither a name nor a title are available, so use the widget ID
171
- $message = _x( '%3$s widget reactivated', '3: Widget ID', 'mainwp-child-reports' );
172
- }
173
-
174
- $message = sprintf( $message, $name, $title, $widget_id );
175
-
176
- self::log(
177
- $message,
178
- compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
179
- null,
180
- array( $sidebar_id => $action )
181
- );
182
- }
183
- }
184
-
185
- static protected function handle_widget_removal( $old, $new ) {
186
- $all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
187
- $all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
188
-
189
- $deleted_widget_ids = array_diff( $all_old_widget_ids, $all_new_widget_ids );
190
-
191
- foreach ( $deleted_widget_ids as $widget_id ) {
192
- $sidebar_id = '';
193
-
194
- foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
195
- if ( in_array( $widget_id, $old_widget_ids ) ) {
196
- $sidebar_id = $old_sidebar_id;
197
- break;
198
- }
199
- }
200
-
201
- $action = 'removed';
202
- $name = self::get_widget_name( $widget_id );
203
- $title = self::get_widget_title( $widget_id );
204
- $labels = self::get_context_labels();
205
- $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
206
-
207
- if ( $name && $title ) {
208
- $message = _x( '%1$s widget named "%2$s" removed from "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
209
- } elseif ( $name ) {
210
- // Empty title, but we have the name
211
- $message = _x( '%1$s widget removed from "%3$s"', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
212
- } elseif ( $title ) {
213
- // Likely a single widget since no name is available
214
- $message = _x( 'Unknown widget type named "%2$s" removed from "%3$s"', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
215
- } else {
216
- // Neither a name nor a title are available, so use the widget ID
217
- $message = _x( '%4$s widget removed from "%3$s"', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
218
- }
219
-
220
- $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
221
-
222
- self::log(
223
- $message,
224
- compact( 'widget_id', 'sidebar_id' ),
225
- null,
226
- array( $sidebar_id => $action )
227
- );
228
- }
229
- }
230
-
231
- static protected function handle_widget_addition( $old, $new ) {
232
- $all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
233
- $all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
234
- $added_widget_ids = array_diff( $all_new_widget_ids, $all_old_widget_ids );
235
-
236
- foreach ( $added_widget_ids as $widget_id ) {
237
- $sidebar_id = '';
238
-
239
- foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
240
- if ( in_array( $widget_id, $new_widget_ids ) ) {
241
- $sidebar_id = $new_sidebar_id;
242
- break;
243
- }
244
- }
245
-
246
- $action = 'added';
247
- $name = self::get_widget_name( $widget_id );
248
- $title = self::get_widget_title( $widget_id );
249
- $labels = self::get_context_labels();
250
- $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
251
-
252
- if ( $name && $title ) {
253
- $message = _x( '%1$s widget named "%2$s" added to "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
254
- } elseif ( $name ) {
255
- // Empty title, but we have the name
256
- $message = _x( '%1$s widget added to "%3$s"', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
257
- } elseif ( $title ) {
258
- // Likely a single widget since no name is available
259
- $message = _x( 'Unknown widget type named "%2$s" added to "%3$s"', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
260
- } else {
261
- // Neither a name nor a title are available, so use the widget ID
262
- $message = _x( '%4$s widget added to "%3$s"', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
263
- }
264
-
265
- $message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
266
-
267
- self::log(
268
- $message,
269
- compact( 'widget_id', 'sidebar_id' ), // @todo Do we care about sidebar_id in meta if it is already context? But there is no 'context' for what the context signifies
270
- null,
271
- array( $sidebar_id => $action )
272
- );
273
- }
274
- }
275
-
276
- static protected function handle_widget_reordering( $old, $new ) {
277
- $all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
278
-
279
- foreach ( $all_sidebar_ids as $sidebar_id ) {
280
- if ( $old[ $sidebar_id ] === $new[ $sidebar_id ] ) {
281
- continue;
282
- }
283
-
284
- // Use intersect to ignore widget additions and removals
285
- $all_widget_ids = array_unique( array_merge( $old[ $sidebar_id ], $new[ $sidebar_id ] ) );
286
- $common_widget_ids = array_intersect( $old[ $sidebar_id ], $new[ $sidebar_id ] );
287
- $uncommon_widget_ids = array_diff( $all_widget_ids, $common_widget_ids );
288
- $new_widget_ids = array_values( array_diff( $new[ $sidebar_id ], $uncommon_widget_ids ) );
289
- $old_widget_ids = array_values( array_diff( $old[ $sidebar_id ], $uncommon_widget_ids ) );
290
- $widget_order_changed = ( $new_widget_ids !== $old_widget_ids );
291
-
292
- if ( $widget_order_changed ) {
293
- $labels = self::get_context_labels();
294
- $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
295
- $old_widget_ids = $old[ $sidebar_id ];
296
- $message = _x( 'Widgets reordered in "%s"', 'Sidebar name', 'mainwp-child-reports' );
297
- $message = sprintf( $message, $sidebar_name );
298
-
299
- self::log(
300
- $message,
301
- compact( 'sidebar_id', 'old_widget_ids' ),
302
- null,
303
- array( $sidebar_id => 'sorted' )
304
- );
305
- }
306
- }
307
-
308
- }
309
-
310
- static protected function handle_widget_moved( $old, $new ) {
311
- $all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
312
-
313
- foreach ( $all_sidebar_ids as $new_sidebar_id ) {
314
- if ( $old[ $new_sidebar_id ] === $new[ $new_sidebar_id ] ) {
315
- continue;
316
- }
317
-
318
- $new_widget_ids = array_diff( $new[ $new_sidebar_id ], $old[ $new_sidebar_id ] );
319
-
320
- foreach ( $new_widget_ids as $widget_id ) {
321
- // Now find the sidebar that the widget was originally located in, as long it is not wp_inactive_widgets
322
- $old_sidebar_id = null;
323
- foreach ( $old as $sidebar_id => $old_widget_ids ) {
324
- if ( in_array( $widget_id, $old_widget_ids ) ) {
325
- $old_sidebar_id = $sidebar_id;
326
- break;
327
- }
328
- }
329
-
330
- if ( ! $old_sidebar_id || 'wp_inactive_widgets' === $old_sidebar_id || 'wp_inactive_widgets' === $new_sidebar_id ) {
331
- continue;
332
- }
333
-
334
- assert( $old_sidebar_id !== $new_sidebar_id );
335
-
336
- $name = self::get_widget_name( $widget_id );
337
- $title = self::get_widget_title( $widget_id );
338
- $labels = self::get_context_labels();
339
- $old_sidebar_name = isset( $labels[ $old_sidebar_id ] ) ? $labels[ $old_sidebar_id ] : $old_sidebar_id;
340
- $new_sidebar_name = isset( $labels[ $new_sidebar_id ] ) ? $labels[ $new_sidebar_id ] : $new_sidebar_id;
341
-
342
- if ( $name && $title ) {
343
- $message = _x( '%1$s widget named "%2$s" moved from "%4$s" to "%5$s"', '1: Name, 2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
344
- } elseif ( $name ) {
345
- // Empty title, but we have the name
346
- $message = _x( '%1$s widget moved from "%4$s" to "%5$s"', '1: Name, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
347
- } elseif ( $title ) {
348
- // Likely a single widget since no name is available
349
- $message = _x( 'Unknown widget type named "%2$s" moved from "%4$s" to "%5$s"', '2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
350
- } else {
351
- // Neither a name nor a title are available, so use the widget ID
352
- $message = _x( '%3$s widget moved from "%4$s" to "%5$s"', '3: Widget ID, 4: Old Sidebar Name, 5: New Sidebar Name', 'mainwp-child-reports' );
353
- }
354
-
355
- $message = sprintf( $message, $name, $title, $widget_id, $old_sidebar_name, $new_sidebar_name );
356
- $sidebar_id = $new_sidebar_id;
357
-
358
- self::log(
359
- $message,
360
- compact( 'widget_id', 'sidebar_id', 'old_sidebar_id' ),
361
- null,
362
- array( $sidebar_id => 'moved', )
363
- );
364
- }
365
- }
366
-
367
- }
368
-
369
- public static function callback_updated_option( $option_name, $old_value, $new_value ) {
370
- if ( ! preg_match( '/^widget_(.+)$/', $option_name, $matches ) || ! is_array( $new_value ) ) {
371
- return;
372
- }
373
-
374
- $is_multi = ! empty( $new_value['_multiwidget'] );
375
- $widget_id_base = $matches[1];
376
-
377
- $creates = array();
378
- $updates = array();
379
- $deletes = array();
380
-
381
- if ( $is_multi ) {
382
- $widget_id_format = "$widget_id_base-%d";
383
-
384
- unset( $new_value['_multiwidget'] );
385
- unset( $old_value['_multiwidget'] );
386
-
387
-
388
- $created_widget_numbers = array_diff( array_keys( $new_value ), array_keys( $old_value ) );
389
-
390
- foreach ( $created_widget_numbers as $widget_number ) {
391
- $instance = $new_value[ $widget_number ];
392
- $widget_id = sprintf( $widget_id_format, $widget_number );
393
- $name = self::get_widget_name( $widget_id );
394
- $title = ! empty( $instance['title'] ) ? $instance['title'] : null;
395
- $sidebar_id = self::get_widget_sidebar_id( $widget_id ); // @todo May not be assigned yet
396
-
397
- $creates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
398
- }
399
-
400
- $updated_widget_numbers = array_intersect( array_keys( $old_value ), array_keys( $new_value ) );
401
-
402
- foreach ( $updated_widget_numbers as $widget_number ) {
403
- $new_instance = $new_value[ $widget_number ];
404
- $old_instance = $old_value[ $widget_number ];
405
-
406
- if ( $old_instance !== $new_instance ) {
407
- $widget_id = sprintf( $widget_id_format, $widget_number );
408
- $name = self::get_widget_name( $widget_id );
409
- $title = ! empty( $new_instance['title'] ) ? $new_instance['title'] : null;
410
- $sidebar_id = self::get_widget_sidebar_id( $widget_id );
411
- $labels = self::get_context_labels();
412
- $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
413
-
414
- $updates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'old_instance', 'sidebar_name' );
415
- }
416
- }
417
-
418
- $deleted_widget_numbers = array_diff( array_keys( $old_value ), array_keys( $new_value ) );
419
-
420
- foreach ( $deleted_widget_numbers as $widget_number ) {
421
- $instance = $old_value[ $widget_number ];
422
- $widget_id = sprintf( $widget_id_format, $widget_number );
423
- $name = self::get_widget_name( $widget_id );
424
- $title = ! empty( $instance['title'] ) ? $instance['title'] : null;
425
- $sidebar_id = self::get_widget_sidebar_id( $widget_id ); // @todo May not be assigned anymore
426
-
427
- $deletes[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
428
- }
429
- } else {
430
- $widget_id = $widget_id_base;
431
- $name = $widget_id; // There aren't names available for single widgets
432
- $title = ! empty( $new_value['title'] ) ? $new_value['title'] : null;
433
- $sidebar_id = self::get_widget_sidebar_id( $widget_id );
434
- $old_instance = $old_value;
435
- $labels = self::get_context_labels();
436
- $sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
437
-
438
- $updates[] = compact( 'widget_id', 'title', 'name', 'sidebar_id', 'old_instance', 'sidebar_name' );
439
- }
440
-
441
- foreach ( $updates as $update ) {
442
- if ( $update['name'] && $update['title'] ) {
443
- $message = _x( '%1$s widget named "%2$s" in "%3$s" updated', '1: Name, 2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
444
- } elseif ( $update['name'] ) {
445
- // Empty title, but we have the name
446
- $message = _x( '%1$s widget in "%3$s" updated', '1: Name, 3: Sidebar Name', 'mainwp-child-reports' );
447
- } elseif ( $update['title'] ) {
448
- // Likely a single widget since no name is available
449
- $message = _x( 'Unknown widget type named "%2$s" in "%3$s" updated', '2: Title, 3: Sidebar Name', 'mainwp-child-reports' );
450
- } else {
451
- // Neither a name nor a title are available, so use the widget ID
452
- $message = _x( '%4$s widget in "%3$s" updated', '4: Widget ID, 3: Sidebar Name', 'mainwp-child-reports' );
453
- }
454
-
455
- $message = sprintf( $message, $update['name'], $update['title'], $update['sidebar_name'], $update['widget_id'] );
456
- $contexts = array( $update['sidebar_id'] => 'updated' );
457
-
458
- unset( $update['title'], $update['name'] );
459
-
460
- self::log( $message, $update, null, $contexts );
461
- }
462
-
463
- if ( self::$verbose_widget_created_deleted_actions ) {
464
- foreach ( $creates as $create ) {
465
- if ( $create['name'] && $create['title'] ) {
466
- $message = _x( '%1$s widget named "%2$s" created', '1: Name, 2: Title', 'mainwp-child-reports' );
467
- } elseif ( $create['name'] ) {
468
- // Empty title, but we have the name
469
- $message = _x( '%1$s widget created', '1: Name', 'mainwp-child-reports' );
470
- } elseif ( $create['title'] ) {
471
- // Likely a single widget since no name is available
472
- $message = _x( 'Unknown widget type named "%2$s" created', '2: Title', 'mainwp-child-reports' );
473
- } else {
474
- // Neither a name nor a title are available, so use the widget ID
475
- $message = _x( '%3$s widget created', '3: Widget ID', 'mainwp-child-reports' );
476
- }
477
-
478
- $message = sprintf( $message, $create['name'], $create['title'], $create['widget_id'] );
479
- $contexts = array( $create['sidebar_id'] => 'created' );
480
-
481
- unset( $create['title'], $create['name'] );
482
-
483
- self::log( $message, $create, null, $contexts );
484
- }
485
-
486
- foreach ( $deletes as $delete ) {
487
- if ( $delete['name'] && $delete['title'] ) {
488
- $message = _x( '%1$s widget named "%2$s" deleted', '1: Name, 2: Title', 'mainwp-child-reports' );
489
- } elseif ( $delete['name'] ) {
490
- // Empty title, but we have the name
491
- $message = _x( '%1$s widget deleted', '1: Name', 'mainwp-child-reports' );
492
- } elseif ( $delete['title'] ) {
493
- // Likely a single widget since no name is available
494
- $message = _x( 'Unknown widget type named "%2$s" deleted', '2: Title', 'mainwp-child-reports' );
495
- } else {
496
- // Neither a name nor a title are available, so use the widget ID
497
- $message = _x( '%3$s widget deleted', '3: Widget ID', 'mainwp-child-reports' );
498
- }
499
-
500
- $message = sprintf( $message, $delete['name'], $delete['title'], $delete['widget_id'] );
501
- $contexts = array( $delete['sidebar_id'] => 'deleted' );
502
-
503
- unset( $delete['title'], $delete['name'] );
504
-
505
- self::log( $message, $delete, null, $contexts );
506
- }
507
- }
508
- }
509
-
510
- public static function get_widget_title( $widget_id ) {
511
- $instance = self::get_widget_instance( $widget_id );
512
- return ! empty( $instance['title'] ) ? $instance['title'] : null;
513
- }
514
-
515
- public static function get_widget_name( $widget_id ) {
516
- $widget_obj = self::get_widget_object( $widget_id );
517
- return $widget_obj ? $widget_obj->name : null;
518
- }
519
-
520
- public static function parse_widget_id( $widget_id ) {
521
- if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
522
- return array(
523
- 'id_base' => $matches[1],
524
- 'widget_number' => intval( $matches[2] ),
525
- );
526
- } else {
527
- return null;
528
- }
529
- }
530
-
531
- public static function get_widget_object( $widget_id ) {
532
- global $wp_widget_factory;
533
-
534
- $parsed_widget_id = self::parse_widget_id( $widget_id );
535
-
536
- if ( ! $parsed_widget_id ) {
537
- return null;
538
- }
539
-
540
- $id_base = $parsed_widget_id['id_base'];
541
-
542
- $id_base_to_widget_class_map = array_combine(
543
- wp_list_pluck( $wp_widget_factory->widgets, 'id_base' ),
544
- array_keys( $wp_widget_factory->widgets )
545
- );
546
-
547
- if ( ! isset( $id_base_to_widget_class_map[ $id_base ] ) ) {
548
- return null;
549
- }
550
-
551
- return $wp_widget_factory->widgets[ $id_base_to_widget_class_map[ $id_base ] ];
552
- }
553
-
554
- public static function get_widget_instance( $widget_id ) {
555
- $instance = null;
556
- $parsed_widget_id = self::parse_widget_id( $widget_id );
557
- $widget_obj = self::get_widget_object( $widget_id );
558
-
559
- if ( $widget_obj && $parsed_widget_id ) {
560
- $settings = $widget_obj->get_settings();
561
- $multi_number = $parsed_widget_id['widget_number'];
562
-
563
- if ( isset( $settings[ $multi_number ] ) && ! empty( $settings[ $multi_number ]['title'] ) ) {
564
- $instance = $settings[ $multi_number ];
565
- }
566
- } else {
567
- // Single widgets, try our best guess at the option used
568
- $potential_instance = get_option( "widget_{$widget_id}" );
569
-
570
- if ( ! empty( $potential_instance ) && ! empty( $potential_instance['title'] ) ) {
571
- $instance = $potential_instance;
572
- }
573
- }
574
-
575
- return $instance;
576
- }
577
-
578
- public static function get_sidebars_widgets() {
579
- return apply_filters( 'sidebars_widgets', get_option( 'sidebars_widgets', array() ) );
580
- }
581
-
582
- public static function get_widget_sidebar_id( $widget_id ) {
583
- $sidebars_widgets = self::get_sidebars_widgets();
584
-
585
- unset( $sidebars_widgets['array_version'] );
586
-
587
- foreach ( $sidebars_widgets as $sidebar_id => $widget_ids ) {
588
- if ( in_array( $widget_id, $widget_ids ) ) {
589
- return $sidebar_id;
590
- }
591
- }
592
-
593
- return 'orphaned_widgets';
594
- }
595
-
596
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/wordfence.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connector_Wordfence extends MainWP_WP_Stream_Connector {
4
-
5
- public static $name = 'wordfence_scan';
6
-
7
- public static $actions = array(
8
- 'mainwp_reports_wordfence_scan',
9
- );
10
-
11
- public static function get_label() {
12
- return __( 'Wordfence', 'default' );
13
- }
14
-
15
- public static function get_action_labels() {
16
- return array(
17
- 'mainwp_reports_wordfence_scan' => __( 'Wordfence scan', 'default' ),
18
- );
19
- }
20
-
21
- public static function get_context_labels() {
22
- return array(
23
- 'wordfence_scans' => __( 'Wordfence scan', 'mainwp-child-reports' ),
24
- );
25
- }
26
-
27
- public static function action_links( $links, $record ) {
28
- if (isset($record->object_id)) {
29
- }
30
- return $links;
31
- }
32
-
33
- public static function callback_mainwp_reports_wordfence_scan( $message, $scan_time, $details, $result = '') {
34
- self::log(
35
- $message,
36
- compact('scan_time', 'result', 'details'),
37
- 0,
38
- array( 'wordfence_scans' => 'mainwp_reports_wordfence_scan' )
39
- );
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
connectors/wptimecapsule.php DELETED
@@ -1,76 +0,0 @@
1
- <?php
2
- if ( class_exists( 'MainWP_WP_Stream_Connector' ) ) {
3
- class MainWP_WP_Stream_Connector_Wptimecapsule extends MainWP_WP_Stream_Connector {
4
-
5
- /**
6
- * Connector slug
7
- *
8
- * @var string
9
- */
10
- public static $name = 'wptimecapsule_backups';
11
-
12
- /**
13
- * Actions registered for this connector
14
- *
15
- * @var array
16
- */
17
- public static $actions = array(
18
- 'mainwp_wptimecapsule_backup',
19
- );
20
-
21
- /**
22
- * Return translated connector label
23
- *
24
- * @return string Translated connector label
25
- */
26
- public static function get_label() {
27
- return __( 'WP Time Capsule', 'mainwp-child' );
28
- }
29
-
30
- /**
31
- * Return translated action labels
32
- *
33
- * @return array Action label translations
34
- */
35
- public static function get_action_labels() {
36
- return array(
37
- 'mainwp_wptimecapsule_backup' => __( 'WP Time Capsule Backup', 'mainwp-child' ),
38
- );
39
- }
40
-
41
- /**
42
- * Return translated context labels
43
- *
44
- * @return array Context label translations
45
- */
46
- public static function get_context_labels() {
47
- return array(
48
- 'wptimecapsule_backups' => __( 'WP Time Capsule Backups', 'mainwp-child' ),
49
- );
50
- }
51
-
52
- /**
53
- * Add action links to Stream drop row in admin list screen
54
- *
55
- * @filter wp_stream_action_links_{connector}
56
- *
57
- * @param array $links Previous links registered
58
- * @param int $record Stream record
59
- *
60
- * @return array Action links
61
- */
62
- public static function action_links( $links, $record ) {
63
- return $links;
64
- }
65
-
66
- public static function callback_mainwp_wptimecapsule_backup( $message, $type, $backup_time ) {
67
- self::log(
68
- $message,
69
- compact( 'type', 'backup_time' ),
70
- 0,
71
- array( 'wptimecapsule_backups' => 'mainwp_wptimecapsule_backup' )
72
- );
73
- }
74
- }
75
- }
76
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
exporters/class-exporter-csv.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Exporter_CSV extends Exporter {
5
+ /**
6
+ * Exporter name
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'CSV';
11
+
12
+ /**
13
+ * Exporter slug
14
+ *
15
+ * @var string
16
+ */
17
+ public $slug = 'csv';
18
+
19
+ /**
20
+ * Outputs CSV data for download
21
+ *
22
+ * @param array $data Array of data to output.
23
+ * @param array $columns Column names included in data set.
24
+ * @return void
25
+ */
26
+ public function output_file( $data, $columns ) {
27
+ if ( ! defined( 'WP_MAINWP_STREAM_TESTS' ) || ( defined( 'WP_MAINWP_STREAM_TESTS' ) && ! WP_MAINWP_STREAM_TESTS ) ) {
28
+ header( 'Content-type: text/csv' );
29
+ header( 'Content-Disposition: attachment; filename="stream.csv"' );
30
+ }
31
+
32
+ $output = join( ',', array_values( $columns ) ) . "\n";
33
+ foreach ( $data as $row ) {
34
+ $output .= join( ',', $row ) . "\n";
35
+ }
36
+
37
+ echo $output; // @codingStandardsIgnoreLine text-only output
38
+ if ( ! defined( 'WP_MAINWP_STREAM_TESTS' ) || ( defined( 'WP_MAINWP_STREAM_TESTS' ) && ! WP_MAINWP_STREAM_TESTS ) ) {
39
+ exit;
40
+ }
41
+ }
42
+ }
exporters/class-exporter-json.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WP_MainWP_Stream;
3
+
4
+ class Exporter_JSON extends Exporter {
5
+ /**
6
+ * Exporter name
7
+ *
8
+ * @var string
9
+ */
10
+ public $name = 'JSON';
11
+
12
+ /**
13
+ * Exporter slug
14
+ *
15
+ * @var string
16
+ */
17
+ public $slug = 'json';
18
+
19
+ /**
20
+ * Outputs JSON data for download
21
+ *
22
+ * @param array $data Array of data to output.
23
+ * @param array $columns Column names included in data set.
24
+ * @return void
25
+ */
26
+ public function output_file( $data, $columns ) {
27
+ if ( ! defined( 'WP_MAINWP_STREAM_TESTS' ) || ( defined( 'WP_MAINWP_STREAM_TESTS' ) && ! WP_MAINWP_STREAM_TESTS ) ) {
28
+ header( 'Content-type: text/json' );
29
+ header( 'Content-Disposition: attachment; filename="stream.json"' );
30
+ }
31
+
32
+ if ( function_exists( 'wp_json_encode' ) ) {
33
+ $output = wp_json_encode( $data );
34
+ } else {
35
+ $output = json_encode( $data ); // @codingStandardsIgnoreLine fallback to discouraged function
36
+ }
37
+
38
+ echo $output; // @codingStandardsIgnoreLine text-only output
39
+
40
+ if ( ! defined( 'WP_MAINWP_STREAM_TESTS' ) || ( defined( 'WP_MAINWP_STREAM_TESTS' ) && ! WP_MAINWP_STREAM_TESTS ) ) {
41
+ exit;
42
+ }
43
+ }
44
+ }
includes/admin.php DELETED
@@ -1,560 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Admin {
4
-
5
- public static $screen_id = array();
6
-
7
- public static $list_table = null;
8
-
9
- public static $disable_access = false;
10
-
11
- const ADMIN_BODY_CLASS = 'mainwp_wp_stream_screen';
12
- const RECORDS_PAGE_SLUG = 'mainwp-reports-page';
13
- const SETTINGS_PAGE_SLUG = 'mainwp_wp_stream_settings';
14
- const ADMIN_PARENT_PAGE = 'options-general.php';
15
- const VIEW_CAP = 'view_stream';
16
- const SETTINGS_CAP = 'manage_options';
17
- const PRELOAD_AUTHORS_MAX = 50;
18
-
19
- public static function load() {
20
- // User and role caps
21
- add_filter( 'user_has_cap', array( __CLASS__, '_filter_user_caps' ), 10, 4 );
22
- add_filter( 'role_has_cap', array( __CLASS__, '_filter_role_caps' ), 10, 3 );
23
-
24
- self::$disable_access = apply_filters( 'mainwp_wp_stream_disable_admin_access', false );
25
-
26
- // Admin notices
27
- add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
28
-
29
- // Add admin body class
30
- add_filter( 'admin_body_class', array( __CLASS__, 'admin_body_class' ) );
31
-
32
- // Load admin scripts and styles
33
- add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
34
- add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_menu_css' ) );
35
-
36
- // Reset MainWP Reports database
37
- add_action( 'wp_ajax_mainwp_wp_stream_reset', array( __CLASS__, 'ajax_reset_reports' ) );
38
-
39
- // Reset MainWP Reports settings
40
- add_action( 'wp_ajax_mainwp_wp_stream_defaults', array( __CLASS__, 'wp_ajax_defaults' ) );
41
-
42
-
43
- // Auto purge setup
44
- add_action( 'wp_loaded', array( __CLASS__, 'purge_schedule_setup' ) );
45
- add_action( 'mainwp_wp_stream_auto_purge', array( __CLASS__, 'purge_scheduled_action' ) );
46
-
47
- // Admin notices
48
- add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
49
-
50
- // Ajax authors list
51
- add_action( 'wp_ajax_mainwp_wp_stream_filters', array( __CLASS__, 'ajax_filters' ) );
52
-
53
- // Ajax author's name by ID
54
- add_action( 'wp_ajax_mainwp_wp_stream_get_filter_value_by_id', array( __CLASS__, 'get_filter_value_by_id' ) );
55
-
56
- add_filter('updraftplus_save_last_backup', array( __CLASS__, 'hookUpdraftplusSaveLastBackup' ));
57
- // hmbkp_backup_complete
58
- add_action('mainwp_child_reports_log', array( __CLASS__, 'hook_reports_log' ), 10, 1);
59
- }
60
-
61
- public static function admin_notices() {
62
- $message = mainwp_wp_stream_filter_input( INPUT_GET, 'message' );
63
-
64
- switch ( $message ) {
65
- case 'child_reports_data_erased':
66
- printf( '<div class="updated"><p>%s</p></div>', __( 'All records have been successfully erased.', 'mainwp-child-reports' ) );
67
- break;
68
- case 'child_reports_settings_reset':
69
- printf( '<div class="updated"><p>%s</p></div>', __( 'All site settings have been successfully reset.', 'mainwp-child-reports' ) );
70
- break;
71
- }
72
- }
73
-
74
- public static function hookUpdraftplusSaveLastBackup($last_backup) {
75
-
76
- if (!is_array($last_backup))
77
- return $last_backup;
78
-
79
- if (isset($last_backup['backup_time'])) {
80
- if (empty($last_backup['success']))
81
- return $last_backup;
82
-
83
- $backup_time = $last_backup['backup_time'];
84
- $backup = $last_backup['backup_array'];
85
-
86
- $message = "";
87
- $backup_type = "";
88
- if (isset($backup['db'])) {
89
- $message .= "database, ";
90
- $backup_type .= "database, ";
91
- }
92
- if (isset($backup['plugins'])) {
93
- $message .= "plugins, ";
94
- $backup_type .= "plugins, ";
95
- }
96
-
97
- if (isset($backup['themes'])) {
98
- $message .= "themes, ";
99
- $backup_type .= "themes, ";
100
- }
101
-
102
- $message = rtrim($message, ', ');
103
- $message = "Updraftplus backup " . $message ." finished";
104
-
105
- $backup_type = rtrim($backup_type, ', ');
106
-
107
- $size = "N/A";
108
- if (isset($backup['db-size'])) {
109
- $size = $backup['db-size'];
110
- } else if (isset($backup['themes-size'])) {
111
- $size = $backup['themes-size'];
112
- }
113
- $destination = "";
114
-
115
- // to logging updraftplus backup
116
- do_action("updraftplus_backup", $destination , $message, __('Finished', 'mainwp-child-reports'), $backup_type, $backup_time);
117
- }
118
- return $last_backup;
119
- }
120
-
121
- public static function hook_reports_log($ext_name = '') {
122
- do_action('mainwp_child_log', $ext_name);
123
- }
124
-
125
- static function get_record_meta_data($record, $meta_key) {
126
-
127
- if (empty($record))
128
- return "";
129
- $value = "";
130
- if (isset($record->meta)) {
131
- $meta = $record->meta;
132
- if (isset($meta[$meta_key])) {
133
- $value = $meta[$meta_key];
134
- $value = current($value);
135
- if ($meta_key == "author_meta") {
136
- $value = unserialize($value);
137
- $value = $value['display_name'];
138
- }
139
-
140
- }
141
- }
142
- return $value;
143
- }
144
-
145
- public static function admin_enqueue_scripts( $hook ) {
146
- // wp_register_script( 'select2', MAINWP_WP_STREAM_URL . 'ui/select2/select2.min.js', array( 'jquery' ), '3.4.5', true );
147
- // wp_register_style( 'select2', MAINWP_WP_STREAM_URL . 'ui/select2/select2.css', array(), '3.4.5' );
148
- wp_register_script( 'timeago', MAINWP_WP_STREAM_URL . 'ui/timeago/jquery.timeago.js', array(), '1.4.1', true );
149
-
150
- $locale = strtolower( substr( get_locale(), 0, 2 ) );
151
- $file_tmpl = 'ui/timeago/locales/jquery.timeago.%s.js';
152
-
153
- if ( file_exists( MAINWP_WP_STREAM_DIR . sprintf( $file_tmpl, $locale ) ) ) {
154
- wp_register_script( 'timeago-locale', MAINWP_WP_STREAM_URL . sprintf( $file_tmpl, $locale ), array( 'timeago' ), '1' );
155
- } else {
156
- wp_register_script( 'timeago-locale', MAINWP_WP_STREAM_URL . sprintf( $file_tmpl, 'en' ), array( 'timeago' ), '1' );
157
- }
158
-
159
- wp_enqueue_style( 'mainwp-wp-stream-admin', MAINWP_WP_STREAM_URL . 'ui/admin.css', array(), MainWP_WP_Stream::VERSION );
160
-
161
- //$script_screens = array( 'plugins.php', 'user-edit.php', 'user-new.php', 'profile.php' );
162
-
163
- if ( 'index.php' === $hook ) {
164
-
165
- } elseif ( in_array( $hook, self::$screen_id ) || $hook == 'settings_page_mainwp-reports-page' ) {
166
- wp_register_script( 'child-report-select2', MAINWP_WP_STREAM_URL . 'ui/select2/select2.min.js', array( 'jquery' ), '3.4.5', true );
167
- wp_register_style( 'child-report-select2', MAINWP_WP_STREAM_URL . 'ui/select2/select2.css', array(), '3.4.5' );
168
-
169
- wp_enqueue_script( 'child-report-select2' );
170
- wp_enqueue_style( 'child-report-select2' );
171
-
172
- wp_enqueue_script( 'timeago' );
173
- wp_enqueue_script( 'timeago-locale' );
174
-
175
- wp_enqueue_script( 'mainwp-wp-stream-admin', MAINWP_WP_STREAM_URL . 'ui/admin.js', array( 'jquery', 'child-report-select2', 'heartbeat' ), MainWP_WP_Stream::VERSION );
176
- wp_localize_script(
177
- 'mainwp-wp-stream-admin',
178
- 'mainwp_wp_stream',
179
- array(
180
- 'i18n' => array(
181
- 'confirm_purge' => __( 'Are you sure you want to delete all MainWP Child Reports activity records from the database? This cannot be undone.', 'mainwp-child-reports' ),
182
- 'confirm_defaults' => __( 'Are you sure you want to reset all site settings to default? This cannot be undone.', 'mainwp-child-reports' ),
183
- 'confirm_uninstall' => __( 'Are you sure you want to uninstall and deactivate MainWP Child Reports? This will delete all MainWP Child Reports tables from the database and cannot be undone.', 'mainwp-child-reports' ),
184
- ),
185
- 'gmt_offset' => get_option( 'gmt_offset' ),
186
- 'current_screen' => $hook,
187
- 'current_page' => isset( $_GET['paged'] ) ? esc_js( $_GET['paged'] ) : '1',
188
- 'current_order' => isset( $_GET['order'] ) ? esc_js( $_GET['order'] ) : 'desc',
189
- 'current_query' => json_encode( $_GET ),
190
- 'current_query_count' => count( $_GET ),
191
- 'filters' => self::$list_table ? self::$list_table->get_filters() : false,
192
- 'locale' => esc_js( $locale )
193
- )
194
- );
195
- }
196
- }
197
-
198
-
199
- public static function admin_body_class( $classes ) {
200
- if ( isset( $_GET['page'] ) && false !== strpos( $_GET['page'], self::RECORDS_PAGE_SLUG ) ) {
201
- $classes .= sprintf( ' %s ', self::ADMIN_BODY_CLASS );
202
- }
203
-
204
- return $classes;
205
- }
206
-
207
- public static function admin_menu_css() {
208
- wp_register_style( 'jquery-ui', '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/themes/base/jquery-ui.css', array(), '1.10.1' );
209
- wp_register_style( 'mainwp-wp-stream-datepicker', MAINWP_WP_STREAM_URL . 'ui/datepicker.css', array( 'jquery-ui' ), MainWP_WP_Stream::VERSION );
210
-
211
- // Make sure we're working off a clean version
212
- include( ABSPATH . WPINC . '/version.php' );
213
- }
214
-
215
- public static function register_update_hook( $file, $callback, $version ) {
216
- if ( ! is_admin() ) {
217
- return;
218
- }
219
-
220
- $plugin = plugin_basename( $file );
221
-
222
- if ( is_plugin_active_for_network( $plugin ) ) {
223
- $current_versions = get_site_option( MainWP_WP_Stream_Install::KEY . '_connectors', array() );
224
- $network = true;
225
- } elseif ( is_plugin_active( $plugin ) ) {
226
- $current_versions = get_option( MainWP_WP_Stream_Install::KEY . '_connectors', array() );
227
- $network = false;
228
- } else {
229
- return;
230
- }
231
-
232
- if ( version_compare( $version, $current_versions[ $plugin ], '>' ) ) {
233
- call_user_func( $callback, $current_versions[ $plugin ], $network );
234
- $current_versions[ $plugin ] = $version;
235
- }
236
-
237
- if ( $network ) {
238
- update_site_option( MainWP_WP_Stream_Install::KEY . '_registered_connectors', $current_versions );
239
- } else {
240
- update_option( MainWP_WP_Stream_Install::KEY . '_registered_connectors', $current_versions );
241
- }
242
-
243
- return;
244
- }
245
-
246
- public static function register_list_table() {
247
- require_once MAINWP_WP_STREAM_INC_DIR . 'list-table.php';
248
- $param = array();
249
- if (isset(self::$screen_id['main'])) {
250
- $param['screen'] = self::$screen_id['main'];
251
- }
252
- self::$list_table = new MainWP_WP_Stream_List_Table( $param );
253
- }
254
-
255
- public static function render_reports_page() {
256
- do_action('mainwp-child-pageheader', 'reports-page');
257
- self::$list_table->prepare_items();
258
- echo '<div class="mainwp_child_reports_wrap">';
259
- self::$list_table->display();
260
- echo '</div>';
261
- do_action('mainwp-child-pagefooter', 'reports-page');
262
- }
263
-
264
- public static function render_reports_settings() {
265
-
266
- $option_key = MainWP_WP_Stream_Settings::$option_key;
267
- $form_action = apply_filters( 'mainwp_wp_stream_settings_form_action', admin_url( 'options.php' ) );
268
- $sections = MainWP_WP_Stream_Settings::get_fields();
269
- //settings_errors();
270
- do_action('mainwp-child-pageheader', 'reports-settings')
271
- ?>
272
- <div class="postbox">
273
- <div class="inside">
274
- <form method="post" action="<?php echo esc_attr( $form_action ) ?>" enctype="multipart/form-data">
275
- <?php
276
- $i = 0;
277
- foreach ( $sections as $section => $data ) {
278
- $i++;
279
- settings_fields( $option_key );
280
- do_settings_sections( $option_key );
281
- }
282
- submit_button();
283
- ?>
284
- </form>
285
- </div>
286
- </div>
287
-
288
- <?php
289
- do_action('mainwp-child-pagefooter', 'reports-settings');
290
- }
291
-
292
- public static function ajax_reset_reports() {
293
- check_ajax_referer( 'stream_nonce', 'mainwp_wp_stream_nonce' );
294
-
295
- if ( current_user_can( self::SETTINGS_CAP ) ) {
296
- self::erase_stream_records();
297
- //MainWP_WP_Stream_Install::check_to_copy_data();
298
- wp_redirect(
299
- add_query_arg(
300
- array(
301
- 'page' => 'mainwp-reports-settings',
302
- 'message' => 'child_reports_data_erased'
303
- ),
304
- admin_url( 'options-general.php' )
305
- )
306
- );
307
- exit;
308
- } else {
309
- wp_die( "You don't have sufficient privileges to do this action." );
310
- }
311
- }
312
-
313
- private static function erase_stream_records() {
314
- global $wpdb;
315
-
316
- $where = '';
317
- if ( is_multisite() && ! is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN ) ) {
318
- $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
319
- }
320
-
321
- $wpdb->query(
322
- $wpdb->prepare(
323
- "DELETE `stream`, `context`, `meta`
324
- FROM {$wpdb->mainwp_reports} AS `stream`
325
- LEFT JOIN {$wpdb->mainwp_reportscontext} AS `context`
326
- ON `context`.`record_id` = `stream`.`ID`
327
- LEFT JOIN {$wpdb->mainwp_reportsmeta} AS `meta`
328
- ON `meta`.`record_id` = `stream`.`ID`
329
- WHERE `stream`.`type` = %s
330
- $where;",
331
- 'stream'
332
- )
333
- );
334
- }
335
-
336
- public static function wp_ajax_defaults() {
337
- check_ajax_referer( 'stream_nonce', 'mainwp_wp_stream_nonce' );
338
-
339
- if ( ! is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN ) ) {
340
- wp_die( "You don't have sufficient privileges to do this action." );
341
- }
342
-
343
- if ( current_user_can( self::SETTINGS_CAP ) ) {
344
- self::reset_stream_settings();
345
- wp_redirect(
346
- add_query_arg(
347
- array(
348
- 'page' => is_network_admin() ? 'mainwp_wp_stream_network_settings' : 'mainwp_wp_stream_settings',
349
- 'message' => 'child_reports_settings_reset',
350
- ),
351
- is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN ) ? network_admin_url( self::ADMIN_PARENT_PAGE ) : admin_url( self::ADMIN_PARENT_PAGE )
352
- )
353
- );
354
- exit;
355
- } else {
356
- wp_die( "You don't have sufficient privileges to do this action." );
357
- }
358
- }
359
-
360
- private static function reset_stream_settings() {
361
- global $wpdb;
362
-
363
- $blogs = get_sites();
364
-
365
- if ( $blogs ) {
366
- foreach ( $blogs as $blog ) {
367
- switch_to_blog( $blog['blog_id'] );
368
- delete_option( MainWP_WP_Stream_Settings::KEY );
369
- }
370
- restore_current_blog();
371
- }
372
- }
373
-
374
- public static function purge_schedule_setup() {
375
- if ( ! wp_next_scheduled( 'mainwp_wp_stream_auto_purge' ) ) {
376
- wp_schedule_event( time(), 'twicedaily', 'mainwp_wp_stream_auto_purge' );
377
- }
378
- }
379
-
380
- public static function purge_scheduled_action() {
381
- global $wpdb;
382
-
383
- // Don't purge if in Network Admin if Stream isn't network enabled
384
- if ( is_network_admin() && is_multisite() && ! is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN ) ) {
385
- return;
386
- }
387
-
388
- if ( is_multisite() && is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN ) ) {
389
- $options = (array) get_site_option( MainWP_WP_Stream_Settings::NETWORK_KEY, array() );
390
- } else {
391
- $options = MainWP_WP_Stream_Settings::get_options();
392
- }
393
-
394
- $days = !isset($options['general_records_ttl']) ? 180 : intval($options['general_records_ttl']);
395
-
396
- if (empty($days))
397
- return;
398
-
399
- $date = new DateTime( 'now', $timezone = new DateTimeZone( 'UTC' ) );
400
-
401
- $date->sub( DateInterval::createFromDateString( "$days days" ) );
402
-
403
- $where = " AND (`stream`.`created` < STR_TO_DATE(" . $wpdb->prepare('%s', $date->format( 'Y-m-d H:i:s' )) . ", '%Y-%m-%d %H:%i:%s')) ";
404
- //$where = $wpdb->prepare( ' AND `stream`.`created` < %s', $date->format( 'Y-m-d H:i:s' ) );
405
-
406
- if ( is_multisite() && ! is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN ) ) {
407
- $where .= $wpdb->prepare( ' AND `blog_id` = %d', get_current_blog_id() );
408
- }
409
- $sql = "DELETE `stream`, `context`, `meta`
410
- FROM {$wpdb->mainwp_reports} AS `stream`
411
- LEFT JOIN {$wpdb->mainwp_reportscontext} AS `context`
412
- ON `context`.`record_id` = `stream`.`ID`
413
- LEFT JOIN {$wpdb->mainwp_reportsmeta} AS `meta`
414
- ON `meta`.`record_id` = `stream`.`ID`
415
- WHERE `stream`.`type` = 'stream'
416
- {$where};" ;
417
-
418
-
419
-
420
- $wpdb->query( $sql );
421
- }
422
-
423
- private static function _role_can_view_stream( $role ) {
424
- if ( in_array( $role, array('administrator')) ) {
425
- return true;
426
- }
427
-
428
- return false;
429
- }
430
-
431
- public static function _filter_user_caps( $allcaps, $caps, $args, $user = null ) {
432
- global $wp_roles;
433
-
434
- if ( ! isset( $wp_roles ) ) {
435
- $wp_roles = new WP_Roles();
436
- }
437
-
438
- $user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
439
-
440
- $roles = array_unique(
441
- array_merge(
442
- $user->roles,
443
- array_filter(
444
- array_keys( $user->caps ),
445
- array( $wp_roles, 'is_role' )
446
- )
447
- )
448
- );
449
-
450
- foreach ( $caps as $cap ) {
451
- if ( self::VIEW_CAP === $cap ) {
452
- foreach ( $roles as $role ) {
453
- if ( self::_role_can_view_stream( $role ) ) {
454
- $allcaps[ $cap ] = true;
455
- break 2;
456
- }
457
- }
458
- }
459
- }
460
-
461
- return $allcaps;
462
- }
463
-
464
- public static function _filter_role_caps( $allcaps, $cap, $role ) {
465
- if ( self::VIEW_CAP === $cap && self::_role_can_view_stream( $role ) ) {
466
- $allcaps[ $cap ] = true;
467
- }
468
-
469
- return $allcaps;
470
- }
471
-
472
- public static function ajax_filters() {
473
- if ( ! defined( 'DOING_AJAX' ) ) {
474
- wp_die( '-1' );
475
- }
476
-
477
- check_ajax_referer( 'mainwp_creport_filters_user_search_nonce', 'nonce' );
478
-
479
- switch ( mainwp_wp_stream_filter_input( INPUT_GET, 'filter' ) ) {
480
- case 'author':
481
- $users = array_merge(
482
- array( 0 => (object) array( 'display_name' => 'WP-CLI' ) ),
483
- get_users()
484
- );
485
-
486
- // `search` arg for get_users() is not enough
487
- $users = array_filter(
488
- $users,
489
- function ( $user ) {
490
- return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( mainwp_wp_stream_filter_input( INPUT_GET, 'q' ) ) );
491
- }
492
- );
493
-
494
- if ( count( $users ) > self::PRELOAD_AUTHORS_MAX ) {
495
- $users = array_slice( $users, 0, self::PRELOAD_AUTHORS_MAX );
496
- // @todo $extra is not used
497
- $extra = array(
498
- 'id' => 0,
499
- 'disabled' => true,
500
- 'text' => sprintf( _n( 'One more result...', '%d more results...', $results_count - self::PRELOAD_AUTHORS_MAX, 'mainwp-child-reports' ), $results_count - self::PRELOAD_AUTHORS_MAX ),
501
- );
502
- }
503
-
504
- // Get gravatar / roles for final result set
505
- $results = self::get_authors_record_meta( $users );
506
-
507
- break;
508
- }
509
- if ( isset( $results ) ) {
510
- echo json_encode( array_values( $results ) );
511
- }
512
- die();
513
- }
514
-
515
- public static function get_filter_value_by_id() {
516
- $filter = mainwp_wp_stream_filter_input( INPUT_POST, 'filter' );
517
- switch ( $filter ) {
518
- case 'author':
519
- $id = mainwp_wp_stream_filter_input( INPUT_POST, 'id' );
520
- if ( $id === '0' ) {
521
- $value = 'WP-CLI';
522
- break;
523
- }
524
- $user = get_userdata( $id );
525
- if ( ! $user || is_wp_error( $user ) ) {
526
- $value = '';
527
- } else {
528
- $value = $user->display_name;
529
- }
530
- break;
531
- default:
532
- $value = '';
533
- break;
534
- }
535
- echo json_encode( $value );
536
- wp_die();
537
- }
538
-
539
- public static function get_authors_record_meta( $authors ) {
540
- require_once MAINWP_WP_STREAM_INC_DIR . 'class-wp-stream-author.php';
541
-
542
- $authors_records = array();
543
-
544
- foreach ( $authors as $user_id => $args ) {
545
- $author = new MainWP_WP_Stream_Author( $user_id );
546
- $disabled = isset( $args['disabled'] ) ? $args['disabled'] : null;
547
-
548
- $authors_records[ $user_id ] = array(
549
- 'text' => $author->get_display_name(),
550
- 'id' => $user_id,
551
- 'label' => $author->get_display_name(),
552
- 'icon' => $author->get_avatar_src( 32 ),
553
- 'title' => '',
554
- 'disabled' => $disabled,
555
- );
556
- }
557
-
558
- return $authors_records;
559
- }
560
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-stream-author.php DELETED
@@ -1,176 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Author {
4
-
5
-
6
- public $id;
7
- public $meta = array();
8
- protected $user;
9
-
10
- function __construct( $user_id, $author_meta = array() ) {
11
- $this->id = $user_id;
12
- $this->meta = $author_meta;
13
-
14
- if ( $this->id ) {
15
- $this->user = new WP_User( $this->id );
16
- }
17
- }
18
-
19
- function __get( $name ) {
20
- if ( 'display_name' === $name ) {
21
- return $this->get_display_name();
22
- } elseif ( 'avatar_img' === $name ) {
23
- return $this->get_avatar_img();
24
- } elseif ( 'avatar_src' === $name ) {
25
- return $this->get_avatar_src();
26
- } elseif ( 'role' === $name ) {
27
- return $this->get_role();
28
- } elseif ( 'agent' === $name ) {
29
- return $this->get_agent();
30
- } elseif ( ! empty( $this->user ) && 0 !== $this->user->ID ) {
31
- return $this->user->$name;
32
- } else {
33
- throw new Exception( "Unrecognized magic '$name'" );
34
- }
35
- }
36
-
37
- function get_display_name() {
38
- if ( 0 === $this->id ) {
39
- if ( isset( $this->meta['system_user_name'] ) ) {
40
- return esc_html( $this->meta['system_user_name'] );
41
- }
42
- return esc_html__( 'N/A', 'mainwp-child-reports' );
43
- } else {
44
- if ( $this->is_deleted() ) {
45
- if ( ! empty( $this->meta['display_name'] ) ) {
46
- return $this->meta['display_name'];
47
- } elseif ( ! empty( $this->meta['user_login'] ) ) {
48
- return $this->meta['user_login'];
49
- } else {
50
- return esc_html__( 'N/A', 'mainwp-child-reports' );
51
- }
52
- } elseif ( ! empty( $this->user->display_name ) ) {
53
- return $this->user->display_name;
54
- } else {
55
- return $this->user->user_login;
56
- }
57
- }
58
- }
59
-
60
- function get_agent() {
61
- $agent = null;
62
-
63
- if ( ! empty( $this->meta['agent'] ) ) {
64
- $agent = $this->meta['agent'];
65
- } elseif ( ! empty( $this->meta['is_wp_cli'] ) ) {
66
- $agent = 'wp_cli'; // legacy
67
- }
68
-
69
- return $agent;
70
- }
71
-
72
- function get_avatar_img( $size = 80 ) {
73
- if ( ! get_option( 'show_avatars' ) ) {
74
- return false;
75
- }
76
-
77
- if ( 0 === $this->id ) {
78
- $url = MAINWP_WP_STREAM_URL . 'ui/mainwp-reports-icons/wp-cli.png';
79
- $avatar = sprintf( '<img alt="%1$s" src="%2$s" class="avatar avatar-%3$s photo" height="%3$s" width="%3$s">', esc_attr( $this->get_display_name() ), esc_url( $url ), esc_attr( $size ) );
80
- } else {
81
- if ( $this->is_deleted() ) {
82
- $email = $this->meta['user_email'];
83
- $avatar = get_avatar( $email, $size );
84
- } else {
85
- $avatar = get_avatar( $this->id, $size );
86
- }
87
- }
88
-
89
- return $avatar;
90
- }
91
-
92
- function get_avatar_src( $size = 80 ) {
93
- $img = $this->get_avatar_img( $size );
94
-
95
- if ( ! $img ) {
96
- return false;
97
- }
98
-
99
- if ( 1 === preg_match( '/src=([\'"])(.*?)\1/', $img, $matches ) ) {
100
- $src = html_entity_decode( $matches[2] );
101
- } else {
102
- return false;
103
- }
104
-
105
- return $src;
106
- }
107
-
108
- function get_role() {
109
- global $wp_roles;
110
-
111
- if ( ! empty( $this->meta['author_role'] ) && isset( $wp_roles->role_names[ $this->meta['author_role'] ] ) ) {
112
- $author_role = $wp_roles->role_names[ $this->meta['author_role'] ];
113
- } elseif ( ! empty( $this->meta['user_role_label'] ) ) {
114
- $author_role = $this->meta['user_role_label'];
115
- } elseif ( isset( $this->user->roles[0] ) && isset( $wp_roles->role_names[ $this->user->roles[0] ] ) ) {
116
- $author_role = $wp_roles->role_names[ $this->user->roles[0] ];
117
- } else {
118
- $author_role = null;
119
- }
120
-
121
- return $author_role;
122
- }
123
-
124
- function get_records_page_url() {
125
- $url = add_query_arg(
126
- array(
127
- 'page' => MainWP_WP_Stream_Admin::RECORDS_PAGE_SLUG,
128
- 'author' => absint( $this->id ),
129
- ),
130
- self_admin_url( MainWP_WP_Stream_Admin::ADMIN_PARENT_PAGE )
131
- );
132
-
133
- return $url;
134
- }
135
-
136
- function is_deleted() {
137
- return ( 0 !== $this->id && 0 === $this->user->ID );
138
- }
139
-
140
- function is_wp_cli() {
141
- return ( 'wp_cli' === $this->get_agent() );
142
- }
143
-
144
- function __toString() {
145
- return $this->get_display_name();
146
- }
147
-
148
- static function get_current_agent() {
149
- $agent = null;
150
-
151
- if ( defined( 'WP_CLI' ) ) {
152
- $agent = 'wp_cli';
153
- } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
154
- $agent = 'wp_cron';
155
- }
156
-
157
- $agent = apply_filters( 'mainwp_wp_stream_current_agent', $agent );
158
-
159
- return $agent;
160
- }
161
-
162
- static function get_agent_label( $agent ) {
163
- if ( 'wp_cli' === $agent ) {
164
- $label = esc_html__( 'via WP-CLI', 'mainwp-child-reports' );
165
- } elseif ( 'wp_cron' === $agent ) {
166
- $label = esc_html__( 'during WP Cron', 'mainwp-child-reports' );
167
- } else {
168
- $label = null;
169
- }
170
-
171
- $label = apply_filters( 'mainwp_wp_stream_agent_label', $label, $agent );
172
-
173
- return $label;
174
- }
175
-
176
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/connector.php DELETED
@@ -1,212 +0,0 @@
1
- <?php
2
-
3
- abstract class MainWP_WP_Stream_Connector {
4
-
5
- public static $name = null;
6
- public static $actions = array();
7
- public static $prev_stream = null;
8
-
9
- public static function register() {
10
- $class = get_called_class();
11
-
12
- foreach ( $class::$actions as $action ) {
13
- add_action( $action, array( $class, 'callback' ), null, 5 );
14
- }
15
-
16
- add_filter( 'mainwp_wp_stream_action_links_' . $class::$name, array( $class, 'action_links' ), 10, 2 );
17
- }
18
-
19
- public static function callback() {
20
- $action = current_filter();
21
- $class = get_called_class();
22
- $callback = array( $class, 'callback_' . str_replace( '-', '_', $action ) );
23
-
24
- // For the sake of testing, trigger an action with the name of the callback
25
- if ( defined( 'MAINWP_STREAM_TESTS' ) ) {
26
- do_action( 'mainwp_wp_stream_test_' . $callback[1] );
27
- }
28
-
29
- // Call the real function
30
- if ( is_callable( $callback ) ) {
31
- return call_user_func_array( $callback, func_get_args() );
32
- }
33
- }
34
-
35
- public static function action_links( $links, $record ) {
36
- return $links;
37
- }
38
-
39
- public static function log( $message, $args, $object_id, $contexts, $user_id = null ) {
40
- // Prevent inserting Excluded Context & Actions
41
- foreach ( $contexts as $context => $action ) {
42
- if ( ! MainWP_WP_Stream_Connectors::is_logging_enabled( 'contexts', $context ) ) {
43
- unset( $contexts[ $context ] );
44
- } else {
45
- if ( ! MainWP_WP_Stream_Connectors::is_logging_enabled( 'actions', $action ) ) {
46
- unset( $contexts[ $context ] );
47
- }
48
- }
49
- }
50
-
51
- if ( count( $contexts ) == 0 ){
52
- return ;
53
- }
54
-
55
- $created_timestamp = null;
56
-
57
- if (is_array($contexts) && is_array($args)) {
58
- if (isset($contexts['plugins']) && !empty($contexts['plugins']) ) {
59
- if (isset($args['slug']) && ( $args['slug'] == 'mainwp-child/mainwp-child.php' || $args['slug'] == 'mainwp-child-reports/mainwp-child-reports.php' )) {
60
- $hide_child_plugins = get_option('mainwp_creport_hide_child_plugins', 'yes');
61
- if ($hide_child_plugins == 'yes') {
62
- return false;
63
- } else {
64
- $branding_text = MainWP_WP_Stream::get_instance()->get_branding_title();
65
- if (!empty($branding_text)) {
66
- if ($args['slug'] == 'mainwp-child/mainwp-child.php') {
67
- $args['name'] = $branding_text;
68
- } else {
69
- $args['name'] = $branding_text . ' Reports';
70
- }
71
- }
72
- }
73
- }
74
- }
75
-
76
- $created_timestamp = 0;
77
- $child_context = '';
78
-
79
- if ( isset($contexts['backwpup_backups']) ) {
80
- $child_context = 'backwpup_backups';
81
- } elseif ( isset($contexts['backupwordpress_backups']) ) {
82
- $child_context = 'backupwordpress_backups';
83
- } elseif ( isset($contexts['backupbuddy_backups']) ) {
84
- $child_context = 'backupbuddy_backups';
85
- } elseif ( isset($contexts['wordfence_scans']) ) {
86
- $child_context = 'wordfence_scans';
87
- } elseif ( isset($contexts['updraftplus_backups']) ) {
88
- $child_context = 'updraftplus_backups';
89
- } elseif ( isset($contexts['wptimecapsule_backups']) ) {
90
- $child_context = 'wptimecapsule_backups';
91
- }
92
-
93
- if ( !empty($child_context) ) {
94
- if (is_array($args)) {
95
- if (isset($args['backup_time'])) {
96
- $created_timestamp = $args['backup_time'];
97
- } else if (isset($args['scan_time'])) {
98
- $created_timestamp = $args['scan_time'];
99
- }
100
- }
101
-
102
- if (empty($created_timestamp) )
103
- return;
104
-
105
- $saved_item = MainWP_WP_Stream_Log::get_instance()->get_log( array( 'context' => $child_context, 'created' => date("Y-m-d H:i:s", $created_timestamp ) ) );
106
-
107
- if ($saved_item)
108
- return;
109
- }
110
- }
111
-
112
- $class = get_called_class();
113
-
114
- return MainWP_WP_Stream_Log::get_instance()->log(
115
- $class::$name,
116
- $message,
117
- $args,
118
- $object_id,
119
- $contexts,
120
- $user_id,
121
- $created_timestamp
122
- );
123
- }
124
-
125
- public static function delayed_log( $handle ) {
126
- $args = func_get_args();
127
-
128
- array_shift( $args );
129
-
130
- self::$delayed[ $handle ] = $args;
131
-
132
- add_action( 'shutdown', array( __CLASS__, 'delayed_log_commit' ) );
133
- }
134
-
135
- public static function delayed_log_commit() {
136
- foreach ( self::$delayed as $handle => $args ) {
137
- call_user_func_array( array( __CLASS__, 'log' ) , $args );
138
- }
139
- }
140
-
141
- public static function get_changed_keys( $old_value, $new_value, $deep = false ) {
142
- if ( ! is_array( $old_value ) && ! is_array( $new_value ) ) {
143
- return array();
144
- }
145
-
146
- if ( ! is_array( $old_value ) ) {
147
- return array_keys( $new_value );
148
- }
149
-
150
- if ( ! is_array( $new_value ) ) {
151
- return array_keys( $old_value );
152
- }
153
-
154
- $diff = array_udiff_assoc(
155
- $old_value,
156
- $new_value,
157
- function( $value1, $value2 ) {
158
- return maybe_serialize( $value1 ) !== maybe_serialize( $value2 );
159
- }
160
- );
161
-
162
- $result = array_keys( $diff );
163
-
164
- // find unexisting keys in old or new value
165
- $common_keys = array_keys( array_intersect_key( $old_value, $new_value ) );
166
- $unique_keys_old = array_values( array_diff( array_keys( $old_value ), $common_keys ) );
167
- $unique_keys_new = array_values( array_diff( array_keys( $new_value ), $common_keys ) );
168
- $result = array_merge( $result, $unique_keys_old, $unique_keys_new );
169
-
170
- // remove numeric indexes
171
- $result = array_filter(
172
- $result,
173
- function( $value ) {
174
- // check if is not valid number (is_int, is_numeric and ctype_digit are not enough)
175
- return (string) (int) $value !== (string) $value;
176
- }
177
- );
178
-
179
- $result = array_values( array_unique( $result ) );
180
-
181
- if ( false === $deep ) {
182
- return $result; // Return an numerical based array with changed TOP PARENT keys only
183
- }
184
-
185
- $result = array_fill_keys( $result, null );
186
-
187
- foreach ( $result as $key => $val ) {
188
- if ( in_array( $key, $unique_keys_old ) ) {
189
- $result[ $key ] = false; // Removed
190
- }
191
- elseif ( in_array( $key, $unique_keys_new ) ) {
192
- $result[ $key ] = true; // Added
193
- }
194
- elseif ( $deep ) { // Changed, find what changed, only if we're allowed to explore a new level
195
- if ( is_array( $old_value[ $key ] ) && is_array( $new_value[ $key ] ) ) {
196
- $inner = array();
197
- $parent = $key;
198
- $deep--;
199
- $changed = self::get_changed_keys( $old_value[ $key ], $new_value[ $key ], $deep );
200
- foreach ( $changed as $child => $change ) {
201
- $inner[ $parent . '::' . $child ] = $change;
202
- }
203
- $result[ $key ] = 0; // Changed parent which has a changed children
204
- $result = array_merge( $result, $inner );
205
- }
206
- }
207
- }
208
-
209
- return $result;
210
- }
211
-
212
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/connectors.php DELETED
@@ -1,169 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Connectors {
4
-
5
- public static $connectors = array();
6
-
7
- public static $term_labels = array(
8
- 'stream_connector' => array(),
9
- 'stream_context' => array(),
10
- 'stream_action' => array(),
11
- );
12
-
13
- protected static $admin_notices = array();
14
-
15
- public static function load() {
16
- add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
17
-
18
- require_once MAINWP_WP_STREAM_INC_DIR . 'connector.php';
19
-
20
- $connectors = array(
21
- 'comments',
22
- 'editor',
23
- 'installer',
24
- 'media',
25
- 'menus',
26
- 'posts',
27
- 'users',
28
- 'widgets',
29
- 'updraftplus',
30
- 'backupwordpress',
31
- 'backwpup',
32
- 'backupbuddy',
33
- 'wordfence',
34
- 'maintenance',
35
- 'wptimecapsule'
36
- );
37
- $classes = array();
38
- foreach ( $connectors as $connector ) {
39
- include_once MAINWP_WP_STREAM_DIR . '/connectors/' . $connector .'.php';
40
- $class = "MainWP_WP_Stream_Connector_$connector";
41
- $classes[] = $class;
42
- }
43
-
44
- $exclude_all_connector = false;
45
-
46
- // Check if logging action is enable for user or provide a hook for plugin to override on specific cases
47
- if ( ! self::is_logging_enabled_for_user() ) {
48
- $exclude_all_connector = true;
49
- }
50
-
51
- // Check if logging action is enable for ip or provide a hook for plugin to override on specific cases
52
- if ( ! self::is_logging_enabled_for_ip() ) {
53
- $exclude_all_connector = true;
54
- }
55
-
56
- self::$connectors = apply_filters( 'mainwp_client_reports_connectors', $classes );
57
-
58
- foreach ( self::$connectors as $connector ) {
59
- self::$term_labels['stream_connector'][ $connector::$name ] = $connector::get_label();
60
- }
61
-
62
- // Get excluded connectors
63
- $excluded_connectors = MainWP_WP_Stream_Settings::get_excluded_by_key( 'connectors' );
64
-
65
- foreach ( self::$connectors as $connector ) {
66
- // Check if the connectors extends the MainWP_WP_Stream_Connector class, if not skip it
67
- if ( ! is_subclass_of( $connector, 'MainWP_WP_Stream_Connector' ) ) {
68
- self::$admin_notices[] = sprintf(
69
- __( "%s class wasn't loaded because it doesn't extends the %s class.", 'mainwp-child-reports' ),
70
- $connector,
71
- 'MainWP_WP_Stream_Connector'
72
- );
73
-
74
- continue;
75
- }
76
-
77
- // Store connector label
78
- if ( ! in_array( $connector::$name, self::$term_labels['stream_connector'] ) ) {
79
- self::$term_labels['stream_connector'][ $connector::$name ] = $connector::get_label();
80
- }
81
-
82
- $is_excluded_connector = apply_filters( 'mainwp_wp_stream_check_connector_is_excluded', in_array( $connector::$name, $excluded_connectors ), $connector::$name, $excluded_connectors );
83
-
84
- if ( $is_excluded_connector ) {
85
- continue;
86
- }
87
-
88
- if ( ! $exclude_all_connector ) {
89
- $connector::register();
90
- }
91
-
92
- // Add new terms to our label lookup array
93
- self::$term_labels['stream_action'] = array_merge(
94
- self::$term_labels['stream_action'],
95
- $connector::get_action_labels()
96
- );
97
- self::$term_labels['stream_context'] = array_merge(
98
- self::$term_labels['stream_context'],
99
- $connector::get_context_labels()
100
- );
101
- }
102
-
103
- do_action( 'mainwp_wp_stream_after_connectors_registration', self::$term_labels['stream_connector'] );
104
- }
105
-
106
- public static function admin_notices() {
107
- if ( ! empty( self::$admin_notices ) ) :
108
- ?>
109
- <div class="error">
110
- <?php foreach ( self::$admin_notices as $message ) : ?>
111
- <?php echo wpautop( esc_html( $message ) ) // xss ok ?>
112
- <?php endforeach; ?>
113
- </div>
114
- <?php
115
- endif;
116
- }
117
-
118
- public static function is_logging_enabled_for_user( $user = null ) {
119
- if ( is_null( $user ) ) {
120
- $user = wp_get_current_user();
121
- }
122
-
123
- $bool = true;
124
- $user_roles = array_values( $user->roles );
125
- $excluded_authors = MainWP_WP_Stream_Settings::get_excluded_by_key( 'authors' );
126
- $excluded_roles = MainWP_WP_Stream_Settings::get_excluded_by_key( 'roles' );
127
-
128
- // Don't log excluded users
129
- if ( in_array( $user->ID, $excluded_authors ) ) {
130
- $bool = false;
131
- }
132
-
133
- // Don't log excluded user roles
134
- if ( 0 !== count( array_intersect( $user_roles, $excluded_roles ) ) ) {
135
- $bool = false;
136
- }
137
-
138
- // If the user is not a valid user then we always log the action
139
- if ( ! ( $user instanceof WP_User ) || 0 === $user->ID ) {
140
- $bool = true;
141
- }
142
-
143
- return apply_filters( 'mainwp_wp_stream_record_log', $bool, $user, get_called_class() );
144
- }
145
-
146
- public static function is_logging_enabled_for_ip( $ip = null ) {
147
- if ( is_null( $ip ) ) {
148
- $ip = mainwp_wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
149
- } else {
150
- $ip = mainwp_wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
151
- }
152
-
153
- // If ip is not valid the we will log the action
154
- if ( false === $ip ) {
155
- $bool = true;
156
- } else {
157
- $bool = self::is_logging_enabled( 'ip_addresses', $ip );
158
- }
159
-
160
- return apply_filters( 'mainwp_wp_stream_ip_record_log', $bool, $ip, get_called_class() );
161
- }
162
-
163
- public static function is_logging_enabled( $column, $value ) {
164
- $excluded_values = MainWP_WP_Stream_Settings::get_excluded_by_key( $column );
165
- $bool = ( ! in_array( $value, $excluded_values ) );
166
-
167
- return $bool;
168
- }
169
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/context-query.php DELETED
@@ -1,132 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Context_Query {
4
-
5
- public $relation = null;
6
-
7
- function __construct( $query = false ) {
8
- if ( $query ) {
9
- $this->parse_query_vars( $query );
10
- }
11
- }
12
-
13
- function parse_query_vars( $query ) {
14
- $context_query = array();
15
-
16
- // Check for 'flat' query params
17
- foreach ( array( 'connector', 'context', 'action' ) as $key ) {
18
- foreach ( array( '', '__in', '__not_in' ) as $i => $suffix ) {
19
- $lookup = array( '=', 'IN', 'NOT IN' );
20
- $compare = $lookup[ $i ];
21
- if ( ! empty( $query[ $key . $suffix ] ) ) {
22
- $context_query[] = array(
23
- $key => array(
24
- 'value' => $query[ $key . $suffix ],
25
- 'compare' => $compare,
26
- ),
27
- );
28
- }
29
- }
30
- }
31
-
32
- if ( ! empty( $query['context_query'] ) ) {
33
- $context_query = array_merge( $context_query, $query['context_query'] );
34
- }
35
-
36
- if ( isset( $context_query['relation'] ) && 'OR' === strtoupper( $context_query['relation'] ) ) {
37
- $this->relation = 'OR';
38
- } else {
39
- $this->relation = 'AND';
40
- }
41
-
42
- $this->queries = $context_query;
43
- }
44
-
45
- function get_sql() {
46
- global $wpdb;
47
-
48
- if ( empty( $this->queries ) ) {
49
- return array( 'join' => '', 'where' => '' );
50
- }
51
-
52
- $context_table = MainWP_WP_Stream_DB::$table_context;
53
- $main_table = MainWP_WP_Stream_DB::$table;
54
- $meta_id_column = 'meta_id';
55
-
56
- $join = array();
57
- $where = array();
58
-
59
- $queries = $this->queries;
60
-
61
- $meta_query = new WP_Meta_Query;
62
-
63
- foreach ( $queries as $i => $query ) {
64
- foreach ( $query as $key => $args ) {
65
- $type = $meta_query->get_cast_for_type( isset( $args['type'] ) ? $args['type'] : '' );
66
-
67
- $value = isset( $args['value'] ) ? $args['value'] : null;
68
-
69
- // Allow 'context' => array('val1', 'val2') as well
70
- if ( is_null( $value ) ) {
71
- $args = array( 'value' => $args );
72
- $value = $args['value'];
73
- }
74
-
75
- if ( isset( $args['compare'] ) ) {
76
- $compare = strtoupper( $args['compare'] );
77
- } else {
78
- $compare = is_array( $value ) ? 'IN' : '=';
79
- }
80
-
81
- $operators = array(
82
- '=',
83
- '!=',
84
- 'LIKE',
85
- 'NOT LIKE',
86
- 'IN',
87
- 'NOT IN',
88
- 'REGEXP',
89
- 'NOT REGEXP',
90
- 'RLIKE',
91
- );
92
-
93
- if ( ! in_array( $compare, $operators ) ) {
94
- $compare = '=';
95
- }
96
-
97
- if ( 'IN' === substr( $compare, -2 ) ) {
98
- if ( ! is_array( $value ) ) {
99
- $value = preg_split( '/[,\s]+/', $value );
100
- }
101
- $compare_string = '(' . substr( str_repeat( ',%s', count( $value ) ), 1 ) . ')';
102
- } elseif ( 'LIKE' === substr( $compare, -4 ) ) {
103
- $value = '%' . $wpdb->esc_like( $value ) . '%';
104
- $compare_string = '%s';
105
- } else {
106
- $compare_string = '%s';
107
- }
108
-
109
- if ( ! empty( $where[ $i ] ) ) {
110
- $where[ $i ] .= ' AND ';
111
- } else {
112
- $where[ $i ] = '';
113
- }
114
-
115
- $where[ $i ] = ' (' . $where[ $i ] . $wpdb->prepare( "CAST($context_table.$key AS {$type}) {$compare} {$compare_string})", $value );
116
- }
117
- }
118
-
119
- $where = array_filter( $where );
120
-
121
- if ( empty( $where ) ) {
122
- $where = '';
123
- } else {
124
- $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )';
125
- }
126
-
127
- $join = implode( "\n", $join );
128
-
129
- return apply_filters_ref_array( 'get_context_sql', array( compact( 'join', 'where' ), $this->queries ) );
130
- }
131
-
132
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/dashboard.php DELETED
@@ -1,92 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Dashboard_Widget {
4
-
5
- public static function load() {
6
-
7
- }
8
-
9
- public static function widget_row( $item, $i = null ) {
10
- require_once MAINWP_WP_STREAM_INC_DIR . 'class-wp-stream-author.php';
11
-
12
- $author_meta = mainwp_wp_stream_get_meta( $item->ID, 'author_meta', true );
13
- $author = new MainWP_WP_Stream_Author( (int) $item->author, $author_meta );
14
-
15
- $time_author = sprintf(
16
- _x(
17
- '%1$s ago by <a href="%2$s">%3$s</a>',
18
- '1: Time, 2: User profile URL, 3: User display name',
19
- 'stream'
20
- ),
21
- human_time_diff( strtotime( $item->created ) ),
22
- esc_url( $author->get_records_page_url() ),
23
- esc_html( $author->get_display_name() )
24
- );
25
-
26
- if ( $author->get_agent() ) {
27
- $time_author .= sprintf( ' %s', MainWP_WP_Stream_Author::get_agent_label( $author->get_agent() ) );
28
- }
29
-
30
- $class = ( isset( $i ) && $i % 2 ) ? 'alternate' : '';
31
-
32
- ob_start()
33
- ?><li class="<?php echo esc_html( $class ) ?>" data-id="<?php echo esc_html( $item->ID ) ?>">
34
- <div class="record-avatar">
35
- <a href="<?php echo esc_url( $author->get_records_page_url() ) ?>">
36
- <?php echo $author->get_avatar_img( 72 ); // xss ok ?>
37
- </a>
38
- </div>
39
- <span class="record-meta"><?php echo $time_author; // xss ok ?></span>
40
- <br />
41
- <?php echo esc_html( $item->summary ) ?>
42
- </li><?php
43
-
44
- return ob_get_clean();
45
- }
46
-
47
-
48
- public static function live_update( $response, $data ) {
49
- if ( ! isset( $data['wp-mainwp-stream-heartbeat-last-id'] ) ) {
50
- return;
51
- }
52
-
53
- $send = array();
54
-
55
- $last_id = intval( $data['wp-mainwp-stream-heartbeat-last-id'] );
56
-
57
- $updated_items = self::gather_updated_items( $last_id );
58
-
59
- if ( ! empty( $updated_items ) ) {
60
- ob_start();
61
- foreach ( $updated_items as $item ) {
62
- echo self::widget_row( $item ); //xss okay
63
- }
64
-
65
- $send = ob_get_clean();
66
- }
67
-
68
- return $send;
69
- }
70
-
71
- public static function gather_updated_items( $last_id, $query = array(), $last_created = null ) {
72
- if ( false === $last_id ) {
73
- return '';
74
- }
75
- $default = array();
76
-
77
- if (!empty($last_created)) {
78
- $default['created_greater_than'] = $last_created;
79
- } else {
80
- $default['record_greater_than'] = (int) $last_id;
81
- }
82
-
83
- // Filter default
84
- $query = wp_parse_args( $query, $default );
85
-
86
- // Run query
87
- $items = mainwp_wp_stream_query( $query );
88
-
89
- return $items;
90
- }
91
-
92
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/db-updates.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Version 3.0.8
4
+ *
5
+ * Force update for older versions to call \dbdelta in install() method to fix column widths.
6
+ *
7
+ * @param string $db_version
8
+ * @param string $current_version
9
+ *
10
+ * @return string
11
+ */
12
+ function wp_mainwp_stream_update_auto_308( $db_version, $current_version ) {
13
+ $plugin = wp_mainwp_stream_get_instance();
14
+ $plugin->install->install( $current_version );
15
+
16
+ return $current_version;
17
+ }
18
+
19
+ /**
20
+ * Version 3.0.2
21
+ *
22
+ * @param string $db_version
23
+ * @param string $current_version
24
+ *
25
+ * @return string
26
+ */
27
+ function wp_mainwp_stream_update_302( $db_version, $current_version ) {
28
+ global $wpdb;
29
+
30
+ $stream_entries = $wpdb->get_results( "SELECT * FROM {$wpdb->base_prefix}mainwp_stream" );
31
+ foreach ( $stream_entries as $entry ) {
32
+ $class = 'Connector_' . $entry->context;
33
+ if ( class_exists( $class ) ) {
34
+ $connector = new $class();
35
+ $wpdb->update(
36
+ $wpdb->base_prefix . 'mainwp_stream', array(
37
+ 'connector' => $connector->name,
38
+ ), array(
39
+ 'ID' => $entry->ID,
40
+ )
41
+ );
42
+ } else {
43
+ $wpdb->update(
44
+ $wpdb->base_prefix . 'mainwp_stream', array(
45
+ 'connector' => strtolower( $entry->connector ),
46
+ ), array(
47
+ 'ID' => $entry->ID,
48
+ )
49
+ );
50
+ }
51
+ }
52
+
53
+ return $current_version;
54
+ }
55
+
56
+ /**
57
+ * Version 3.0.0
58
+ *
59
+ * Update from 1.4.9
60
+ *
61
+ * @param string $db_version
62
+ * @param string $current_version
63
+ *
64
+ * @return string
65
+ */
66
+ function wp_mainwp_stream_update_auto_300( $db_version, $current_version ) {
67
+ global $wpdb;
68
+
69
+ // Get only the author_meta values that are double-serialized
70
+ $wpdb->query( "RENAME TABLE {$wpdb->base_prefix}mainwp_stream TO {$wpdb->base_prefix}mainwp_stream_tmp, {$wpdb->base_prefix}mainwp_stream_context TO {$wpdb->base_prefix}mainwp_stream_context_tmp" );
71
+
72
+ $plugin = wp_mainwp_stream_get_instance();
73
+ $plugin->install->install( $current_version );
74
+
75
+ $date = new DateTime( 'now', $timezone = new DateTimeZone( 'UTC' ) );
76
+ $date->modify('-3 month');
77
+ $where = " AND `created` > STR_TO_DATE(" . $wpdb->prepare('%s', $date->format( 'Y-m-d H:i:s' )) . ", '%Y-%m-%d %H:%i:%s') ";
78
+ $orderby = ' ORDER BY ID DESC ';
79
+
80
+ $starting_row = 0;
81
+ $rows_per_round = 5000;
82
+
83
+ $stream_entries = $wpdb->get_results( "SELECT * FROM {$wpdb->base_prefix}mainwp_stream_tmp WHERE 1 = 1 " . $where . $orderby . $wpdb->prepare( " LIMIT %d, %d", $starting_row, $rows_per_round ) );
84
+
85
+ while ( ! empty( $stream_entries ) ) {
86
+ foreach ( $stream_entries as $entry ) {
87
+ $context = $wpdb->get_row(
88
+ $wpdb->prepare( "SELECT * FROM {$wpdb->base_prefix}mainwp_stream_context_tmp WHERE record_id = %s LIMIT 1", $entry->ID )
89
+ );
90
+
91
+ $new_entry = array(
92
+ 'site_id' => $entry->site_id,
93
+ 'blog_id' => $entry->blog_id,
94
+ 'user_id' => $entry->author,
95
+ 'user_role' => $entry->author_role,
96
+ 'summary' => $entry->summary,
97
+ 'created' => $entry->created,
98
+ 'connector' => $context->connector,
99
+ 'context' => $context->context,
100
+ 'action' => $context->action,
101
+ 'ip' => $entry->ip,
102
+ );
103
+
104
+ if ( $entry->object_id && 0 !== $entry->object_id ) {
105
+ $new_entry['object_id'] = $entry->object_id;
106
+ }
107
+
108
+ $wpdb->insert( $wpdb->base_prefix . 'mainwp_stream', $new_entry );
109
+
110
+ }
111
+
112
+ $starting_row += $rows_per_round;
113
+
114
+ $stream_entries = $wpdb->get_results( "SELECT * FROM {$wpdb->base_prefix}mainwp_stream_tmp WHERE 1 = 1 " . $where . $orderby . $wpdb->prepare( "LIMIT %d, %d", $starting_row, $rows_per_round ) );
115
+ }
116
+
117
+ $wpdb->query( "DROP TABLE {$wpdb->base_prefix}mainwp_stream_tmp, {$wpdb->base_prefix}mainwp_stream_context_tmp" );
118
+
119
+ return $current_version;
120
+ }
includes/db.php DELETED
@@ -1,191 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_DB {
4
-
5
- public static $instance;
6
-
7
- public static $table;
8
-
9
- public static $table_meta;
10
-
11
- public static $table_context;
12
-
13
- public function __construct() {
14
- global $wpdb;
15
-
16
- $prefix = $wpdb->prefix;
17
-
18
- self::$table = $prefix . 'mainwp_stream';
19
- self::$table_meta = $prefix . 'mainwp_stream_meta';
20
- self::$table_context = $prefix . 'mainwp_stream_context';
21
-
22
- $wpdb->mainwp_reports = self::$table;
23
- $wpdb->mainwp_reportsmeta = self::$table_meta;
24
- $wpdb->mainwp_reportscontext = self::$table_context;
25
-
26
- // Hack for get_metadata
27
- $wpdb->recordmeta = self::$table_meta;
28
- }
29
-
30
- public static function get_instance() {
31
- if ( ! self::$instance ) {
32
- $class = __CLASS__;
33
- self::$instance = new $class;
34
- }
35
-
36
- return self::$instance;
37
- }
38
-
39
- public function get_table_names() {
40
- return array(
41
- self::$table,
42
- self::$table_meta,
43
- self::$table_context,
44
- );
45
- }
46
-
47
- public function insert( $recordarr ) {
48
- global $wpdb;
49
-
50
- $recordarr = apply_filters( 'mainwp_wp_stream_record_array', $recordarr );
51
-
52
- // Allow extensions to handle the saving process
53
- if ( empty( $recordarr ) ) {
54
- return;
55
- }
56
-
57
- $fields = array( 'object_id', 'site_id', 'blog_id', 'author', 'author_role', 'created', 'summary', 'parent', 'visibility', 'ip' );
58
- $data = array_intersect_key( $recordarr, array_flip( $fields ) );
59
- $data = array_filter( $data );
60
-
61
- // TODO: Check/Validate *required* fields
62
-
63
- $result = $wpdb->insert(
64
- self::$table,
65
- $data
66
- );
67
-
68
- if ( 1 === $result ) {
69
- $record_id = $wpdb->insert_id;
70
- } else {
71
- do_action( 'mainwp_wp_stream_post_insert_error', $recordarr );
72
- return $result;
73
- }
74
-
75
- self::$instance->prev_record = $record_id;
76
-
77
- $connector = $recordarr['connector'];
78
-
79
- foreach ( (array) $recordarr['contexts'] as $context => $action ) {
80
- $this->insert_context( $record_id, $connector, $context, $action );
81
- }
82
-
83
- foreach ( $recordarr['meta'] as $key => $vals ) {
84
- // If associative array, serialize it, otherwise loop on its members
85
- if ( is_array( $vals ) && 0 !== key( $vals ) ) {
86
- $vals = array( $vals );
87
- }
88
- foreach ( (array) $vals as $val ) {
89
- $val = maybe_serialize( $val );
90
- if (empty($val))
91
- continue;
92
- $this->insert_meta( $record_id, $key, $val );
93
- }
94
- }
95
-
96
- do_action( 'mainwp_wp_stream_post_inserted', $record_id, $recordarr );
97
-
98
- return $record_id;
99
- }
100
-
101
- public function insert_context( $record_id, $connector, $context, $action ) {
102
- global $wpdb;
103
-
104
- $result = $wpdb->insert(
105
- self::$table_context,
106
- array(
107
- 'record_id' => $record_id,
108
- 'connector' => $connector,
109
- 'context' => $context,
110
- 'action' => $action,
111
- )
112
- );
113
-
114
- return $result;
115
- }
116
-
117
-
118
- public function get_report( $args = array() ) {
119
- if (!is_array($args))
120
- return false;
121
- global $wpdb;
122
- $where = "";
123
- $left_join = "";
124
- if (isset($args['context'])) {
125
- $left_join = " LEFT JOIN " . self::$table_context . " AS `context` ON `stream`.`ID` = `context`.`record_id` " ;
126
- }
127
-
128
- foreach ($args as $key => $value) {
129
- if ($key == 'context') {
130
- $where .= $wpdb->prepare( ' `context`.`context` = %s AND ', $value);
131
- } else
132
- $where .= $wpdb->prepare( ' `stream`.`' . $key . '` = %s AND ', $value);
133
- }
134
-
135
- $where = rtrim($where, "AND ");
136
-
137
- if (!empty($where)) {
138
- $where .= " AND blog_id = " . apply_filters( 'blog_id_logged', is_network_admin() ? 0 : get_current_blog_id() );
139
- $result = $wpdb->get_row( 'SELECT `stream`.* FROM ' . self::$table . ' AS `stream` ' . $left_join . ' WHERE ' . $where );
140
- return $result;
141
- }
142
- return false;
143
- }
144
-
145
- public function delete_report( $args = array() ) {
146
- if (!is_array($args))
147
- return false;
148
-
149
- global $wpdb;
150
- $sql = "";
151
-
152
- foreach ($args as $key => $value) {
153
- $sql .= $key ." = " . $value . " AND ";
154
- }
155
-
156
- $sql = rtrim($sql, "AND ");
157
-
158
- if ( ! empty( $sql ) ) {
159
- $sql .= " AND blog_id = " . apply_filters( 'blog_id_logged', is_network_admin() ? 0 : get_current_blog_id() );
160
-
161
- $sql = $wpdb->prepare( 'SELECT ID FROM ' . self::$table . ' WHERE %s ', $sql );
162
- $record_id = $wpdb->get_var( $sql );
163
- if ($record_id) {
164
- $sql = $wpdb->prepare( 'DELETE FROM ' . self::$table . ' WHERE %s ', $sql );
165
- $wpdb->query( $sql );
166
- $sql = $wpdb->prepare( 'DELETE FROM ' . self::$table_context . ' WHERE record_id = %d ', $record_id );
167
- $wpdb->query( $sql );
168
- $sql = $wpdb->prepare( 'DELETE FROM ' . self::$table_meta . ' WHERE record_id = %d ', $record_id );
169
- $wpdb->query( $sql );
170
- return true;
171
- }
172
- }
173
- return false;
174
- }
175
-
176
- public function insert_meta( $record_id, $key, $val ) {
177
- global $wpdb;
178
-
179
- $result = $wpdb->insert(
180
- self::$table_meta,
181
- array(
182
- 'record_id' => $record_id,
183
- 'meta_key' => $key,
184
- 'meta_value' => $val,
185
- )
186
- );
187
-
188
- return $result;
189
- }
190
-
191
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/feeds/atom.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ header( 'Content-Type: ' . feed_content_type( 'atom' ) . '; charset=' . get_option( 'blog_charset' ), true );
3
+ printf( '<?xml version="1.0" encoding="%s"?>', esc_attr( get_option( 'blog_charset' ) ) );
4
+ ?>
5
+
6
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xml:lang="<?php echo esc_attr( bloginfo_rss( 'language' ) ); ?>" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" <?php do_action( 'atom_ns' ); ?>>
7
+ <title><?php bloginfo_rss( 'name' ); ?> - <?php esc_html_e( 'Reports Feed', 'mainwp-child-reports' ); ?></title>
8
+ <link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
9
+ <link href="<?php echo esc_url( $records_admin_url ); ?>" />
10
+ <subtitle type="html"><?php esc_html( bloginfo_rss( 'description' ) ); ?></subtitle>
11
+ <updated><?php echo esc_html( mysql2date( 'c', $latest_record, false ) ); ?></updated>
12
+ <id><?php echo esc_url( $latest_link ); ?></id>
13
+ <sy:updatePeriod><?php echo esc_html( 'hourly' ); ?></sy:updatePeriod>
14
+ <sy:updateFrequency><?php echo absint( 1 ); ?></sy:updateFrequency>
15
+ <?php
16
+ /**
17
+ * Action fires during RSS head
18
+ */
19
+ do_action( 'atom_head' );
20
+
21
+ foreach ( $records as $record ) :
22
+ $record_link = add_query_arg(
23
+ array(
24
+ 'record__in' => $record->ID,
25
+ ),
26
+ $records_admin_url
27
+ );
28
+
29
+ $author = get_userdata( $record->author );
30
+ $display_name = isset( $author->display_name ) ? $author->display_name : 'N/A';
31
+ ?>
32
+ <entry>
33
+ <title type="html"><![CDATA[[<?php echo esc_html( $domain ); ?>] <?php echo esc_html( $record->summary ); // xss ok ?> ]]></title>
34
+ <link href="<?php echo esc_url( $record_link ); ?>" />
35
+ <updated><?php echo esc_html( mysql2date( 'c', $record->created, false ) ); ?></updated>
36
+ <author>
37
+ <name><?php echo esc_html( $display_name ); ?></name>
38
+ </author>
39
+ <category term="connector" label="<?php echo esc_html( $record->connector ); ?>" />
40
+ <category term="context" label="<?php echo esc_html( $record->context ); ?>"/>
41
+ <category term="action" label="<?php echo esc_html( $record->action ); ?>" />
42
+ <category term="ip" label="<?php echo esc_html( $record->ip ); ?>" />
43
+ <id><?php echo esc_url( $record_link ); ?></id>
44
+ <summary type="html"><![CDATA[- <?php echo esc_html( $display_name ); ?> ]]></summary>
45
+ <?php
46
+ /**
47
+ * Action fires during Atom item
48
+ */
49
+ do_action( 'atom_item' )
50
+ ?>
51
+ </entry>
52
+ <?php endforeach; ?>
53
+ </feed>
54
+ <?php
includes/feeds/json.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+ header( 'Content-type: application/json; charset=' . get_option( 'blog_charset' ), true );
3
+ if ( version_compare( PHP_VERSION, '5.4', '<' ) ) {
4
+ echo wp_mainwp_stream_json_encode( $records ); // xss ok
5
+ } else {
6
+ echo wp_mainwp_stream_json_encode( $records, JSON_PRETTY_PRINT ); // xss ok
7
+ }
includes/feeds/rss-2.0.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ header( 'Content-Type: ' . feed_content_type( 'rss-http' ) . '; charset=' . get_option( 'blog_charset' ), true );
3
+ printf( '<?xml version="1.0" encoding="%s"?>', esc_attr( get_option( 'blog_charset' ) ) );
4
+ ?>
5
+
6
+ <rss version="2.0"
7
+ xmlns:content="http://purl.org/rss/1.0/modules/content/"
8
+ xmlns:wfw="http://wellformedweb.org/CommentAPI/"
9
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
10
+ xmlns:atom="http://www.w3.org/2005/Atom"
11
+ xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
12
+ xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
13
+ <?php
14
+ /**
15
+ * Action fires during RSS xmls printing
16
+ */
17
+ do_action( 'rss2_ns' )
18
+ ?>
19
+ >
20
+ <channel>
21
+ <title><?php bloginfo_rss( 'name' ); ?> - <?php esc_html_e( 'Reports Feed', 'mainwp-child-reports' ); ?></title>
22
+ <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
23
+ <link><?php echo esc_url( $records_admin_url ); ?></link>
24
+ <description><?php bloginfo_rss( 'description' ); ?></description>
25
+ <lastBuildDate><?php echo esc_html( mysql2date( 'r', $latest_record, false ) ); ?></lastBuildDate>
26
+ <language><?php bloginfo_rss( 'language' ); ?></language>
27
+ <sy:updatePeriod><?php echo esc_html( 'hourly' ); ?></sy:updatePeriod>
28
+ <sy:updateFrequency><?php echo absint( 1 ); ?></sy:updateFrequency>
29
+ <?php
30
+ /**
31
+ * Action fires during RSS head
32
+ */
33
+ do_action( 'rss2_head' );
34
+
35
+ foreach ( $records as $record ) :
36
+ $record_link = add_query_arg(
37
+ array(
38
+ 'record__in' => $record->ID,
39
+ ),
40
+ $records_admin_url
41
+ );
42
+
43
+ $author = get_userdata( $record->author );
44
+ $display_name = isset( $author->display_name ) ? $author->display_name : 'N/A';
45
+ ?>
46
+ <item>
47
+ <title><![CDATA[ <?php echo esc_html( $record->summary ); // xss ok ?> ]]></title>
48
+ <pubDate><?php echo esc_html( mysql2date( 'r', $record->created, false ) ); ?></pubDate>
49
+ <dc:creator><?php echo esc_html( $display_name ); ?></dc:creator>
50
+ <category domain="connector"><![CDATA[ <?php echo esc_html( $record->connector ); ?> ]]></category>
51
+ <category domain="context"><![CDATA[ <?php echo esc_html( $record->context ); ?> ]]></category>
52
+ <category domain="action"><![CDATA[ <?php echo esc_html( $record->action ); ?> ]]></category>
53
+ <category domain="ip"><?php echo esc_html( $record->ip ); ?></category>
54
+ <guid isPermaLink="false"><?php echo esc_url( $record_link ); ?></guid>
55
+ <link><?php echo esc_url( $record_link ); ?></link>
56
+ <?php
57
+ /**
58
+ * Action fires during RSS item
59
+ */
60
+ do_action( 'rss2_item' )
61
+ ?>
62
+ </item>
63
+ <?php endforeach; ?>
64
+ </channel>
65
+ </rss>
includes/functions.php CHANGED
@@ -1,30 +1,139 @@
1
- <?php
2
-
3
- /**
4
- * Converts a time into an ISO 8601 extended formatted string.
5
- *
6
- * @param int|bool $time Seconds since unix epoc
7
- * @param int $offset Hour offset
8
- *
9
- * @return string an ISO 8601 extended formatted time
10
- */
11
- function mainwp_wp_stream_get_iso_8601_extended_date( $time = false, $offset = 0 ) {
12
- if ( $time ) {
13
- $microtime = (float) $time . '.0000';
14
- } else {
15
- $microtime = microtime( true );
16
- }
17
-
18
- $micro_seconds = sprintf( '%06d', ( $microtime - floor( $microtime ) ) * 1000000 );
19
- $offset_string = sprintf( 'Etc/GMT%s%s', $offset < 0 ? '+' : '-', abs( $offset ) );
20
-
21
- $timezone = new DateTimeZone( $offset_string );
22
- $date = new DateTime( date( 'Y-m-d H:i:s.' . $micro_seconds, $microtime ), $timezone );
23
-
24
- return sprintf(
25
- '%s%03d%s',
26
- $date->format( 'Y-m-d\TH:i:s.' ),
27
- floor( $date->format( 'u' ) / 1000 ),
28
- $date->format( 'O' )
29
- );
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Gets a specific external variable by name and optionally filters it.
4
+ *
5
+ * This is a polyfill function intended to be used in place of PHP's
6
+ * filter_input() function, which can occasionally be unreliable.
7
+ *
8
+ * @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV.
9
+ * @param string $variable_name Name of a variable to get.
10
+ * @param int $filter The ID of the filter to apply.
11
+ * @param mixed $options Associative array of options or bitwise disjunction of flags. If filter accepts options, flags can be provided in "flags" field of array.
12
+ *
13
+ * @return Value of the requested variable on success, FALSE if the filter fails, or NULL if the $variable_name is not set.
14
+ */
15
+ function wp_mainwp_stream_filter_input( $type, $variable_name, $filter = null, $options = array() ) {
16
+ return call_user_func_array( array( '\WP_MainWP_Stream\Filter_Input', 'super' ), func_get_args() );
17
+ }
18
+
19
+ /**
20
+ * Filters a variable with a specified filter.
21
+ *
22
+ * This is a polyfill function intended to be used in place of PHP's
23
+ * filter_var() function, which can occasionally be unreliable.
24
+ *
25
+ * @param string $var Value to filter.
26
+ * @param int $filter The ID of the filter to apply.
27
+ * @param mixed $options Associative array of options or bitwise disjunction of flags. If filter accepts options, flags can be provided in "flags" field of array. For the "callback" filter, callable type should be passed. The callback must accept one argument, the value to be filtered, and return the value after filtering/sanitizing it.
28
+ *
29
+ * @return Returns the filtered data, or FALSE if the filter fails.
30
+ */
31
+ function wp_mainwp_stream_filter_var( $var, $filter = null, $options = array() ) {
32
+ return call_user_func_array( array( '\WP_MainWP_Stream\Filter_Input', 'filter' ), func_get_args() );
33
+ }
34
+
35
+ /**
36
+ * Converts a time into an ISO 8601 extended formatted string.
37
+ *
38
+ * @param int|bool $time Seconds since unix epoc
39
+ * @param int $offset Hour offset
40
+ *
41
+ * @return string an ISO 8601 extended formatted time
42
+ */
43
+ function wp_mainwp_stream_get_iso_8601_extended_date( $time = false, $offset = 0 ) {
44
+ if ( $time ) {
45
+ $microtime = (float) $time . '.0000';
46
+ } else {
47
+ $microtime = microtime( true );
48
+ }
49
+
50
+ $micro_seconds = sprintf( '%06d', ( $microtime - floor( $microtime ) ) * 1000000 );
51
+ $offset_string = sprintf( 'Etc/GMT%s%s', $offset < 0 ? '+' : '-', abs( $offset ) );
52
+
53
+ $timezone = new DateTimeZone( $offset_string );
54
+ $date = new DateTime( date( 'Y-m-d H:i:s.' . $micro_seconds, $microtime ), $timezone );
55
+
56
+ return sprintf(
57
+ '%s%03d%s',
58
+ $date->format( 'Y-m-d\TH:i:s.' ),
59
+ floor( $date->format( 'u' ) / 1000 ),
60
+ $date->format( 'O' )
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Encode to JSON in a way that is also backwards compatible
66
+ *
67
+ * @param mixed $data
68
+ * @param int $options (optional)
69
+ * @param int $depth (optional)
70
+ *
71
+ * @return string
72
+ */
73
+ function wp_mainwp_stream_json_encode( $data, $options = 0, $depth = 512 ) {
74
+ if ( function_exists( 'wp_json_encode' ) ) {
75
+ $json = wp_json_encode( $data, $options, $depth );
76
+ } else {
77
+ // @codingStandardsIgnoreStart
78
+ if ( version_compare( PHP_VERSION, '5.5', '<' ) ) {
79
+ $json = json_encode( $data, $options );
80
+ } else {
81
+ $json = json_encode( $data, $options, $depth );
82
+ }
83
+ // @codingStandardsIgnoreEnd
84
+ }
85
+
86
+ return $json;
87
+ }
88
+
89
+ /**
90
+ * Return an array of sites for a network in a way that is also backwards compatible
91
+ *
92
+ * @param string|array $args
93
+ *
94
+ * @return array
95
+ */
96
+ function wp_mainwp_stream_get_sites( $args = array() ) {
97
+ if ( function_exists( 'get_sites' ) ) {
98
+ $sites = get_sites( $args );
99
+ } else {
100
+ $sites = array();
101
+ foreach ( wp_get_sites( $args ) as $site ) { // @codingStandardsIgnoreLine Specifically for old version of WP first, in order to provide backward compatibility
102
+ $sites[] = WP_Site::get_instance( $site['blog_id'] );
103
+ }
104
+ }
105
+
106
+ return $sites;
107
+ }
108
+
109
+ /**
110
+ * Check if Stream is running on WordPress.com VIP
111
+ *
112
+ * @return bool
113
+ */
114
+ function wp_mainwp_stream_is_vip() {
115
+ return function_exists( 'wpcom_vip_load_plugin' );
116
+ }
117
+
118
+ /**
119
+ * True if native WP Cron is enabled, otherwise false
120
+ *
121
+ * @return bool
122
+ */
123
+ function wp_mainwp_stream_is_cron_enabled() {
124
+ return ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ? false : true;
125
+ }
126
+
127
+ /**
128
+ * Get the asset min suffix if defined.
129
+ *
130
+ * @return string
131
+ */
132
+ function wp_mainwp_stream_min_suffix() {
133
+ $min = '';
134
+ // if ( ! defined( 'SCRIPT_DEBUG' ) || false === SCRIPT_DEBUG ) {
135
+ // $min = 'min.';
136
+ // }
137
+
138
+ return $min;
139
+ }
includes/install.php DELETED
@@ -1,376 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Install {
4
-
5
- const KEY = 'mainwp_child_reports_db';
6
-
7
- public static $table_prefix;
8
-
9
- public static $db_version;
10
-
11
- public static $current;
12
-
13
- public static $update_versions;
14
-
15
- public static $update_required = false;
16
-
17
- public static $success_db;
18
-
19
- private static $instance = false;
20
-
21
- public static $import_connectors;
22
-
23
- public static function get_instance() {
24
- if ( empty( self::$instance ) ) {
25
- self::$instance = new self();
26
- }
27
-
28
- return self::$instance;
29
- }
30
-
31
- function __construct() {
32
- global $wpdb;
33
-
34
- self::$current = MainWP_WP_Stream::VERSION;
35
- self::$db_version = self::get_db_version();
36
-
37
- self::$table_prefix = $wpdb->prefix;
38
- self::$import_connectors = array('comment', 'editor', 'installer', 'media', 'menus', 'posts', 'users', 'widgets');
39
- //add_action( 'init', array( __CLASS__ , 'check' ) );
40
- self::check();
41
- }
42
-
43
- public static function check() {
44
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
45
- return;
46
- }
47
-
48
- global $wpdb;
49
-
50
- // if ( empty( self::$db_version ) ) {
51
- self::install( self::$current );
52
- // self::copy_stream_db();
53
- // }
54
-
55
- if ( version_compare( self::$db_version, self::$current, '!=') ) {
56
- self::check_updates();
57
- update_site_option( self::KEY, self::$current );
58
- }
59
-
60
- if (false === get_option('mainwp_creport_first_time_activated')) {
61
- $sql = "SELECT MIN( created ) AS first_time " .
62
- "FROM {$wpdb->prefix}mainwp_stream " .
63
- "WHERE created != '0000-00-00 00:00:00'";
64
- $result = $wpdb->get_results( $sql, ARRAY_A );
65
- $time = time();
66
- if (isset($result[0]) && !empty($result[0]['first_time'])) {
67
- $time = strtotime( $result[0]['first_time'] );
68
- }
69
- update_option('mainwp_creport_first_time_activated', $time);
70
- }
71
-
72
- // if ('yes' == get_option('mainwp_child_reports_check_to_copy_data', false)) {
73
- // add_action( 'all_admin_notices', array( __CLASS__, 'update_notice_hook' ) );
74
- // }
75
- }
76
-
77
- public static function check_to_copy_data() {
78
- $stream_db_version = get_site_option( 'wp_stream_db' ); // store db version of the plugin stream 1.4.9
79
- //update_option('mainwp_child_reports_check_to_copy_data', 'yes');
80
- return;
81
- }
82
-
83
- public static function copy_stream_db() {
84
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
85
- return;
86
- }
87
- $stream_db_version = get_site_option( 'wp_stream_db' ); // store db version of the plugin stream 1.4.9
88
- if (version_compare($stream_db_version, '1.4.9', '='))
89
- self::copy_stream_149_db();
90
- else if (version_compare($stream_db_version, '3.0' , '>=')) {
91
- self::copy_stream_300_db();
92
- }
93
- update_option('mainwp_child_reports_copied_data_ok', 'yes');
94
- update_option('mainwp_child_reports_check_to_copy_data', '');
95
- }
96
-
97
- public static function copy_stream_149_db() {
98
- global $wpdb;
99
- if ( is_multisite() ) {
100
- return;
101
- }
102
- if ( $wpdb->get_var( "SHOW TABLES LIKE '" . $wpdb->prefix . "stream'" ) !== $wpdb->prefix . "stream" )
103
- return;
104
- if ( $wpdb->get_var( "SHOW TABLES LIKE '" . $wpdb->prefix . "stream_context'" ) !== $wpdb->prefix . "stream_context" )
105
- return;
106
- if ( $wpdb->get_var( "SHOW TABLES LIKE '" . $wpdb->prefix . "stream_meta'" ) !== $wpdb->prefix . "stream_meta" )
107
- return;
108
-
109
- $sql = "SELECT * FROM {$wpdb->prefix}stream";
110
-
111
- $blog_stream = $wpdb->get_results( $sql, ARRAY_A );
112
-
113
- foreach ( $blog_stream as $key => $stream_entry ) {
114
- $prev_entry_id = $stream_entry['ID'];
115
-
116
- unset( $stream_entry['ID'] );
117
-
118
- $wpdb->insert( $wpdb->prefix . 'mainwp_stream', $stream_entry );
119
- $stream_entry_id = $wpdb->insert_id;
120
-
121
- $sql = "SELECT * FROM {$wpdb->prefix}stream_context WHERE record_id = $prev_entry_id";
122
-
123
- $blog_stream_context = $wpdb->get_results( $sql, ARRAY_A );
124
-
125
- foreach ( $blog_stream_context as $key => $stream_context ) {
126
- unset( $stream_context['meta_id'] );
127
- $stream_context['record_id'] = $stream_entry_id;
128
-
129
- $wpdb->insert( $wpdb->prefix . 'mainwp_stream_context', $stream_context );
130
- }
131
-
132
- $sql = "SELECT * FROM {$wpdb->prefix}stream_meta WHERE record_id = $prev_entry_id";
133
-
134
- $blog_stream_meta = $wpdb->get_results( $sql, ARRAY_A );
135
-
136
- foreach ( $blog_stream_meta as $key => $stream_meta ) {
137
- unset( $stream_meta['meta_id'] );
138
- $stream_meta['record_id'] = $stream_entry_id;
139
-
140
- $wpdb->insert( $wpdb->prefix . 'mainwp_stream_meta', $stream_meta );
141
- }
142
- }
143
-
144
- }
145
-
146
- public static function copy_stream_300_db() {
147
- global $wpdb;
148
- if ( is_multisite() ) {
149
- return;
150
- }
151
- if ( $wpdb->get_var( "SHOW TABLES LIKE '" . $wpdb->prefix . "stream'" ) !== $wpdb->prefix . "stream" )
152
- return;
153
- if ( $wpdb->get_var( "SHOW TABLES LIKE '" . $wpdb->prefix . "stream_meta'" ) !== $wpdb->prefix . "stream_meta" )
154
- return;
155
-
156
- $timeout = 20 * 60 * 60; /*20 minutes*/
157
- $mem = '512M';
158
- // @codingStandardsIgnoreStart
159
- @ini_set( 'memory_limit', $mem );
160
- @set_time_limit( $timeout );
161
- @ini_set( 'max_execution_time', $timeout );
162
-
163
- $sql = "SELECT * FROM {$wpdb->prefix}stream";
164
-
165
- $blog_stream = $wpdb->get_results( $sql, ARRAY_A );
166
- $printed_connector = array();
167
-
168
- foreach ( $blog_stream as $key => $stream_entry ) {
169
-
170
- if (!in_array($stream_entry['connector'], self::$import_connectors)) {
171
- continue;
172
- }
173
-
174
- if ('users' == $stream_entry['connector'] && 'login' == $stream_entry['action']) {
175
- continue;
176
- }
177
-
178
- $prev_entry_id = $stream_entry['ID'];
179
-
180
- $insert_entry = array(
181
- 'site_id' => $stream_entry['site_id'],
182
- 'blog_id' => $stream_entry['blog_id'],
183
- 'object_id' => $stream_entry['object_id'],
184
- 'author' => $stream_entry['user_id'],
185
- 'author_role' => $stream_entry['user_role'],
186
- 'author_role' => $stream_entry['user_role'],
187
- 'summary' => $stream_entry['summary'],
188
- 'visibility' => 'publish',
189
- 'parent' => 0,
190
- 'type' => 'stream',
191
- 'created' => $stream_entry['created'],
192
- 'ip' => $stream_entry['ip']
193
- );
194
-
195
- $wpdb->insert( $wpdb->prefix . 'mainwp_stream', $insert_entry );
196
- $stream_entry_id = $wpdb->insert_id;
197
-
198
- $insert_context = array(
199
- 'record_id' => $stream_entry_id,
200
- 'context' => $stream_entry['context'],
201
- 'action' => $stream_entry['action'],
202
- 'connector' => $stream_entry['connector']
203
- );
204
- $wpdb->insert( $wpdb->prefix . 'mainwp_stream_context', $insert_context );
205
-
206
- $sql = "SELECT * FROM {$wpdb->prefix}stream_meta WHERE record_id = $prev_entry_id";
207
-
208
- $blog_stream_meta = $wpdb->get_results( $sql, ARRAY_A );
209
-
210
- foreach ( $blog_stream_meta as $key => $stream_meta ) {
211
- unset( $stream_meta['meta_id'] );
212
- $stream_meta['record_id'] = $stream_entry_id;
213
- $wpdb->insert( $wpdb->prefix . 'mainwp_stream_meta', $stream_meta );
214
- }
215
- }
216
-
217
- }
218
-
219
- public static function update_notice_hook() {
220
- // if ( ! current_user_can( WP_Stream_Admin::VIEW_CAP ) ) {
221
- // return;
222
- // }
223
-
224
- if ( ! isset( $_REQUEST['mainwp_wp_stream_update'] ) ) {
225
- // self::prompt_copy_data();
226
- } else {
227
- // check_admin_referer( 'mainwp_wp_stream_update_db' );
228
- // if ( isset( $_REQUEST['mainwp_reports_copy_db_submit'] ) ) {
229
- // self::copy_stream_db();
230
- //
231
- // } else if ( isset( $_REQUEST['mainwp_reports_continue_submit'] ) ) {
232
- // //update_option('mainwp_child_reports_check_to_copy_data', '');
233
- // }
234
- }
235
-
236
- // if ('yes' == get_option('mainwp_child_reports_copied_data_ok')) {
237
- // self::prompt_copy_data_status();
238
- // }
239
- }
240
-
241
- public static function prompt_copy_data() {
242
- ?>
243
- <div class="updated">
244
- <form method="post" action="<?php echo esc_url( remove_query_arg( 'message' ) ) ?>">
245
- <?php wp_nonce_field( 'mainwp_wp_stream_update_db' ) ?>
246
- <input type="hidden" name="mainwp_wp_stream_update" value="not_update_and_continue"/>
247
- <p><strong><?php esc_html_e( 'Do you want to import logs from the Stream plugin?', 'mainwp-child-reports' ) ?></strong></p>
248
- <p class="submit">
249
- <?php submit_button( esc_html__( 'Yes', 'mainwp-child-reports' ), 'primary', 'mainwp_reports_copy_db_submit', false ) ?>
250
- <?php submit_button( esc_html__( 'No', 'mainwp-child-reports' ), 'primary', 'mainwp_reports_continue_submit', false ) ?>
251
- </p
252
- </form>
253
- </div>
254
- <?php
255
- }
256
-
257
- public static function prompt_copy_data_status() {
258
- printf( '<div class="updated"><p>%s</p></div>', __( 'Logs have been successfully imported.', 'mainwp-child-reports' ) );
259
- delete_option('mainwp_child_reports_copied_data_ok');
260
- }
261
-
262
- public static function get_db_version() {
263
-
264
- $version = get_site_option( self::KEY );
265
-
266
- return $version;
267
- }
268
-
269
- public static function install( $current ) {
270
- global $wpdb;
271
-
272
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
273
-
274
- $prefix = $wpdb->prefix;
275
-
276
- $sql = "CREATE TABLE {$prefix}mainwp_stream (
277
- ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
278
- site_id bigint(20) unsigned NOT NULL DEFAULT '1',
279
- blog_id bigint(20) unsigned NOT NULL DEFAULT '0',
280
- object_id bigint(20) unsigned NULL,
281
- author bigint(20) unsigned NOT NULL DEFAULT '0',
282
- author_role varchar(20) NOT NULL DEFAULT '',
283
- summary longtext NOT NULL,
284
- visibility varchar(20) NOT NULL DEFAULT 'publish',
285
- parent bigint(20) unsigned NOT NULL DEFAULT '0',
286
- type varchar(20) NOT NULL DEFAULT 'stream',
287
- created datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
288
- ip varchar(39) NULL,
289
- PRIMARY KEY (ID),
290
- KEY site_id (site_id),
291
- KEY blog_id (blog_id),
292
- KEY parent (parent),
293
- KEY author (author),
294
- KEY created (created)
295
- )";
296
-
297
- if ( ! empty( $wpdb->charset ) ) {
298
- $sql .= " CHARACTER SET $wpdb->charset";
299
- }
300
-
301
- if ( ! empty( $wpdb->collate ) ) {
302
- $sql .= " COLLATE $wpdb->collate";
303
- }
304
-
305
- $sql .= ';';
306
-
307
- dbDelta( $sql );
308
-
309
- $sql = "CREATE TABLE {$prefix}mainwp_stream_context (
310
- meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
311
- record_id bigint(20) unsigned NOT NULL,
312
- context varchar(100) NOT NULL,
313
- action varchar(100) NOT NULL,
314
- connector varchar(100) NOT NULL,
315
- PRIMARY KEY (meta_id),
316
- KEY context (context),
317
- KEY action (action),
318
- KEY connector (connector)
319
- )";
320
-
321
- if ( ! empty( $wpdb->charset ) ) {
322
- $sql .= " CHARACTER SET $wpdb->charset";
323
- }
324
-
325
- if ( ! empty( $wpdb->collate ) ) {
326
- $sql .= " COLLATE $wpdb->collate";
327
- }
328
-
329
- $sql .= ';';
330
-
331
- dbDelta( $sql );
332
-
333
- $sql = "CREATE TABLE {$prefix}mainwp_stream_meta (
334
- meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
335
- record_id bigint(20) unsigned NOT NULL,
336
- meta_key varchar(200) NOT NULL,
337
- meta_value text NOT NULL,
338
- PRIMARY KEY (meta_id),
339
- KEY record_id (record_id),
340
- KEY meta_key (meta_key(100))
341
- )";
342
-
343
- if ( ! empty( $wpdb->charset ) ) {
344
- $sql .= " CHARACTER SET $wpdb->charset";
345
- }
346
-
347
- if ( ! empty( $wpdb->collate ) ) {
348
- $sql .= " COLLATE $wpdb->collate";
349
- }
350
-
351
- $sql .= ';';
352
-
353
- dbDelta( $sql );
354
-
355
- update_site_option( self::KEY, self::$current );
356
-
357
- return $current;
358
- }
359
-
360
- public static function check_updates() {
361
- global $wpdb;
362
-
363
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
364
- $current_version = self::get_db_version();
365
-
366
- $prefix = self::$table_prefix;
367
-
368
- if (version_compare($current_version, '0.0.4', '<')) {
369
- $wpdb->query( "ALTER TABLE {$prefix}mainwp_stream_meta CHANGE `meta_value` `meta_value` TEXT " . ( !empty($wpdb->charset) ? "CHARACTER SET " . $wpdb->charset : "" ) . ( !empty($wpdb->collate) ? " COLLATE " . $wpdb->collate : "" ) . " NOT NULL;");
370
- if ( $wpdb->get_var( "SHOW INDEX FROM {$prefix}mainwp_stream_meta WHERE column_name = 'meta_value'")) {
371
- $wpdb->query( "ALTER TABLE {$prefix}mainwp_stream_meta DROP INDEX meta_value");
372
- }
373
- }
374
-
375
- }
376
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/Carbon.php ADDED
@@ -0,0 +1,2214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Carbon package.
5
+ *
6
+ * (c) Brian Nesbitt <brian@nesbot.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Carbon;
13
+
14
+ use Closure;
15
+ use DateTime;
16
+ use DateTimeZone;
17
+ use DateInterval;
18
+ use DatePeriod;
19
+ use InvalidArgumentException;
20
+
21
+ /**
22
+ * A simple API extension for DateTime
23
+ *
24
+ * @property integer $year
25
+ * @property integer $month
26
+ * @property integer $day
27
+ * @property integer $hour
28
+ * @property integer $minute
29
+ * @property integer $second
30
+ * @property integer $timestamp seconds since the Unix Epoch
31
+ * @property-read integer $micro
32
+ * @property-read integer $dayOfWeek 0 (for Sunday) through 6 (for Saturday)
33
+ * @property-read integer $dayOfYear 0 through 365
34
+ * @property-read integer $weekOfMonth 1 through 6
35
+ * @property-read integer $weekOfYear ISO-8601 week number of year, weeks starting on Monday
36
+ * @property-read integer $daysInMonth number of days in the given month
37
+ * @property-read integer $age does a diffInYears() with default parameters
38
+ * @property-read integer $quarter the quarter of this instance, 1 - 4
39
+ * @property-read integer $offset the timezone offset in seconds from UTC
40
+ * @property-read integer $offsetHours the timezone offset in hours from UTC
41
+ * @property-read boolean $dst daylight savings time indicator, true if DST, false otherwise
42
+ * @property-read boolean $local checks if the timezone is local, true if local, false otherwise
43
+ * @property-read boolean $utc checks if the timezone is UTC, true if UTC, false otherwise
44
+ * @property-read string $timezoneName
45
+ * @property-read string $tzName
46
+ *
47
+ * @property-read DateTimeZone $timezone the current timezone
48
+ * @property-read DateTimeZone $tz alias of timezone
49
+ * @property-write DateTimeZone|string $timezone the current timezone
50
+ * @property-write DateTimeZone|string $tz alias of timezone
51
+ *
52
+ */
53
+ class Carbon extends DateTime
54
+ {
55
+ /**
56
+ * The day constants
57
+ */
58
+ const SUNDAY = 0;
59
+ const MONDAY = 1;
60
+ const TUESDAY = 2;
61
+ const WEDNESDAY = 3;
62
+ const THURSDAY = 4;
63
+ const FRIDAY = 5;
64
+ const SATURDAY = 6;
65
+
66
+ /**
67
+ * Names of days of the week.
68
+ *
69
+ * @var array
70
+ */
71
+ protected static $days = array(
72
+ self::SUNDAY => 'Sunday',
73
+ self::MONDAY => 'Monday',
74
+ self::TUESDAY => 'Tuesday',
75
+ self::WEDNESDAY => 'Wednesday',
76
+ self::THURSDAY => 'Thursday',
77
+ self::FRIDAY => 'Friday',
78
+ self::SATURDAY => 'Saturday'
79
+ );
80
+
81
+ /**
82
+ * Terms used to detect if a time passed is a relative date for testing purposes
83
+ *
84
+ * @var array
85
+ */
86
+ protected static $relativeKeywords = array(
87
+ 'this',
88
+ 'next',
89
+ 'last',
90
+ 'tomorrow',
91
+ 'yesterday',
92
+ '+',
93
+ '-',
94
+ 'first',
95
+ 'last',
96
+ 'ago'
97
+ );
98
+
99
+ /**
100
+ * Number of X in Y
101
+ */
102
+ const YEARS_PER_CENTURY = 100;
103
+ const YEARS_PER_DECADE = 10;
104
+ const MONTHS_PER_YEAR = 12;
105
+ const WEEKS_PER_YEAR = 52;
106
+ const DAYS_PER_WEEK = 7;
107
+ const HOURS_PER_DAY = 24;
108
+ const MINUTES_PER_HOUR = 60;
109
+ const SECONDS_PER_MINUTE = 60;
110
+
111
+ /**
112
+ * Default format to use for __toString method when type juggling occurs.
113
+ *
114
+ * @var string
115
+ */
116
+ const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s';
117
+
118
+ /**
119
+ * Format to use for __toString method when type juggling occurs.
120
+ *
121
+ * @var string
122
+ */
123
+ protected static $toStringFormat = self::DEFAULT_TO_STRING_FORMAT;
124
+
125
+ /**
126
+ * A test Carbon instance to be returned when now instances are created
127
+ *
128
+ * @var Carbon
129
+ */
130
+ protected static $testNow;
131
+
132
+ /**
133
+ * Creates a DateTimeZone from a string or a DateTimeZone
134
+ *
135
+ * @param DateTimeZone|string $object
136
+ *
137
+ * @return DateTimeZone
138
+ *
139
+ * @throws InvalidArgumentException
140
+ */
141
+ protected static function safeCreateDateTimeZone($object)
142
+ {
143
+ if ($object instanceof DateTimeZone) {
144
+ return $object;
145
+ }
146
+
147
+ $tz = @timezone_open((string) $object);
148
+
149
+ if ($tz === false) {
150
+ throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')');
151
+ }
152
+
153
+ return $tz;
154
+ }
155
+
156
+ ///////////////////////////////////////////////////////////////////
157
+ //////////////////////////// CONSTRUCTORS /////////////////////////
158
+ ///////////////////////////////////////////////////////////////////
159
+
160
+ /**
161
+ * Create a new Carbon instance.
162
+ *
163
+ * Please see the testing aids section (specifically static::setTestNow())
164
+ * for more on the possibility of this constructor returning a test instance.
165
+ *
166
+ * @param string $time
167
+ * @param DateTimeZone|string $tz
168
+ */
169
+ public function __construct($time = null, $tz = null)
170
+ {
171
+ // If the class has a test now set and we are trying to create a now()
172
+ // instance then override as required
173
+ if (static::hasTestNow() && (empty($time) || $time === 'now' || static::hasRelativeKeywords($time))) {
174
+ $testInstance = clone static::getTestNow();
175
+ if (static::hasRelativeKeywords($time)) {
176
+ $testInstance->modify($time);
177
+ }
178
+
179
+ //shift the time according to the given time zone
180
+ if ($tz !== NULL && $tz != static::getTestNow()->tz) {
181
+ $testInstance->setTimezone($tz);
182
+ } else {
183
+ $tz = $testInstance->tz;
184
+ }
185
+
186
+ $time = $testInstance->toDateTimeString();
187
+ }
188
+
189
+ if ($tz !== null) {
190
+ parent::__construct($time, static::safeCreateDateTimeZone($tz));
191
+ } else {
192
+ parent::__construct($time);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Create a Carbon instance from a DateTime one
198
+ *
199
+ * @param DateTime $dt
200
+ *
201
+ * @return static
202
+ */
203
+ public static function instance(DateTime $dt)
204
+ {
205
+ return new static($dt->format('Y-m-d H:i:s.u'), $dt->getTimeZone());
206
+ }
207
+
208
+ /**
209
+ * Create a carbon instance from a string. This is an alias for the
210
+ * constructor that allows better fluent syntax as it allows you to do
211
+ * Carbon::parse('Monday next week')->fn() rather than
212
+ * (new Carbon('Monday next week'))->fn()
213
+ *
214
+ * @param string $time
215
+ * @param DateTimeZone|string $tz
216
+ *
217
+ * @return static
218
+ */
219
+ public static function parse($time = null, $tz = null)
220
+ {
221
+ return new static($time, $tz);
222
+ }
223
+
224
+ /**
225
+ * Get a Carbon instance for the current date and time
226
+ *
227
+ * @param DateTimeZone|string $tz
228
+ *
229
+ * @return static
230
+ */
231
+ public static function now($tz = null)
232
+ {
233
+ return new static(null, $tz);
234
+ }
235
+
236
+ /**
237
+ * Create a Carbon instance for today
238
+ *
239
+ * @param DateTimeZone|string $tz
240
+ *
241
+ * @return static
242
+ */
243
+ public static function today($tz = null)
244
+ {
245
+ return static::now($tz)->startOfDay();
246
+ }
247
+
248
+ /**
249
+ * Create a Carbon instance for tomorrow
250
+ *
251
+ * @param DateTimeZone|string $tz
252
+ *
253
+ * @return static
254
+ */
255
+ public static function tomorrow($tz = null)
256
+ {
257
+ return static::today($tz)->addDay();
258
+ }
259
+
260
+ /**
261
+ * Create a Carbon instance for yesterday
262
+ *
263
+ * @param DateTimeZone|string $tz
264
+ *
265
+ * @return static
266
+ */
267
+ public static function yesterday($tz = null)
268
+ {
269
+ return static::today($tz)->subDay();
270
+ }
271
+
272
+ /**
273
+ * Create a Carbon instance for the greatest supported date.
274
+ *
275
+ * @return Carbon
276
+ */
277
+ public static function maxValue()
278
+ {
279
+ return static::createFromTimestamp(PHP_INT_MAX);
280
+ }
281
+
282
+ /**
283
+ * Create a Carbon instance for the lowest supported date.
284
+ *
285
+ * @return Carbon
286
+ */
287
+ public static function minValue()
288
+ {
289
+ return static::createFromTimestamp(~PHP_INT_MAX);
290
+ }
291
+
292
+ /**
293
+ * Create a new Carbon instance from a specific date and time.
294
+ *
295
+ * If any of $year, $month or $day are set to null their now() values
296
+ * will be used.
297
+ *
298
+ * If $hour is null it will be set to its now() value and the default values
299
+ * for $minute and $second will be their now() values.
300
+ * If $hour is not null then the default values for $minute and $second
301
+ * will be 0.
302
+ *
303
+ * @param integer $year
304
+ * @param integer $month
305
+ * @param integer $day
306
+ * @param integer $hour
307
+ * @param integer $minute
308
+ * @param integer $second
309
+ * @param DateTimeZone|string $tz
310
+ *
311
+ * @return static
312
+ */
313
+ public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null)
314
+ {
315
+ $year = ($year === null) ? date('Y') : $year;
316
+ $month = ($month === null) ? date('n') : $month;
317
+ $day = ($day === null) ? date('j') : $day;
318
+
319
+ if ($hour === null) {
320
+ $hour = date('G');
321
+ $minute = ($minute === null) ? date('i') : $minute;
322
+ $second = ($second === null) ? date('s') : $second;
323
+ } else {
324
+ $minute = ($minute === null) ? 0 : $minute;
325
+ $second = ($second === null) ? 0 : $second;
326
+ }
327
+
328
+ return static::createFromFormat('Y-n-j G:i:s', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz);
329
+ }
330
+
331
+ /**
332
+ * Create a Carbon instance from just a date. The time portion is set to now.
333
+ *
334
+ * @param integer $year
335
+ * @param integer $month
336
+ * @param integer $day
337
+ * @param DateTimeZone|string $tz
338
+ *
339
+ * @return static
340
+ */
341
+ public static function createFromDate($year = null, $month = null, $day = null, $tz = null)
342
+ {
343
+ return static::create($year, $month, $day, null, null, null, $tz);
344
+ }
345
+
346
+ /**
347
+ * Create a Carbon instance from just a time. The date portion is set to today.
348
+ *
349
+ * @param integer $hour
350
+ * @param integer $minute
351
+ * @param integer $second
352
+ * @param DateTimeZone|string $tz
353
+ *
354
+ * @return static
355
+ */
356
+ public static function createFromTime($hour = null, $minute = null, $second = null, $tz = null)
357
+ {
358
+ return static::create(null, null, null, $hour, $minute, $second, $tz);
359
+ }
360
+
361
+ /**
362
+ * Create a Carbon instance from a specific format
363
+ *
364
+ * @param string $format
365
+ * @param string $time
366
+ * @param DateTimeZone|string $tz
367
+ *
368
+ * @return static
369
+ *
370
+ * @throws InvalidArgumentException
371
+ */
372
+ public static function createFromFormat($format, $time, $tz = null)
373
+ {
374
+ if ($tz !== null) {
375
+ $dt = parent::createFromFormat($format, $time, static::safeCreateDateTimeZone($tz));
376
+ } else {
377
+ $dt = parent::createFromFormat($format, $time);
378
+ }
379
+
380
+ if ($dt instanceof DateTime) {
381
+ return static::instance($dt);
382
+ }
383
+
384
+ $errors = static::getLastErrors();
385
+ throw new InvalidArgumentException(implode(PHP_EOL, $errors['errors']));
386
+ }
387
+
388
+ /**
389
+ * Create a Carbon instance from a timestamp
390
+ *
391
+ * @param integer $timestamp
392
+ * @param DateTimeZone|string $tz
393
+ *
394
+ * @return static
395
+ */
396
+ public static function createFromTimestamp($timestamp, $tz = null)
397
+ {
398
+ return static::now($tz)->setTimestamp($timestamp);
399
+ }
400
+
401
+ /**
402
+ * Create a Carbon instance from an UTC timestamp
403
+ *
404
+ * @param integer $timestamp
405
+ *
406
+ * @return static
407
+ */
408
+ public static function createFromTimestampUTC($timestamp)
409
+ {
410
+ return new static('@'.$timestamp);
411
+ }
412
+
413
+ /**
414
+ * Get a copy of the instance
415
+ *
416
+ * @return static
417
+ */
418
+ public function copy()
419
+ {
420
+ return static::instance($this);
421
+ }
422
+
423
+ ///////////////////////////////////////////////////////////////////
424
+ ///////////////////////// GETTERS AND SETTERS /////////////////////
425
+ ///////////////////////////////////////////////////////////////////
426
+
427
+ /**
428
+ * Get a part of the Carbon object
429
+ *
430
+ * @param string $name
431
+ *
432
+ * @throws InvalidArgumentException
433
+ *
434
+ * @return string|integer|DateTimeZone
435
+ */
436
+ public function __get($name)
437
+ {
438
+ switch ($name) {
439
+ case 'year':
440
+ case 'month':
441
+ case 'day':
442
+ case 'hour':
443
+ case 'minute':
444
+ case 'second':
445
+ case 'micro':
446
+ case 'dayOfWeek':
447
+ case 'dayOfYear':
448
+ case 'weekOfYear':
449
+ case 'daysInMonth':
450
+ case 'timestamp':
451
+ $formats = array(
452
+ 'year' => 'Y',
453
+ 'month' => 'n',
454
+ 'day' => 'j',
455
+ 'hour' => 'G',
456
+ 'minute' => 'i',
457
+ 'second' => 's',
458
+ 'micro' => 'u',
459
+ 'dayOfWeek' => 'w',
460
+ 'dayOfYear' => 'z',
461
+ 'weekOfYear' => 'W',
462
+ 'daysInMonth' => 't',
463
+ 'timestamp' => 'U',
464
+ );
465
+
466
+ return (int) $this->format($formats[$name]);
467
+
468
+ case 'weekOfMonth':
469
+ return (int) ceil($this->day / self::DAYS_PER_WEEK);
470
+
471
+ case 'age':
472
+ return (int) $this->diffInYears();
473
+
474
+ case 'quarter':
475
+ return (int) ceil($this->month / 3);
476
+
477
+ case 'offset':
478
+ return $this->getOffset();
479
+
480
+ case 'offsetHours':
481
+ return $this->getOffset() / self::SECONDS_PER_MINUTE / self::MINUTES_PER_HOUR;
482
+
483
+ case 'dst':
484
+ return $this->format('I') == '1';
485
+
486
+ case 'local':
487
+ return $this->offset == $this->copy()->setTimezone(date_default_timezone_get())->offset;
488
+
489
+ case 'utc':
490
+ return $this->offset == 0;
491
+
492
+ case 'timezone':
493
+ case 'tz':
494
+ return $this->getTimezone();
495
+
496
+ case 'timezoneName':
497
+ case 'tzName':
498
+ return $this->getTimezone()->getName();
499
+
500
+ default:
501
+ throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name));
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Check if an attribute exists on the object
507
+ *
508
+ * @param string $name
509
+ *
510
+ * @return boolean
511
+ */
512
+ public function __isset($name)
513
+ {
514
+ try {
515
+ $this->__get($name);
516
+ } catch (InvalidArgumentException $e) {
517
+ return false;
518
+ }
519
+
520
+ return true;
521
+ }
522
+
523
+ /**
524
+ * Set a part of the Carbon object
525
+ *
526
+ * @param string $name
527
+ * @param string|integer|DateTimeZone $value
528
+ *
529
+ * @throws InvalidArgumentException
530
+ */
531
+ public function __set($name, $value)
532
+ {
533
+ switch ($name) {
534
+ case 'year':
535
+ parent::setDate($value, $this->month, $this->day);
536
+ break;
537
+
538
+ case 'month':
539
+ parent::setDate($this->year, $value, $this->day);
540
+ break;
541
+
542
+ case 'day':
543
+ parent::setDate($this->year, $this->month, $value);
544
+ break;
545
+
546
+ case 'hour':
547
+ parent::setTime($value, $this->minute, $this->second);
548
+ break;
549
+
550
+ case 'minute':
551
+ parent::setTime($this->hour, $value, $this->second);
552
+ break;
553
+
554
+ case 'second':
555
+ parent::setTime($this->hour, $this->minute, $value);
556
+ break;
557
+
558
+ case 'timestamp':
559
+ parent::setTimestamp($value);
560
+ break;
561
+
562
+ case 'timezone':
563
+ case 'tz':
564
+ $this->setTimezone($value);
565
+ break;
566
+
567
+ default:
568
+ throw new InvalidArgumentException(sprintf("Unknown setter '%s'", $name));
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Set the instance's year
574
+ *
575
+ * @param integer $value
576
+ *
577
+ * @return static
578
+ */
579
+ public function year($value)
580
+ {
581
+ $this->year = $value;
582
+
583
+ return $this;
584
+ }
585
+
586
+ /**
587
+ * Set the instance's month
588
+ *
589
+ * @param integer $value
590
+ *
591
+ * @return static
592
+ */
593
+ public function month($value)
594
+ {
595
+ $this->month = $value;
596
+
597
+ return $this;
598
+ }
599
+
600
+ /**
601
+ * Set the instance's day
602
+ *
603
+ * @param integer $value
604
+ *
605
+ * @return static
606
+ */
607
+ public function day($value)
608
+ {
609
+ $this->day = $value;
610
+
611
+ return $this;
612
+ }
613
+
614
+ /**
615
+ * Set the date all together
616
+ *
617
+ * @param integer $year
618
+ * @param integer $month
619
+ * @param integer $day
620
+ *
621
+ * @return static
622
+ */
623
+ public function setDate($year, $month, $day)
624
+ {
625
+ parent::setDate($year, $month, $day);
626
+
627
+ return $this;
628
+ }
629
+
630
+ /**
631
+ * Set the instance's hour
632
+ *
633
+ * @param integer $value
634
+ *
635
+ * @return static
636
+ */
637
+ public function hour($value)
638
+ {
639
+ $this->hour = $value;
640
+
641
+ return $this;
642
+ }
643
+
644
+ /**
645
+ * Set the instance's minute
646
+ *
647
+ * @param integer $value
648
+ *
649
+ * @return static
650
+ */
651
+ public function minute($value)
652
+ {
653
+ $this->minute = $value;
654
+
655
+ return $this;
656
+ }
657
+
658
+ /**
659
+ * Set the instance's second
660
+ *
661
+ * @param integer $value
662
+ *
663
+ * @return static
664
+ */
665
+ public function second($value)
666
+ {
667
+ $this->second = $value;
668
+
669
+ return $this;
670
+ }
671
+
672
+ /**
673
+ * Set the time all together
674
+ *
675
+ * @param integer $hour
676
+ * @param integer $minute
677
+ * @param integer $second
678
+ * @param integer $microseconds
679
+ *
680
+ * @return static
681
+ */
682
+ public function setTime($hour, $minute, $second = 0, $microseconds = 0 )
683
+ {
684
+ parent::setTime($hour, $minute, $second, $microseconds );
685
+
686
+ return $this;
687
+ }
688
+
689
+ /**
690
+ * Set the date and time all together
691
+ *
692
+ * @param integer $year
693
+ * @param integer $month
694
+ * @param integer $day
695
+ * @param integer $hour
696
+ * @param integer $minute
697
+ * @param integer $second
698
+ *
699
+ * @return static
700
+ */
701
+ public function setDateTime($year, $month, $day, $hour, $minute, $second = 0)
702
+ {
703
+ return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second);
704
+ }
705
+
706
+ /**
707
+ * Set the instance's timestamp
708
+ *
709
+ * @param integer $value
710
+ *
711
+ * @return static
712
+ */
713
+ public function timestamp($value)
714
+ {
715
+ $this->timestamp = $value;
716
+
717
+ return $this;
718
+ }
719
+
720
+ /**
721
+ * Alias for setTimezone()
722
+ *
723
+ * @param DateTimeZone|string $value
724
+ *
725
+ * @return static
726
+ */
727
+ public function timezone($value)
728
+ {
729
+ return $this->setTimezone($value);
730
+ }
731
+
732
+ /**
733
+ * Alias for setTimezone()
734
+ *
735
+ * @param DateTimeZone|string $value
736
+ *
737
+ * @return static
738
+ */
739
+ public function tz($value)
740
+ {
741
+ return $this->setTimezone($value);
742
+ }
743
+
744
+ /**
745
+ * Set the instance's timezone from a string or object
746
+ *
747
+ * @param DateTimeZone|string $value
748
+ *
749
+ * @return static
750
+ */
751
+ public function setTimezone($value)
752
+ {
753
+ parent::setTimezone(static::safeCreateDateTimeZone($value));
754
+
755
+ return $this;
756
+ }
757
+
758
+ ///////////////////////////////////////////////////////////////////
759
+ ///////////////////////// TESTING AIDS ////////////////////////////
760
+ ///////////////////////////////////////////////////////////////////
761
+
762
+ /**
763
+ * Set a Carbon instance (real or mock) to be returned when a "now"
764
+ * instance is created. The provided instance will be returned
765
+ * specifically under the following conditions:
766
+ * - A call to the static now() method, ex. Carbon::now()
767
+ * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
768
+ * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now')
769
+ *
770
+ * Note the timezone parameter was left out of the examples above and
771
+ * has no affect as the mock value will be returned regardless of its value.
772
+ *
773
+ * To clear the test instance call this method using the default
774
+ * parameter of null.
775
+ *
776
+ * @param Carbon $testNow
777
+ */
778
+ public static function setTestNow(Carbon $testNow = null)
779
+ {
780
+ static::$testNow = $testNow;
781
+ }
782
+
783
+ /**
784
+ * Get the Carbon instance (real or mock) to be returned when a "now"
785
+ * instance is created.
786
+ *
787
+ * @return static the current instance used for testing
788
+ */
789
+ public static function getTestNow()
790
+ {
791
+ return static::$testNow;
792
+ }
793
+
794
+ /**
795
+ * Determine if there is a valid test instance set. A valid test instance
796
+ * is anything that is not null.
797
+ *
798
+ * @return boolean true if there is a test instance, otherwise false
799
+ */
800
+ public static function hasTestNow()
801
+ {
802
+ return static::getTestNow() !== null;
803
+ }
804
+
805
+ /**
806
+ * Determine if there is a relative keyword in the time string, this is to
807
+ * create dates relative to now for test instances. e.g.: next tuesday
808
+ *
809
+ * @param string $time
810
+ *
811
+ * @return boolean true if there is a keyword, otherwise false
812
+ */
813
+ public static function hasRelativeKeywords($time)
814
+ {
815
+ // skip common format with a '-' in it
816
+ if (preg_match('/[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}/', $time) !== 1) {
817
+ foreach (static::$relativeKeywords as $keyword) {
818
+ if (stripos($time, $keyword) !== false) {
819
+ return true;
820
+ }
821
+ }
822
+ }
823
+
824
+ return false;
825
+ }
826
+
827
+ ///////////////////////////////////////////////////////////////////
828
+ /////////////////////// STRING FORMATTING /////////////////////////
829
+ ///////////////////////////////////////////////////////////////////
830
+
831
+ /**
832
+ * Format the instance with the current locale. You can set the current
833
+ * locale using setlocale() http://php.net/setlocale.
834
+ *
835
+ * @param string $format
836
+ *
837
+ * @return string
838
+ */
839
+ public function formatLocalized($format)
840
+ {
841
+ // Check for Windows to find and replace the %e
842
+ // modifier correctly
843
+ if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
844
+ $format = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $format);
845
+ }
846
+
847
+ return strftime($format, $this->timestamp);
848
+ }
849
+
850
+ /**
851
+ * Reset the format used to the default when type juggling a Carbon instance to a string
852
+ *
853
+ */
854
+ public static function resetToStringFormat()
855
+ {
856
+ static::setToStringFormat(self::DEFAULT_TO_STRING_FORMAT);
857
+ }
858
+
859
+ /**
860
+ * Set the default format used when type juggling a Carbon instance to a string
861
+ *
862
+ * @param string $format
863
+ */
864
+ public static function setToStringFormat($format)
865
+ {
866
+ static::$toStringFormat = $format;
867
+ }
868
+
869
+ /**
870
+ * Format the instance as a string using the set format
871
+ *
872
+ * @return string
873
+ */
874
+ public function __toString()
875
+ {
876
+ return $this->format(static::$toStringFormat);
877
+ }
878
+
879
+ /**
880
+ * Format the instance as date
881
+ *
882
+ * @return string
883
+ */
884
+ public function toDateString()
885
+ {
886
+ return $this->format('Y-m-d');
887
+ }
888
+
889
+ /**
890
+ * Format the instance as a readable date
891
+ *
892
+ * @return string
893
+ */
894
+ public function toFormattedDateString()
895
+ {
896
+ return $this->format('M j, Y');
897
+ }
898
+
899
+ /**
900
+ * Format the instance as time
901
+ *
902
+ * @return string
903
+ */
904
+ public function toTimeString()
905
+ {
906
+ return $this->format('H:i:s');
907
+ }
908
+
909
+ /**
910
+ * Format the instance as date and time
911
+ *
912
+ * @return string
913
+ */
914
+ public function toDateTimeString()
915
+ {
916
+ return $this->format('Y-m-d H:i:s');
917
+ }
918
+
919
+ /**
920
+ * Format the instance with day, date and time
921
+ *
922
+ * @return string
923
+ */
924
+ public function toDayDateTimeString()
925
+ {
926
+ return $this->format('D, M j, Y g:i A');
927
+ }
928
+
929
+ /**
930
+ * Format the instance as ATOM
931
+ *
932
+ * @return string
933
+ */
934
+ public function toAtomString()
935
+ {
936
+ return $this->format(self::ATOM);
937
+ }
938
+
939
+ /**
940
+ * Format the instance as COOKIE
941
+ *
942
+ * @return string
943
+ */
944
+ public function toCookieString()
945
+ {
946
+ return $this->format(self::COOKIE);
947
+ }
948
+
949
+ /**
950
+ * Format the instance as ISO8601
951
+ *
952
+ * @return string
953
+ */
954
+ public function toIso8601String()
955
+ {
956
+ return $this->format(self::ISO8601);
957
+ }
958
+
959
+ /**
960
+ * Format the instance as RFC822
961
+ *
962
+ * @return string
963
+ */
964
+ public function toRfc822String()
965
+ {
966
+ return $this->format(self::RFC822);
967
+ }
968
+
969
+ /**
970
+ * Format the instance as RFC850
971
+ *
972
+ * @return string
973
+ */
974
+ public function toRfc850String()
975
+ {
976
+ return $this->format(self::RFC850);
977
+ }
978
+
979
+ /**
980
+ * Format the instance as RFC1036
981
+ *
982
+ * @return string
983
+ */
984
+ public function toRfc1036String()
985
+ {
986
+ return $this->format(self::RFC1036);
987
+ }
988
+
989
+ /**
990
+ * Format the instance as RFC1123
991
+ *
992
+ * @return string
993
+ */
994
+ public function toRfc1123String()
995
+ {
996
+ return $this->format(self::RFC1123);
997
+ }
998
+
999
+ /**
1000
+ * Format the instance as RFC2822
1001
+ *
1002
+ * @return string
1003
+ */
1004
+ public function toRfc2822String()
1005
+ {
1006
+ return $this->format(self::RFC2822);
1007
+ }
1008
+
1009
+ /**
1010
+ * Format the instance as RFC3339
1011
+ *
1012
+ * @return string
1013
+ */
1014
+ public function toRfc3339String()
1015
+ {
1016
+ return $this->format(self::RFC3339);
1017
+ }
1018
+
1019
+ /**
1020
+ * Format the instance as RSS
1021
+ *
1022
+ * @return string
1023
+ */
1024
+ public function toRssString()
1025
+ {
1026
+ return $this->format(self::RSS);
1027
+ }
1028
+
1029
+ /**
1030
+ * Format the instance as W3C
1031
+ *
1032
+ * @return string
1033
+ */
1034
+ public function toW3cString()
1035
+ {
1036
+ return $this->format(self::W3C);
1037
+ }
1038
+
1039
+ ///////////////////////////////////////////////////////////////////
1040
+ ////////////////////////// COMPARISONS ////////////////////////////
1041
+ ///////////////////////////////////////////////////////////////////
1042
+
1043
+ /**
1044
+ * Determines if the instance is equal to another
1045
+ *
1046
+ * @param Carbon $dt
1047
+ *
1048
+ * @return boolean
1049
+ */
1050
+ public function eq(Carbon $dt)
1051
+ {
1052
+ return $this == $dt;
1053
+ }
1054
+
1055
+ /**
1056
+ * Determines if the instance is not equal to another
1057
+ *
1058
+ * @param Carbon $dt
1059
+ *
1060
+ * @return boolean
1061
+ */
1062
+ public function ne(Carbon $dt)
1063
+ {
1064
+ return !$this->eq($dt);
1065
+ }
1066
+
1067
+ /**
1068
+ * Determines if the instance is greater (after) than another
1069
+ *
1070
+ * @param Carbon $dt
1071
+ *
1072
+ * @return boolean
1073
+ */
1074
+ public function gt(Carbon $dt)
1075
+ {
1076
+ return $this > $dt;
1077
+ }
1078
+
1079
+ /**
1080
+ * Determines if the instance is greater (after) than or equal to another
1081
+ *
1082
+ * @param Carbon $dt
1083
+ *
1084
+ * @return boolean
1085
+ */
1086
+ public function gte(Carbon $dt)
1087
+ {
1088
+ return $this >= $dt;
1089
+ }
1090
+
1091
+ /**
1092
+ * Determines if the instance is less (before) than another
1093
+ *
1094
+ * @param Carbon $dt
1095
+ *
1096
+ * @return boolean
1097
+ */
1098
+ public function lt(Carbon $dt)
1099
+ {
1100
+ return $this < $dt;
1101
+ }
1102
+
1103
+ /**
1104
+ * Determines if the instance is less (before) or equal to another
1105
+ *
1106
+ * @param Carbon $dt
1107
+ *
1108
+ * @return boolean
1109
+ */
1110
+ public function lte(Carbon $dt)
1111
+ {
1112
+ return $this <= $dt;
1113
+ }
1114
+
1115
+ /**
1116
+ * Determines if the instance is between two others
1117
+ *
1118
+ * @param Carbon $dt1
1119
+ * @param Carbon $dt2
1120
+ * @param boolean $equal Indicates if a > and < comparison should be used or <= or >=
1121
+ *
1122
+ * @return boolean
1123
+ */
1124
+ public function between(Carbon $dt1, Carbon $dt2, $equal = true)
1125
+ {
1126
+ if ($dt1->gt($dt2)) {
1127
+ $temp = $dt1;
1128
+ $dt1 = $dt2;
1129
+ $dt2 = $temp;
1130
+ }
1131
+
1132
+ if ($equal) {
1133
+ return $this->gte($dt1) && $this->lte($dt2);
1134
+ } else {
1135
+ return $this->gt($dt1) && $this->lt($dt2);
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Get the minimum instance between a given instance (default now) and the current instance.
1141
+ *
1142
+ * @param Carbon $dt
1143
+ *
1144
+ * @return static
1145
+ */
1146
+ public function min(Carbon $dt = null)
1147
+ {
1148
+ $dt = ($dt === null) ? static::now($this->tz) : $dt;
1149
+
1150
+ return $this->lt($dt) ? $this : $dt;
1151
+ }
1152
+
1153
+ /**
1154
+ * Get the maximum instance between a given instance (default now) and the current instance.
1155
+ *
1156
+ * @param Carbon $dt
1157
+ *
1158
+ * @return static
1159
+ */
1160
+ public function max(Carbon $dt = null)
1161
+ {
1162
+ $dt = ($dt === null) ? static::now($this->tz) : $dt;
1163
+
1164
+ return $this->gt($dt) ? $this : $dt;
1165
+ }
1166
+
1167
+ /**
1168
+ * Determines if the instance is a weekday
1169
+ *
1170
+ * @return boolean
1171
+ */
1172
+ public function isWeekday()
1173
+ {
1174
+ return ($this->dayOfWeek != self::SUNDAY && $this->dayOfWeek != self::SATURDAY);
1175
+ }
1176
+
1177
+ /**
1178
+ * Determines if the instance is a weekend day
1179
+ *
1180
+ * @return boolean
1181
+ */
1182
+ public function isWeekend()
1183
+ {
1184
+ return !$this->isWeekDay();
1185
+ }
1186
+
1187
+ /**
1188
+ * Determines if the instance is yesterday
1189
+ *
1190
+ * @return boolean
1191
+ */
1192
+ public function isYesterday()
1193
+ {
1194
+ return $this->toDateString() === static::yesterday($this->tz)->toDateString();
1195
+ }
1196
+
1197
+ /**
1198
+ * Determines if the instance is today
1199
+ *
1200
+ * @return boolean
1201
+ */
1202
+ public function isToday()
1203
+ {
1204
+ return $this->toDateString() === static::now($this->tz)->toDateString();
1205
+ }
1206
+
1207
+ /**
1208
+ * Determines if the instance is tomorrow
1209
+ *
1210
+ * @return boolean
1211
+ */
1212
+ public function isTomorrow()
1213
+ {
1214
+ return $this->toDateString() === static::tomorrow($this->tz)->toDateString();
1215
+ }
1216
+
1217
+ /**
1218
+ * Determines if the instance is in the future, ie. greater (after) than now
1219
+ *
1220
+ * @return boolean
1221
+ */
1222
+ public function isFuture()
1223
+ {
1224
+ return $this->gt(static::now($this->tz));
1225
+ }
1226
+
1227
+ /**
1228
+ * Determines if the instance is in the past, ie. less (before) than now
1229
+ *
1230
+ * @return boolean
1231
+ */
1232
+ public function isPast()
1233
+ {
1234
+ return $this->lt(static::now($this->tz));
1235
+ }
1236
+
1237
+ /**
1238
+ * Determines if the instance is a leap year
1239
+ *
1240
+ * @return boolean
1241
+ */
1242
+ public function isLeapYear()
1243
+ {
1244
+ return $this->format('L') == '1';
1245
+ }
1246
+
1247
+ /**
1248
+ * Checks if the passed in date is the same day as the instance current day.
1249
+ *
1250
+ * @param Carbon $dt
1251
+ * @return boolean
1252
+ */
1253
+ public function isSameDay(Carbon $dt)
1254
+ {
1255
+ return $this->toDateString() === $dt->toDateString();
1256
+ }
1257
+
1258
+ ///////////////////////////////////////////////////////////////////
1259
+ /////////////////// ADDITIONS AND SUBSTRACTIONS ///////////////////
1260
+ ///////////////////////////////////////////////////////////////////
1261
+
1262
+ /**
1263
+ * Add years to the instance. Positive $value travel forward while
1264
+ * negative $value travel into the past.
1265
+ *
1266
+ * @param integer $value
1267
+ *
1268
+ * @return static
1269
+ */
1270
+ public function addYears($value)
1271
+ {
1272
+ return $this->modify((int) $value . ' year');
1273
+ }
1274
+
1275
+ /**
1276
+ * Add a year to the instance
1277
+ *
1278
+ * @return static
1279
+ */
1280
+ public function addYear()
1281
+ {
1282
+ return $this->addYears(1);
1283
+ }
1284
+
1285
+ /**
1286
+ * Remove a year from the instance
1287
+ *
1288
+ * @return static
1289
+ */
1290
+ public function subYear()
1291
+ {
1292
+ return $this->addYears(-1);
1293
+ }
1294
+
1295
+ /**
1296
+ * Remove years from the instance.
1297
+ *
1298
+ * @param integer $value
1299
+ *
1300
+ * @return static
1301
+ */
1302
+ public function subYears($value)
1303
+ {
1304
+ return $this->addYears(-1 * $value);
1305
+ }
1306
+
1307
+ /**
1308
+ * Add months to the instance. Positive $value travels forward while
1309
+ * negative $value travels into the past.
1310
+ *
1311
+ * @param integer $value
1312
+ *
1313
+ * @return static
1314
+ */
1315
+ public function addMonths($value)
1316
+ {
1317
+ return $this->modify((int) $value . ' month');
1318
+ }
1319
+
1320
+ /**
1321
+ * Add a month to the instance
1322
+ *
1323
+ * @return static
1324
+ */
1325
+ public function addMonth()
1326
+ {
1327
+ return $this->addMonths(1);
1328
+ }
1329
+
1330
+ /**
1331
+ * Remove a month from the instance
1332
+ *
1333
+ * @return static
1334
+ */
1335
+ public function subMonth()
1336
+ {
1337
+ return $this->addMonths(-1);
1338
+ }
1339
+
1340
+ /**
1341
+ * Remove months from the instance
1342
+ *
1343
+ * @param integer $value
1344
+ *
1345
+ * @return static
1346
+ */
1347
+ public function subMonths($value)
1348
+ {
1349
+ return $this->addMonths(-1 * $value);
1350
+ }
1351
+
1352
+ /**
1353
+ * Add days to the instance. Positive $value travels forward while
1354
+ * negative $value travels into the past.
1355
+ *
1356
+ * @param integer $value
1357
+ *
1358
+ * @return static
1359
+ */
1360
+ public function addDays($value)
1361
+ {
1362
+ return $this->modify((int) $value . ' day');
1363
+ }
1364
+
1365
+ /**
1366
+ * Add a day to the instance
1367
+ *
1368
+ * @return static
1369
+ */
1370
+ public function addDay()
1371
+ {
1372
+ return $this->addDays(1);
1373
+ }
1374
+
1375
+ /**
1376
+ * Remove a day from the instance
1377
+ *
1378
+ * @return static
1379
+ */
1380
+ public function subDay()
1381
+ {
1382
+ return $this->addDays(-1);
1383
+ }
1384
+
1385
+ /**
1386
+ * Remove days from the instance
1387
+ *
1388
+ * @param integer $value
1389
+ *
1390
+ * @return static
1391
+ */
1392
+ public function subDays($value)
1393
+ {
1394
+ return $this->addDays(-1 * $value);
1395
+ }
1396
+
1397
+ /**
1398
+ * Add weekdays to the instance. Positive $value travels forward while
1399
+ * negative $value travels into the past.
1400
+ *
1401
+ * @param integer $value
1402
+ *
1403
+ * @return static
1404
+ */
1405
+ public function addWeekdays($value)
1406
+ {
1407
+ return $this->modify((int) $value . ' weekday');
1408
+ }
1409
+
1410
+ /**
1411
+ * Add a weekday to the instance
1412
+ *
1413
+ * @return static
1414
+ */
1415
+ public function addWeekday()
1416
+ {
1417
+ return $this->addWeekdays(1);
1418
+ }
1419
+
1420
+ /**
1421
+ * Remove a weekday from the instance
1422
+ *
1423
+ * @return static
1424
+ */
1425
+ public function subWeekday()
1426
+ {
1427
+ return $this->addWeekdays(-1);
1428
+ }
1429
+
1430
+ /**
1431
+ * Remove weekdays from the instance
1432
+ *
1433
+ * @param integer $value
1434
+ *
1435
+ * @return static
1436
+ */
1437
+ public function subWeekdays($value)
1438
+ {
1439
+ return $this->addWeekdays(-1 * $value);
1440
+ }
1441
+
1442
+ /**
1443
+ * Add weeks to the instance. Positive $value travels forward while
1444
+ * negative $value travels into the past.
1445
+ *
1446
+ * @param integer $value
1447
+ *
1448
+ * @return static
1449
+ */
1450
+ public function addWeeks($value)
1451
+ {
1452
+ return $this->modify((int) $value . ' week');
1453
+ }
1454
+
1455
+ /**
1456
+ * Add a week to the instance
1457
+ *
1458
+ * @return static
1459
+ */
1460
+ public function addWeek()
1461
+ {
1462
+ return $this->addWeeks(1);
1463
+ }
1464
+
1465
+ /**
1466
+ * Remove a week from the instance
1467
+ *
1468
+ * @return static
1469
+ */
1470
+ public function subWeek()
1471
+ {
1472
+ return $this->addWeeks(-1);
1473
+ }
1474
+
1475
+ /**
1476
+ * Remove weeks to the instance
1477
+ *
1478
+ * @param integer $value
1479
+ *
1480
+ * @return static
1481
+ */
1482
+ public function subWeeks($value)
1483
+ {
1484
+ return $this->addWeeks(-1 * $value);
1485
+ }
1486
+
1487
+ /**
1488
+ * Add hours to the instance. Positive $value travels forward while
1489
+ * negative $value travels into the past.
1490
+ *
1491
+ * @param integer $value
1492
+ *
1493
+ * @return static
1494
+ */
1495
+ public function addHours($value)
1496
+ {
1497
+ return $this->modify((int) $value . ' hour');
1498
+ }
1499
+
1500
+ /**
1501
+ * Add an hour to the instance
1502
+ *
1503
+ * @return static
1504
+ */
1505
+ public function addHour()
1506
+ {
1507
+ return $this->addHours(1);
1508
+ }
1509
+
1510
+ /**
1511
+ * Remove an hour from the instance
1512
+ *
1513
+ * @return static
1514
+ */
1515
+ public function subHour()
1516
+ {
1517
+ return $this->addHours(-1);
1518
+ }
1519
+
1520
+ /**
1521
+ * Remove hours from the instance
1522
+ *
1523
+ * @param integer $value
1524
+ *
1525
+ * @return static
1526
+ */
1527
+ public function subHours($value)
1528
+ {
1529
+ return $this->addHours(-1 * $value);
1530
+ }
1531
+
1532
+ /**
1533
+ * Add minutes to the instance. Positive $value travels forward while
1534
+ * negative $value travels into the past.
1535
+ *
1536
+ * @param integer $value
1537
+ *
1538
+ * @return static
1539
+ */
1540
+ public function addMinutes($value)
1541
+ {
1542
+ return $this->modify((int) $value . ' minute');
1543
+ }
1544
+
1545
+ /**
1546
+ * Add a minute to the instance
1547
+ *
1548
+ * @return static
1549
+ */
1550
+ public function addMinute()
1551
+ {
1552
+ return $this->addMinutes(1);
1553
+ }
1554
+
1555
+ /**
1556
+ * Remove a minute from the instance
1557
+ *
1558
+ * @return static
1559
+ */
1560
+ public function subMinute()
1561
+ {
1562
+ return $this->addMinutes(-1);
1563
+ }
1564
+
1565
+ /**
1566
+ * Remove minutes from the instance
1567
+ *
1568
+ * @param integer $value
1569
+ *
1570
+ * @return static
1571
+ */
1572
+ public function subMinutes($value)
1573
+ {
1574
+ return $this->addMinutes(-1 * $value);
1575
+ }
1576
+
1577
+ /**
1578
+ * Add seconds to the instance. Positive $value travels forward while
1579
+ * negative $value travels into the past.
1580
+ *
1581
+ * @param integer $value
1582
+ *
1583
+ * @return static
1584
+ */
1585
+ public function addSeconds($value)
1586
+ {
1587
+ return $this->modify((int) $value . ' second');
1588
+ }
1589
+
1590
+ /**
1591
+ * Add a second to the instance
1592
+ *
1593
+ * @return static
1594
+ */
1595
+ public function addSecond()
1596
+ {
1597
+ return $this->addSeconds(1);
1598
+ }
1599
+
1600
+ /**
1601
+ * Remove a second from the instance
1602
+ *
1603
+ * @return static
1604
+ */
1605
+ public function subSecond()
1606
+ {
1607
+ return $this->addSeconds(-1);
1608
+ }
1609
+
1610
+ /**
1611
+ * Remove seconds from the instance
1612
+ *
1613
+ * @param integer $value
1614
+ *
1615
+ * @return static
1616
+ */
1617
+ public function subSeconds($value)
1618
+ {
1619
+ return $this->addSeconds(-1 * $value);
1620
+ }
1621
+
1622
+ ///////////////////////////////////////////////////////////////////
1623
+ /////////////////////////// DIFFERENCES ///////////////////////////
1624
+ ///////////////////////////////////////////////////////////////////
1625
+
1626
+ /**
1627
+ * Get the difference in years
1628
+ *
1629
+ * @param Carbon $dt
1630
+ * @param boolean $abs Get the absolute of the difference
1631
+ *
1632
+ * @return integer
1633
+ */
1634
+ public function diffInYears(Carbon $dt = null, $abs = true)
1635
+ {
1636
+ $dt = ($dt === null) ? static::now($this->tz) : $dt;
1637
+
1638
+ return (int) $this->diff($dt, $abs)->format('%r%y');
1639
+ }
1640
+
1641
+ /**
1642
+ * Get the difference in months
1643
+ *
1644
+ * @param Carbon $dt
1645
+ * @param boolean $abs Get the absolute of the difference
1646
+ *
1647
+ * @return integer
1648
+ */
1649
+ public function diffInMonths(Carbon $dt = null, $abs = true)
1650
+ {
1651
+ $dt = ($dt === null) ? static::now($this->tz) : $dt;
1652
+
1653
+ return $this->diffInYears($dt, $abs) * self::MONTHS_PER_YEAR + $this->diff($dt, $abs)->format('%r%m');
1654
+ }
1655
+
1656
+ /**
1657
+ * Get the difference in weeks
1658
+ *
1659
+ * @param Carbon $dt
1660
+ * @param boolean $abs Get the absolute of the difference
1661
+ *
1662
+ * @return integer
1663
+ */
1664
+ public function diffInWeeks(Carbon $dt = null, $abs = true)
1665
+ {
1666
+ return (int) ($this->diffInDays($dt, $abs) / self::DAYS_PER_WEEK);
1667
+ }
1668
+
1669
+ /**
1670
+ * Get the difference in days
1671
+ *
1672
+ * @param Carbon $dt
1673
+ * @param boolean $abs Get the absolute of the difference
1674
+ *
1675
+ * @return integer
1676
+ */
1677
+ public function diffInDays(Carbon $dt = null, $abs = true)
1678
+ {
1679
+ $dt = ($dt === null) ? static::now($this->tz) : $dt;
1680
+
1681
+ return (int) $this->diff($dt, $abs)->format('%r%a');
1682
+ }
1683
+
1684
+ /**
1685
+ * Get the difference in days using a filter closure
1686
+ *
1687
+ * @param Closure $callback
1688
+ * @param Carbon $dt
1689
+ * @param boolean $abs Get the absolute of the difference
1690
+ *
1691
+ * @return int
1692
+ */
1693
+ public function diffInDaysFiltered(Closure $callback, Carbon $dt = null, $abs = true)
1694
+ {
1695
+ $start = $this;
1696
+ $end = ($dt === null) ? static::now($this->tz) : $dt;
1697
+ $inverse = false;
1698
+
1699
+ if ($end < $start) {
1700
+ $start = $end;
1701
+ $end = $this;
1702
+ $inverse = true;
1703
+ }
1704
+
1705
+ $period = new DatePeriod($start, new DateInterval('P1D'), $end);
1706
+ $days = array_filter(iterator_to_array($period), function (DateTime $date) use ($callback) {
1707
+ return call_user_func($callback, Carbon::instance($date));
1708
+ });
1709
+
1710
+ $diff = count($days);
1711
+
1712
+ return $inverse && !$abs ? -$diff : $diff;
1713
+ }
1714
+
1715
+ /**
1716
+ * Get the difference in weekdays
1717
+ *
1718
+ * @param Carbon $dt
1719
+ * @param boolean $abs Get the absolute of the difference
1720
+ *
1721
+ * @return int
1722
+ */
1723
+ public function diffInWeekdays(Carbon $dt = null, $abs = true)
1724
+ {
1725
+ return $this->diffInDaysFiltered(function (Carbon $date) {
1726
+ return $date->isWeekday();
1727
+ }, $dt, $abs);
1728
+ }
1729
+
1730
+ /**
1731
+ * Get the difference in weekend days using a filter
1732
+ *
1733
+ * @param Carbon $dt
1734
+ * @param boolean $abs Get the absolute of the difference
1735
+ *
1736
+ * @return int
1737
+ */
1738
+ public function diffInWeekendDays(Carbon $dt = null, $abs = true)
1739
+ {
1740
+ return $this->diffInDaysFiltered(function (Carbon $date) {
1741
+ return $date->isWeekend();
1742
+ }, $dt, $abs);
1743
+ }
1744
+
1745
+ /**
1746
+ * Get the difference in hours
1747
+ *
1748
+ * @param Carbon $dt
1749
+ * @param boolean $abs Get the absolute of the difference
1750
+ *
1751
+ * @return integer
1752
+ */
1753
+ public function diffInHours(Carbon $dt = null, $abs = true)
1754
+ {
1755
+ return (int) ($this->diffInSeconds($dt, $abs) / self::SECONDS_PER_MINUTE / self::MINUTES_PER_HOUR);
1756
+ }
1757
+
1758
+ /**
1759
+ * Get the difference in minutes
1760
+ *
1761
+ * @param Carbon $dt
1762
+ * @param boolean $abs Get the absolute of the difference
1763
+ *
1764
+ * @return integer
1765
+ */
1766
+ public function diffInMinutes(Carbon $dt = null, $abs = true)
1767
+ {
1768
+ return (int) ($this->diffInSeconds($dt, $abs) / self::SECONDS_PER_MINUTE);
1769
+ }
1770
+
1771
+ /**
1772
+ * Get the difference in seconds
1773
+ *
1774
+ * @param Carbon $dt
1775
+ * @param boolean $abs Get the absolute of the difference
1776
+ *
1777
+ * @return integer
1778
+ */
1779
+ public function diffInSeconds(Carbon $dt = null, $abs = true)
1780
+ {
1781
+ $value = (($dt === null) ? time() : $dt->getTimestamp()) - $this->getTimestamp();
1782
+
1783
+ return $abs ? abs($value) : $value;
1784
+ }
1785
+
1786
+ /**
1787
+ * Get the difference in a human readable format.
1788
+ *
1789
+ * When comparing a value in the past to default now:
1790
+ * 1 hour ago
1791
+ * 5 months ago
1792
+ *
1793
+ * When comparing a value in the future to default now:
1794
+ * 1 hour from now
1795
+ * 5 months from now
1796
+ *
1797
+ * When comparing a value in the past to another value:
1798
+ * 1 hour before
1799
+ * 5 months before
1800
+ *
1801
+ * When comparing a value in the future to another value:
1802
+ * 1 hour after
1803
+ * 5 months after
1804
+ *
1805
+ * @param Carbon $other
1806
+ *
1807
+ * @return string
1808
+ */
1809
+ public function diffForHumans(Carbon $other = null)
1810
+ {
1811
+ $isNow = $other === null;
1812
+
1813
+ if ($isNow) {
1814
+ $other = static::now($this->tz);
1815
+ }
1816
+
1817
+ $isFuture = $this->gt($other);
1818
+
1819
+ $delta = $other->diffInSeconds($this);
1820
+
1821
+ // a little weeks per month, 365 days per year... good enough!!
1822
+ $divs = array(
1823
+ 'second' => self::SECONDS_PER_MINUTE,
1824
+ 'minute' => self::MINUTES_PER_HOUR,
1825
+ 'hour' => self::HOURS_PER_DAY,
1826
+ 'day' => self::DAYS_PER_WEEK,
1827
+ 'week' => 30 / self::DAYS_PER_WEEK,
1828
+ 'month' => self::MONTHS_PER_YEAR
1829
+ );
1830
+
1831
+ $unit = 'year';
1832
+
1833
+ foreach ($divs as $divUnit => $divValue) {
1834
+ if ($delta < $divValue) {
1835
+ $unit = $divUnit;
1836
+ break;
1837
+ }
1838
+
1839
+ $delta = $delta / $divValue;
1840
+ }
1841
+
1842
+ $delta = (int) $delta;
1843
+
1844
+ if ($delta == 0) {
1845
+ $delta = 1;
1846
+ }
1847
+
1848
+ $txt = $delta . ' ' . $unit;
1849
+ $txt .= $delta == 1 ? '' : 's';
1850
+
1851
+ if ($isNow) {
1852
+ if ($isFuture) {
1853
+ return $txt . ' from now';
1854
+ }
1855
+
1856
+ return $txt . ' ago';
1857
+ }
1858
+
1859
+ if ($isFuture) {
1860
+ return $txt . ' after';
1861
+ }
1862
+
1863
+ return $txt . ' before';
1864
+ }
1865
+
1866
+ ///////////////////////////////////////////////////////////////////
1867
+ //////////////////////////// MODIFIERS ////////////////////////////
1868
+ ///////////////////////////////////////////////////////////////////
1869
+
1870
+ /**
1871
+ * Resets the time to 00:00:00
1872
+ *
1873
+ * @return static
1874
+ */
1875
+ public function startOfDay()
1876
+ {
1877
+ return $this->hour(0)->minute(0)->second(0);
1878
+ }
1879
+
1880
+ /**
1881
+ * Resets the time to 23:59:59
1882
+ *
1883
+ * @return static
1884
+ */
1885
+ public function endOfDay()
1886
+ {
1887
+ return $this->hour(23)->minute(59)->second(59);
1888
+ }
1889
+
1890
+ /**
1891
+ * Resets the date to the first day of the month and the time to 00:00:00
1892
+ *
1893
+ * @return static
1894
+ */
1895
+ public function startOfMonth()
1896
+ {
1897
+ return $this->startOfDay()->day(1);
1898
+ }
1899
+
1900
+ /**
1901
+ * Resets the date to end of the month and time to 23:59:59
1902
+ *
1903
+ * @return static
1904
+ */
1905
+ public function endOfMonth()
1906
+ {
1907
+ return $this->day($this->daysInMonth)->endOfDay();
1908
+ }
1909
+
1910
+ /**
1911
+ * Resets the date to the first day of the year and the time to 00:00:00
1912
+ *
1913
+ * @return static
1914
+ */
1915
+ public function startOfYear()
1916
+ {
1917
+ return $this->month(1)->startOfMonth();
1918
+ }
1919
+
1920
+ /**
1921
+ * Resets the date to end of the year and time to 23:59:59
1922
+ *
1923
+ * @return static
1924
+ */
1925
+ public function endOfYear()
1926
+ {
1927
+ return $this->month(self::MONTHS_PER_YEAR)->endOfMonth();
1928
+ }
1929
+
1930
+ /**
1931
+ * Resets the date to the first day of the decade and the time to 00:00:00
1932
+ *
1933
+ * @return static
1934
+ */
1935
+ public function startOfDecade()
1936
+ {
1937
+ return $this->startOfYear()->year($this->year - $this->year % self::YEARS_PER_DECADE);
1938
+ }
1939
+
1940
+ /**
1941
+ * Resets the date to end of the decade and time to 23:59:59
1942
+ *
1943
+ * @return static
1944
+ */
1945
+ public function endOfDecade()
1946
+ {
1947
+ return $this->endOfYear()->year($this->year - $this->year % self::YEARS_PER_DECADE + self::YEARS_PER_DECADE - 1);
1948
+ }
1949
+
1950
+ /**
1951
+ * Resets the date to the first day of the century and the time to 00:00:00
1952
+ *
1953
+ * @return static
1954
+ */
1955
+ public function startOfCentury()
1956
+ {
1957
+ return $this->startOfYear()->year($this->year - $this->year % self::YEARS_PER_CENTURY);
1958
+ }
1959
+
1960
+ /**
1961
+ * Resets the date to end of the century and time to 23:59:59
1962
+ *
1963
+ * @return static
1964
+ */
1965
+ public function endOfCentury()
1966
+ {
1967
+ return $this->endOfYear()->year($this->year - $this->year % self::YEARS_PER_CENTURY + self::YEARS_PER_CENTURY - 1);
1968
+ }
1969
+
1970
+ /**
1971
+ * Resets the date to the first day of the ISO-8601 week (Monday) and the time to 00:00:00
1972
+ *
1973
+ * @return static
1974
+ */
1975
+ public function startOfWeek()
1976
+ {
1977
+ if ($this->dayOfWeek != self::MONDAY) $this->previous(self::MONDAY);
1978
+ return $this->startOfDay();
1979
+ }
1980
+
1981
+ /**
1982
+ * Resets the date to end of the ISO-8601 week (Sunday) and time to 23:59:59
1983
+ *
1984
+ * @return static
1985
+ */
1986
+ public function endOfWeek()
1987
+ {
1988
+ if ($this->dayOfWeek != self::SUNDAY) $this->next(self::SUNDAY);
1989
+ return $this->endOfDay();
1990
+ }
1991
+
1992
+ /**
1993
+ * Modify to the next occurance of a given day of the week.
1994
+ * If no dayOfWeek is provided, modify to the next occurance
1995
+ * of the current day of the week. Use the supplied consts
1996
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
1997
+ *
1998
+ * @param int $dayOfWeek
1999
+ *
2000
+ * @return mixed
2001
+ */
2002
+ public function next($dayOfWeek = null)
2003
+ {
2004
+ if ($dayOfWeek === null) {
2005
+ $dayOfWeek = $this->dayOfWeek;
2006
+ }
2007
+
2008
+ return $this->startOfDay()->modify('next ' . self::$days[$dayOfWeek]);
2009
+ }
2010
+
2011
+ /**
2012
+ * Modify to the previous occurance of a given day of the week.
2013
+ * If no dayOfWeek is provided, modify to the previous occurance
2014
+ * of the current day of the week. Use the supplied consts
2015
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2016
+ *
2017
+ * @param int $dayOfWeek
2018
+ *
2019
+ * @return mixed
2020
+ */
2021
+ public function previous($dayOfWeek = null)
2022
+ {
2023
+ if ($dayOfWeek === null) {
2024
+ $dayOfWeek = $this->dayOfWeek;
2025
+ }
2026
+
2027
+ return $this->startOfDay()->modify('last ' . self::$days[$dayOfWeek]);
2028
+ }
2029
+
2030
+ /**
2031
+ * Modify to the first occurance of a given day of the week
2032
+ * in the current month. If no dayOfWeek is provided, modify to the
2033
+ * first day of the current month. Use the supplied consts
2034
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2035
+ *
2036
+ * @param int $dayOfWeek
2037
+ *
2038
+ * @return mixed
2039
+ */
2040
+ public function firstOfMonth($dayOfWeek = null)
2041
+ {
2042
+ $this->startOfDay();
2043
+
2044
+ if ($dayOfWeek === null) {
2045
+ return $this->day(1);
2046
+ }
2047
+
2048
+ return $this->modify('first ' . self::$days[$dayOfWeek] . ' of ' . $this->format('F') . ' ' . $this->year);
2049
+ }
2050
+
2051
+ /**
2052
+ * Modify to the last occurance of a given day of the week
2053
+ * in the current month. If no dayOfWeek is provided, modify to the
2054
+ * last day of the current month. Use the supplied consts
2055
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2056
+ *
2057
+ * @param int $dayOfWeek
2058
+ *
2059
+ * @return mixed
2060
+ */
2061
+ public function lastOfMonth($dayOfWeek = null)
2062
+ {
2063
+ $this->startOfDay();
2064
+
2065
+ if ($dayOfWeek === null) {
2066
+ return $this->day($this->daysInMonth);
2067
+ }
2068
+
2069
+ return $this->modify('last ' . self::$days[$dayOfWeek] . ' of ' . $this->format('F') . ' ' . $this->year);
2070
+ }
2071
+
2072
+ /**
2073
+ * Modify to the given occurance of a given day of the week
2074
+ * in the current month. If the calculated occurance is outside the scope
2075
+ * of the current month, then return false and no modifications are made.
2076
+ * Use the supplied consts to indicate the desired dayOfWeek, ex. static::MONDAY.
2077
+ *
2078
+ * @param int $nth
2079
+ * @param int $dayOfWeek
2080
+ *
2081
+ * @return mixed
2082
+ */
2083
+ public function nthOfMonth($nth, $dayOfWeek)
2084
+ {
2085
+ $dt = $this->copy()->firstOfMonth();
2086
+ $check = $dt->format('Y-m');
2087
+ $dt->modify('+' . $nth . ' ' . self::$days[$dayOfWeek]);
2088
+
2089
+ return ($dt->format('Y-m') === $check) ? $this->modify($dt) : false;
2090
+ }
2091
+
2092
+ /**
2093
+ * Modify to the first occurance of a given day of the week
2094
+ * in the current quarter. If no dayOfWeek is provided, modify to the
2095
+ * first day of the current quarter. Use the supplied consts
2096
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2097
+ *
2098
+ * @param int $dayOfWeek
2099
+ *
2100
+ * @return mixed
2101
+ */
2102
+ public function firstOfQuarter($dayOfWeek = null)
2103
+ {
2104
+ return $this->day(1)->month($this->quarter * 3 - 2)->firstOfMonth($dayOfWeek);
2105
+ }
2106
+
2107
+ /**
2108
+ * Modify to the last occurance of a given day of the week
2109
+ * in the current quarter. If no dayOfWeek is provided, modify to the
2110
+ * last day of the current quarter. Use the supplied consts
2111
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2112
+ *
2113
+ * @param int $dayOfWeek
2114
+ *
2115
+ * @return mixed
2116
+ */
2117
+ public function lastOfQuarter($dayOfWeek = null)
2118
+ {
2119
+ return $this->day(1)->month($this->quarter * 3)->lastOfMonth($dayOfWeek);
2120
+ }
2121
+
2122
+ /**
2123
+ * Modify to the given occurance of a given day of the week
2124
+ * in the current quarter. If the calculated occurance is outside the scope
2125
+ * of the current quarter, then return false and no modifications are made.
2126
+ * Use the supplied consts to indicate the desired dayOfWeek, ex. static::MONDAY.
2127
+ *
2128
+ * @param int $nth
2129
+ * @param int $dayOfWeek
2130
+ *
2131
+ * @return mixed
2132
+ */
2133
+ public function nthOfQuarter($nth, $dayOfWeek)
2134
+ {
2135
+ $dt = $this->copy()->day(1)->month($this->quarter * 3);
2136
+ $last_month = $dt->month;
2137
+ $year = $dt->year;
2138
+ $dt->firstOfQuarter()->modify('+' . $nth . ' ' . self::$days[$dayOfWeek]);
2139
+
2140
+ return ($last_month < $dt->month || $year !== $dt->year) ? false : $this->modify($dt);
2141
+ }
2142
+
2143
+ /**
2144
+ * Modify to the first occurance of a given day of the week
2145
+ * in the current year. If no dayOfWeek is provided, modify to the
2146
+ * first day of the current year. Use the supplied consts
2147
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2148
+ *
2149
+ * @param int $dayOfWeek
2150
+ *
2151
+ * @return mixed
2152
+ */
2153
+ public function firstOfYear($dayOfWeek = null)
2154
+ {
2155
+ return $this->month(1)->firstOfMonth($dayOfWeek);
2156
+ }
2157
+
2158
+ /**
2159
+ * Modify to the last occurance of a given day of the week
2160
+ * in the current year. If no dayOfWeek is provided, modify to the
2161
+ * last day of the current year. Use the supplied consts
2162
+ * to indicate the desired dayOfWeek, ex. static::MONDAY.
2163
+ *
2164
+ * @param int $dayOfWeek
2165
+ *
2166
+ * @return mixed
2167
+ */
2168
+ public function lastOfYear($dayOfWeek = null)
2169
+ {
2170
+ return $this->month(self::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek);
2171
+ }
2172
+
2173
+ /**
2174
+ * Modify to the given occurance of a given day of the week
2175
+ * in the current year. If the calculated occurance is outside the scope
2176
+ * of the current year, then return false and no modifications are made.
2177
+ * Use the supplied consts to indicate the desired dayOfWeek, ex. static::MONDAY.
2178
+ *
2179
+ * @param int $nth
2180
+ * @param int $dayOfWeek
2181
+ *
2182
+ * @return mixed
2183
+ */
2184
+ public function nthOfYear($nth, $dayOfWeek)
2185
+ {
2186
+ $dt = $this->copy()->firstOfYear()->modify('+' . $nth . ' ' . self::$days[$dayOfWeek]);
2187
+
2188
+ return $this->year == $dt->year ? $this->modify($dt) : false;
2189
+ }
2190
+
2191
+ /**
2192
+ * Modify the current instance to the average of a given instance (default now) and the current instance.
2193
+ *
2194
+ * @param Carbon $dt
2195
+ *
2196
+ * @return static
2197
+ */
2198
+ public function average(Carbon $dt = null)
2199
+ {
2200
+ $dt = ($dt === null) ? static::now($this->tz) : $dt;
2201
+
2202
+ return $this->addSeconds((int) ($this->diffInSeconds($dt, false) / 2));
2203
+ }
2204
+
2205
+ /**
2206
+ * Check if its the birthday. Compares the date/month values of the two dates.
2207
+ * @param Carbon $dt
2208
+ * @return boolean
2209
+ */
2210
+ public function isBirthday(Carbon $dt)
2211
+ {
2212
+ return $this->month === $dt->month && $this->day === $dt->day;
2213
+ }
2214
+ }
includes/list-table.php DELETED
@@ -1,679 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_List_Table extends WP_List_Table {
4
-
5
- function __construct( $args = array() ) {
6
-
7
- $screen_id = isset( $args['screen'] ) ? $args['screen'] : null;
8
- $screen_id = apply_filters( 'mainwp_wp_stream_list_table_screen_id', $screen_id );
9
-
10
- parent::__construct(
11
- array(
12
- 'post_type' => 'stream',
13
- 'plural' => 'records',
14
- 'screen' => $screen_id,
15
- )
16
- );
17
-
18
- add_screen_option(
19
- 'per_page',
20
- array(
21
- 'default' => 20,
22
- 'label' => __( 'Records per page', 'mainwp-child-reports' ),
23
- 'option' => 'mainwp_child_reports_per_page',
24
- )
25
- );
26
-
27
- // Check for default hidden columns
28
- $this->get_hidden_columns();
29
-
30
- add_filter( 'screen_settings', array( $this, 'screen_controls' ), 10, 2 );
31
- add_filter( 'set-screen-option', array( __CLASS__, 'set_screen_option' ), 10, 3 );
32
-
33
- set_screen_options();
34
- }
35
-
36
- function extra_tablenav( $which ) {
37
- if ( 'top' === $which ) {
38
- $this->filters_form();
39
- }
40
- }
41
-
42
- function get_columns(){
43
- return apply_filters(
44
- 'mainwp_wp_stream_list_table_columns',
45
- array(
46
- 'date' => __( 'Date', 'default' ),
47
- 'summary' => __( 'Summary', 'default' ),
48
- 'author' => __( 'Author', 'default' ),
49
- 'connector' => __( 'Connector', 'mainwp-child-reports' ),
50
- 'context' => __( 'Context', 'mainwp-child-reports' ),
51
- 'action' => __( 'Action', 'mainwp-child-reports' ),
52
- 'ip' => __( 'IP Address', 'mainwp-child-reports' ),
53
- 'id' => __( 'Record ID', 'mainwp-child-reports' ),
54
- )
55
- );
56
- }
57
-
58
- function get_sortable_columns() {
59
- return array(
60
- 'id' => array( 'ID', false ),
61
- 'date' => array( 'date', false ),
62
- );
63
- }
64
-
65
- function get_hidden_columns() {
66
- if ( ! $user = wp_get_current_user() ) {
67
- return array();
68
- }
69
-
70
- // Directly checking the user meta; to check whether user has changed screen option or not
71
- $hidden = get_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', true );
72
-
73
- // If user meta is not found; add the default hidden column 'id'
74
- if ( ! $hidden ) {
75
- $hidden = array( 'id' );
76
- update_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', $hidden );
77
- }
78
-
79
- return $hidden;
80
- }
81
-
82
- function prepare_items() {
83
- $columns = $this->get_columns();
84
- $sortable = $this->get_sortable_columns();
85
- $hidden = $this->get_hidden_columns();
86
-
87
- $this->_column_headers = array( $columns, $hidden, $sortable );
88
-
89
- $this->items = $this->get_records();
90
-
91
- $total_items = $this->get_total_found_rows();
92
-
93
- $this->set_pagination_args(
94
- array(
95
- 'total_items' => $total_items,
96
- 'per_page' => $this->get_items_per_page( 'mainwp_child_reports_per_page', 20 ),
97
- )
98
- );
99
- }
100
-
101
- function column_cb( $item ) {
102
- return sprintf(
103
- '<input type="checkbox" name="%1$s[]" value="%2$s" />',
104
- /*$1%s*/
105
- 'mainwp_wp_stream_checkbox',
106
- /*$2%s*/
107
- $item->ID
108
- );
109
- }
110
-
111
- function get_records() {
112
- $args = array();
113
-
114
- // Parse sorting params
115
- if ( ! $order = mainwp_wp_stream_filter_input( INPUT_GET, 'order' ) ) {
116
- $order = 'DESC';
117
- }
118
- if ( ! $orderby = mainwp_wp_stream_filter_input( INPUT_GET, 'orderby' ) ) {
119
- $orderby = 'created';
120
- }
121
- $args['order'] = $order;
122
- $args['orderby'] = $orderby;
123
-
124
- // Filters
125
- $allowed_params = array(
126
- 'connector',
127
- 'context',
128
- 'action',
129
- 'author',
130
- 'author_role',
131
- 'object_id',
132
- 'search',
133
- 'date',
134
- 'date_from',
135
- 'date_to',
136
- 'record__in',
137
- 'blog_id',
138
- 'ip',
139
- );
140
-
141
- foreach ( $allowed_params as $param ) {
142
- $paramval = mainwp_wp_stream_filter_input( INPUT_GET, $param );
143
- if ( $paramval || '0' === $paramval ) {
144
- $args[ $param ] = $paramval;
145
- }
146
- }
147
- $args['paged'] = $this->get_pagenum();
148
-
149
- if ( ! isset( $args['records_per_page'] ) ) {
150
- $args['records_per_page'] = $this->get_items_per_page( 'mainwp_child_reports_per_page', 20 );
151
- }
152
-
153
- $items = mainwp_wp_stream_query( $args );
154
-
155
- return $items;
156
- }
157
-
158
- function get_total_found_rows() {
159
- global $wpdb;
160
-
161
- return $wpdb->get_var( 'SELECT FOUND_ROWS()' );
162
- }
163
-
164
- function column_default( $item, $column_name ) {
165
- switch ( $column_name ) {
166
- case 'date' :
167
- // $date_string = sprintf(
168
- // '<time datetime="%s" class="relative-time">%s</time>',
169
- // $item->created,
170
- // get_date_from_gmt( $item->created, 'Y/m/d' )
171
- // );
172
- // $out = $this->column_link( $date_string, 'date', date( 'Y/m/d', strtotime( $item->created ) ) );
173
- // $out .= '<br />';
174
- // $out .= get_date_from_gmt( $item->created, 'h:i:s A' );
175
- //
176
- $created = date( 'Y-m-d H:i:s', strtotime( $item->created ) );
177
- $date_string = sprintf(
178
- '<time datetime="%s" class="relative-time record-created">%s</time>',
179
- mainwp_wp_stream_get_iso_8601_extended_date( strtotime( $item->created ) ),
180
- get_date_from_gmt( $created, 'Y/m/d' )
181
- );
182
- $out = $this->column_link( $date_string, 'date', get_date_from_gmt( $created, 'Y/m/d' ) );
183
- $out .= '<br />';
184
- $out .= get_date_from_gmt( $created, 'h:i:s A' );
185
- $out .= '<span class="timestamp" timestamp="' . strtotime( $item->created ) . '"></span>';
186
- break;
187
- case 'summary' :
188
- $out = $item->summary;
189
- if ( $item->object_id ) {
190
- $out .= $this->column_link(
191
- '<span class="dashicons dashicons-search stream-filter-object-id"></span>',
192
- array(
193
- 'object_id' => $item->object_id,
194
- 'context' => $item->context,
195
- ),
196
- null,
197
- __( 'View all records for this object', 'mainwp-child-reports' )
198
- );
199
- }
200
- $out .= $this->get_action_links( $item );
201
- break;
202
-
203
- case 'author' :
204
- require_once MAINWP_WP_STREAM_INC_DIR . 'class-wp-stream-author.php';
205
-
206
- $author_meta = mainwp_wp_stream_get_meta( $item->ID, 'author_meta', true );
207
- $author = new MainWP_WP_Stream_Author( (int) $item->author, $author_meta );
208
-
209
- $out = sprintf(
210
- '<a href="%s">%s <span>%s</span></a>%s%s%s',
211
- $author->get_records_page_url(),
212
- $author->get_avatar_img( 80 ),
213
- $author->get_display_name(),
214
- $author->is_deleted() ? sprintf( '<br /><small class="deleted">%s</small>', esc_html__( 'Deleted User', 'mainwp-child-reports' ) ) : '',
215
- $author->get_role() ? sprintf( '<br /><small>%s</small>', $author->get_role() ) : '',
216
- $author->get_agent() ? sprintf( '<br /><small>%s</small>', MainWP_WP_Stream_Author::get_agent_label( $author->get_agent() ) ) : ''
217
- );
218
- break;
219
-
220
- case 'connector':
221
- case 'context':
222
- case 'action':
223
- $out = $this->column_link( $this->get_term_title( $item->{$column_name}, $column_name ), $column_name, $item->{$column_name} );
224
- break;
225
-
226
- case 'ip' :
227
- $out = $this->column_link( $item->{$column_name}, 'ip', $item->{$column_name} );
228
- break;
229
-
230
- case 'id' :
231
- $out = absint( $item->ID );
232
- break;
233
-
234
- case 'blog_id':
235
- $blog = ( $item->blog_id && is_multisite() ) ? get_blog_details( $item->blog_id ) : MainWP_WP_Stream_Network::get_network_blog();
236
- $out = sprintf(
237
- '<a href="%s"><span>%s</span></a>',
238
- add_query_arg( array( 'blog_id' => $blog->blog_id ), network_admin_url( 'admin.php?page=mainwp_wp_stream' ) ),
239
- esc_html( $blog->blogname )
240
- );
241
- break;
242
-
243
- default :
244
- $inserted_columns = apply_filters( 'mainwp_wp_stream_register_column_defaults', $new_columns = array() );
245
-
246
- if ( ! empty( $inserted_columns ) && is_array( $inserted_columns ) ) {
247
- foreach ( $inserted_columns as $column_title ) {
248
- if ( $column_title == $column_name && has_action( 'mainwp_wp_stream_insert_column_default-' . $column_title ) ) {
249
- $out = do_action( 'mainwp_wp_stream_insert_column_default-' . $column_title, $item );
250
- } else {
251
- $out = $column_name;
252
- }
253
- }
254
- } else {
255
- $out = $column_name; // xss ok
256
- }
257
- }
258
-
259
- echo $out; // xss ok
260
- }
261
-
262
- public static function get_action_links( $record ) {
263
- $out = '';
264
- $action_links = apply_filters( 'mainwp_wp_stream_action_links_' . $record->connector, array(), $record );
265
- $custom_links = apply_filters( 'mainwp_wp_stream_custom_action_links_' . $record->connector, array(), $record );
266
-
267
- if ( $action_links || $custom_links ) {
268
- $out .= '<div class="row-actions">';
269
- }
270
-
271
- $links = array();
272
- if ( $action_links && is_array( $action_links ) ) {
273
- foreach ( $action_links as $al_title => $al_href ) {
274
- $links[] = sprintf(
275
- '<span><a href="%s" class="action-link">%s</a></span>',
276
- $al_href,
277
- $al_title
278
- );
279
- }
280
- }
281
-
282
- if ( $custom_links && is_array( $custom_links ) ) {
283
- foreach ( $custom_links as $key => $link ) {
284
- $links[] = $link;
285
- }
286
- }
287
-
288
- $out .= implode( ' | ', $links );
289
-
290
- if ( $action_links || $custom_links ) {
291
- $out .= '</div>';
292
- }
293
-
294
- return $out;
295
- }
296
-
297
- function column_link( $display, $key, $value = null, $title = null ) {
298
- $url = add_query_arg(
299
- array(
300
- 'page' => MainWP_WP_Stream_Admin::RECORDS_PAGE_SLUG,
301
- ),
302
- self_admin_url( MainWP_WP_Stream_Admin::ADMIN_PARENT_PAGE )
303
- );
304
-
305
- $args = ! is_array( $key ) ? array( $key => $value ) : $key;
306
-
307
- foreach ( $args as $k => $v ) {
308
- $url = add_query_arg( $k, $v, $url );
309
- }
310
-
311
- return sprintf(
312
- '<a href="%s" title="%s">%s</a>',
313
- esc_url( $url ),
314
- esc_attr( $title ),
315
- $display
316
- );
317
- }
318
-
319
- public function get_term_title( $term, $type ) {
320
- if ( isset( MainWP_WP_Stream_Connectors::$term_labels[ "stream_$type" ][ $term ] ) ) {
321
- return MainWP_WP_Stream_Connectors::$term_labels[ "stream_$type" ][ $term ];
322
- }
323
- else {
324
- return $term;
325
- }
326
- }
327
-
328
- function assemble_records( $column, $table = '' ) {
329
- $setting_key = self::get_column_excluded_setting_key( $column );
330
-
331
- $exclude_hide_previous_records = isset( MainWP_WP_Stream_Settings::$options['exclude_hide_previous_records'] ) ? MainWP_WP_Stream_Settings::$options['exclude_hide_previous_records'] : 0;
332
- $hide_disabled_column_filter = apply_filters( 'mainwp_wp_stream_list_table_hide_disabled_ ' . $setting_key, ( 0 === $exclude_hide_previous_records ) ? false : true );
333
-
334
- // @todo eliminate special condition for authors, especially using a WP_User object as the value; should use string or stringifiable object
335
- if ( 'author' === $column ) {
336
- require_once MAINWP_WP_STREAM_INC_DIR . 'class-wp-stream-author.php';
337
- $all_records = array();
338
-
339
- // If the number of users exceeds the max authors constant value then return an empty array and use AJAX instead
340
- $user_count = count_users();
341
- $total_users = $user_count['total_users'];
342
- if ( $total_users > MainWP_WP_Stream_Admin::PRELOAD_AUTHORS_MAX ) {
343
- return array();
344
- }
345
-
346
- $authors = array_map(
347
- function ( $user_id ) {
348
- return new MainWP_WP_Stream_Author( $user_id );
349
- },
350
- get_users( array( 'fields' => 'ID' ) )
351
- );
352
- $authors[] = new MainWP_WP_Stream_Author( 0, array( 'is_wp_cli' => true ) );
353
-
354
- if ( $hide_disabled_column_filter ) {
355
- $excluded_records = MainWP_WP_Stream_Settings::get_excluded_by_key( $setting_key );
356
- }
357
-
358
- foreach ( $authors as $author ) {
359
- if ( $hide_disabled_column_filter && in_array( $author->id, $excluded_records ) ) {
360
- continue;
361
- }
362
- $all_records[ $author->id ] = $author->get_display_name();
363
- }
364
- } else {
365
- $prefixed_column = sprintf( 'stream_%s', $column );
366
- $all_records = MainWP_WP_Stream_Connectors::$term_labels[ $prefixed_column ];
367
-
368
- if ( true === $hide_disabled_column_filter ) {
369
- $excluded_records = MainWP_WP_Stream_Settings::get_excluded_by_key( $setting_key );
370
- foreach ( array_keys( $all_records ) as $_connector ) {
371
- if ( in_array( $_connector, $excluded_records ) ) {
372
- unset( $all_records[ $_connector ] );
373
- }
374
- }
375
- }
376
- }
377
-
378
- $existing_records = mainwp_wp_stream_existing_records( $column, $table );
379
-
380
- $active_records = array();
381
- $disabled_records = array();
382
-
383
- foreach ( $all_records as $record => $label ) {
384
- if ( array_key_exists( $record, $existing_records ) ) {
385
- $active_records[ $record ] = array( 'label' => $label, 'disabled' => '' );
386
- } else {
387
- $disabled_records[ $record ] = array( 'label' => $label, 'disabled' => 'disabled="disabled"' );
388
- }
389
- }
390
-
391
- // Remove WP-CLI pseudo user if no records with user=0 exist
392
- if ( isset( $disabled_records[0] ) ) {
393
- unset( $disabled_records[0] );
394
- }
395
-
396
- $sort = function ( $a, $b ) use ( $column ) {
397
- $label_a = (string) $a['label'];
398
- $label_b = (string) $b['label'];
399
- if ( $label_a === $label_b ) {
400
- return 0;
401
- }
402
- return strtolower( $label_a ) < strtolower( $label_b ) ? -1 : 1;
403
- };
404
- uasort( $active_records, $sort );
405
- uasort( $disabled_records, $sort );
406
-
407
- // Not using array_merge() in order to preserve the array index for the Authors dropdown which uses the user_id as the key
408
- $all_records = $active_records + $disabled_records;
409
-
410
- return $all_records;
411
- }
412
-
413
- public function get_filters() {
414
- $filters = array();
415
-
416
- require_once MAINWP_WP_STREAM_INC_DIR . 'date-interval.php';
417
- $date_interval = new MainWP_WP_Stream_Date_Interval();
418
-
419
- $filters['date'] = array(
420
- 'title' => __( 'dates', 'mainwp-child-reports' ),
421
- 'items' => $date_interval->intervals,
422
- );
423
-
424
- $authors_records = MainWP_WP_Stream_Admin::get_authors_record_meta(
425
- $this->assemble_records( 'author', 'stream' )
426
- );
427
-
428
- $filters['author'] = array(
429
- 'title' => __( 'authors', 'mainwp-child-reports' ),
430
- 'items' => $authors_records,
431
- 'ajax' => count( $authors_records ) <= 0,
432
- );
433
-
434
- $filters['connector'] = array(
435
- 'title' => __( 'connectors', 'mainwp-child-reports' ),
436
- 'items' => $this->assemble_records( 'connector' ),
437
- );
438
-
439
- $filters['context'] = array(
440
- 'title' => __( 'contexts', 'mainwp-child-reports' ),
441
- 'items' => $this->assemble_records( 'context' ),
442
- );
443
-
444
- $filters['action'] = array(
445
- 'title' => __( 'actions', 'mainwp-child-reports' ),
446
- 'items' => $this->assemble_records( 'action' ),
447
- );
448
-
449
- return apply_filters( 'mainwp_wp_stream_list_table_filters', $filters );
450
- }
451
-
452
- function filters_form() {
453
- $user_id = get_current_user_id();
454
- $filters = $this->get_filters();
455
-
456
- $filters_string = sprintf( '<input type="hidden" name="page" value="%s"/>', 'mainwp-reports-page' );
457
- $filters_string .= sprintf( '<span class="filter_info hidden">%s</span>', esc_html__( 'Show filter controls via the screen options tab above.', 'mainwp-child-reports' ) );
458
-
459
- foreach ( $filters as $name => $data ) {
460
- if ( 'date' === $name ) {
461
- $filters_string .= $this->filter_date( $data['items'] );
462
- continue;
463
- }
464
- $filters_string .= $this->filter_select( $name, $data['title'], isset( $data['items'] ) ? $data['items'] : array(), isset( $data['ajax'] ) && $data['ajax'] );
465
- }
466
-
467
- $filters_string .= sprintf( '<input type="submit" id="record-query-submit" class="button" value="%s">', __( 'Filter', 'default' ) );
468
- $filters_string .= wp_nonce_field( 'mainwp_creport_filters_user_search_nonce', 'mainwp_creport_filters_user_search_nonce' );
469
-
470
- $url = self_admin_url( MainWP_WP_Stream_Admin::ADMIN_PARENT_PAGE );
471
-
472
- printf( '<div class="alignleft actions">%s</div>', $filters_string ); // xss ok
473
- }
474
-
475
- function filter_select( $name, $title, $items, $ajax ) {
476
- if ( $ajax ) {
477
- $out = sprintf(
478
- '<input type="hidden" name="%s" class="chosen-select" value="%s" data-placeholder="%s"/>',
479
- esc_attr( $name ),
480
- esc_attr( mainwp_wp_stream_filter_input( INPUT_GET, $name ) ),
481
- esc_html( $title )
482
- );
483
- } else {
484
- $options = array( '<option value=""></option>' );
485
- $selected = mainwp_wp_stream_filter_input( INPUT_GET, $name );
486
- foreach ( $items as $v => $label ) {
487
- $options[] = sprintf(
488
- '<option value="%s" %s %s %s title="%s">%s</option>',
489
- $v,
490
- selected( $v, $selected, false ),
491
- isset( $label['disabled'] ) ? $label['disabled'] : '', // xss ok
492
- isset( $label['icon'] ) ? sprintf( ' data-icon="%s"', esc_attr( $label['icon'] ) ) : '',
493
- isset( $label['tooltip'] ) ? esc_attr( $label['tooltip'] ) : '',
494
- $label['label']
495
- );
496
- }
497
- $out = sprintf(
498
- '<select name="%s" class="chosen-select" data-placeholder="%s">%s</select>',
499
- esc_attr( $name ),
500
- sprintf( esc_attr__( 'Show all %s', 'mainwp-child-reports' ), $title ),
501
- implode( '', $options )
502
- );
503
- }
504
-
505
- return $out;
506
- }
507
-
508
- function filter_search() {
509
- $out = sprintf(
510
- '<p class="search-box">
511
- <label class="screen-reader-text" for="record-search-input">%1$s:</label>
512
- <input type="search" id="record-search-input" name="search" value="%2$s" />
513
- <input type="submit" name="" id="search-submit" class="button" value="%1$s" />
514
- </p>',
515
- esc_attr__( 'Search Records', 'mainwp-child-reports' ),
516
- isset( $_GET['search'] ) ? esc_attr( wp_unslash( $_GET['search'] ) ) : null
517
- );
518
-
519
- return $out;
520
- }
521
-
522
- function filter_date( $items ) {
523
- wp_enqueue_style( 'jquery-ui' );
524
- wp_enqueue_style( 'mainwp-wp-stream-datepicker' );
525
- wp_enqueue_script( 'jquery-ui-datepicker' );
526
-
527
- $date_predefined = mainwp_wp_stream_filter_input( INPUT_GET, 'date_predefined' );
528
- $date_from = mainwp_wp_stream_filter_input( INPUT_GET, 'date_from' );
529
- $date_to = mainwp_wp_stream_filter_input( INPUT_GET, 'date_to' );
530
-
531
- ob_start();
532
- ?>
533
- <div class="date-interval">
534
-
535
- <select class="field-predefined hide-if-no-js" name="date_predefined" data-placeholder="<?php _e( 'All Time', 'mainwp-child-reports' ); ?>">
536
- <option></option>
537
- <option value="custom" <?php selected( 'custom' === $date_predefined ); ?>><?php esc_attr_e( 'Custom', 'default' ) ?></option>
538
- <?php foreach ( $items as $key => $interval ) {
539
- printf(
540
- '<option value="%s" data-from="%s" data-to="%s" %s>%s</option>',
541
- esc_attr( $key ),
542
- esc_attr( $interval['start']->format( 'Y/m/d' ) ),
543
- esc_attr( $interval['end']->format( 'Y/m/d' ) ),
544
- selected( $key === $date_predefined ),
545
- esc_html( $interval['label'] )
546
- ); // xss ok
547
- } ?>
548
- </select>
549
-
550
- <div class="date-inputs">
551
- <div class="box">
552
- <i class="date-remove dashicons"></i>
553
- <input type="text"
554
- name="date_from"
555
- class="date-picker field-from"
556
- placeholder="<?php esc_attr_e( 'Start Date', 'default' ) ?>"
557
- value="<?php echo esc_attr( $date_from ) ?>">
558
- </div>
559
- <span class="connector dashicons"></span>
560
-
561
- <div class="box">
562
- <i class="date-remove dashicons"></i>
563
- <input type="text"
564
- name="date_to"
565
- class="date-picker field-to"
566
- placeholder="<?php esc_attr_e( 'End Date', 'default' ) ?>"
567
- value="<?php echo esc_attr( $date_to ) ?>">
568
- </div>
569
- </div>
570
-
571
- </div>
572
- <?php
573
-
574
- return ob_get_clean();
575
- }
576
-
577
- function display() {
578
- $url = self_admin_url( MainWP_WP_Stream_Admin::ADMIN_PARENT_PAGE );
579
-
580
- echo '<form method="get" action="' . esc_url( $url ) . '">';
581
- echo $this->filter_search(); // xss ok
582
-
583
- parent::display();
584
- echo '</form>';
585
- }
586
-
587
- function display_tablenav( $which ) {
588
- if ( 'top' === $which ) : ?>
589
- <div class="tablenav <?php echo esc_attr( $which ); ?>">
590
- <?php
591
- $this->pagination( $which );
592
- $this->extra_tablenav( $which );
593
- ?>
594
-
595
- <br class="clear" />
596
- </div>
597
- <?php else : ?>
598
- <div class="tablenav <?php echo esc_attr( $which ); ?>">
599
- <?php
600
- do_action( 'mainwp_wp_stream_after_list_table' );
601
- $this->pagination( $which );
602
- $this->extra_tablenav( $which );
603
- ?>
604
-
605
- <br class="clear" />
606
- </div>
607
- <?php
608
- endif;
609
- }
610
-
611
- static function set_screen_option( $dummy, $option, $value ) {
612
- if ( 'mainwp_child_reports_per_page' === $option ) {
613
- return $value;
614
- } else {
615
- return $dummy;
616
- }
617
- }
618
-
619
- static function set_live_update_option( $dummy, $option, $value ) {
620
- if ( 'stream_live_update_records' === $option ) {
621
- $value = $_POST['stream_live_update_records'];
622
- return $value;
623
- } else {
624
- return $dummy;
625
- }
626
- }
627
-
628
- public function screen_controls( $status, $args ) {
629
- $user_id = get_current_user_id();
630
- $option = get_user_meta( $user_id, 'stream_live_update_records', true );
631
-
632
- $mainwp_creport_live_update_records_nonce = wp_create_nonce( 'mainwp_creport_live_update_records_nonce' );
633
-
634
- ob_start();
635
- ?>
636
- <fieldset>
637
- <h5><?php esc_html_e( 'Live updates', 'mainwp-child-reports' ) ?></h5>
638
-
639
- <div>
640
- <input type="hidden" name="mainwp_creport_live_update_nonce" id="mainwp_creport_live_update_nonce" value="<?php echo esc_attr( $mainwp_creport_live_update_records_nonce ) ?>" />
641
- </div>
642
- <div>
643
- <input type="hidden" name="enable_live_update_user" id="enable_live_update_user" value="<?php echo absint( $user_id ) ?>" />
644
- </div>
645
- <div class="metabox-prefs stream-live-update-checkbox">
646
- <label for="enable_live_update">
647
- <input type="checkbox" value="on" name="enable_live_update" id="enable_live_update" <?php checked( $option, 'on' ) ?> />
648
- <?php esc_html_e( 'Enabled', 'mainwp-child-reports' ) ?><span class="spinner"></span>
649
- </label>
650
- </div>
651
- </fieldset>
652
- <?php
653
- return ob_get_clean();
654
- }
655
-
656
- function get_column_excluded_setting_key( $column ) {
657
- switch ( $column ) {
658
- case 'connector':
659
- $output = 'connectors';
660
- break;
661
- case 'context':
662
- $output = 'contexts';
663
- break;
664
- case 'action':
665
- $output = 'action';
666
- break;
667
- case 'ip':
668
- $output = 'ip_addresses';
669
- break;
670
- case 'author':
671
- $output = 'authors';
672
- break;
673
- default:
674
- $output = false;
675
- }
676
-
677
- return $output;
678
- }
679
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/live-update.php DELETED
@@ -1,141 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Live_Update {
4
-
5
- public static $list_table = null;
6
-
7
- public static function load() {
8
- // Heartbeat live update
9
- add_filter( 'heartbeat_received', array( __CLASS__, 'heartbeat_received' ), 10, 2 );
10
-
11
- // Enable/Disable live update per user
12
- add_action( 'wp_ajax_mainwp_stream_enable_live_update', array( __CLASS__, 'enable_live_update' ) );
13
-
14
- }
15
-
16
- public static function enable_live_update() {
17
- check_ajax_referer( 'mainwp_creport_live_update_records_nonce', 'nonce' );
18
-
19
- $input = array(
20
- 'checked' => FILTER_SANITIZE_STRING,
21
- 'user' => FILTER_SANITIZE_STRING,
22
- );
23
-
24
- $input = filter_input_array( INPUT_POST, $input );
25
-
26
- if ( false === $input ) {
27
- wp_send_json_error( 'Error in live update checkbox' );
28
- }
29
-
30
- $checked = ( 'checked' === $input['checked'] ) ? 'on' : 'off';
31
-
32
- $user = (int) $input['user'];
33
-
34
- $success = update_user_meta( $user, 'stream_live_update_records', $checked );
35
-
36
- if ( $success ) {
37
- wp_send_json_success( 'Live Updates Enabled' );
38
- } else {
39
- wp_send_json_error( 'Live Updates checkbox error' );
40
- }
41
- }
42
-
43
- public static function live_update( $response, $data ) {
44
- if ( ! isset( $data['wp-mainwp-stream-heartbeat-last-id'] ) ) {
45
- return;
46
- }
47
-
48
- if ( ! isset( $data['wp-mainwp-stream-heartbeat-last-created'] ) ) {
49
- return;
50
- }
51
-
52
- $last_id = intval( $data['wp-mainwp-stream-heartbeat-last-id'] );
53
- $last_created = intval( $data['wp-mainwp-stream-heartbeat-last-created'] );
54
-
55
- $query = $data['wp-mainwp-stream-heartbeat-query'];
56
- if ( empty( $query ) ) {
57
- $query = array();
58
- }
59
-
60
- // Decode the query
61
- $query = json_decode( wp_kses_stripslashes( $query ) );
62
-
63
- $updated_items = MainWP_WP_Stream_Dashboard_Widget::gather_updated_items( $last_id, $query, $last_created );
64
-
65
- if ( ! empty( $updated_items ) ) {
66
- ob_start();
67
- foreach ( $updated_items as $item ) {
68
- self::$list_table->single_row( $item );
69
- }
70
-
71
- $send = ob_get_clean();
72
- } else {
73
- $send = '';
74
- }
75
-
76
- return $send;
77
- }
78
-
79
- public static function heartbeat_received( $response, $data ) {
80
- $option = get_option( 'dashboard_mainwp_stream_activity_options' );
81
- $enable_stream_update = true; //( 'off' !== get_user_meta( get_current_user_id(), 'stream_live_update_records', true ) );
82
- $enable_dashboard_update = false; //( 'off' !== ( $option['live_update'] ) );
83
-
84
- // Register list table
85
- require_once MAINWP_WP_STREAM_INC_DIR . 'list-table.php';
86
- self::$list_table = new MainWP_WP_Stream_List_Table( array( 'screen' => 'mainwp-child_page_' . MainWP_WP_Stream_Admin::RECORDS_PAGE_SLUG ) );
87
- self::$list_table->prepare_items();
88
-
89
- $total_items = isset( self::$list_table->_pagination_args['total_items'] ) ? self::$list_table->_pagination_args['total_items'] : null;
90
- $total_pages = isset( self::$list_table->_pagination_args['total_pages'] ) ? self::$list_table->_pagination_args['total_pages'] : null;
91
- $per_page = isset( self::$list_table->_pagination_args['per_page'] ) ? self::$list_table->_pagination_args['per_page'] : null;
92
-
93
- if ( isset( $data['wp-mainwp-stream-heartbeat'] ) && isset( $total_items ) ) {
94
- $response['total_items'] = $total_items;
95
- $response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
96
- }
97
-
98
- if ( isset( $data['wp-mainwp-stream-heartbeat'] ) && 'live-update' === $data['wp-mainwp-stream-heartbeat'] && $enable_stream_update ) {
99
-
100
- if ( ! empty( $data['wp-mainwp-stream-heartbeat'] ) ) {
101
- if ( isset( $total_pages ) ) {
102
- $response['total_pages'] = $total_pages;
103
- $response['total_pages_i18n'] = number_format_i18n( $total_pages );
104
-
105
- $query_args = json_decode( $data['wp-mainwp-stream-heartbeat-query'], true );
106
- $query_args['paged'] = $total_pages;
107
-
108
- $response['last_page_link'] = add_query_arg( $query_args, admin_url( 'admin.php' ) );
109
- } else {
110
- $response['total_pages'] = 0;
111
- }
112
- }
113
-
114
- $response['wp-mainwp-stream-heartbeat'] = self::live_update( $response, $data );
115
-
116
- } elseif ( isset( $data['wp-mainwp-stream-heartbeat'] ) && 'dashboard-update' === $data['wp-mainwp-stream-heartbeat'] && $enable_dashboard_update ) {
117
-
118
- $per_page = isset( $option['records_per_page'] ) ? absint( $option['records_per_page'] ) : 5;
119
-
120
- if ( isset( $total_items ) ) {
121
- $total_pages = ceil( $total_items / $per_page );
122
- $response['total_pages'] = $total_pages;
123
- $response['total_pages_i18n'] = number_format_i18n( $total_pages );
124
-
125
- $query_args['page'] = MainWP_WP_Stream_Admin::RECORDS_PAGE_SLUG;
126
- $query_args['paged'] = $total_pages;
127
-
128
- $response['last_page_link'] = add_query_arg( $query_args, admin_url( 'admin.php' ) );
129
- }
130
-
131
- $response['per_page'] = $per_page;
132
- $response['wp-mainwp-stream-heartbeat'] = MainWP_WP_Stream_Dashboard_Widget::live_update( $response, $data );
133
-
134
- } else {
135
- $response['log'] = 'fail';
136
- }
137
-
138
- return $response;
139
- }
140
-
141
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/log.php DELETED
@@ -1,86 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Log {
4
-
5
- public static $instance = null;
6
-
7
- public $prev_record;
8
-
9
- public static function load() {
10
-
11
- $log_handler = apply_filters( 'mainwp_wp_stream_log_handler', __CLASS__ );
12
-
13
- self::$instance = new $log_handler;
14
- }
15
-
16
- public static function get_instance() {
17
- if ( ! self::$instance ) {
18
- $class = __CLASS__;
19
- self::$instance = new $class;
20
- }
21
-
22
- return self::$instance;
23
- }
24
-
25
- public function log( $connector, $message, $args, $object_id, $contexts, $user_id = null , $created_timestamp = null) {
26
- global $wpdb;
27
-
28
- if ( is_null( $user_id ) ) {
29
- $user_id = get_current_user_id();
30
- }
31
- require_once MAINWP_WP_STREAM_INC_DIR . 'class-wp-stream-author.php';
32
-
33
- $user = new WP_User( $user_id );
34
- $roles = get_option( $wpdb->get_blog_prefix() . 'user_roles' );
35
-
36
- if ( ! isset( $args['author_meta'] ) ) {
37
- $args['author_meta'] = array(
38
- 'user_email' => $user->user_email,
39
- 'display_name' => ( defined( 'WP_CLI' ) && empty( $user->display_name ) ) ? 'WP-CLI' : $user->display_name,
40
- 'user_login' => $user->user_login,
41
- 'user_role_label' => ! empty( $user->roles ) ? $roles[ $user->roles[0] ]['name'] : null,
42
- 'agent' => MainWP_WP_Stream_Author::get_current_agent(),
43
- );
44
-
45
- if ( ( defined( 'WP_CLI' ) ) && function_exists( 'posix_getuid' ) ) {
46
- $uid = posix_getuid();
47
- $user_info = posix_getpwuid( $uid );
48
-
49
- $args['author_meta']['system_user_id'] = $uid;
50
- $args['author_meta']['system_user_name'] = $user_info['name'];
51
- }
52
- }
53
-
54
- // Remove meta with null values from being logged
55
- $meta = array_filter(
56
- $args,
57
- function ( $var ) {
58
- return ! is_null( $var );
59
- }
60
- );
61
-
62
- $recordarr = array(
63
- 'object_id' => $object_id,
64
- 'site_id' => is_multisite() ? get_current_site()->id : 1,
65
- 'blog_id' => apply_filters( 'blog_id_logged', is_network_admin() ? 0 : get_current_blog_id() ),
66
- 'author' => $user_id,
67
- 'author_role' => ! empty( $user->roles ) ? $user->roles[0] : null,
68
- 'created' => !empty($created_timestamp) ? gmdate("Y-m-d H:i:s", $created_timestamp) : current_time( 'mysql', 1 ),
69
- 'summary' => vsprintf( $message, $args ),
70
- 'parent' => self::$instance->prev_record,
71
- 'connector' => $connector,
72
- 'contexts' => $contexts,
73
- 'meta' => $meta,
74
- 'ip' => mainwp_wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP ),
75
- );
76
-
77
- $record_id = MainWP_WP_Stream_DB::get_instance()->insert( $recordarr );
78
-
79
- return $record_id;
80
- }
81
-
82
- public function get_log( $agrs = array()) {
83
- return MainWP_WP_Stream_DB::get_instance()->get_report( $agrs );
84
- }
85
-
86
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/network.php DELETED
@@ -1,370 +0,0 @@
1
- <?php
2
- /**
3
- * Multisite Network Class
4
- *
5
- * @author X-Team <x-team.com>
6
- * @author Chris Olbekson <chris@x-team.com>
7
- *
8
- */
9
-
10
- class MainWP_WP_Stream_Network {
11
-
12
- const NETWORK_SETTINGS_PAGE_SLUG = 'mainwp_wp_stream_network_settings';
13
- const DEFAULT_SETTINGS_PAGE_SLUG = 'mainwp_wp_stream_default_settings';
14
-
15
- function __construct() {
16
- $this->actions();
17
- $this->filters();
18
- }
19
-
20
- function actions() {
21
- add_action( 'init', array( $this, 'ajax_network_admin' ), 1 );
22
- add_action( 'admin_bar_menu', array( $this, 'network_admin_bar_menu' ), 99, 1 );
23
- add_action( 'network_admin_menu', array( 'MainWP_WP_Stream_Admin', 'register_menu' ) );
24
- add_action( 'network_admin_notices', array( 'MainWP_WP_Stream_Admin', 'admin_notices' ) );
25
- add_action( 'wpmuadminedit', array( $this, 'network_options_action' ) );
26
- add_action( 'wp_network_dashboard_setup', array( 'MainWP_WP_Stream_Dashboard_Widget', 'stream_activity' ) );
27
- add_action( 'admin_menu', array( $this, 'admin_menu_screens' ) );
28
- add_action( 'network_admin_menu', array( $this, 'admin_menu_screens' ) );
29
- add_action( 'update_site_option_' . MainWP_WP_Stream_Settings::NETWORK_KEY, array( $this, 'updated_option_ttl_remove_records' ), 10, 3 );
30
- }
31
-
32
- function filters() {
33
- add_filter( 'mainwp_wp_stream_disable_admin_access', array( __CLASS__, 'disable_admin_access' ) );
34
- add_filter( 'mainwp_wp_stream_settings_form_action', array( $this, 'settings_form_action' ) );
35
- add_filter( 'mainwp_wp_stream_settings_form_description', array( $this, 'settings_form_description' ) );
36
- add_filter( 'mainwp_wp_stream_options_fields', array( $this, 'get_network_admin_fields' ) );
37
- add_filter( 'mainwp_wp_stream_options', array( $this, 'get_network_options' ), 10, 2 );
38
- add_filter( 'mainwp_wp_stream_serialized_labels', array( $this, 'get_settings_translations' ) );
39
- add_filter( 'mainwp_wp_stream_list_table_filters', array( $this, 'list_table_filters' ) );
40
- add_filter( 'stream_toggle_filters', array( $this, 'toggle_filters' ) );
41
- add_filter( 'mainwp_wp_stream_list_table_screen_id', array( $this, 'list_table_screen_id' ) );
42
- add_filter( 'mainwp_wp_stream_query_args', array( __CLASS__, 'set_network_option_value' ) );
43
- add_filter( 'mainwp_wp_stream_list_table_columns', array( $this, 'network_admin_columns' ) );
44
- }
45
-
46
- function ajax_network_admin() {
47
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX && is_multisite() && preg_match( '#^' . network_admin_url() . '#i', $_SERVER['HTTP_REFERER'] ) ) {
48
- define( 'WP_NETWORK_ADMIN', true );
49
- }
50
- }
51
-
52
- public static function is_network_activated() {
53
- if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
54
- require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
55
- }
56
-
57
- return is_plugin_active_for_network( MAINWP_WP_STREAM_PLUGIN );
58
- }
59
-
60
- function network_admin_bar_menu( $admin_bar ) {
61
- if ( ! self::is_network_activated() ) {
62
- return;
63
- }
64
- }
65
-
66
- public static function get_network_blog() {
67
- $blog = new stdClass;
68
- $blog->blog_id = 0;
69
- $blog->blogname = __( 'Network Admin', 'default' );
70
-
71
- return $blog;
72
- }
73
-
74
- public static function disable_admin_access( $disable_access ) {
75
- if ( ! is_network_admin() && self::is_network_activated() ) {
76
- $settings = (array) get_site_option( MainWP_WP_Stream_Settings::NETWORK_KEY, array() );
77
-
78
- if ( isset( $settings['general_enable_site_access'] ) && false === $settings['general_enable_site_access'] ) {
79
- return true;
80
- }
81
- }
82
-
83
- return $disable_access;
84
- }
85
-
86
- function admin_menu_screens() {
87
- if ( ! is_network_admin() ) {
88
- return;
89
- }
90
-
91
- }
92
-
93
- function updated_option_ttl_remove_records( $option_key, $new_value, $old_value ) {
94
- MainWP_WP_Stream_Settings::updated_option_ttl_remove_records( $old_value, $new_value );
95
- }
96
-
97
- function settings_form_action( $action ) {
98
- if ( is_network_admin() ) {
99
- $current_page = mainwp_wp_stream_filter_input( INPUT_GET, 'page' );
100
- $action = add_query_arg( array( 'action' => $current_page ), 'edit.php' );
101
- }
102
-
103
- return $action;
104
- }
105
-
106
- function settings_form_description( $description ) {
107
- if ( ! is_network_admin() ) {
108
- return;
109
- }
110
-
111
- $current_page = mainwp_wp_stream_filter_input( INPUT_GET, 'page' );
112
-
113
- switch ( $current_page ) {
114
- case self::NETWORK_SETTINGS_PAGE_SLUG :
115
- $description = __( 'These settings apply to all sites on the network.', 'mainwp-child-reports' );
116
- break;
117
- case self::DEFAULT_SETTINGS_PAGE_SLUG :
118
- $description = __( 'These default settings will apply to new sites created on the network. These settings do not alter existing sites.', 'mainwp-child-reports' );
119
- break;
120
- }
121
-
122
- return $description;
123
- }
124
-
125
- function get_network_admin_fields( $fields ) {
126
- if ( ! self::is_network_activated() ) {
127
- return $fields;
128
- }
129
-
130
- $stream_hidden_options = apply_filters(
131
- 'mainwp_wp_stream_hidden_option_fields',
132
- array(
133
- 'general' => array(
134
- 'delete_all_records',
135
- 'records_ttl',
136
- ),
137
- )
138
- );
139
-
140
- $network_hidden_options = apply_filters(
141
- 'mainwp_wp_stream_network_option_fields',
142
- array(
143
- 'general' => array(
144
- 'role_access',
145
- 'private_feeds',
146
- ),
147
- 'exclude' => array(
148
- 'authors',
149
- 'roles',
150
- 'connectors',
151
- 'contexts',
152
- 'actions',
153
- 'ip_addresses',
154
- 'hide_previous_records',
155
- ),
156
- )
157
- );
158
-
159
- // Remove settings based on context
160
- if ( MainWP_WP_Stream_Settings::NETWORK_KEY === MainWP_WP_Stream_Settings::$option_key ) {
161
- $hidden_options = $network_hidden_options;
162
- } else {
163
- $hidden_options = $stream_hidden_options;
164
- }
165
-
166
- foreach ( $fields as $section_key => $section ) {
167
- foreach ( $section['fields'] as $key => $field ) {
168
- if ( ! isset( $hidden_options[ $section_key ] ) ) {
169
- continue;
170
- }
171
- if ( in_array( $field['name'], $hidden_options[ $section_key ] ) ) {
172
- unset( $fields[ $section_key ]['fields'][ $key ] );
173
- }
174
- }
175
- }
176
-
177
- // Add settings based on context
178
- if ( MainWP_WP_Stream_Settings::NETWORK_KEY === MainWP_WP_Stream_Settings::$option_key ) {
179
- $new_fields['general']['fields'][] = array(
180
- 'name' => 'enable_site_access',
181
- 'title' => __( 'Enable Site Access', 'mainwp-child-reports' ),
182
- 'after_field' => __( 'Enabled', 'mainwp-child-reports' ),
183
- 'default' => 1,
184
- 'desc' => __( 'When site access is disabled MainWP Child Reports can only be accessed from the network administration.', 'mainwp-child-reports' ),
185
- 'type' => 'checkbox',
186
- );
187
-
188
- $fields = array_merge_recursive( $new_fields, $fields );
189
-
190
- $reset_site_settings_href = add_query_arg(
191
- array(
192
- 'action' => 'mainwp_wp_stream_defaults',
193
- 'mainwp_wp_stream_nonce' => wp_create_nonce( 'stream_nonce' ),
194
- ),
195
- admin_url( 'admin-ajax.php' )
196
- );
197
-
198
- $fields['general']['fields'][] = array(
199
- 'name' => 'reset_site_settings',
200
- 'title' => __( 'Reset Site Settings', 'mainwp-child-reports' ),
201
- 'type' => 'link',
202
- 'href' => $reset_site_settings_href,
203
- 'desc' => __( 'Warning: Clicking this will override all site settings with defaults.', 'mainwp-child-reports' ),
204
- 'default' => 0,
205
- );
206
- }
207
-
208
- // Remove empty settings sections
209
- foreach ( $fields as $section_key => $section ) {
210
- if ( empty( $section['fields'] ) ) {
211
- unset( $fields[ $section_key ] );
212
- }
213
- }
214
-
215
- return $fields;
216
- }
217
-
218
- function get_settings_translations( $labels ) {
219
- $network_key = MainWP_WP_Stream_Settings::NETWORK_KEY;
220
- $defaults_key = MainWP_WP_Stream_Settings::DEFAULTS_KEY;
221
-
222
- if ( ! isset( $labels[ $network_key ] ) ) {
223
- $labels[ $network_key ] = array();
224
- }
225
-
226
- if ( ! isset( $labels[ $defaults_key ] ) ) {
227
- $labels[ $defaults_key ] = array();
228
- }
229
-
230
- foreach ( MainWP_WP_Stream_Settings::get_fields() as $section_slug => $section ) {
231
- foreach ( $section['fields'] as $field ) {
232
- $labels[ $network_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
233
- $labels[ $defaults_key ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
234
- }
235
- }
236
-
237
- return $labels;
238
- }
239
-
240
- function network_options_action() {
241
- $allowed_referers = array(
242
- self::NETWORK_SETTINGS_PAGE_SLUG,
243
- self::DEFAULT_SETTINGS_PAGE_SLUG,
244
- );
245
- if ( ! isset( $_GET['action'] ) || ! in_array( $_GET['action'], $allowed_referers ) ) {
246
- return;
247
- }
248
-
249
- $options = isset( $_POST['option_page'] ) ? explode( ',', stripslashes( $_POST['option_page'] ) ) : null;
250
-
251
- if ( $options ) {
252
-
253
- foreach ( $options as $option ) {
254
- $option = trim( $option );
255
- $value = null;
256
-
257
- $sections = MainWP_WP_Stream_Settings::get_fields();
258
- foreach ( $sections as $section_name => $section ) {
259
- foreach ( $section['fields'] as $field_idx => $field ) {
260
- $option_key = $section_name . '_' . $field['name'];
261
- if ( isset( $_POST[ $option ][ $option_key ] ) ) {
262
- $value[ $option_key ] = $_POST[ $option ][ $option_key ];
263
- } else {
264
- $value[ $option_key ] = false;
265
- }
266
- }
267
- }
268
-
269
- if ( ! is_array( $value ) ) {
270
- $value = trim( $value );
271
- }
272
-
273
- update_site_option( $option, $value );
274
- }
275
- }
276
-
277
- if ( ! count( get_settings_errors() ) ) {
278
- add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'default' ), 'updated' );
279
- }
280
-
281
- set_transient( 'settings_errors', get_settings_errors(), 30 );
282
-
283
- $go_back = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
284
- wp_redirect( $go_back );
285
- exit;
286
- }
287
-
288
- function get_network_options( $options, $option_key ) {
289
- if ( is_network_admin() ) {
290
- $options = wp_parse_args(
291
- (array) get_site_option( $option_key, array() ),
292
- MainWP_WP_Stream_Settings::get_defaults( $option_key )
293
- );
294
- }
295
-
296
- return $options;
297
- }
298
-
299
- function list_table_filters( $filters ) {
300
- if ( is_network_admin() && ! wp_is_large_network() ) {
301
- $blogs = array();
302
-
303
- // display network blog as the first option
304
- $network_blog = self::get_network_blog();
305
-
306
- $blogs['network'] = array(
307
- 'label' => $network_blog->blogname,
308
- 'disabled' => '',
309
- );
310
-
311
- // add all sites
312
- foreach ( (array) get_sites() as $blog ) {
313
- $blog_data = get_blog_details( $blog );
314
-
315
- $blogs[ $blog['blog_id'] ] = array(
316
- 'label' => $blog_data->blogname,
317
- 'disabled' => '',
318
- );
319
- }
320
-
321
- $filters['blog_id'] = array(
322
- 'title' => __( 'sites', 'mainwp-child-reports' ),
323
- 'items' => $blogs,
324
- );
325
- }
326
-
327
- return $filters;
328
- }
329
-
330
- function toggle_filters( $filters ) {
331
- if ( is_network_admin() ) {
332
- $filters['blog_id'] = esc_html__( 'Site', 'mainwp-child-reports' );
333
- }
334
-
335
- return $filters;
336
- }
337
-
338
- function list_table_screen_id( $screen_id ) {
339
- if ( $screen_id && is_network_admin() ) {
340
- if ( '-network' !== substr( $screen_id, -8 ) ) {
341
- $screen_id .= '-network';
342
- }
343
- }
344
-
345
- return $screen_id;
346
- }
347
-
348
- public static function set_network_option_value( $args ) {
349
- if ( isset( $args['blog_id'] ) && 'network' === $args['blog_id'] ) {
350
- $args['blog_id'] = 0;
351
- }
352
-
353
- return $args;
354
- }
355
-
356
- function network_admin_columns( $columns ) {
357
- if ( is_network_admin() ) {
358
- $columns = array_merge(
359
- array_slice( $columns, 0, -1 ),
360
- array(
361
- 'blog_id' => esc_html__( 'Site', 'mainwp-child-reports' ),
362
- ),
363
- array_slice( $columns, -1 )
364
- );
365
- }
366
-
367
- return $columns;
368
- }
369
-
370
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/query.php DELETED
@@ -1,432 +0,0 @@
1
- <?php
2
-
3
- class MainWP_WP_Stream_Query {
4
-
5
- public static $instance;
6
-
7
- public static function get_instance() {
8
- if ( ! self::$instance ) {
9
- $class = __CLASS__;
10
- self::$instance = new $class;
11
- }
12
-
13
- return self::$instance;
14
- }
15
-
16
- public function query( $args ) {
17
- global $wpdb;
18
-
19
- $defaults = array(
20
- // Pagination params
21
- 'records_per_page' => get_option( 'posts_per_page' ),
22
- 'paged' => 1,
23
- // Search param
24
- 'search' => null,
25
- // Stream core fields filtering
26
- 'type' => 'stream',
27
- 'object_id' => null,
28
- 'ip' => null,
29
- 'site_id' => is_multisite() ? get_current_site()->id : 1,
30
- 'blog_id' => is_network_admin() ? null : get_current_blog_id(),
31
- // Author params
32
- 'author' => null,
33
- 'author_role' => null,
34
- // Date-based filters
35
- 'date' => null,
36
- 'date_from' => null,
37
- 'date_to' => null,
38
- // Visibility filters
39
- 'visibility' => null,
40
- // __in params
41
- 'record_greater_than' => null,
42
- 'created_greater_than' => null,
43
- 'record__in' => array(),
44
- 'record__not_in' => array(),
45
- 'record_parent' => '',
46
- 'record_parent__in' => array(),
47
- 'record_parent__not_in' => array(),
48
- 'author__in' => array(),
49
- 'author__not_in' => array(),
50
- 'author_role__in' => array(),
51
- 'author_role__not_in' => array(),
52
- 'ip__in' => array(),
53
- 'ip__not_in' => array(),
54
- // Order
55
- 'order' => 'desc',
56
- 'orderby' => 'created',
57
- // Meta/Taxonomy sub queries
58
- 'meta_query' => array(),
59
- 'context_query' => array(),
60
- // Fields selection
61
- 'fields' => '',
62
- 'ignore_context' => null,
63
- // Hide records that match the exclude rules
64
- 'hide_excluded' => ! empty( MainWP_WP_Stream_Settings::$options['exclude_hide_previous_records'] ),
65
- );
66
-
67
- $args = wp_parse_args( $args, $defaults );
68
-
69
- $args = apply_filters( 'mainwp_wp_stream_query_args', $args );
70
-
71
- if ( true === $args['hide_excluded'] ) {
72
- $args = self::add_excluded_record_args( $args );
73
- }
74
-
75
- $join = '';
76
- $where = '';
77
-
78
- // Only join with context table for correct types of records
79
- if ( ! $args['ignore_context'] ) {
80
- $join = sprintf(
81
- ' INNER JOIN %1$s ON ( %1$s.record_id = %2$s.ID )',
82
- $wpdb->mainwp_reportscontext,
83
- $wpdb->mainwp_reports
84
- );
85
- }
86
-
87
- /**
88
- * PARSE CORE FILTERS
89
- */
90
- if ( $args['object_id'] ) {
91
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.object_id = %d", $args['object_id'] );
92
- }
93
-
94
- if ( $args['type'] ) {
95
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.type = %s", $args['type'] );
96
- }
97
-
98
- if ( $args['ip'] ) {
99
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.ip = %s", mainwp_wp_stream_filter_var( $args['ip'], FILTER_VALIDATE_IP ) );
100
- }
101
-
102
- if ( is_numeric( $args['site_id'] ) ) {
103
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.site_id = %d", $args['site_id'] );
104
- }
105
-
106
- if ( is_numeric( $args['blog_id'] ) ) {
107
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.blog_id = %d", $args['blog_id'] );
108
- }
109
-
110
- if ( $args['search'] ) {
111
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.summary LIKE %s", "%{$args['search']}%" );
112
- }
113
-
114
- if ( $args['author'] || '0' === $args['author'] ) {
115
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.author = %d", (int) $args['author'] );
116
- }
117
-
118
- if ( $args['author_role'] ) {
119
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.author_role = %s", $args['author_role'] );
120
- }
121
-
122
- if ( $args['visibility'] ) {
123
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.visibility = %s", $args['visibility'] );
124
- }
125
-
126
- if (isset($args['hide_child_reports']) && $args['hide_child_reports']) {
127
- $child_record_ids = array();
128
- $sql_meta = "SELECT record_id FROM $wpdb->mainwp_reportsmeta WHERE meta_key = 'slug' AND (meta_value = 'mainwp-child/mainwp-child.php' OR meta_value = 'mainwp-child-reports/mainwp-child-reports.php')";
129
- $ret = $wpdb->get_results( $sql_meta, 'ARRAY_A' );
130
-
131
- if (is_array($ret) && count($ret)> 0) {
132
- foreach($ret as $val) {
133
- $child_record_ids[] = $val['record_id'];
134
- }
135
- }
136
- if (count($child_record_ids) > 0) {
137
- $where .= " AND $wpdb->mainwp_reports.ID NOT IN (" . implode(",", $child_record_ids). ") ";
138
- }
139
- }
140
-
141
- /**
142
- * PARSE DATE FILTERS
143
- */
144
- if ( isset( $args['date'] ) && !empty( $args['date'] ) ) {
145
- $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date'] ) ) );
146
- $where .= " AND (DATE($wpdb->mainwp_reports.created) = STR_TO_DATE(" . $wpdb->prepare('%s', $date) . ", '%Y-%m-%d %H:%i:%s'))";
147
- } else {
148
- if ( isset($args['date_from']) && !empty($args['date_from']) ) {
149
- $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date_from'] ) ) );
150
- $where .= " AND ($wpdb->mainwp_reports.created >= STR_TO_DATE(" . $wpdb->prepare('%s', $date) . ", '%Y-%m-%d %H:%i:%s'))";
151
- }
152
- if ( isset($args['date_to']) && !empty($args['date_to']) ) {
153
- $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['date_to'] ) ) );
154
- $where .= " AND ($wpdb->mainwp_reports.created <= STR_TO_DATE(" . $wpdb->prepare('%s', $date) . ", '%Y-%m-%d %H:%i:%s'))";
155
- }
156
- if ( isset($args['datetime_from']) && !empty($args['datetime_from']) ) {
157
- $date = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $args['datetime_from'] ) ) );
158
- $where .= " AND ($wpdb->mainwp_reports.created >= STR_TO_DATE(" . $wpdb->prepare('%s', $date) . ", '%Y-%m-%d %H:%i:%s'))";
159
- }
160
- }
161
-
162
- /**
163
- * PARSE __IN PARAM FAMILY
164
- */
165
- if ( $args['record_greater_than'] ) {
166
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.ID > %d", (int) $args['record_greater_than'] );
167
- }
168
-
169
- if ( $args['created_greater_than'] ) {
170
- $date = date( 'Y-m-d H:i:s', $args['created_greater_than'] );
171
- $where .= " AND ($wpdb->mainwp_reports.created > STR_TO_DATE(" . $wpdb->prepare('%s', $date) . ", '%Y-%m-%d %H:%i:%s'))";
172
- }
173
-
174
- if ( $args['record__in'] ) {
175
- $record__in = array_filter( (array) $args['record__in'], 'is_numeric' );
176
- if ( ! empty( $record__in ) ) {
177
- $record__in_format = '(' . join( ',', array_fill( 0, count( $record__in ), '%d' ) ) . ')';
178
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.ID IN {$record__in_format}", $record__in );
179
- }
180
- }
181
-
182
- if ( $args['record__not_in'] ) {
183
- $record__not_in = array_filter( (array) $args['record__not_in'], 'is_numeric' );
184
- if ( ! empty( $record__not_in ) ) {
185
- $record__not_in_format = '(' . join( ',', array_fill( 0, count( $record__not_in ), '%d' ) ) . ')';
186
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.ID NOT IN {$record__not_in_format}", $record__not_in );
187
- }
188
- }
189
-
190
- if ( $args['record_parent'] ) {
191
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.parent = %d", (int) $args['record_parent'] );
192
- }
193
-
194
- if ( $args['record_parent__in'] ) {
195
- $record_parent__in = array_filter( (array) $args['record_parent__in'], 'is_numeric' );
196
- if ( ! empty( $record_parent__in ) ) {
197
- $record_parent__in_format = '(' . join( ',', array_fill( 0, count( $record_parent__in ), '%d' ) ) . ')';
198
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.parent IN {$record_parent__in_format}", $record_parent__in );
199
- }
200
- }
201
-
202
- if ( $args['record_parent__not_in'] ) {
203
- $record_parent__not_in = array_filter( (array) $args['record_parent__not_in'], 'is_numeric' );
204
- if ( ! empty( $record_parent__not_in ) ) {
205
- $record_parent__not_in_format = '(' . join( ',', array_fill( 0, count( $record_parent__not_in ), '%d' ) ) . ')';
206
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.parent NOT IN {$record_parent__not_in_format}", $record_parent__not_in );
207
- }
208
- }
209
-
210
- if ( $args['author__in'] ) {
211
- $author__in = array_filter( (array) $args['author__in'], 'is_numeric' );
212
- if ( ! empty( $author__in ) ) {
213
- $author__in_format = '(' . join( ',', array_fill( 0, count( $author__in ), '%d' ) ) . ')';
214
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.author IN {$author__in_format}", $author__in );
215
- }
216
- }
217
-
218
- if ( $args['author__not_in'] ) {
219
- $author__not_in = array_filter( (array) $args['author__not_in'], 'is_numeric' );
220
- if ( ! empty( $author__not_in ) ) {
221
- $author__not_in_format = '(' . join( ',', array_fill( 0, count( $author__not_in ), '%d' ) ) . ')';
222
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.author NOT IN {$author__not_in_format}", $author__not_in );
223
- }
224
- }
225
-
226
- if ( $args['author_role__in'] ) {
227
- if ( ! empty( $args['author_role__in'] ) ) {
228
- $author_role__in = '(' . join( ',', array_fill( 0, count( $args['author_role__in'] ), '%s' ) ) . ')';
229
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.author_role IN {$author_role__in}", $args['author_role__in'] );
230
- }
231
- }
232
-
233
- if ( $args['author_role__not_in'] ) {
234
- if ( ! empty( $args['author_role__not_in'] ) ) {
235
- $author_role__not_in = '(' . join( ',', array_fill( 0, count( $args['author_role__not_in'] ), '%s' ) ) . ')';
236
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.author_role NOT IN {$author_role__not_in}", $args['author_role__not_in'] );
237
- }
238
- }
239
-
240
- if ( $args['ip__in'] ) {
241
- if ( ! empty( $args['ip__in'] ) ) {
242
- $ip__in = '(' . join( ',', array_fill( 0, count( $args['ip__in'] ), '%s' ) ) . ')';
243
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.ip IN {$ip__in}", $args['ip__in'] );
244
- }
245
- }
246
-
247
- if ( $args['ip__not_in'] ) {
248
- if ( ! empty( $args['ip__not_in'] ) ) {
249
- $ip__not_in = '(' . join( ',', array_fill( 0, count( $args['ip__not_in'] ), '%s' ) ) . ')';
250
- $where .= $wpdb->prepare( " AND $wpdb->mainwp_reports.ip NOT IN {$ip__not_in}", $args['ip__not_in'] );
251
- }
252
- }
253
-
254
- /**
255
- * PARSE META QUERY PARAMS
256
- */
257
- $meta_query = new WP_Meta_Query;
258
- $meta_query->parse_query_vars( $args );
259
-
260
- if ( ! empty( $meta_query->queries ) ) {
261
- $mclauses = $meta_query->get_sql( 'mainwp-child-reports', $wpdb->mainwp_reports, 'ID' );
262
- $join .= str_replace( 'stream_id', 'record_id', $mclauses['join'] );
263
- $where .= str_replace( 'stream_id', 'record_id', $mclauses['where'] );
264
- }
265
-
266
- /**
267
- * PARSE CONTEXT PARAMS
268
- */
269
- if ( ! $args['ignore_context'] ) {
270
- $context_query = new MainWP_WP_Stream_Context_Query( $args );
271
- $cclauses = $context_query->get_sql();
272
- $join .= $cclauses['join'];
273
- $where .= $cclauses['where'];
274
- }
275
-
276
- /**
277
- * PARSE PAGINATION PARAMS
278
- */
279
- $page = intval( $args['paged'] );
280
- $perpage = intval( $args['records_per_page'] );
281
-
282
- if ( $perpage >= 0 ) {
283
- $offset = ($page - 1) * $perpage;
284
- $limits = "LIMIT $offset, {$perpage}";
285
- } else {
286
- $limits = '';
287
- }
288
-
289
- /**
290
- * PARSE ORDER PARAMS
291
- */
292
- $order = esc_sql( $args['order'] );
293
- $orderby = esc_sql( $args['orderby'] );
294
- $orderable = array( 'ID', 'site_id', 'blog_id', 'object_id', 'author', 'author_role', 'summary', 'visibility', 'parent', 'type', 'created' );
295
-
296
- if ( in_array( $orderby, $orderable ) ) {
297
- $orderby = $wpdb->mainwp_reports . '.' . $orderby;
298
- } elseif ( in_array( $orderby, array( 'connector', 'context', 'action' ) ) ) {
299
- $orderby = $wpdb->mainwp_reportscontext . '.' . $orderby;
300
- } elseif ( 'meta_value_num' === $orderby && ! empty( $args['meta_key'] ) ) {
301
- $orderby = "CAST($wpdb->mainwp_reportsmeta.meta_value AS SIGNED)";
302
- } elseif ( 'meta_value' === $orderby && ! empty( $args['meta_key'] ) ) {
303
- $orderby = "$wpdb->mainwp_reportsmeta.meta_value";
304
- } else {
305
- $orderby = "$wpdb->mainwp_reports.ID";
306
- }
307
- $orderby = 'ORDER BY ' . $orderby . ' ' . $order;
308
-
309
- /**
310
- * PARSE FIELDS PARAMETER
311
- */
312
- $fields = $args['fields'];
313
- $select = "$wpdb->mainwp_reports.*";
314
-
315
- if ( ! $args['ignore_context'] ) {
316
- $select .= ", $wpdb->mainwp_reportscontext.context, $wpdb->mainwp_reportscontext.action, $wpdb->mainwp_reportscontext.connector";
317
- }
318
-
319
- if ( 'ID' === $fields ) {
320
- $select = "$wpdb->mainwp_reports.ID";
321
- } elseif ( 'summary' === $fields ) {
322
- $select = "$wpdb->mainwp_reports.summary, $wpdb->mainwp_reports.ID";
323
- }
324
-
325
- /**
326
- * BUILD UP THE FINAL QUERY
327
- */
328
- $sql = "SELECT SQL_CALC_FOUND_ROWS $select
329
- FROM $wpdb->mainwp_reports
330
- $join
331
- WHERE 1=1 $where
332
- $orderby
333
- $limits";
334
-
335
- $sql = apply_filters( 'mainwp_wp_stream_query', $sql, $args );
336
-
337
- //error_log($sql);
338
-
339
- $results = $wpdb->get_results( $sql );
340
-
341
- if ( 'with-meta' === $fields && is_array( $results ) && $results ) {
342
- $ids = array_map( 'absint', wp_list_pluck( $results, 'ID' ) );
343
- // to fix issue long query
344
- $start_slice = 0;
345
- $max_slice = 100;
346
-
347
- while( $start_slice <= count($ids)) {
348
- $slice_ids = array_slice($ids, $start_slice, $max_slice);
349
- $start_slice += $max_slice;
350
-
351
- if (!empty($slice_ids)) {
352
- $sql_meta = sprintf(
353
- "SELECT * FROM $wpdb->mainwp_reportsmeta WHERE record_id IN ( %s )",
354
- implode( ',', $slice_ids )
355
- );
356
-
357
- $meta = $wpdb->get_results( $sql_meta );
358
- $ids_f = array_flip( $ids );
359
-
360
- foreach ( $meta as $meta_record ) {
361
- $results[ $ids_f[ $meta_record->record_id ] ]->meta[ $meta_record->meta_key ][] = $meta_record->meta_value;
362
- }
363
- }
364
- }
365
- // end
366
- }
367
-
368
- return $results;
369
- }
370
-
371
- public static function add_excluded_record_args( $args ) {
372
- // Remove record of excluded connector
373
- $args['connector__not_in'] = MainWP_WP_Stream_Settings::get_excluded_by_key( 'connectors' );
374
-
375
- // Remove record of excluded context
376
- $args['context__not_in'] = MainWP_WP_Stream_Settings::get_excluded_by_key( 'contexts' );
377
-
378
- // Remove record of excluded actions
379
- $args['action__not_in'] = MainWP_WP_Stream_Settings::get_excluded_by_key( 'actions' );
380
-
381
- // Remove record of excluded author
382
- $args['author__not_in'] = MainWP_WP_Stream_Settings::get_excluded_by_key( 'authors' );
383
-
384
- // Remove record of excluded author role
385
- $args['author_role__not_in'] = MainWP_WP_Stream_Settings::get_excluded_by_key( 'roles' );
386
-
387
- // Remove record of excluded ip
388
- $args['ip__not_in'] = MainWP_WP_Stream_Settings::get_excluded_by_key( 'ip_addresses' );
389
-
390
- return $args;
391
- }
392
-
393
- }
394
-
395
- function mainwp_wp_stream_query( $args = array() ) {
396
- return MainWP_WP_Stream_Query::get_instance()->query( $args );
397
- }
398
-
399
- function mainwp_wp_stream_get_meta( $record_id, $key = '', $single = false ) {
400
- return maybe_unserialize( get_metadata( 'record', $record_id, $key, $single ) );
401
- }
402
-
403
- function mainwp_wp_stream_update_meta( $record_id, $meta_key, $meta_value, $prev_value = '' ) {
404
- return update_metadata( 'record', $record_id, $meta_key, $meta_value, $prev_value );
405
- }
406
-
407
- function mainwp_wp_stream_existing_records( $column, $table = '' ) {
408
- global $wpdb;
409
-
410
- switch ( $table ) {
411
- case 'stream' :
412
- $rows = $wpdb->get_results( "SELECT {$column} FROM {$wpdb->mainwp_reports} GROUP BY {$column}", 'ARRAY_A' );
413
- break;
414
- case 'meta' :
415
- $rows = $wpdb->get_results( "SELECT {$column} FROM {$wpdb->mainwp_reportsmeta} GROUP BY {$column}", 'ARRAY_A' );
416
- break;
417
- default :
418
- $rows = $wpdb->get_results( "SELECT {$column} FROM {$wpdb->mainwp_reportscontext} GROUP BY {$column}", 'ARRAY_A' );
419
- }
420
-
421
- if ( is_array( $rows ) && ! empty( $rows ) ) {
422
- foreach ( $rows as $row ) {
423
- foreach ( $row as $cell => $value ) {
424
- $output_array[ $value ] = $value;
425
- }
426
- }
427
- return (array) $output_array;
428
- } else {
429
- $column = sprintf( 'stream_%s', $column );
430
- return isset( MainWP_WP_Stream_Connectors::$term_labels[ $column ] ) ? MainWP_WP_Stream_Connectors::$term_labels[ $column ] : array();
431
- }
432
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/settings.php DELETED
@@ -1,701 +0,0 @@
1
- <?php
2
- /**
3
- * Settings class
4
- *
5
- * @author X-Team <x-team.com>
6
- * @author Shady Sharaf <shady@x-team.com>
7
- */
8
- class MainWP_WP_Stream_Settings {
9
-
10
- const KEY = 'mainwp_wp_stream';
11
- const NETWORK_KEY = 'mainwp_wp_stream_network';
12
- const DEFAULTS_KEY = 'mainwp_wp_stream_defaults';
13
- public static $options = array();
14
- public static $option_key = '';
15
- public static $fields = array();
16
- public static function load() {
17
- self::$option_key = self::get_option_key();
18
- self::$options = self::get_options();
19
-
20
- // Register settings, and fields
21
- add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
22
-
23
- // Check if we need to flush rewrites rules
24
- add_action( 'update_option_' . self::KEY, array( __CLASS__, 'updated_option_trigger_flush_rules' ), 10, 2 );
25
-
26
- // Remove records when records TTL is shortened
27
- add_action( 'update_option_' . self::KEY, array( __CLASS__, 'updated_option_ttl_remove_records' ), 10, 2 );
28
-
29
- add_filter( 'mainwp_wp_stream_serialized_labels', array( __CLASS__, 'get_settings_translations' ) );
30
-
31
- // Ajax callback function to search users
32
- add_action( 'wp_ajax_mainwp_stream_get_users', array( __CLASS__, 'get_users' ) );
33
-
34
- // Ajax callback function to search IPs
35
- add_action( 'wp_ajax_mainwp_stream_get_ips', array( __CLASS__, 'get_ips' ) );
36
- }
37
-
38
- public static function get_users(){
39
- if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( MainWP_WP_Stream_Admin::SETTINGS_CAP ) ) {
40
- return;
41
- }
42
-
43
- check_ajax_referer( 'stream_get_users', 'nonce' );
44
-
45
- $response = (object) array(
46
- 'status' => false,
47
- 'message' => esc_html__( 'There was an error in the request', 'mainwp-child-reports' ),
48
- );
49
-
50
- $search = ( isset( $_POST['find'] )? wp_unslash( trim( $_POST['find'] ) ) : '' );
51
- $request = (object) array(
52
- 'find' => $search,
53
- );
54
-
55
- add_filter( 'user_search_columns', array( __CLASS__, 'add_display_name_search_columns' ), 10, 3 );
56
-
57
- $users = new WP_User_Query(
58
- array(
59
- 'search' => "*{$request->find}*",
60
- 'search_columns' => array(
61
- 'user_login',
62
- 'user_nicename',
63
- 'user_email',
64
- 'user_url',
65
- ),
66
- 'orderby' => 'display_name',
67
- 'number' => MainWP_WP_Stream_Admin::PRELOAD_AUTHORS_MAX,
68
- )
69
- );
70
-
71
- remove_filter( 'user_search_columns', array( __CLASS__, 'add_display_name_search_columns' ), 10 );
72
-
73
- if ( 0 === $users->get_total() ) {
74
- wp_send_json_error( $response );
75
- }
76
-
77
- $response->status = true;
78
- $response->message = '';
79
- $response->users = array();
80
-
81
- require_once MAINWP_WP_STREAM_INC_DIR . 'class-wp-stream-author.php';
82
-
83
- foreach ( $users->results as $key => $user ) {
84
- $author = new MainWP_WP_Stream_Author( $user->ID );
85
-
86
- $args = array(
87
- 'id' => $author->ID,
88
- 'text' => $author->display_name,
89
- );
90
-
91
- $args['tooltip'] = esc_attr(
92
- sprintf(
93
- __( "ID: %d\nUser: %s\nEmail: %s\nRole: %s", 'mainwp-child-reports' ),
94
- $author->id,
95
- $author->user_login,
96
- $author->user_email,
97
- ucwords( $author->get_role() )
98
- )
99
- );
100
-
101
- $args['icon'] = $author->get_avatar_src( 32 );
102
-
103
- $response->users[] = $args;
104
- }
105
-
106
- if ( empty( $search ) || preg_match( '/wp|cli|system|unknown/i', $search ) ) {
107
- $author = new MainWP_WP_Stream_Author( 0 );
108
- $response->users[] = array(
109
- 'id' => $author->id,
110
- 'text' => $author->get_display_name(),
111
- 'icon' => $author->get_avatar_src( 32 ),
112
- 'tooltip' => esc_html__( 'Actions performed by the system when a user is not logged in (e.g. auto site upgrader, or invoking WP-CLI without --user)', 'mainwp-child-reports' ),
113
- );
114
- }
115
-
116
- wp_send_json_success( $response );
117
- }
118
-
119
- public static function get_ips(){
120
- if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( MainWP_WP_Stream_Admin::SETTINGS_CAP ) ) {
121
- return;
122
- }
123
-
124
- check_ajax_referer( 'stream_get_ips', 'nonce' );
125
-
126
- global $wpdb;
127
-
128
- $results = $wpdb->get_col(
129
- $wpdb->prepare(
130
- "
131
- SELECT distinct(`ip`)
132
- FROM `{$wpdb->mainwp_reports}`
133
- WHERE `ip` LIKE %s
134
- ORDER BY inet_aton(`ip`) ASC
135
- LIMIT %d;
136
- ",
137
- $wpdb->esc_like( $_POST['find'] ) . '%',
138
- $_POST['limit']
139
- )
140
- );
141
-
142
- wp_send_json_success( $results );
143
- }
144
-
145
- public static function add_display_name_search_columns( $search_columns, $search, $query ){
146
- $search_columns[] = 'display_name';
147
-
148
- return $search_columns;
149
- }
150
-
151
- public static function get_option_key() {
152
- $option_key = self::KEY;
153
-
154
- $current_page = mainwp_wp_stream_filter_input( INPUT_GET, 'page' );
155
-
156
- if ( ! $current_page ) {
157
- $current_page = mainwp_wp_stream_filter_input( INPUT_GET, 'action' );
158
- }
159
-
160
- if ( 'mainwp_wp_stream_default_settings' === $current_page ) {
161
- $option_key = self::DEFAULTS_KEY;
162
- }
163
-
164
- if ( 'mainwp_wp_stream_network_settings' === $current_page ) {
165
- $option_key = self::NETWORK_KEY;
166
- }
167
-
168
- return apply_filters( 'mainwp_wp_stream_settings_option_key', $option_key );
169
- }
170
-
171
- public static function get_fields() {
172
- if ( empty( self::$fields ) ) {
173
- if (!class_exists('MainWP_WP_Stream_Admin'))
174
- require_once MAINWP_WP_STREAM_INC_DIR . 'admin.php';
175
-
176
- $branding_text = MainWP_WP_Stream::get_instance()->get_branding_title();
177
- $branding_name = !empty($branding_text) ? $branding_text : 'MainWP Child';
178
- $chk_label = 'Hide ' . $branding_name . ' Reports from reports';
179
-
180
- $chk_desc = 'If selected, the ' . $branding_name . ' Reports plugin will be left out from reports for this site.';
181
- $hide_child_plugins = get_option('mainwp_creport_hide_child_plugins', 'yes');
182
- // to fix can not set default checked checkbox
183
- $checkbox_hide_childs = '<tr><th scope="row"><label for="mainwp_creport_hide_child_plugins">' . $chk_label;
184
- $checkbox_hide_childs .= '</label></th><td><label><input name="mainwp_creport_hide_child_plugins" id="mainwp_creport_hide_child_plugins" value="1" type="checkbox" ' . ($hide_child_plugins == 'yes' ? 'checked' : '') . '> ';
185
- $checkbox_hide_childs .= '</label><p class="description">' . $chk_desc . '</p></td></tr>';
186
-
187
- self::$fields = array(
188
- 'general' => array(
189
- 'title' => esc_html__( 'General', 'default' ),
190
- 'fields' => array(
191
- array(
192
- 'name' => 'records_ttl',
193
- 'title' => esc_html__( 'Keep Records for', 'mainwp-child-reports' ),
194
- 'type' => 'number',
195
- 'class' => 'small-text',
196
- 'desc' => esc_html__( 'Maximum number of days to keep activity records. Leave blank to keep records forever.', 'mainwp-child-reports' ),
197
- 'default' => 180,
198
- 'after_field' => esc_html__( 'days', 'mainwp-child-reports' ),
199
- ),
200
- array(
201
- 'name' => 'period_of_time',
202
- 'title' => esc_html__( 'Minimum time between posts/pages update reports', 'mainwp-child-reports' ),
203
- 'type' => 'select',
204
- 'choices' => array( '0' => '0', '30' => '30', '60' => '60', '90' => '90', '120' => '120'),
205
- 'desc' => '',
206
- 'default' => 30,
207
- 'current_value' => array( '30' ),
208
- 'after_field' => esc_html__( 'minutes', 'mainwp-child-reports' ) . $checkbox_hide_childs, // to add checkbox
209
- ),
210
- array(
211
- 'name' => 'delete_all_records',
212
- 'title' => 'Reset ' . $branding_name . ' Reports Database',
213
- 'type' => 'link',
214
- 'href' => add_query_arg(
215
- array(
216
- 'action' => 'mainwp_wp_stream_reset',
217
- 'mainwp_wp_stream_nonce' => wp_create_nonce( 'stream_nonce' ),
218
- ),
219
- admin_url( 'admin-ajax.php' )
220
- ),
221
- 'desc' => esc_html__( 'Warning: Clicking this will delete all activity records from the database.', 'mainwp-child-reports' ),
222
- 'default' => 0,
223
- ),
224
- ),
225
- ),
226
- );
227
- }
228
-
229
- return self::$fields;
230
- }
231
-
232
- public static function get_options() {
233
- $option_key = self::$option_key;
234
-
235
- $defaults = self::get_defaults( $option_key );
236
-
237
- if ( self::DEFAULTS_KEY === $option_key ) {
238
- return $defaults;
239
- }
240
-
241
- return apply_filters(
242
- 'mainwp_wp_stream_options',
243
- wp_parse_args(
244
- (array) get_option( $option_key, array() ),
245
- $defaults
246
- ),
247
- $option_key
248
- );
249
- }
250
-
251
- public static function get_defaults() {
252
- $fields = self::get_fields();
253
- $defaults = array();
254
-
255
- foreach ( $fields as $section_name => $section ) {
256
- foreach ( $section['fields'] as $field ) {
257
- $defaults[ $section_name.'_'.$field['name'] ] = isset( $field['default'] )
258
- ? $field['default']
259
- : null;
260
- }
261
- }
262
-
263
- return apply_filters(
264
- 'mainwp_wp_stream_option_defaults',
265
- wp_parse_args(
266
- (array) get_site_option( self::DEFAULTS_KEY, array() ),
267
- $defaults
268
- )
269
- );
270
- }
271
-
272
- public static function register_settings() {
273
- $sections = self::get_fields();
274
-
275
- register_setting( self::$option_key, self::$option_key, array( 'MainWP_WP_Stream_Settings', 'sanitize_settings' ) );
276
-
277
- foreach ( $sections as $section_name => $section ) {
278
- add_settings_section(
279
- $section_name,
280
- null,
281
- '__return_false',
282
- self::$option_key
283
- );
284
-
285
- foreach ( $section['fields'] as $field_idx => $field ) {
286
- if ( ! isset( $field['type'] ) ) { // No field type associated, skip, no GUI
287
- continue;
288
- }
289
- add_settings_field(
290
- $field['name'],
291
- $field['title'],
292
- ( isset( $field['callback'] ) ? $field['callback'] : array( __CLASS__, 'output_field' ) ),
293
- self::$option_key,
294
- $section_name,
295
- $field + array(
296
- 'section' => $section_name,
297
- 'label_for' => sprintf( '%s_%s_%s', self::$option_key, $section_name, $field['name'] ), // xss ok
298
- )
299
- );
300
- }
301
- }
302
- }
303
-
304
- public static function sanitize_settings( $input ) {
305
- if (isset($_POST['mainwp_creport_hide_child_plugins'])) {
306
- update_option('mainwp_creport_hide_child_plugins', 'yes');
307
- } else {
308
- update_option('mainwp_creport_hide_child_plugins', 'no');
309
- }
310
- return $input;
311
- }
312
-
313
- public static function updated_option_trigger_flush_rules( $old_value, $new_value ) {
314
- if ( is_array( $new_value ) && is_array( $old_value ) ) {
315
- $new_value = ( array_key_exists( 'general_private_feeds', $new_value ) ) ? $new_value['general_private_feeds'] : 0;
316
- $old_value = ( array_key_exists( 'general_private_feeds', $old_value ) ) ? $old_value['general_private_feeds'] : 0;
317
-
318
- if ( $new_value !== $old_value ) {
319
- delete_option( 'rewrite_rules' );
320
- }
321
- }
322
- }
323
-
324
- public static function render_field( $field ) {
325
- $output = null;
326
- $type = isset( $field['type'] ) ? $field['type'] : null;
327
- $section = isset( $field['section'] ) ? $field['section'] : null;
328
- $name = isset( $field['name'] ) ? $field['name'] : null;
329
- $class = isset( $field['class'] ) ? $field['class'] : null;
330
- $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : null;
331
- $description = isset( $field['desc'] ) ? $field['desc'] : null;
332
- $href = isset( $field['href'] ) ? $field['href'] : null;
333
- $after_field = isset( $field['after_field'] ) ? $field['after_field'] : null;
334
- $default = isset( $field['default'] ) ? $field['default'] : null;
335
- $title = isset( $field['title'] ) ? $field['title'] : null;
336
- $nonce = isset( $field['nonce'] ) ? $field['nonce'] : null;
337
- $current_value = self::$options[ $section . '_' . $name ];
338
- $option_key = self::$option_key;
339
-
340
-
341
- if ( is_callable( $current_value ) ) {
342
- $current_value = call_user_func( $current_value );
343
- }
344
-
345
- if ( ! $type || ! $section || ! $name ) {
346
- return;
347
- }
348
-
349
- if ( 'multi_checkbox' === $type
350
- && ( empty( $field['choices'] ) || ! is_array( $field['choices'] ) )
351
- ) {
352
- return;
353
- }
354
-
355
- switch ( $type ) {
356
- case 'text':
357
- case 'number':
358
- $output = sprintf(
359
- '<input type="%1$s" name="%2$s[%3$s_%4$s]" id="%2$s_%3$s_%4$s" class="%5$s" placeholder="%6$s" value="%7$s" /> %8$s',
360
- esc_attr( $type ),
361
- esc_attr( $option_key ),
362
- esc_attr( $section ),
363
- esc_attr( $name ),
364
- esc_attr( $class ),
365
- esc_attr( $placeholder ),
366
- esc_attr( $current_value ),
367
- $after_field // xss ok
368
- );
369
- break;
370
- case 'checkbox':
371
- $output = sprintf(
372
- '<label><input type="checkbox" name="%1$s[%2$s_%3$s]" id="%1$s[%2$s_%3$s]" value="1" %4$s /> %5$s</label>',
373
- esc_attr( $option_key ),
374
- esc_attr( $section ),
375
- esc_attr( $name ),
376
- checked( $current_value, 1, false ),
377
- $after_field // xss ok
378
- );
379
- break;
380
- case 'multi_checkbox':
381
- $output = sprintf(
382
- '<div id="%1$s[%2$s_%3$s]"><fieldset>',
383
- esc_attr( $option_key ),
384
- esc_attr( $section ),
385
- esc_attr( $name )
386
- );
387
- // Fallback if nothing is selected
388
- $output .= sprintf(
389
- '<input type="hidden" name="%1$s[%2$s_%3$s][]" value="__placeholder__" />',
390
- esc_attr( $option_key ),
391
- esc_attr( $section ),
392
- esc_attr( $name )
393
- );
394
- $current_value = (array) $current_value;
395
- $choices = $field['choices'];
396
- if ( is_callable( $choices ) ) {
397
- $choices = call_user_func( $choices );
398
- }
399
- foreach ( $choices as $value => $label ) {
400
- $output .= sprintf(
401
- '<label>%1$s <span>%2$s</span></label><br />',
402
- sprintf(
403
- '<input type="checkbox" name="%1$s[%2$s_%3$s][]" value="%4$s" %5$s />',
404
- esc_attr( $option_key ),
405
- esc_attr( $section ),
406
- esc_attr( $name ),
407
- esc_attr( $value ),
408
- checked( in_array( $value, $current_value ), true, false )
409
- ),
410
- esc_html( $label )
411
- );
412
- }
413
- $output .= '</fieldset></div>';
414
- break;
415
- case 'select':
416
- $current_value = (array) self::$options[ $section . '_' . $name ];
417
- $default_value = isset( $default['value'] ) ? $default['value'] : '-1';
418
- $default_name = isset( $default['name'] ) ? $default['name'] : 'Choose Setting';
419
-
420
- $output = sprintf(
421
- '<select name="%1$s[%2$s_%3$s]" id="%1$s_%2$s_%3$s">',
422
- esc_attr( $option_key ),
423
- esc_attr( $section ),
424
- esc_attr( $name )
425
- );
426
- $output .= sprintf(
427
- '<option value="%1$s" %2$s>%3$s</option>',
428
- esc_attr( $default_value ),
429
- selected( in_array( $default_value, $current_value ), true, false ),
430
- esc_html( $default_name )
431
- );
432
- foreach ( $field['choices'] as $value => $label ) {
433
- $output .= sprintf(
434
- '<option value="%1$s" %2$s>%3$s</option>',
435
- esc_attr( $value ),
436
- selected( in_array( $value, $current_value ), true, false ),
437
- esc_html( $label )
438
- );
439
- }
440
- $output .= '</select>';
441
- $output .= $after_field;
442
- break;
443
- case 'file':
444
- $output = sprintf(
445
- '<input type="file" name="%1$s[%2$s_%3$s]" id="%1$s_%2$s_%3$s" class="%4$s">',
446
- esc_attr( $option_key ),
447
- esc_attr( $section ),
448
- esc_attr( $name ),
449
- esc_attr( $class )
450
- );
451
- break;
452
- case 'link':
453
- $output = sprintf(
454
- '<a id="%1$s_%2$s_%3$s" class="%4$s" href="%5$s">%6$s</a>',
455
- esc_attr( $option_key ),
456
- esc_attr( $section ),
457
- esc_attr( $name ),
458
- esc_attr( $class ),
459
- esc_attr( $href ),
460
- esc_attr( $title )
461
- );
462
- break;
463
- case 'select2' :
464
- if ( ! isset ( $current_value ) ) {
465
- $current_value = array();
466
- }
467
-
468
- if ( false !== ( $key = array_search( '__placeholder__', $current_value ) ) ) {
469
- unset( $current_value[ $key ] );
470
- }
471
-
472
- $data_values = array();
473
- $selected_values = array();
474
- if ( isset( $field['choices'] ) ) {
475
- $choices = $field['choices'];
476
- if ( is_callable( $choices ) ) {
477
- $param = ( isset( $field['param'] ) ) ? $field['param'] : null;
478
- $choices = call_user_func( $choices, $param );
479
- }
480
- foreach ( $choices as $key => $value ) {
481
- $data_values[] = array( 'id' => $key, 'text' => $value );
482
- if ( in_array( $key, $current_value ) ) {
483
- $selected_values[] = array( 'id' => $key, 'text' => $value );
484
- }
485
- }
486
- $class .= ' with-source';
487
- } else {
488
- foreach ( $current_value as $value ) {
489
- if ( '__placeholder__' === $value || '' === $value ) {
490
- continue;
491
- }
492
- $selected_values[] = array( 'id' => $value, 'text' => $value );
493
- }
494
- }
495
-
496
- $output = sprintf(
497
- '<div id="%1$s[%2$s_%3$s]">',
498
- esc_attr( $option_key ),
499
- esc_attr( $section ),
500
- esc_attr( $name )
501
- );
502
- $output .= sprintf(
503
- '<input type="hidden" data-values=\'%1$s\' data-selected=\'%2$s\' value="%3$s" class="select2-select %4$s" data-select-placeholder="%5$s-%6$s-select-placeholder" %7$s />',
504
- esc_attr( json_encode( $data_values ) ),
505
- esc_attr( json_encode( $selected_values ) ),
506
- esc_attr( implode( ',', $current_value ) ),
507
- $class,
508
- esc_attr( $section ),
509
- esc_attr( $name ),
510
- isset( $nonce ) ? sprintf( ' data-nonce="%s"', esc_attr( wp_create_nonce( $nonce ) ) ) : ''
511
- );
512
- // to store data with default value if nothing is selected
513
- $output .= sprintf(
514
- '<input type="hidden" name="%1$s[%2$s_%3$s][]" class="%2$s-%3$s-select-placeholder" value="__placeholder__" />',
515
- esc_attr( $option_key ),
516
- esc_attr( $section ),
517
- esc_attr( $name )
518
- );
519
- $output .= '</div>';
520
- break;
521
- case 'select2_user_role':
522
- $current_value = (array)$current_value;
523
- $data_values = array();
524
-
525
- if ( isset( $field['choices'] ) ) {
526
- $choices = $field['choices'];
527
- if ( is_callable( $choices ) ) {
528
- $param = ( isset( $field['param'] ) ) ? $field['param'] : null;
529
- $choices = call_user_func( $choices, $param );
530
- }
531
- } else {
532
- $choices = array();
533
- }
534
-
535
- foreach ( $choices as $key => $role ) {
536
- $args = array( 'id' => $key, 'text' => $role );
537
- $users = get_users( array( 'role' => $key ) );
538
- if ( count( $users ) ) {
539
- $args['user_count'] = sprintf( _n( '1 user', '%s users', count( $users ), 'mainwp-child-reports' ), count( $users ) );
540
- }
541
- $data_values[] = $args;
542
- }
543
-
544
- $selected_values = array();
545
- foreach ( $current_value as $value ) {
546
- if ( ! is_string( $value ) && ! is_numeric( $value ) ) {
547
- continue;
548
- }
549
-
550
- if ( '__placeholder__' === $value || '' === $value ) {
551
- continue;
552
- }
553
-
554
- if ( is_numeric( $value ) ) {
555
- $user = new WP_User( $value );
556
- $selected_values[] = array( 'id' => $user->ID, 'text' => $user->display_name );
557
- } else {
558
- foreach ( $data_values as $role ) {
559
- if ( $role['id'] !== $value ) {
560
- continue;
561
- }
562
- $selected_values[] = $role;
563
- }
564
- }
565
- }
566
-
567
- $output = sprintf(
568
- '<div id="%1$s[%2$s_%3$s]">',
569
- esc_attr( $option_key ),
570
- esc_attr( $section ),
571
- esc_attr( $name )
572
- );
573
- $output .= sprintf(
574
- '<input type="hidden" data-values=\'%1$s\' data-selected=\'%2$s\' value="%3$s" class="select2-select %5$s" data-select-placeholder="%4$s-%5$s-select-placeholder" data-nonce="%6$s" />',
575
- json_encode( $data_values ),
576
- json_encode( $selected_values ),
577
- esc_attr( implode( ',', $current_value ) ),
578
- esc_attr( $section ),
579
- esc_attr( $name ),
580
- esc_attr( wp_create_nonce( 'stream_get_users' ) )
581
- );
582
- // to store data with default value if nothing is selected
583
- $output .= sprintf(
584
- '<input type="hidden" name="%1$s[%2$s_%3$s][]" class="%2$s-%3$s-select-placeholder" value="__placeholder__" />',
585
- esc_attr( $option_key ),
586
- esc_attr( $section ),
587
- esc_attr( $name )
588
- );
589
- $output .= '</div>';
590
- break;
591
- }
592
- $output .= ! empty( $description ) ? sprintf( '<p class="description">%s</p>', $description /* xss ok */ ) : null;
593
-
594
- return $output;
595
- }
596
-
597
- public static function output_field( $field ) {
598
- $method = 'output_' . $field['name'];
599
-
600
- if ( method_exists( __CLASS__, $method ) ) {
601
- return call_user_func( array( __CLASS__, $method ), $field );
602
- }
603
-
604
- $output = self::render_field( $field );
605
-
606
- echo $output; // xss okay
607
- }
608
-
609
- public static function get_roles() {
610
- $wp_roles = new WP_Roles();
611
- $roles = array();
612
-
613
- foreach ( $wp_roles->get_names() as $role => $label ) {
614
- $roles[ $role ] = translate_user_role( $label );
615
- }
616
-
617
- return $roles;
618
- }
619
-
620
- public static function get_connectors() {
621
- return MainWP_WP_Stream_Connectors::$term_labels['stream_connector'];
622
- }
623
-
624
- public static function get_default_connectors() {
625
- return array_keys( MainWP_WP_Stream_Connectors::$term_labels['stream_connector'] );
626
- }
627
-
628
- public static function get_terms_labels( $column ) {
629
- $return_labels = array();
630
-
631
- if ( isset ( MainWP_WP_Stream_Connectors::$term_labels[ 'stream_' . $column ] ) ) {
632
- $return_labels = MainWP_WP_Stream_Connectors::$term_labels[ 'stream_' . $column ];
633
- ksort( $return_labels );
634
- }
635
-
636
- return $return_labels;
637
- }
638
-
639
- public static function get_active_connectors() {
640
- $excluded_connectors = self::get_excluded_by_key( 'connectors' );
641
- $active_connectors = array_diff( array_keys( self::get_terms_labels( 'connector' ) ), $excluded_connectors );
642
- $active_connectors = wp_list_filter( $active_connectors, array( '__placeholder__' ), 'NOT' );
643
-
644
- return $active_connectors;
645
- }
646
-
647
- public static function get_excluded_by_k