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 +541 -353 4.876.76 View file →
@@ -2,14 +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;
97 + public $request_id;
98 + public $config;
99 + public $pending_changes = array();
100 + public $installed_packages = array();
12 101
13 102 public function __construct($db, $settings, $info, $config) {
14 103 $this->db = $db;
15 104 $this->settings = $settings;
@@ -14,36 +103,115 @@
14 103 $this->db = $db;
15 104 $this->settings = $settings;
16 105 $this->bvinfo = $info;
17 106 $this->request_id = WPRInfo::getRequestID();
18 - $this->ip_header = array_key_exists('ip_header', $config) ? $config['ip_header'] : false;
19 - $this->ignored_events = array_key_exists('ignored_events', $config) ? $config['ignored_events'] : array();
107 + $this->config = is_array($config) ? $config : array();
20 108 }
21 109
22 110 function init() {
23 - $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 + }
24 117 }
25 118
26 - function get_post($post_id) {
27 - $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;
28 177 $data = array('id' => $post_id);
29 - if (!empty($post)) {
30 - $data['title'] = $post->post_title;
31 - $data['status'] = $post->post_status;
32 - $data['type'] = $post->post_type;
33 - $data['url'] = get_permalink($post_id);
34 - $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);
35 198 }
36 199 return $data;
37 200 }
38 201
39 - function get_comment($comment_id) {
40 - $comment = get_comment($comment_id);
41 - $data = array('id' => $comment_id);
42 - if (!empty($comment)) {
43 - $data['author'] = $comment->comment_author;
44 - $data['post_id'] = $comment->comment_post_ID;
45 - }
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);
46 214 return $data;
47 215 }
48 216
49 217 function get_term($term_id) {
@@ -48,9 +216,9 @@
48 216
49 217 function get_term($term_id) {
50 218 $term = get_term($term_id);
51 219 $data = array('id' => $term_id);
52 - if (!empty($term)) {
220 + if (!empty($term) && !is_wp_error($term)) {
53 221 $data['name'] = $term->name;
54 222 $data['slug'] = $term->slug;
55 223 $data['taxonomy'] = $term->taxonomy;
56 224 }
@@ -56,71 +224,45 @@
56 224 }
57 225 return $data;
58 226 }
59 227
60 - function get_user($user_id) {
61 - $user = get_userdata($user_id);
62 - $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);
63 231 if (!empty($user)) {
64 232 $data['username'] = $user->user_login;
65 233 $data['email'] = $user->user_email;
66 - $data['role'] = $user->roles;
234 + $data['role'] = isset($user->roles) ? $user->roles : array();
67 235 }
68 236 return $data;
69 237 }
70 238
71 - function get_blog($blog_id) {
72 - $blog = get_blog_details($blog_id);
73 - $data = array('id' => $blog_id);
74 - if (!empty($blog)) {
75 - $data['name'] = $blog->blogname;
76 - $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');
77 244 }
78 245 return $data;
79 246 }
80 247
81 - function wc_get_attribute($attribute_id, $attribute_data = null) {
82 - $data = array('id' => $attribute_id);
83 - if (!is_null($attribute_data) && is_array($attribute_data)) {
84 - $data['name'] = $attribute_data['attribute_label'];
85 - $data['slug'] = $attribute_data['attribute_name'];
86 - } else {
87 - $attribute = wc_get_attribute($attribute_id);
88 - if (!empty($attribute)) {
89 - $data['name'] = $attribute->name;
90 - $data['slug'] = substr($attribute->slug, 3);
91 - }
92 - }
93 - return $data;
94 - }
95 -
96 - function wc_get_tax_rate($tax_rate_id, $tax_rate) {
97 - $data = array('id' => $tax_rate_id);
98 - if (!empty($tax_rate)) {
99 - $data['name'] = array_key_exists('tax_rate_name', $tax_rate) ? $tax_rate['tax_rate_name'] : '';
100 - $data['country'] = array_key_exists('tax_rate_country', $tax_rate) ? $tax_rate['tax_rate_country'] : '';
101 - $data['rate'] = array_key_exists('tax_rate', $tax_rate) ? $tax_rate['tax_rate'] : '';
102 - }
103 - return $data;
104 - }
105 -
106 248 function get_ip($ipHeader) {
107 249 $ip = '127.0.0.1';
108 250 if ($ipHeader && is_array($ipHeader)) {
109 251 if (array_key_exists($ipHeader['hdr'], $_SERVER)) {
110 - $_ips = preg_split("/(,| |\t)/", $_SERVER[$ipHeader['hdr']]);
252 + $_ips = preg_split("/(,| |\t)/", WPRHelper::getRawParam('SERVER', $ipHeader['hdr']));
111 253 if (array_key_exists(intval($ipHeader['pos']), $_ips)) {
112 254 $ip = $_ips[intval($ipHeader['pos'])];
113 255 }
114 256 }
115 257 } else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
116 - $ip = $_SERVER['REMOTE_ADDR'];
258 + $ip = WPRHelper::getRawParam('SERVER', 'REMOTE_ADDR');
117 259 }
118 260
119 261 $ip = trim($ip);
120 - if (preg_match('/^\[([0-9a-fA-F:]+)\](:[0-9]+)$/', $ip, $matches)) {
262 + if (WPRHelper::safePregMatch('/^\[([0-9a-fA-F:]+)\](:[0-9]+)$/', $ip, $matches)) {
121 263 $ip = $matches[1];
122 - } elseif (preg_match('/^([0-9.]+)(:[0-9]+)$/', $ip, $matches)) {
264 + } elseif (WPRHelper::safePregMatch('/^([0-9.]+)(:[0-9]+)$/', $ip, $matches)) {
123 265 $ip = $matches[1];
124 266 }
125 267
126 268 return $ip;
@@ -125,14 +267,21 @@
125 267
126 268 return $ip;
127 269 }
128 270
129 - 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;
130 279 if (!function_exists('wp_get_current_user')) {
131 280 @include_once(ABSPATH . "wp-includes/pluggable.php");
132 281 }
133 282
134 - $user = wp_get_current_user();
283 + $user = $user ? $user : wp_get_current_user();
135 284 $values = array();
136 285 if (!empty($user)) {
137 286 $values["user_id"] = $user->ID;
138 287 $values["username"] = $user->user_login;
@@ -138,388 +287,427 @@
138 287 $values["username"] = $user->user_login;
139 288 }
140 289 $values["request_id"] = $this->request_id;
141 290 $values["site_id"] = get_current_blog_id();
142 - $values["ip"] = $this->get_ip($this->ip_header);
143 - $values["event_type"] = current_filter();
144 - $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;
145 295 $values["time"] = time();
146 - $this->db->replaceIntoBVTable(BVWPActLog::$actlog_table, $values);
296 + return $this->db->replaceIntoBVTable(BVWPActLog::$actlog_table, $values) !== false;
147 297 }
148 298
149 - function is_key_ignored($ignored_keys, $value) {
150 - $is_ignored = false;
151 - if (array_key_exists("post_types_regex", $ignored_keys)) {
152 - foreach ($ignored_keys['post_types_regex'] as $val) {
153 - if (preg_match($val, $value)) {
154 - return true;
155 - }
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';
156 311 }
312 + $event_data[$entity] = $snapshot;
157 313 }
158 - if (array_key_exists("post_types", $ignored_keys)) {
159 - foreach ($ignored_keys['post_types'] as $val) {
160 - if ($val == $value) {
161 - return true;
162 - }
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;
163 337 }
338 + if ($update && empty($data['changed_fields'])) return;
164 339 }
165 - return $is_ignored;
340 + $this->add_activity($data);
166 341 }
167 342
168 - function user_login_handler($user_login, $user) {
169 - $event_data = array("user" => $this->get_user($user->ID));
170 - $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)));
171 346 }
172 347
173 - function user_logout_handler($user_id) {
174 - $user = $this->get_user($user_id);
175 - $event_data = array("user" => $user);
176 - $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)));
177 352 }
178 353
179 - function password_reset_handler($user, $new_pass) {
180 - if (!empty($user)) {
181 - $event_data = array("user" => $this->get_user($user->ID));
182 - $this->add_activity($event_data);
183 - }
354 + /* ---------- comments ---------- */
355 +
356 + function skip_comment($comment) {
357 + return !$comment || !$this->selected('comment_types', $comment->comment_type);
184 358 }
185 359
186 - function comment_handler($comment_id) {
187 - $comment = $this->get_comment($comment_id);
188 - $post = $this->get_post($comment['post_id']);
189 - $event_data = array(
190 - "comment" => $comment,
191 - "post" => $post
192 - );
193 - $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 + ));
194 367 }
195 368
196 - function comment_status_changed_handler($new_status, $old_status, $comment) {
197 - $post = $this->get_post($comment->comment_post_ID);
198 - $event_data = array(
199 - "comment" => $this->get_comment($comment->comment_ID),
200 - "post" => $post,
201 - "old_status" => $old_status,
202 - "new_status" => $new_status
203 - );
204 - $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 + ));
205 377 }
206 378
207 - function post_handler($post_id) {
208 - $post = $this->get_post($post_id);
209 - if ($this->is_key_ignored($this->ignored_events, $post["type"]))
210 - return;
211 - $event_data = array();
212 - if ($post["type"] === "product") {
213 - $event_data["product"] = $post;
214 - } elseif ($post["type"] === "shop_order") {
215 - $event_data["order"] = $post;
216 - } else {
217 - $event_data["post"] = $post;
218 - }
219 - $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)));
220 383 }
221 384
222 - function post_saved_handler($post_id, $post, $update) {
223 - $post = $this->get_post($post_id);
224 - if ($this->is_key_ignored($this->ignored_events, $post["type"]))
225 - return;
226 - $event_data = array();
227 - if ($post["type"] === "product") {
228 - $event_data["product"] = $post;
229 - } elseif ($post["type"] === "shop_order") {
230 - $event_data["order"] = $post;
231 - } else {
232 - $event_data["post"] = $post;
233 - }
234 - $event_data["updated"] = $update;
235 - $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)));
236 387 }
237 388
238 - function term_handler($term_id) {
239 - $term = $this->get_term($term_id);
240 - $event_data = array(
241 - "term" => $term,
242 - );
243 - $this->add_activity($event_data);
389 + function user_handler($user_id) {
390 + $this->add_activity(array('user' => $this->get_user($user_id)));
244 391 }
245 392
246 - function term_updation_handler($data, $term_id) {
247 - $term = $this->get_term($term_id);
248 - $event_data = array(
249 - "term" => $term,
250 - "new_term" => $data
251 - );
252 - $this->add_activity($event_data);
253 - 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)));
254 395 }
255 396
256 - function term_deletion_handler($term_id) {
257 - $event_data = array(
258 - "term" => array("id" => $term_id)
259 - );
260 - $this->add_activity($event_data);
397 + function user_deleted_handler($id, $reassign, $user) {
398 + $this->add_activity(array('user' => $this->get_user($user, $id)));
261 399 }
262 400
263 - function user_handler($user_id) {
264 - $user = $this->get_user($user_id);
265 - $event_data = array(
266 - "user" => $user,
267 - );
268 - $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));
269 403 }
270 404
271 - function user_update_handler($user_id, $old_userdata) {
272 - $new_userdata = $this->get_user($user_id);
273 - $event_data = array(
274 - "old_user" => $this->get_user($old_userdata->ID),
275 - "user" => $new_userdata,
276 - );
277 - $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);
278 411 }
279 412
280 - function plugin_action_handler($plugin) {
281 - $event_data = array("plugin" => $plugin);
282 - $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);
283 416 }
284 417
285 - function theme_action_handler($theme_name) {
286 - $event_data = array("theme" => $theme_name);
287 - $this->add_activity($event_data);
418 + function password_reset_handler($user) {
419 + if (!empty($user)) $this->add_activity(array('user' => $this->get_user($user)));
288 420 }
289 421
290 - function mu_handler($blog_id) {
291 - $blog = $this->get_blog($blog_id);
292 - $event_data = array(
293 - "blog" => $blog
294 - );
295 - $this->add_activity($event_data);
422 + /* ---------- plugins, themes, sites ---------- */
423 +
424 + function plugin_handler($plugin) {
425 + $this->add_activity(array('plugin' => $plugin));
296 426 }
297 427
298 - function mu_site_handler($blog) {
299 - $event_data = array(
300 - "blog" => $this->get_blog($blog->blog_id)
301 - );
302 - $this->add_activity($event_data);
428 + function plugin_deleted_handler($plugin, $deleted) {
429 + if ($deleted) $this->add_activity(array('plugin' => $plugin));
303 430 }
304 431
305 - function woocommerce_attribute_created_handler($attribute_id, $attribute_data) {
306 - $event_data = array(
307 - "attribute" => $this->wc_get_attribute($attribute_id, $attribute_data)
308 - );
309 - $this->add_activity($event_data);
432 + function theme_handler($theme_name) {
433 + $this->add_activity(array('theme' => $theme_name));
310 434 }
311 435
312 - function woocommerce_attribute_handler($attribute_id) {
313 - $event_data = array(
314 - "attribute" => $this->wc_get_attribute($attribute_id)
315 - );
316 - $this->add_activity($event_data);
436 + function theme_deleted_handler($stylesheet, $deleted) {
437 + if ($deleted) $this->add_activity(array('theme' => $stylesheet));
317 438 }
318 439
319 - function woocommerce_tax_rate_handler($tax_rate_id, $tax_rate) {
320 - $event_data = array(
321 - "tax_rate" => $this->wc_get_tax_rate($tax_rate_id, $tax_rate)
322 - );
323 - $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;
324 446 }
325 447
326 - function woocommerce_tax_rate_deleted_handler($tax_rate_id) {
327 - $event_data = array(
328 - "tax_rate" => array("id" => $tax_rate_id)
329 - );
330 - $this->add_activity($event_data);
448 + function site_handler($site) {
449 + $this->add_activity(array('blog' => $this->get_site($site)));
331 450 }
332 451
333 - function woocommerce_grant_product_download_access_handler($data) {
334 - $event_data = array(
335 - "download_id" => $data['download_id'],
336 - "user_id" => $data['user_id'],
337 - "order_id" => $data['order_id'],
338 - "product_id" => $data['product_id']
339 - );
340 - $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);
341 455 }
342 456
343 - function woocommerce_revoke_access_to_product_download_handler($download_id, $product_id, $order_id) {
344 - $event_data = array(
345 - "download_id" => $download_id,
346 - "product_id" => $product_id,
347 - "order_id" => $order_id
348 - );
349 - $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';
350 466 }
351 467
352 - function woocommerce_shipping_zone_method_handler($instance_id, $method_id, $zone_id) {
353 - $event_data = array(
354 - "instance_id" => absint ($instance_id),
355 - "method_id" => $method_id,
356 - "zone_id" => $zone_id
357 - );
358 - $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);
359 475 }
360 476
361 - function get_plugin_update_data($plugins) {
362 - $data = array();
363 - if (!empty($plugins) && defined('WP_PLUGIN_DIR')) {
364 - foreach ($plugins as $plugin) {
365 - $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
366 - $install_data = array('title' => $plugin_data['Name'], 'version' => $plugin_data['Version']);
367 - array_push($data, $install_data);
368 - }
369 - }
370 - return $data;
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);
371 482 }
372 483
373 - function get_theme_update_data($themes) {
374 - $data = array();
375 - if (!empty($themes)) {
376 - foreach ($themes as $theme) {
377 - $theme_data = wp_get_theme($theme);
378 - $install_data = array('title' => $theme_data['Name'], 'version' => $theme_data['Version']);
379 - array_push($data, $install_data);
380 - }
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;
381 496 }
382 - 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);
383 501 }
384 502
385 - function get_plugin_install_data($upgrader) {
386 - $data = array();
387 - if ($upgrader->bulk != "1") {
388 - $plugin_data = $upgrader->new_plugin_data;
389 - $install_data = array('title' => $plugin_data['Name'], 'version' => $plugin_data['Version']);
390 - array_push($data, $install_data);
391 - }
392 - return $data;
393 - }
503 + /* ---------- upgrades ---------- */
394 504
395 - function get_theme_install_data($upgrader) {
396 - $data = array();
397 - $theme_data = $upgrader->new_theme_data;
398 - $install_data = array('title' => $theme_data['Name'], 'version' => $theme_data['Version']);
399 - array_push($data, $install_data);
400 - return $data;
505 + function is_relative_package_path($file) {
506 + return is_string($file) && $file !== '' && $file[0] !== '/' && strpos($file, '..') === false;
401 507 }
402 508
403 - function get_update_data($options) {
404 - $event_data = array('action' => 'update');
405 - if ($options['type'] === 'plugin') {
406 - $event_data['type'] = 'plugin';
407 - if (array_key_exists("plugin", $options)) {
408 - $options['plugins'] = array($options['plugin']);
409 - unset($options['plugin']);
410 - }
411 - $event_data['plugins'] = $this->get_plugin_update_data($options['plugins']);
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]]);
412 517 }
413 - else if ($options['type'] === 'theme') {
414 - $event_data['type'] = 'theme';
415 - if (array_key_exists("theme", $options)) {
416 - $options['themes'] = array($options['theme']);
417 - unset($options['theme']);
418 - }
419 - $event_data['themes'] = $this->get_theme_update_data($options['themes']);
420 - }
421 - return $event_data;
518 + return $result;
422 519 }
423 520
424 - function get_install_data($upgrader, $options) {
425 - $event_data = array('action' => 'install');
426 - if ($options['type'] === 'plugin') {
427 - $event_data['type'] = 'plugin';
428 - $event_data['plugins'] = $this->get_plugin_install_data($upgrader);
429 - }
430 - else if ($options['type'] === 'theme') {
431 - $event_data['type'] = 'theme';
432 - $event_data['themes'] = $this->get_theme_install_data($upgrader);
433 - }
434 - return $event_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)));
435 526 }
436 527
437 528 function upgrade_handler($upgrader, $data) {
438 - $event_data = array();
529 + if (!is_array($data) || !isset($data['action'], $data['type']) || !in_array($data['type'], array('plugin', 'theme'), true))
530 + return;
531 + $type = $data['type'];
439 532 if ($data['action'] === 'update') {
440 - if ('core' === $data['type']) {
441 - return;
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);
442 537 }
443 - $event_data = $this->get_update_data($data);
444 - } else if ($data['action'] === 'install') {
445 - $event_data = $this->get_install_data($upgrader, $data);
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))));
446 541 }
447 - $this->add_activity($event_data);
448 542 }
449 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]);
551 + }
552 + }
553 + }
554 +
450 555 function core_upgrade_handler($new_wp_version) {
451 556 global $wp_version;
452 - $event_data = array();
453 - $event_data['type'] = 'core';
454 - $event_data['wp_core'] = array('prev_version' => $wp_version, 'new_version' => $new_wp_version);
455 - $this->add_activity($event_data);
557 + $this->add_activity(array('type' => 'core', 'wp_core' => array('prev_version' => $wp_version, 'new_version' => $new_wp_version)));
456 558 }
457 559
458 - /* ADDING ACTION AND LISTENERS FOR SENSING EVENTS. */
459 - public function add_actions_and_listeners() {
460 - /* SENSORS FOR POST AND PAGE CHANGES */
461 - add_action('pre_post_update', array($this, 'post_handler'));
462 - add_action('save_post', array($this, 'post_saved_handler'), 10, 3);
463 - add_action('post_stuck', array($this, 'post_handler'));
464 - add_action('post_unstuck', array($this, 'post_handler'));
465 - add_action('delete_post', array($this, 'post_handler'));
560 + function title_and_version($headers) {
561 + return array(
562 + 'title' => isset($headers['Name']) ? $headers['Name'] : '',
563 + 'version' => isset($headers['Version']) ? $headers['Version'] : ''
564 + );
565 + }
466 566
467 - /* SENSORS FOR COMMENTS */
468 - add_action('comment_post', array($this, 'comment_handler'));
469 - add_action('edit_comment', array($this, 'comment_handler'));
470 - add_action('transition_comment_status', array($this, 'comment_status_changed_handler'), 10, 3);
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;
575 + }
471 576
472 - /* SENSORS FOR TAG AND CATEGORY CHANGES */
473 - add_action('create_term', array($this, 'term_handler'));
474 - add_action('pre_delete_term', array($this, 'term_handler'));
475 - add_action('delete_term', array($this, 'term_deletion_handler'));
476 - add_filter('wp_update_term_data', array($this, 'term_updation_handler'), 10, 2);
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 + }
477 584
478 - /* SENSORS FOR USER CHANGES*/
479 - add_action('user_register', array($this, 'user_handler'));
480 - add_action('wpmu_new_user', array($this, 'user_handler'));
481 - add_action('profile_update', array($this, 'user_update_handler'), 10, 2);
482 - add_action('delete_user', array($this, 'user_handler'));
483 - add_action('wpmu_delete_user', array($this, 'user_handler'));
585 + /* ---------- WooCommerce products and orders ---------- */
484 586
485 - /* SENSORS FOR PLUGIN AND THEME*/
486 - add_action('activate_plugin', array($this, 'plugin_action_handler'));
487 - add_action('deactivate_plugin', array($this, 'plugin_action_handler'));
488 - add_action('switch_theme', array($this, 'theme_action_handler'));
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 + }
489 593
490 - /* SENSORS FOR MULTISITE CHANGES */
491 - add_action('wp_insert_site', array($this, 'mu_site_handler'));
492 - add_action('archive_blog', array($this, 'mu_handler'));
493 - add_action('unarchive_blog', array( $this, 'mu_handler'));
494 - add_action('activate_blog', array($this, 'mu_handler'));
495 - add_action('deactivate_blog', array($this, 'mu_handler'));
496 - add_action('wp_delete_site', array($this, 'mu_site_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 + }
497 600
498 - /* SENSORS USER ACTIONS AT FRONTEND */
499 - add_action('wp_login', array($this, 'user_login_handler'), 10, 2);
500 - add_action('wp_logout', array( $this, 'user_logout_handler'), 5, 1);
501 - add_action('password_reset', array( $this, 'password_reset_handler'), 10, 2);
601 + function product_before_save_handler($product) {
602 + $this->remember_changes($product, $this->option('product_fields'));
603 + }
502 604
503 - /* SENSOR FOR PLUGIN, THEME, WPCORE UPGRADES */
504 - add_action('upgrader_process_complete', array($this, 'upgrade_handler'), 10, 2);
505 - add_action('_core_updated_successfully', array($this, 'core_upgrade_handler'), 10, 1);
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 + }
506 614
507 - /* SENSORS FOR WOOCOMMERCE EVENTS */
508 - add_action('woocommerce_attribute_added', array($this, 'woocommerce_attribute_created_handler'), 10, 2);
509 - add_action('woocommerce_attribute_updated', array($this, 'woocommerce_attribute_handler'), 10, 1);
510 - add_action('woocommerce_before_attribute_delete', array($this, 'woocommerce_attribute_handler'), 10, 1);
511 - add_action('woocommerce_attribute_deleted', array($this, 'woocommerce_attribute_handler'), 10, 1);
615 + function order_before_save_handler($order) {
616 + $this->remember_changes($order, $this->option('order_fields'));
617 + }
512 618
513 - add_action('woocommerce_tax_rate_added', array($this, 'woocommerce_tax_rate_handler'), 10, 2);
514 - add_action('woocommerce_tax_rate_deleted', array($this, 'woocommerce_tax_rate_deleted_handler'), 10, 1);
515 - add_action('woocommerce_tax_rate_updated', array($this, 'woocommerce_tax_rate_handler'), 10, 2);
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 + }
516 624
517 - add_action('woocommerce_grant_product_download_access', array($this, 'woocommerce_grant_product_download_access_handler'), 10, 1);
518 - add_action('woocommerce_ajax_revoke_access_to_product_download', array($this, 'woocommerce_revoke_access_to_product_download_handler'), 10, 3);
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 + }
519 629
520 - add_action('woocommerce_shipping_zone_method_added', array($this, 'woocommerce_shipping_zone_method_handler'), 10, 3);
521 - add_action('woocommerce_shipping_zone_method_status_toggled', array($this, 'woocommerce_shipping_zone_method_handler'), 10, 3);
522 - add_action('woocommerce_shipping_zone_method_deleted', array($this, 'woocommerce_shipping_zone_method_handler'), 10, 3);
630 + function order_deleted_handler($id) {
631 + $this->add_activity(array('order' => array('id' => $id)));
523 632 }
633 +
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 + }
637 +
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();
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 + }
524 712 }
525 -endif;
713 +endif;