PluginProbe
The WP Remote WordPress Plugin / 6.76
The WP Remote WordPress Plugin v6.76
6.76 6.72 6.69 6.65 6.62 6.48 6.47 4.87 4.97 5.05 5.09 5.16 5.22 5.24 5.25 5.38 5.41 5.42 5.45 5.47 5.53 5.56 5.65 5.68 5.72 All 54 releases
← All changes | wp_actlog.php +537 -366 6.486.76 View file →
@@ -2,17 +2,103 @@
2 2
3 3 if (!defined('ABSPATH')) exit;
4 4 if (!class_exists('BVWPActLog')) :
5 5
6 + /*
7 + * A sensor, not an interpreter. The server decides which hooks and keys are
8 + * watched; each handler records what WordPress handed it and nothing more.
9 + * Meaning (created, published, which setting changed) is assigned on the server.
10 + */
6 11 class BVWPActLog {
7 12
8 13 public static $actlog_table = 'activities_store';
14 + # Post content and comment bodies larger than this are omitted from the row.
15 + const CONTENT_MAX_BYTES = 16384;
16 + # A serialized row larger than this is stored without content, or not at all.
17 + # The server refuses rows above 4 MiB and ships up to 1000 rows per request.
18 + const EVENT_DATA_MAX_BYTES = 32768;
19 + # Column widths for tables created by actlog wing 1.1+ and for tables
20 + # widened through the alteractlogtable callback.
21 + const IP_MAX_LENGTH = 50;
22 + const EVENT_TYPE_MAX_LENGTH = 60;
23 +
24 + # hook => array(handler, accepted args, priority). The server sends the hooks
25 + # to register; only hooks named here can ever be bound.
26 + public static $hooks = array(
27 + 'wp_after_insert_post' => array('post_saved_handler', 4),
28 + 'deleted_post' => array('post_deleted_handler', 2),
29 + 'post_stuck' => array('post_handler'),
30 + 'post_unstuck' => array('post_handler'),
31 + 'wp_insert_comment' => array('comment_handler', 2),
32 + 'edit_comment' => array('comment_handler'),
33 + 'transition_comment_status' => array('comment_status_handler', 3),
34 + 'created_term' => array('term_handler'),
35 + 'edited_term' => array('term_handler'),
36 + 'delete_term' => array('term_deleted_handler', 4),
37 + 'user_register' => array('user_handler'),
38 + 'profile_update' => array('user_updated_handler', 2),
39 + 'deleted_user' => array('user_deleted_handler', 3),
40 + 'set_user_role' => array('role_handler', 3),
41 + 'add_user_role' => array('role_handler', 2),
42 + 'remove_user_role' => array('role_handler', 2),
43 + 'activated_plugin' => array('plugin_handler'),
44 + 'deactivated_plugin' => array('plugin_handler'),
45 + 'deleted_plugin' => array('plugin_deleted_handler', 2),
46 + 'switch_theme' => array('theme_handler'),
47 + 'deleted_theme' => array('theme_deleted_handler', 2),
48 + 'wp_insert_site' => array('site_handler'),
49 + 'wp_delete_site' => array('site_handler'),
50 + 'wp_update_site' => array('site_updated_handler', 2),
51 + 'wp_login' => array('login_handler', 2),
52 + 'wp_logout' => array('logout_handler'),
53 + 'after_password_reset' => array('password_reset_handler'),
54 + 'upgrader_install_package_result' => array('package_result_handler', 2, PHP_INT_MAX),
55 + 'upgrader_process_complete' => array('upgrade_handler', 2),
56 + 'automatic_updates_complete' => array('automatic_updates_handler'),
57 + '_core_updated_successfully' => array('core_upgrade_handler'),
58 + 'added_post_meta' => array('post_meta_handler', 4),
59 + 'updated_post_meta' => array('post_meta_handler', 4),
60 + 'deleted_post_meta' => array('post_meta_handler', 4),
61 + 'added_user_meta' => array('user_meta_handler', 4),
62 + 'updated_user_meta' => array('user_meta_handler', 4),
63 + 'deleted_user_meta' => array('user_meta_handler', 4),
64 + 'added_option' => array('option_handler', 3),
65 + 'updated_option' => array('option_handler', 3),
66 + 'deleted_option' => array('option_handler', 3),
67 + # Multisite network options (sitemeta).
68 + 'add_site_option' => array('option_handler', 3),
69 + 'update_site_option' => array('option_handler', 3),
70 + 'delete_site_option' => array('option_handler', 3),
71 + 'woocommerce_before_product_object_save' => array('product_before_save_handler'),
72 + 'woocommerce_after_product_object_save' => array('product_saved_handler'),
73 + 'woocommerce_new_order' => array('order_handler', 2),
74 + 'woocommerce_before_order_object_save' => array('order_before_save_handler'),
75 + 'woocommerce_after_order_object_save' => array('order_saved_handler'),
76 + 'woocommerce_order_status_changed' => array('order_status_handler', 4),
77 + 'woocommerce_trash_order' => array('order_handler'),
78 + 'woocommerce_delete_order' => array('order_deleted_handler'),
79 + 'trashed_post' => array('order_post_handler'),
80 + 'untrashed_post' => array('order_post_handler'),
81 + 'woocommerce_attribute_added' => array('attribute_handler', 2),
82 + 'woocommerce_attribute_updated' => array('attribute_updated_handler', 3),
83 + 'woocommerce_attribute_deleted' => array('attribute_deleted_handler', 3),
84 + 'woocommerce_tax_rate_added' => array('tax_rate_handler', 2),
85 + 'woocommerce_tax_rate_updated' => array('tax_rate_handler', 2),
86 + 'woocommerce_tax_rate_deleted' => array('tax_rate_deleted_handler'),
87 + 'woocommerce_shipping_zone_method_added' => array('shipping_method_handler', 3),
88 + 'woocommerce_shipping_zone_method_status_toggled' => array('shipping_method_handler', 4),
89 + 'woocommerce_shipping_zone_method_deleted' => array('shipping_method_handler', 3),
90 + 'woocommerce_grant_product_download_access' => array('download_granted_handler'),
91 + 'woocommerce_ajax_revoke_access_to_product_download' => array('download_revoked_handler', 4)
92 + );
93 +
9 94 public $db;
10 95 public $settings;
11 96 public $bvinfo;
12 97 public $request_id;
13 - public $ip_header;
14 - public $ignored_events;
98 + public $config;
99 + public $pending_changes = array();
100 + public $installed_packages = array();
15 101
16 102 public function __construct($db, $settings, $info, $config) {
17 103 $this->db = $db;
18 104 $this->settings = $settings;
@@ -17,36 +103,115 @@
17 103 $this->db = $db;
18 104 $this->settings = $settings;
19 105 $this->bvinfo = $info;
20 106 $this->request_id = WPRInfo::getRequestID();
21 - $this->ip_header = array_key_exists('ip_header', $config) ? $config['ip_header'] : false;
22 - $this->ignored_events = array_key_exists('ignored_events', $config) ? $config['ignored_events'] : array();
107 + $this->config = is_array($config) ? $config : array();
23 108 }
24 109
25 110 function init() {
26 - $this->add_actions_and_listeners();
111 + add_action('wpr_clear_actlog_config', array($this, 'clearConfig'));
112 + foreach ($this->option('hooks') as $hook) {
113 + if (!is_string($hook) || !isset(self::$hooks[$hook])) continue;
114 + $binding = self::$hooks[$hook];
115 + $this->listen($hook, $binding[0], isset($binding[1]) ? $binding[1] : 1, isset($binding[2]) ? $binding[2] : 10);
116 + }
27 117 }
28 118
29 - function get_post($post_id) {
30 - $post = get_post($post_id);
119 + # Fired from the plugin's uninstall hook; this module owns its own table.
120 + public function clearConfig() {
121 + $this->db->dropBVTable(BVWPActLog::$actlog_table);
122 + }
123 +
124 + function option($key) {
125 + return isset($this->config[$key]) && is_array($this->config[$key]) ? $this->config[$key] : array();
126 + }
127 +
128 + function watching($hook) {
129 + return in_array($hook, $this->option('hooks'), true);
130 + }
131 +
132 + # Everything captured by name (post types, comment types, setting keys) is
133 + # selected from one server list of the same shape: allow mode captures only
134 + # the listed names, deny mode everything except them.
135 + function selected($list, $name) {
136 + $list = $this->option($list);
137 + $listed = $this->listed($list, $name);
138 + return isset($list['mode']) && $list['mode'] === 'deny' ? !$listed : $listed;
139 + }
140 +
141 + function listed($list, $name) {
142 + if (!is_string($name)) return false;
143 + if (isset($list['names']) && in_array($name, (array) $list['names'], true)) return true;
144 + foreach (isset($list['patterns']) ? (array) $list['patterns'] : array() as $pattern) {
145 + if (is_string($pattern) && WPRHelper::safePregMatch($pattern, $name)) return true;
146 + }
147 + return false;
148 + }
149 +
150 + /*
151 + * Every hook goes through this wrapper so that a WordPress or plugin API change
152 + * that breaks one handler can never break the site. Filters get their first
153 + * argument back untouched when the handler fails or the event is ignored.
154 + */
155 + function listen($hook, $handler, $accepted_args = 1, $priority = 10) {
156 + $self = $this;
157 + add_filter($hook, function() use ($self, $handler) {
158 + $args = func_get_args();
159 + $passthrough = isset($args[0]) ? $args[0] : null;
160 + try {
161 + $result = call_user_func_array(array($self, $handler), $args);
162 + return is_null($result) ? $passthrough : $result;
163 + } catch (Throwable $e) {
164 + return $passthrough;
165 + } catch (Exception $e) {
166 + # PHP 5 does not define Throwable, so keep the legacy exception path safe.
167 + return $passthrough;
168 + }
169 + }, $priority, $accepted_args);
170 + }
171 +
172 + /* ---------- snapshots ---------- */
173 +
174 + function get_post($post_id, $post = null, $with_details = null) {
175 + $post = is_null($post) ? get_post($post_id) : $post;
176 + $with_details = is_null($with_details) ? !empty($this->config['capture_details']) : $with_details;
31 177 $data = array('id' => $post_id);
32 - if (!empty($post)) {
33 - $data['title'] = $post->post_title;
34 - $data['status'] = $post->post_status;
35 - $data['type'] = $post->post_type;
36 - $data['url'] = get_permalink($post_id);
37 - $data['date'] = $post->post_date;
178 + if (empty($post))
179 + return $data;
180 + $data['title'] = $post->post_title;
181 + $data['status'] = $post->post_status;
182 + $data['type'] = $post->post_type;
183 + $data['parent_id'] = intval($post->post_parent);
184 + $data['url'] = get_permalink($post);
185 + $data['date'] = $post->post_date;
186 + # The reason describes site policy, not this call: identity-only snapshots
187 + # (comment context, settings, deletions) carry no reason on a site with details on.
188 + if (!in_array($post->post_type, array('post', 'page'), true)) {
189 + $data['detail_omission_reason'] = 'unsupported';
190 + } elseif (empty($this->config['capture_details'])) {
191 + $data['detail_omission_reason'] = 'disabled';
192 + } elseif ($with_details) {
193 + $data['slug'] = $post->post_name;
194 + $data['author_id'] = intval($post->post_author);
195 + $data['modified_date'] = $post->post_modified;
196 + $data['excerpt'] = $post->post_excerpt;
197 + $this->add_content($data, 'content', $post->post_content);
38 198 }
39 199 return $data;
40 200 }
41 201
42 - function get_comment($comment_id) {
43 - $comment = get_comment($comment_id);
44 - $data = array('id' => $comment_id);
45 - if (!empty($comment)) {
46 - $data['author'] = $comment->comment_author;
47 - $data['post_id'] = $comment->comment_post_ID;
48 - }
202 + function add_content(&$data, $key, $value) {
203 + $value = is_scalar($value) ? (string) $value : '';
204 + if (strlen($value) < self::CONTENT_MAX_BYTES)
205 + $data[$key] = $value;
206 + else
207 + $data[$key . '_omission_reason'] = 'too_large';
208 + }
209 +
210 + function get_comment($comment) {
211 + $data = array('id' => intval($comment->comment_ID), 'author' => $comment->comment_author, 'post_id' => $comment->comment_post_ID);
212 + if (!empty($this->config['capture_details']))
213 + $this->add_content($data, 'body', $comment->comment_content);
49 214 return $data;
50 215 }
51 216
52 217 function get_term($term_id) {
@@ -51,9 +216,9 @@
51 216
52 217 function get_term($term_id) {
53 218 $term = get_term($term_id);
54 219 $data = array('id' => $term_id);
55 - if (!empty($term)) {
220 + if (!empty($term) && !is_wp_error($term)) {
56 221 $data['name'] = $term->name;
57 222 $data['slug'] = $term->slug;
58 223 $data['taxonomy'] = $term->taxonomy;
59 224 }
@@ -59,54 +224,28 @@
59 224 }
60 225 return $data;
61 226 }
62 227
63 - function get_user($user_id) {
64 - $user = get_userdata($user_id);
65 - $data = array('id' => $user_id);
228 + function get_user($user, $user_id = 0) {
229 + $user = is_object($user) ? $user : get_userdata($user);
230 + $data = array('id' => !empty($user) && isset($user->ID) ? $user->ID : $user_id);
66 231 if (!empty($user)) {
67 232 $data['username'] = $user->user_login;
68 233 $data['email'] = $user->user_email;
69 - $data['role'] = $user->roles;
234 + $data['role'] = isset($user->roles) ? $user->roles : array();
70 235 }
71 236 return $data;
72 237 }
73 238
74 - function get_blog($blog_id) {
75 - $blog = get_blog_details($blog_id);
76 - $data = array('id' => $blog_id);
77 - if (!empty($blog)) {
78 - $data['name'] = $blog->blogname;
79 - $data['url'] = $blog->path;
239 + function get_order($id, $order) {
240 + $data = array('id' => $id);
241 + if (is_object($order)) {
242 + $data['title'] = 'Order #' . $order->get_order_number();
243 + $data['status'] = $order->get_status('edit');
80 244 }
81 245 return $data;
82 246 }
83 247
84 - function wc_get_attribute($attribute_id, $attribute_data = null) {
85 - $data = array('id' => $attribute_id);
86 - if (!is_null($attribute_data) && is_array($attribute_data)) {
87 - $data['name'] = $attribute_data['attribute_label'];
88 - $data['slug'] = $attribute_data['attribute_name'];
89 - } else {
90 - $attribute = wc_get_attribute($attribute_id);
91 - if (!empty($attribute)) {
92 - $data['name'] = $attribute->name;
93 - $data['slug'] = substr($attribute->slug, 3);
94 - }
95 - }
96 - return $data;
97 - }
98 -
99 - function wc_get_tax_rate($tax_rate_id, $tax_rate) {
100 - $data = array('id' => $tax_rate_id);
101 - if (!empty($tax_rate)) {
102 - $data['name'] = array_key_exists('tax_rate_name', $tax_rate) ? $tax_rate['tax_rate_name'] : '';
103 - $data['country'] = array_key_exists('tax_rate_country', $tax_rate) ? $tax_rate['tax_rate_country'] : '';
104 - $data['rate'] = array_key_exists('tax_rate', $tax_rate) ? $tax_rate['tax_rate'] : '';
105 - }
106 - return $data;
107 - }
108 -
109 248 function get_ip($ipHeader) {
110 249 $ip = '127.0.0.1';
111 250 if ($ipHeader && is_array($ipHeader)) {
112 251 if (array_key_exists($ipHeader['hdr'], $_SERVER)) {
@@ -128,14 +267,21 @@
128 267
129 268 return $ip;
130 269 }
131 270
132 - function add_activity($event_data) {
271 + /* ---------- storage ---------- */
272 +
273 + function add_activity($event_data, $user = null) {
274 + $event_data['schema_version'] = 2;
275 + $event_data['hook'] = current_filter();
276 + $serialized = $this->fit($event_data);
277 + if ($serialized === false)
278 + return false;
133 279 if (!function_exists('wp_get_current_user')) {
134 280 @include_once(ABSPATH . "wp-includes/pluggable.php");
135 281 }
136 282
137 - $user = wp_get_current_user();
283 + $user = $user ? $user : wp_get_current_user();
138 284 $values = array();
139 285 if (!empty($user)) {
140 286 $values["user_id"] = $user->ID;
141 287 $values["username"] = $user->user_login;
@@ -141,402 +287,427 @@
141 287 $values["username"] = $user->user_login;
142 288 }
143 289 $values["request_id"] = $this->request_id;
144 290 $values["site_id"] = get_current_blog_id();
145 - $values["ip"] = $this->get_ip($this->ip_header);
146 - $values["event_type"] = current_filter();
147 - $values["event_data"] = maybe_serialize($event_data);
291 + $ip_header = isset($this->config['ip_header']) ? $this->config['ip_header'] : false;
292 + $values["ip"] = substr($this->get_ip($ip_header), 0, self::IP_MAX_LENGTH);
293 + $values["event_type"] = 'bv_activity';
294 + $values["event_data"] = $serialized;
148 295 $values["time"] = time();
149 - $this->db->replaceIntoBVTable(BVWPActLog::$actlog_table, $values);
296 + return $this->db->replaceIntoBVTable(BVWPActLog::$actlog_table, $values) !== false;
150 297 }
151 298
152 - function is_key_ignored($ignored_keys, $value) {
153 - $is_ignored = false;
154 - if (array_key_exists("post_types_regex", $ignored_keys)) {
155 - foreach ($ignored_keys['post_types_regex'] as $val) {
156 - if (WPRHelper::safePregMatch($val, $value)) {
157 - return true;
158 - }
299 + # Content is dropped whole, never truncated, when the row would exceed the cap.
300 + # Returns the serialized row, or false when even identity alone does not fit.
301 + function fit($event_data) {
302 + $serialized = maybe_serialize($event_data);
303 + if (strlen($serialized) <= self::EVENT_DATA_MAX_BYTES)
304 + return $serialized;
305 + foreach ($event_data as $entity => $snapshot) {
306 + if (!is_array($snapshot)) continue;
307 + foreach (array('content', 'excerpt', 'body') as $key) {
308 + if (!isset($snapshot[$key])) continue;
309 + unset($snapshot[$key]);
310 + $snapshot[$key . '_omission_reason'] = 'too_large';
159 311 }
312 + $event_data[$entity] = $snapshot;
160 313 }
161 - if (array_key_exists("post_types", $ignored_keys)) {
162 - foreach ($ignored_keys['post_types'] as $val) {
163 - if ($val == $value) {
164 - return true;
165 - }
314 + $serialized = maybe_serialize($event_data);
315 + return strlen($serialized) <= self::EVENT_DATA_MAX_BYTES ? $serialized : false;
316 + }
317 +
318 + /* ---------- posts ---------- */
319 +
320 + function skip_post($post) {
321 + return !$post || $post->post_status === 'auto-draft' || !$this->selected('post_types', $post->post_type);
322 + }
323 +
324 + # Names of the standard fields that differ are recorded as evidence; the
325 + # snapshots omit content the server must not see, so they cannot show it.
326 + const POST_FIELDS = array('post_title', 'post_content', 'post_excerpt', 'post_status', 'post_name', 'post_author',
327 + 'post_parent', 'post_password', 'menu_order', 'comment_status', 'ping_status', 'post_date');
328 +
329 + function post_saved_handler($id, $post, $update, $before) {
330 + if ($this->skip_post($post)) return;
331 + $data = array('post' => $this->get_post($id, $post), 'updated' => (bool) $update);
332 + if ($before) {
333 + $data['before'] = $this->get_post($id, $before);
334 + $data['changed_fields'] = array();
335 + foreach (self::POST_FIELDS as $field) {
336 + if ((isset($before->$field) ? $before->$field : null) != (isset($post->$field) ? $post->$field : null)) $data['changed_fields'][] = $field;
166 337 }
338 + if ($update && empty($data['changed_fields'])) return;
167 339 }
168 - return $is_ignored;
340 + $this->add_activity($data);
169 341 }
170 342
171 - function user_login_handler($user_login, $user) {
172 - $event_data = array("user" => $this->get_user($user->ID));
173 - $this->add_activity($event_data);
343 + function post_handler($post_id) {
344 + $post = get_post($post_id);
345 + if (!$this->skip_post($post)) $this->add_activity(array('post' => $this->get_post($post_id, $post, false)));
174 346 }
175 347
176 - function user_logout_handler($user_id) {
177 - $user = $this->get_user($user_id);
178 - $event_data = array("user" => $user);
179 - $this->add_activity($event_data);
348 + function post_deleted_handler($id, $post) {
349 + if ($this->watching('woocommerce_delete_order') && $this->is_stored_as_post($post)) return $this->order_post_handler($id, $post);
350 + if ($this->skip_post($post)) return;
351 + $this->add_activity(array('post' => $this->get_post($id, $post, false)));
180 352 }
181 353
182 - function password_reset_handler($user, $new_pass) {
183 - if (!empty($user)) {
184 - $event_data = array("user" => $this->get_user($user->ID));
185 - $this->add_activity($event_data);
186 - }
354 + /* ---------- comments ---------- */
355 +
356 + function skip_comment($comment) {
357 + return !$comment || !$this->selected('comment_types', $comment->comment_type);
187 358 }
188 359
189 - function comment_handler($comment_id) {
190 - $comment = $this->get_comment($comment_id);
191 - $post = $this->get_post($comment['post_id']);
192 - $event_data = array(
193 - "comment" => $comment,
194 - "post" => $post
195 - );
196 - $this->add_activity($event_data);
360 + function comment_handler($comment_id, $comment = null) {
361 + $comment = $comment ? $comment : get_comment($comment_id);
362 + if ($this->skip_comment($comment)) return;
363 + $this->add_activity(array(
364 + 'comment' => $this->get_comment($comment),
365 + 'post' => $this->get_post($comment->comment_post_ID, null, false)
366 + ));
197 367 }
198 368
199 - function comment_status_changed_handler($new_status, $old_status, $comment) {
200 - $post = $this->get_post($comment->comment_post_ID);
201 - $event_data = array(
202 - "comment" => $this->get_comment($comment->comment_ID),
203 - "post" => $post,
204 - "old_status" => $old_status,
205 - "new_status" => $new_status
206 - );
207 - $this->add_activity($event_data);
369 + function comment_status_handler($new_status, $old_status, $comment) {
370 + if ($new_status === $old_status || $old_status === 'new' || $this->skip_comment($comment)) return;
371 + $this->add_activity(array(
372 + 'comment' => $this->get_comment($comment),
373 + 'post' => $this->get_post($comment->comment_post_ID, null, false),
374 + 'old_status' => $old_status,
375 + 'new_status' => $new_status
376 + ));
208 377 }
209 378
210 - function post_handler($post_id) {
211 - $post = $this->get_post($post_id);
212 - if ($this->is_key_ignored($this->ignored_events, $post["type"]))
213 - return;
214 - $event_data = array();
215 - if (!isset($post["type"])) {
216 - $event_data["post"] = $post;
217 - } elseif ($post["type"] === "product") {
218 - $event_data["product"] = $post;
219 - } elseif ($post["type"] === "shop_order") {
220 - $event_data["order"] = $post;
221 - } else {
222 - $event_data["post"] = $post;
223 - }
224 - $this->add_activity($event_data);
379 + /* ---------- terms, users, roles ---------- */
380 +
381 + function term_handler($term_id) {
382 + $this->add_activity(array('term' => $this->get_term($term_id)));
225 383 }
226 384
227 - function post_saved_handler($post_id, $post, $update) {
228 - $post = $this->get_post($post_id);
229 - if ($this->is_key_ignored($this->ignored_events, $post["type"]))
230 - return;
231 - $event_data = array();
232 - if (!isset($post["type"])) {
233 - $event_data["post"] = $post;
234 - } elseif ($post["type"] === "product") {
235 - $event_data["product"] = $post;
236 - } elseif ($post["type"] === "shop_order") {
237 - $event_data["order"] = $post;
238 - } else {
239 - $event_data["post"] = $post;
240 - }
241 - $event_data["updated"] = $update;
242 - $this->add_activity($event_data);
385 + function term_deleted_handler($id, $tt_id, $taxonomy, $term) {
386 + $this->add_activity(array('term' => array('id' => $id, 'name' => $term->name, 'slug' => $term->slug, 'taxonomy' => $taxonomy)));
243 387 }
244 388
245 - function term_handler($term_id) {
246 - $term = $this->get_term($term_id);
247 - $event_data = array(
248 - "term" => $term,
249 - );
250 - $this->add_activity($event_data);
389 + function user_handler($user_id) {
390 + $this->add_activity(array('user' => $this->get_user($user_id)));
251 391 }
252 392
253 - function term_updation_handler($data, $term_id) {
254 - $term = $this->get_term($term_id);
255 - $event_data = array(
256 - "term" => $term,
257 - "new_term" => $data
258 - );
259 - $this->add_activity($event_data);
260 - return $data;
393 + function user_updated_handler($user_id, $old_userdata) {
394 + $this->add_activity(array('old_user' => $this->get_user($old_userdata, $user_id), 'user' => $this->get_user($user_id)));
261 395 }
262 396
263 - function term_deletion_handler($term_id) {
264 - $event_data = array(
265 - "term" => array("id" => $term_id)
266 - );
267 - $this->add_activity($event_data);
397 + function user_deleted_handler($id, $reassign, $user) {
398 + $this->add_activity(array('user' => $this->get_user($user, $id)));
268 399 }
269 400
270 - function user_handler($user_id) {
271 - $user = $this->get_user($user_id);
272 - $event_data = array(
273 - "user" => $user,
274 - );
275 - $this->add_activity($event_data);
401 + function role_handler($id, $role, $old_roles = null) {
402 + $this->add_activity(array('user' => $this->get_user($id), 'role' => $role, 'old_roles' => $old_roles));
276 403 }
277 404
278 - function user_update_handler($user_id, $old_userdata) {
279 - $new_userdata = $this->get_user($user_id);
280 - $event_data = array(
281 - "old_user" => $this->get_user($old_userdata->ID),
282 - "user" => $new_userdata,
283 - );
284 - $this->add_activity($event_data);
405 + /* ---------- sessions ---------- */
406 +
407 + # Login and logout use the hook's subject as the actor: WordPress has already
408 + # cleared the current user when the logout hook fires.
409 + function login_handler($user_login, $user) {
410 + $this->add_activity(array('user' => $this->get_user($user)), $user);
285 411 }
286 412
287 - function plugin_action_handler($plugin) {
288 - $event_data = array("plugin" => $plugin);
289 - $this->add_activity($event_data);
413 + function logout_handler($user_id = 0) {
414 + $user = get_userdata($user_id);
415 + $this->add_activity(array('user' => $this->get_user($user, $user_id)), $user);
290 416 }
291 417
292 - function theme_action_handler($theme_name) {
293 - $event_data = array("theme" => $theme_name);
294 - $this->add_activity($event_data);
418 + function password_reset_handler($user) {
419 + if (!empty($user)) $this->add_activity(array('user' => $this->get_user($user)));
295 420 }
296 421
297 - function mu_handler($blog_id) {
298 - $blog = $this->get_blog($blog_id);
299 - $event_data = array(
300 - "blog" => $blog
301 - );
302 - $this->add_activity($event_data);
422 + /* ---------- plugins, themes, sites ---------- */
423 +
424 + function plugin_handler($plugin) {
425 + $this->add_activity(array('plugin' => $plugin));
303 426 }
304 427
305 - function mu_site_handler($blog) {
306 - $event_data = array(
307 - "blog" => $this->get_blog($blog->blog_id)
308 - );
309 - $this->add_activity($event_data);
428 + function plugin_deleted_handler($plugin, $deleted) {
429 + if ($deleted) $this->add_activity(array('plugin' => $plugin));
310 430 }
311 431
312 - function woocommerce_attribute_created_handler($attribute_id, $attribute_data) {
313 - $event_data = array(
314 - "attribute" => $this->wc_get_attribute($attribute_id, $attribute_data)
315 - );
316 - $this->add_activity($event_data);
432 + function theme_handler($theme_name) {
433 + $this->add_activity(array('theme' => $theme_name));
317 434 }
318 435
319 - function woocommerce_attribute_handler($attribute_id) {
320 - $event_data = array(
321 - "attribute" => $this->wc_get_attribute($attribute_id)
322 - );
323 - $this->add_activity($event_data);
436 + function theme_deleted_handler($stylesheet, $deleted) {
437 + if ($deleted) $this->add_activity(array('theme' => $stylesheet));
324 438 }
325 439
326 - function woocommerce_tax_rate_handler($tax_rate_id, $tax_rate) {
327 - $event_data = array(
328 - "tax_rate" => $this->wc_get_tax_rate($tax_rate_id, $tax_rate)
329 - );
330 - $this->add_activity($event_data);
440 + function get_site($site) {
441 + $data = array('id' => intval($site->blog_id), 'domain' => $site->domain, 'path' => $site->path, 'url' => 'https://' . $site->domain . $site->path);
442 + foreach (array('public', 'archived', 'spam', 'deleted') as $flag)
443 + $data[$flag] = isset($site->$flag) ? (bool) intval($site->$flag) : null;
444 + if (isset($site->blogname)) $data['name'] = $site->blogname;
445 + return $data;
331 446 }
332 447
333 - function woocommerce_tax_rate_deleted_handler($tax_rate_id) {
334 - $event_data = array(
335 - "tax_rate" => array("id" => $tax_rate_id)
336 - );
337 - $this->add_activity($event_data);
448 + function site_handler($site) {
449 + $this->add_activity(array('blog' => $this->get_site($site)));
338 450 }
339 451
340 - function woocommerce_grant_product_download_access_handler($data) {
341 - $event_data = array(
342 - "download_id" => $data['download_id'],
343 - "user_id" => $data['user_id'],
344 - "order_id" => $data['order_id'],
345 - "product_id" => $data['product_id']
346 - );
347 - $this->add_activity($event_data);
452 + function site_updated_handler($site, $before) {
453 + $data = array('blog' => $this->get_site($site), 'before' => $this->get_site($before));
454 + if ($data['blog'] != $data['before']) $this->add_activity($data);
348 455 }
349 456
350 - function woocommerce_revoke_access_to_product_download_handler($download_id, $product_id, $order_id) {
351 - $event_data = array(
352 - "download_id" => $download_id,
353 - "product_id" => $product_id,
354 - "order_id" => $order_id
355 - );
356 - $this->add_activity($event_data);
457 + /* ---------- settings: values ride along, bounded ---------- */
458 +
459 + # Like content, an oversized value is omitted whole and the omission recorded.
460 + function add_value(&$data, $key, $value) {
461 + if (is_null($value)) return;
462 + if (strlen(is_scalar($value) ? (string) $value : maybe_serialize($value)) < self::CONTENT_MAX_BYTES)
463 + $data[$key] = $value;
464 + else
465 + $data[$key . '_omission_reason'] = 'too_large';
357 466 }
358 467
359 - function woocommerce_shipping_zone_method_handler($instance_id, $method_id, $zone_id) {
360 - $event_data = array(
361 - "instance_id" => absint ($instance_id),
362 - "method_id" => $method_id,
363 - "zone_id" => $zone_id
364 - );
365 - $this->add_activity($event_data);
468 + function post_meta_handler($meta_ids, $post_id, $key, $value = null) {
469 + if (!$this->selected('post_meta', $key)) return;
470 + $post = get_post($post_id);
471 + if ($this->skip_post($post)) return;
472 + $data = array('key' => $key, 'post' => $this->get_post($post_id, $post, false));
473 + $this->add_value($data, current_filter() === 'deleted_post_meta' ? 'old_value' : 'new_value', $value);
474 + $this->add_activity($data);
366 475 }
367 476
368 - function get_plugin_update_data($plugins) {
369 - $data = array();
370 - if (!empty($plugins) && defined('WP_PLUGIN_DIR')) {
371 - foreach ($plugins as $plugin) {
372 - $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
373 - $title = isset($plugin_data['Name']) ? $plugin_data['Name'] : '';
374 - $version = isset($plugin_data['Version']) ? $plugin_data['Version'] : '';
375 - $install_data = array('title' => $title, 'version' => $version);
376 - array_push($data, $install_data);
377 - }
477 + function user_meta_handler($meta_ids, $user_id, $key, $value = null) {
478 + if (!$this->selected('user_meta', $key)) return;
479 + $data = array('key' => $key, 'user' => $this->get_user($user_id));
480 + $this->add_value($data, current_filter() === 'deleted_user_meta' ? 'old_value' : 'new_value', $value);
481 + $this->add_activity($data);
482 + }
483 +
484 + # Options and multisite network options (sitemeta). Transients are cache,
485 + # never activity, whatever the server selected. updated_option passes
486 + # (key, old, new) but update_site_option (key, new, old); the add hooks pass
487 + # only the new value and the delete hooks none.
488 + function option_handler($key, $first = null, $second = null) {
489 + if (strpos($key, '_transient_') === 0 || strpos($key, '_site_transient_') === 0) return;
490 + if (!$this->selected('option', $key)) return;
491 + $old = $new = null;
492 + switch (current_filter()) {
493 + case 'updated_option': list($old, $new) = array($first, $second); break;
494 + case 'update_site_option': list($new, $old) = array($first, $second); break;
495 + case 'added_option': case 'add_site_option': $new = $first; break;
378 496 }
379 - return $data;
497 + $data = array('key' => $key);
498 + $this->add_value($data, 'old_value', $old);
499 + $this->add_value($data, 'new_value', $new);
500 + $this->add_activity($data);
380 501 }
381 502
382 - function get_theme_update_data($themes) {
383 - $data = array();
384 - if (!empty($themes)) {
385 - foreach ($themes as $theme) {
386 - $theme_data = wp_get_theme($theme);
387 - $title = isset($theme_data['Name']) ? $theme_data['Name'] : '';
388 - $version = isset($theme_data['Version']) ? $theme_data['Version'] : '';
389 - $install_data = array('title' => $title, 'version' => $version);
390 - array_push($data, $install_data);
391 - }
392 - }
393 - return $data;
503 + /* ---------- upgrades ---------- */
504 +
505 + function is_relative_package_path($file) {
506 + return is_string($file) && $file !== '' && $file[0] !== '/' && strpos($file, '..') === false;
394 507 }
395 508
396 - function get_plugin_install_data($upgrader) {
397 - $data = array();
398 - if ($upgrader->bulk != "1") {
399 - $plugin_data = $upgrader->new_plugin_data;
400 - $title = isset($plugin_data['Name']) ? $plugin_data['Name'] : '';
401 - $version = isset($plugin_data['Version']) ? $plugin_data['Version'] : '';
402 - $install_data = array('title' => $title, 'version' => $version);
403 - array_push($data, $install_data);
509 + # Remember successful installs only. Automatic updates can still roll back
510 + # after this hook, so the activity is emitted at final completion.
511 + function package_result_handler($result, $extra) {
512 + if (!is_array($extra)) return $result;
513 + foreach (array('plugin', 'theme') as $type) {
514 + if (!isset($extra[$type]) || !$this->is_relative_package_path($extra[$type])) continue;
515 + if (is_array($result)) $this->installed_packages[$type][$extra[$type]] = true;
516 + else unset($this->installed_packages[$type][$extra[$type]]);
404 517 }
405 - return $data;
518 + return $result;
406 519 }
407 520
408 - function get_theme_install_data($upgrader) {
409 - $data = array();
410 - $theme_data = $upgrader->new_theme_data;
411 - $title = isset($theme_data['Name']) ? $theme_data['Name'] : '';
412 - $version = isset($theme_data['Version']) ? $theme_data['Version'] : '';
413 - $install_data = array('title' => $title, 'version' => $version);
414 - array_push($data, $install_data);
415 - return $data;
521 + function completed_package($type, $file) {
522 + if (!isset($this->installed_packages[$type][$file])) return;
523 + unset($this->installed_packages[$type][$file]);
524 + $item = $type === 'plugin' ? $this->get_plugin_data($file) : $this->get_theme_data($file);
525 + $this->add_activity(array('action' => 'update', 'type' => $type, $type . 's' => array($item)));
416 526 }
417 527
418 - function get_update_data($options) {
419 - $event_data = array('action' => 'update');
420 - if ($options['type'] === 'plugin') {
421 - $event_data['type'] = 'plugin';
422 - if (array_key_exists("plugin", $options)) {
423 - $options['plugins'] = array($options['plugin']);
424 - unset($options['plugin']);
528 + function upgrade_handler($upgrader, $data) {
529 + if (!is_array($data) || !isset($data['action'], $data['type']) || !in_array($data['type'], array('plugin', 'theme'), true))
530 + return;
531 + $type = $data['type'];
532 + if ($data['action'] === 'update') {
533 + if (isset($upgrader->skin) && $upgrader->skin instanceof Automatic_Upgrader_Skin) return;
534 + $files = isset($data[$type]) ? array($data[$type]) : (isset($data[$type . 's']) ? (array) $data[$type . 's'] : array());
535 + foreach ($files as $file) {
536 + if ($this->is_relative_package_path($file)) $this->completed_package($type, $file);
425 537 }
426 - $event_data['plugins'] = $this->get_plugin_update_data($options['plugins']);
538 + } elseif ($data['action'] === 'install' && isset($upgrader->result) && is_array($upgrader->result)) {
539 + $headers = $type === 'plugin' ? (isset($upgrader->new_plugin_data) ? $upgrader->new_plugin_data : array()) : (isset($upgrader->new_theme_data) ? $upgrader->new_theme_data : array());
540 + $this->add_activity(array('action' => 'install', 'type' => $type, $type . 's' => array($this->title_and_version($headers))));
427 541 }
428 - else if ($options['type'] === 'theme') {
429 - $event_data['type'] = 'theme';
430 - if (array_key_exists("theme", $options)) {
431 - $options['themes'] = array($options['theme']);
432 - unset($options['theme']);
542 + }
543 +
544 + function automatic_updates_handler($results) {
545 + foreach (array('plugin', 'theme') as $type) {
546 + foreach (isset($results[$type]) ? $results[$type] : array() as $result) {
547 + $file = isset($result->item->$type) ? $result->item->$type : null;
548 + if (!$this->is_relative_package_path($file)) continue;
549 + if ($result->result && !is_wp_error($result->result)) $this->completed_package($type, $file);
550 + unset($this->installed_packages[$type][$file]);
433 551 }
434 - $event_data['themes'] = $this->get_theme_update_data($options['themes']);
435 552 }
436 - return $event_data;
437 553 }
438 554
439 - function get_install_data($upgrader, $options) {
440 - $event_data = array('action' => 'install');
441 - if ($options['type'] === 'plugin') {
442 - $event_data['type'] = 'plugin';
443 - $event_data['plugins'] = $this->get_plugin_install_data($upgrader);
444 - }
445 - else if ($options['type'] === 'theme') {
446 - $event_data['type'] = 'theme';
447 - $event_data['themes'] = $this->get_theme_install_data($upgrader);
448 - }
449 - return $event_data;
555 + function core_upgrade_handler($new_wp_version) {
556 + global $wp_version;
557 + $this->add_activity(array('type' => 'core', 'wp_core' => array('prev_version' => $wp_version, 'new_version' => $new_wp_version)));
450 558 }
451 559
452 - function upgrade_handler($upgrader, $data) {
453 - $event_data = array();
454 - if (isset($data['action'])) {
455 - if ($data['action'] === 'update') {
456 - if ('core' === $data['type']) {
457 - return;
458 - }
459 - $event_data = $this->get_update_data($data);
460 - } else if ($data['action'] === 'install') {
461 - $event_data = $this->get_install_data($upgrader, $data);
462 - }
463 - }
464 - $this->add_activity($event_data);
560 + function title_and_version($headers) {
561 + return array(
562 + 'title' => isset($headers['Name']) ? $headers['Name'] : '',
563 + 'version' => isset($headers['Version']) ? $headers['Version'] : ''
564 + );
465 565 }
466 566
467 - function core_upgrade_handler($new_wp_version) {
468 - global $wp_version;
469 - $event_data = array();
470 - $event_data['type'] = 'core';
471 - $event_data['wp_core'] = array('prev_version' => $wp_version, 'new_version' => $new_wp_version);
472 - $this->add_activity($event_data);
567 + function get_plugin_data($file) {
568 + if (!function_exists('get_plugin_data'))
569 + @include_once(ABSPATH . 'wp-admin/includes/plugin.php');
570 + $headers = defined('WP_PLUGIN_DIR') && function_exists('get_plugin_data') ? get_plugin_data(WP_PLUGIN_DIR . '/' . $file, false, false) : array();
571 + $item = $this->title_and_version($headers);
572 + $item['file'] = $file;
573 + $item['slug'] = strpos($file, '/') !== false ? dirname($file) : preg_replace('/\.php$/', '', $file);
574 + return $item;
473 575 }
474 576
475 - /* ADDING ACTION AND LISTENERS FOR SENSING EVENTS. */
476 - public function add_actions_and_listeners() {
477 - /* SENSORS FOR POST AND PAGE CHANGES */
478 - add_action('pre_post_update', array($this, 'post_handler'));
479 - add_action('save_post', array($this, 'post_saved_handler'), 10, 3);
480 - add_action('post_stuck', array($this, 'post_handler'));
481 - add_action('post_unstuck', array($this, 'post_handler'));
482 - add_action('delete_post', array($this, 'post_handler'));
577 + function get_theme_data($stylesheet) {
578 + # Read installed headers without depending on the theme object cache.
579 + $headers = get_file_data(get_theme_root($stylesheet) . '/' . $stylesheet . '/style.css', array('Name' => 'Theme Name', 'Version' => 'Version'));
580 + $item = $this->title_and_version($headers);
581 + $item['stylesheet'] = $stylesheet;
582 + return $item;
583 + }
483 584
484 - /* SENSORS FOR COMMENTS */
485 - add_action('comment_post', array($this, 'comment_handler'));
486 - add_action('edit_comment', array($this, 'comment_handler'));
487 - add_action('transition_comment_status', array($this, 'comment_status_changed_handler'), 10, 3);
585 + /* ---------- WooCommerce products and orders ---------- */
488 586
489 - /* SENSORS FOR TAG AND CATEGORY CHANGES */
490 - add_action('create_term', array($this, 'term_handler'));
491 - add_action('pre_delete_term', array($this, 'term_handler'));
492 - add_action('delete_term', array($this, 'term_deletion_handler'));
493 - add_filter('wp_update_term_data', array($this, 'term_updation_handler'), 10, 2);
587 + # Woo exposes pending changes only before save, so the change set is held
588 + # until the matching after-save hook confirms the write.
589 + function remember_changes($object, $allowed) {
590 + $fields = array_values(array_intersect(array_keys($object->get_changes()), $allowed));
591 + $this->pending_changes[spl_object_hash($object)] = array('new' => !$object->get_id(), 'fields' => $fields);
592 + }
494 593
495 - /* SENSORS FOR USER CHANGES*/
496 - add_action('user_register', array($this, 'user_handler'));
497 - add_action('wpmu_new_user', array($this, 'user_handler'));
498 - add_action('profile_update', array($this, 'user_update_handler'), 10, 2);
499 - add_action('delete_user', array($this, 'user_handler'));
500 - add_action('wpmu_delete_user', array($this, 'user_handler'));
594 + function take_changes($object) {
595 + $key = spl_object_hash($object);
596 + $changes = isset($this->pending_changes[$key]) ? $this->pending_changes[$key] : null;
597 + unset($this->pending_changes[$key]);
598 + return $changes && $object->get_id() ? $changes : null;
599 + }
501 600
502 - /* SENSORS FOR PLUGIN AND THEME*/
503 - add_action('activate_plugin', array($this, 'plugin_action_handler'));
504 - add_action('deactivate_plugin', array($this, 'plugin_action_handler'));
505 - add_action('switch_theme', array($this, 'theme_action_handler'));
601 + function product_before_save_handler($product) {
602 + $this->remember_changes($product, $this->option('product_fields'));
603 + }
506 604
507 - /* SENSORS FOR MULTISITE CHANGES */
508 - add_action('wp_insert_site', array($this, 'mu_site_handler'));
509 - add_action('archive_blog', array($this, 'mu_handler'));
510 - add_action('unarchive_blog', array( $this, 'mu_handler'));
511 - add_action('activate_blog', array($this, 'mu_handler'));
512 - add_action('deactivate_blog', array($this, 'mu_handler'));
513 - add_action('wp_delete_site', array($this, 'mu_site_handler'));
605 + function product_saved_handler($product) {
606 + $changes = $this->take_changes($product);
607 + if (!$changes || (!$changes['new'] && empty($changes['fields']))) return;
608 + $post = array(
609 + 'id' => $product->get_id(), 'title' => $product->get_name('edit'), 'status' => $product->get_status('edit'),
610 + 'type' => $product->is_type('variation') ? 'product_variation' : 'product', 'parent_id' => $product->get_parent_id('edit')
611 + );
612 + $this->add_activity(array('post' => $post, 'updated' => !$changes['new'], 'changed_fields' => $changes['fields']));
613 + }
514 614
515 - /* SENSORS USER ACTIONS AT FRONTEND */
516 - add_action('wp_login', array($this, 'user_login_handler'), 10, 2);
517 - add_action('wp_logout', array( $this, 'user_logout_handler'), 5, 1);
518 - add_action('password_reset', array( $this, 'password_reset_handler'), 10, 2);
615 + function order_before_save_handler($order) {
616 + $this->remember_changes($order, $this->option('order_fields'));
617 + }
519 618
520 - /* SENSOR FOR PLUGIN, THEME, WPCORE UPGRADES */
521 - add_action('upgrader_process_complete', array($this, 'upgrade_handler'), 10, 2);
522 - add_action('_core_updated_successfully', array($this, 'core_upgrade_handler'), 10, 1);
619 + function order_saved_handler($order) {
620 + $changes = $this->take_changes($order);
621 + if (!$changes || $changes['new'] || empty($changes['fields'])) return;
622 + $this->add_activity(array('order' => $this->get_order($order->get_id(), $order), 'changed_fields' => $changes['fields']));
623 + }
523 624
524 - /* SENSORS FOR WOOCOMMERCE EVENTS */
525 - add_action('woocommerce_attribute_added', array($this, 'woocommerce_attribute_created_handler'), 10, 2);
526 - add_action('woocommerce_attribute_updated', array($this, 'woocommerce_attribute_handler'), 10, 1);
527 - add_action('woocommerce_before_attribute_delete', array($this, 'woocommerce_attribute_handler'), 10, 1);
528 - add_action('woocommerce_attribute_deleted', array($this, 'woocommerce_attribute_handler'), 10, 1);
625 + function order_handler($id, $order = null) {
626 + $order = $order ? $order : (function_exists('wc_get_order') ? wc_get_order($id) : null);
627 + $this->add_activity(array('order' => $this->get_order($id, $order)));
628 + }
529 629
530 - add_action('woocommerce_tax_rate_added', array($this, 'woocommerce_tax_rate_handler'), 10, 2);
531 - add_action('woocommerce_tax_rate_deleted', array($this, 'woocommerce_tax_rate_deleted_handler'), 10, 1);
532 - add_action('woocommerce_tax_rate_updated', array($this, 'woocommerce_tax_rate_handler'), 10, 2);
630 + function order_deleted_handler($id) {
631 + $this->add_activity(array('order' => array('id' => $id)));
632 + }
533 633
534 - add_action('woocommerce_grant_product_download_access', array($this, 'woocommerce_grant_product_download_access_handler'), 10, 1);
535 - add_action('woocommerce_ajax_revoke_access_to_product_download', array($this, 'woocommerce_revoke_access_to_product_download_handler'), 10, 3);
634 + function order_status_handler($id, $from, $to, $order) {
635 + $this->add_activity(array('order' => $this->get_order($id, $order), 'old_status' => $from, 'new_status' => $to));
636 + }
536 637
537 - add_action('woocommerce_shipping_zone_method_added', array($this, 'woocommerce_shipping_zone_method_handler'), 10, 3);
538 - add_action('woocommerce_shipping_zone_method_status_toggled', array($this, 'woocommerce_shipping_zone_method_handler'), 10, 3);
539 - add_action('woocommerce_shipping_zone_method_deleted', array($this, 'woocommerce_shipping_zone_method_handler'), 10, 3);
638 + # Orders kept in the posts table are observed through WordPress trash,
639 + # restore and delete, only while the order hooks are watched. With HPOS
640 + # these posts are only synchronization copies.
641 + function is_stored_as_post($post) {
642 + return $post && $post->post_type === 'shop_order' &&
643 + class_exists('\Automattic\WooCommerce\Utilities\OrderUtil') &&
644 + !\Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled();
540 645 }
646 +
647 + function order_post_handler($id, $post = null) {
648 + $post = is_object($post) ? $post : get_post($id);
649 + if (!$this->is_stored_as_post($post)) return;
650 + $this->add_activity(array('order' => array('id' => $id, 'title' => 'Order #' . $id, 'status' => preg_replace('/^wc-/', '', $post->post_status))));
651 + }
652 +
653 + /* ---------- WooCommerce configuration ---------- */
654 +
655 + function get_attribute($attribute_id, $attribute_data) {
656 + $data = array('id' => $attribute_id);
657 + if (is_array($attribute_data)) {
658 + $data['name'] = isset($attribute_data['attribute_label']) ? $attribute_data['attribute_label'] : '';
659 + $data['slug'] = isset($attribute_data['attribute_name']) ? $attribute_data['attribute_name'] : '';
660 + }
661 + return $data;
662 + }
663 +
664 + function attribute_handler($attribute_id, $attribute_data) {
665 + $this->add_activity(array('attribute' => $this->get_attribute($attribute_id, $attribute_data)));
666 + }
667 +
668 + function attribute_updated_handler($attribute_id, $attribute_data, $old_slug = null) {
669 + $attribute = $this->get_attribute($attribute_id, $attribute_data);
670 + if (is_string($old_slug)) $attribute['old_slug'] = $old_slug;
671 + $this->add_activity(array('attribute' => $attribute));
672 + }
673 +
674 + function attribute_deleted_handler($attribute_id, $name = null, $taxonomy = null) {
675 + $attribute = array('id' => $attribute_id);
676 + if (is_string($name)) $attribute['name'] = $name;
677 + if (is_string($taxonomy)) $attribute['slug'] = preg_replace('/^pa_/', '', $taxonomy);
678 + $this->add_activity(array('attribute' => $attribute));
679 + }
680 +
681 + function tax_rate_handler($tax_rate_id, $tax_rate) {
682 + $data = array('id' => $tax_rate_id);
683 + if (is_array($tax_rate)) {
684 + $data['name'] = isset($tax_rate['tax_rate_name']) ? $tax_rate['tax_rate_name'] : '';
685 + $data['country'] = isset($tax_rate['tax_rate_country']) ? $tax_rate['tax_rate_country'] : '';
686 + $data['rate'] = isset($tax_rate['tax_rate']) ? $tax_rate['tax_rate'] : '';
687 + }
688 + $this->add_activity(array('tax_rate' => $data));
689 + }
690 +
691 + function tax_rate_deleted_handler($tax_rate_id) {
692 + $this->add_activity(array('tax_rate' => array('id' => $tax_rate_id)));
693 + }
694 +
695 + function shipping_method_handler($instance_id, $method_id, $zone_id, $enabled = null) {
696 + $data = array('instance_id' => absint($instance_id), 'method_id' => $method_id, 'zone_id' => $zone_id);
697 + if (!is_null($enabled)) $data['enabled'] = (bool) $enabled;
698 + $this->add_activity($data);
699 + }
700 +
701 + function download_granted_handler($data) {
702 + if (!is_array($data)) return;
703 + $event_data = array();
704 + foreach (array('download_id', 'user_id', 'order_id', 'product_id') as $key)
705 + $event_data[$key] = isset($data[$key]) ? $data[$key] : '';
706 + $this->add_activity($event_data);
707 + }
708 +
709 + function download_revoked_handler($download_id, $product_id, $order_id, $permission_id = null) {
710 + $this->add_activity(array('download_id' => $download_id, 'product_id' => $product_id, 'order_id' => $order_id, 'permission_id' => $permission_id));
711 + }
541 712 }
542 -endif;
713 +endif;