$product->get_type(), 'catalog_visibility' => $product->get_catalog_visibility(), 'featured' => $product->get_featured(), 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'sku' => $product->get_sku(), 'stock_status' => $product->get_stock_status(), 'stock_quantity' => $product->get_stock_quantity(), 'virtual' => $product->is_virtual() ? 'yes' : 'no', 'downloadable' => $product->is_downloadable() ? 'yes' : 'no', 'weight' => $product->get_weight(), 'length' => $product->get_length(), 'width' => $product->get_width(), 'height' => $product->get_height(), 'backorders' => $product->get_backorders(), 'upsell_ids' => $product->get_upsell_ids(), 'cross_sell_ids' => $product->get_cross_sell_ids(), 'tax_status' => $product->get_tax_status(), 'tax_class' => $product->get_tax_class(), 'low_stock_amount' => $product->get_low_stock_amount(), 'manage_stock' => $product->get_manage_stock(), 'sold_individually' => $product->get_sold_individually(), 'file_names' => array(), 'file_urls' => array(), ); foreach ( $product->get_downloads() as $download ) { array_push( $product_data['file_names'], $download->get_name() ); array_push( $product_data['file_urls'], $download->get_file() ); } return $product_data; } /** * Get the config setting * * @param string $option_name - Option Name. * @return string * * @since 4.6.0 */ public static function get_config( $option_name ) { // If this is multisite AND we have some kind of value, we can return it. if ( is_multisite() && ! empty( get_site_option( 'woocommerce_' . $option_name ) ) ) { // get_site_option is not empty, so lets return it. return get_site_option( 'woocommerce_' . $option_name ); } else { // Otherwise, looking the sites wp_options table, even in multisite. return get_option( 'woocommerce_' . $option_name ); } } /** * Check post type. * * @param stdClass $post - Post. * @return bool * * @since 4.6.0 */ public static function check_woo_commerce( $post ) { switch ( $post->post_type ) { case 'product': return true; default: return false; } } /** * Get editor link. * * @param stdClass $post - The post. * @return array $editor_link - Name and value link. * * @since 4.6.0 */ public static function get_editor_link( $post ) { $post_type = ''; if ( ! isset( $post ) ) { $editor_link = array( 'name' => '', // Meta key. 'value' => '', // Meta value. ); return $editor_link; } if ( \method_exists( $post, 'get_id' ) ) { $post_type = get_post_type( $post->get_id() ); } else { $post_type = get_post_type( $post->ID ); } // Meta value key. if ( is_a( $post, '\Automattic\WooCommerce\Admin\Overrides\Order' ) || 'shop_order' === $post_type ) { $name = 'EditorLinkOrder'; } elseif ( 'shop_coupon' === $post_type ) { $name = 'EditorLinkCoupon'; } else { $name = 'EditorLinkProduct'; } // Get editor post link URL. if ( is_a( $post, '\Automattic\WooCommerce\Admin\Overrides\Order' ) ) { $value = $post->get_edit_order_url(); } else { $product_id = ''; if ( \method_exists( $post, 'get_id' ) ) { $product_id = $post->get_id(); } else { $product_id = $post->ID; } $value = get_edit_post_link( $product_id ); } // If the URL is not empty then set values. if ( ! empty( $value ) ) { $editor_link = array( 'name' => $name, // Meta key. 'value' => $value, // Meta value. ); } else { $product_id = ''; if ( \method_exists( $post, 'get_id' ) ) { $product_id = $post->get_id(); } else { $product_id = $post->ID; } // Get post object. $post = get_post( $product_id ); // Set URL action. if ( 'revision' === $post_type ) { $action = ''; } else { $action = '&action=edit'; } // Get and check post type object. $post_type_object = get_post_type_object( $post_type ); if ( ! $post_type_object ) { return; } // Set editor link manually. if ( $post_type_object->_edit_link ) { $link = \network_admin_url( sprintf( $post_type_object->_edit_link . $action, $product_id ) ); } else { $link = ''; } $editor_link = array( 'name' => $name, // Meta key. 'value' => $link, // Meta value. ); } return $editor_link; } /** * Alerts for editing of product post type for WooCommerce. * * @param \WP_Post $product - Product post type. * * @since 4.6.0 */ public static function editing_product( $product ) { // Check product post type. if ( 'product' !== $product->post_type ) { return $product; } if ( is_user_logged_in() && is_admin() ) { // Filter $_SERVER array for security. $server_array = filter_input_array( INPUT_SERVER ); $current_path = isset( $server_array['SCRIPT_NAME'] ) ? \sanitize_text_field( \wp_unslash( $server_array['SCRIPT_NAME'] ) ) . '?post=' . $product->ID : false; if ( ! empty( $server_array['HTTP_REFERER'] ) && ! empty( $current_path ) && strpos( $server_array['HTTP_REFERER'], $current_path ) !== false ) { // Ignore this if we were on the same page so we avoid double audit entries. return $product; } if ( ! empty( $product->post_title ) ) { $event = 9072; if ( ( ! Alert_Manager::was_triggered( $event ) || ! Alert_Manager::was_triggered_recently( 9000 ) ) & ! Alert_Manager::was_triggered( 9001 ) ) { $editor_link = self::get_editor_link( $product ); Alert_Manager::trigger_event_if( $event, array( 'PostID' => esc_attr( $product->ID ), 'SKU' => esc_attr( WooCommerce_Sensor::get_product_sku( $product->ID ) ), 'ProductStatus' => sanitize_text_field( $product->post_status ), 'PostStatus' => sanitize_text_field( $product->post_status ), 'ProductTitle' => sanitize_text_field( $product->post_title ), 'ProductUrl' => get_permalink( $product->ID ), $editor_link['name'] => $editor_link['value'], ), array( __CLASS__, 'must_not_be_fresh_post' ) ); } } } return $product; } /** * Check if is a fresh post. * * @return bool - Has triggered or not. * * @since 4.6.0 */ public static function must_not_be_fresh_post() { if ( Alert_Manager::will_or_has_triggered( 9000 ) ) { return false; } return true; } /** * Get Currency symbol. * * @param string $currency - Currency (default: ''). * @return string * * @since 4.6.0 */ public static function get_currency_symbol( $currency = '' ) { $symbols = \get_woocommerce_currency_symbols(); $currency_symbol = isset( $symbols[ $currency ] ) ? $symbols[ $currency ] : ''; return $currency_symbol; } /** * WooCommerce Order Status Changed Event. * * @since 3.3.1 * * @param integer $order_id – Order ID. * @param string $status_from – Status changing from. * @param string $status_to – Status changing to. * @param WC_Order $order – WooCommerce order object. * * @since 4.6.0 */ public static function event_order_status_changed( $order_id, $status_from, $status_to, $order ) { $edit_link = self::get_editor_link( $order ); $event_data = array( 'OrderID' => \esc_attr( $order_id ), 'OrderTitle' => \sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ) ), 'OrderStatus' => \wc_get_order_status_name( $status_to ), 'OrderStatusSlug' => \sanitize_text_field( $status_to ), 'PostStatus' => 'wc-' . $status_to, $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event_if( 9036, $event_data, array( __CLASS__, 'must_not_contain_refund' ) ); } /** * Report additional products being added to an order. * * @param int $item_id - Item ID. * @param object $item - Item data. * @param int $order_id - Order ID. * @return void * * @since 4.6.0 */ public static function event_order_items_added( $item_id, $item, $order_id ) { if ( empty( $order_id ) ) { $order_id = (int) wc_get_order_id_by_order_item_id( $item_id ); if ( ! isset( $order_id ) || intval( $order_id ) <= 0 ) { return; } } if ( $item instanceof \WC_Order_Item_Product ) { $product = $item->get_product(); if ( $product instanceof \WC_Product ) { $order = wc_get_order( $order_id ); $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'ProductTitle' => $product->get_name(), 'ProductID' => $product->get_id(), 'SKU' => WooCommerce_Sensor::get_product_sku( $product->get_id() ), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'added', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event_if( 9130, $event_data, array( __CLASS__, 'ignore_if_new_order' ) ); } } if ( $item instanceof \WC_Order_Item_Fee ) { $order = wc_get_order( $order_id ); if ( $order ) { $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'FeeAmount' => $item->get_amount(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'added', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event_if( 9132, $event_data, array( __CLASS__, 'ignore_if_new_order' ) ); } } if ( $item instanceof \WC_Order_Item_Coupon ) { $order = wc_get_order( $order_id ); if ( $order ) { $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'CouponName' => $item->get_name(), 'CouponValue' => $item->get_discount(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'added', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event_if( 9134, $event_data, array( __CLASS__, 'ignore_if_new_order' ) ); } } if ( $item instanceof \WC_Order_Item_Tax ) { $order = wc_get_order( $order_id ); if ( $order ) { $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'TaxName' => $item->get_name(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'added', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event_if( 9135, $event_data, array( __CLASS__, 'ignore_if_new_order' ) ); } } if ( $item instanceof \WC_Order_Item_Shipping ) { $order = wc_get_order( $order_id ); if ( $order ) { $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'added', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event_if( 9137, $event_data, array( __CLASS__, 'ignore_if_new_order' ) ); } } } /** * Report additional products being removed from an an order. * * @param int $item_id - Item ID. * * @return void * * @since 4.6.0 */ public static function event_order_items_removed( $item_id ) { $order_id = wc_get_order_id_by_order_item_id( $item_id ); $order = wc_get_order( $order_id ); if ( $order instanceof \WC_Order || $order instanceof \WC_Order_Refund ) { if ( isset( $order->get_items()[ $item_id ] ) ) { $item = $order->get_items()[ $item_id ]; if ( ! $item instanceof \WC_Order_Item_Product ) { return; } $product = $item->get_product(); if ( $product instanceof WC_Product ) { $product_name = $product->get_name(); $product_id = $product->get_id(); } else { $product_name = $item->get_name(); $product_id = $item->get_product_id(); } $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'ProductTitle' => $product_name, 'ProductID' => $product_id, 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'removed', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9130, $event_data ); } if ( isset( $order->get_fees()[ $item_id ] ) ) { $item = $order->get_fees()[ $item_id ]; $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'FeeAmount' => $item->get_amount(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'removed', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9132, $event_data ); } if ( isset( $order->get_coupons()[ $item_id ] ) ) { $item = $order->get_coupons()[ $item_id ]; $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'CouponName' => $item->get_name(), 'CouponValue' => $item->get_discount(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'removed', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9134, $event_data ); } if ( isset( $order->get_taxes()[ $item_id ] ) ) { $item = $order->get_taxes()[ $item_id ]; $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'TaxName' => $item->get_name(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'removed', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9135, $event_data ); } if ( isset( $order->get_shipping_methods()[ $item_id ] ) ) { $item = $order->get_shipping_method()[ $item_id ]; $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), 'EventType' => 'removed', $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9137, $event_data ); } } } /** * Detect changes within a WC order. * * @param int $order_id - Order ID being changed. * @param array $items - Items being changed. * @return void * * @since 4.6.0 */ public static function event_order_items_quantity_changed( $order_id, $items ) { $order = wc_get_order( $order_id ); $output = array(); if ( isset( $_POST['items'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( is_array( $_POST['items'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $data = array_map( 'sanitize_text_field', wp_unslash( $_POST['items'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing foreach ( $data as $key => $value ) { $items['order_item_id'][] = $key; $output[ 'order_item_qty' . $key ] = $value; } } else { parse_str( sanitize_text_field( wp_unslash( $_POST['items'] ) ), $output ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } } else { $output = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } if ( ! isset( $items['order_item_id'] ) ) { return; } foreach ( $items['order_item_id'] as $item_id ) { if ( isset( $order->get_items()[ $item_id ] ) ) { $item = $order->get_items()[ $item_id ]; if ( $item instanceof \WC_Order_Item_Product ) { $product = $item->get_product(); $old_quantity = $item->get_quantity(); $order = wc_get_order( $order_id ); $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); if ( $product instanceof WC_Product ) { $product_name = $product->get_name(); $product_id = $product->get_id(); } else { $product_name = $item->get_name(); $product_id = $item->get_product_id(); } if ( isset( $output[ 'order_item_qty' . $item_id ] ) && intval( $output[ 'order_item_qty' . $item_id ] ) !== intval( $old_quantity ) ) { $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'ProductID' => $product_id, 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'NewQuantity' => $output[ 'order_item_qty' . $item_id ], 'OldQuantity' => $old_quantity, 'ProductTitle' => $product_name, 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9131, $event_data ); } } } if ( isset( $order->get_fees()[ $item_id ] ) ) { $item = $order->get_fees()[ $item_id ]; if ( $item instanceof \WC_Order_Item_Fee ) { $order = wc_get_order( $order_id ); $order_post = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_post ); if ( isset( $output[ 'line_total' . $item_id ] ) && intval( $output[ 'line_total' . $item_id ] ) !== intval( $item->get_amount() ) ) { $event_data = array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order ), 'FeeAmount' => $output[ 'line_total' . $item_id ], 'OldFeeAmount' => $item->get_amount(), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), $edit_link['name'] => $edit_link['value'], ); Alert_Manager::trigger_event( 9133, $event_data ); } } } } } /** * Checks if event 9041 has triggered or if it will * trigger. * * @since 3.3.1.1 * * @return boolean */ public static function must_not_contain_refund() { if ( Alert_Manager::will_or_has_triggered( 9041 ) ) { return false; } return true; } /** * Checks if event 9035 has triggered or if it will * trigger. * * @since 1.3.0 * * @return boolean */ public static function ignore_if_new_order() { if ( Alert_Manager::will_or_has_triggered( 9035 ) ) { return false; } return true; } /** * Checks if event 9041 or 9036 has triggered or if it will * trigger. * * @since 4.1.3 * * @return boolean */ public static function must_not_contain_refund_or_modification() { if ( Alert_Manager::has_triggered( 9040 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9041 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9036 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9038 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9120 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9120 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9130 ) || Alert_Manager::was_triggered_recently( 9130 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9131 ) || Alert_Manager::was_triggered_recently( 9131 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9132 ) || Alert_Manager::was_triggered_recently( 9132 ) ) { return false; } // if ( Alert_Manager::will_or_has_triggered( 9133 ) || Alert_Manager::was_triggered_recently( 9133 ) ) { // return false; // } if ( Alert_Manager::will_or_has_triggered( 9134 ) || Alert_Manager::was_triggered_recently( 9134 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9137 ) || Alert_Manager::was_triggered_recently( 9137 ) ) { return false; } return true; } /** * WooCommerce Order Modified. * * @since 3.3.1 * * @param integer $order_id – Order id. * @param WP_Post $oldorder – Old order WP_Post object. * @param WP_Post $neworder – New order WP_Post object. */ public static function check_order_modify_change( $order_id, $oldorder, $neworder ) { if ( 'trash' === $neworder->post_status ) { return 0; } // Dont fire if we know an item was added/removed recently. if ( Alert_Manager::was_triggered_recently( 9120 ) || Alert_Manager::was_triggered_recently( 9130 ) || Alert_Manager::was_triggered_recently( 9131 ) || Alert_Manager::was_triggered_recently( 9132 ) | Alert_Manager::was_triggered_recently( 9133 ) || Alert_Manager::was_triggered_recently( 9134 ) || Alert_Manager::was_triggered_recently( 9135 ) || Alert_Manager::was_triggered_recently( 9137 ) ) { return; } $order = \wc_get_order( $order_id ); $new_order = \wc_get_order( $neworder->ID ); $items = $order->get_items( array( 'fee' ) ); self::event_order_items_quantity_changed( $order_id, $items ); $difference = self::order_recursive_array_diff( (array) WooCommerce_Sensor::get_old_order(), (array) $order ); if ( ! empty( $difference ) ) { // Get editor link. $edit_link = self::get_editor_link( $oldorder ); // Set event data. $event_data = array( 'OrderID' => \esc_attr( $order_id ), 'OrderTitle' => \sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order_id ) ), 'OrderStatus' => \wc_get_order_status_name( $new_order->get_status() ), 'OrderStatusSlug' => \sanitize_text_field( $new_order->get_status() ), 'PostStatus' => 'wc-' . $order->get_status(), $edit_link['name'] => $edit_link['value'], ); // Log event. Alert_Manager::trigger_event_if( 9040, $event_data, array( __CLASS__, 'must_not_contain_refund_or_modification' ) ); } } /** * Little helper to recursively compare arrays, which is this case is WC order details. * * @param array $old_details - Array of old details. * @param array $new_details - Array of new details. * * @return array * * @since 4.6.0 */ public static function order_recursive_array_diff( $old_details, $new_details ) { $r = array(); $ignored_keys = array( 'date_modified', ); foreach ( $old_details as $k => $v ) { if ( in_array( $k, $ignored_keys, true ) ) { continue; } if ( array_key_exists( $k, $new_details ) ) { if ( is_array( $v ) ) { $rad = self::order_recursive_array_diff( $v, (array) $new_details[ $k ] ); if ( count( $rad ) ) { $r[ $k ] = $rad; } } elseif ( $v !== $new_details[ $k ] ) { $r[ $k ] = $v; } } else { $r[ $k ] = $v; } } return $r; } /** * WooCommerce Bulk Order Modified. * * @since 3.3.1 * * @param array $order_ids – Bulk Order ids. * @param string $action – Bulk action to perform. * @return array */ public static function event_bulk_order_actions( $order_ids, $action ) { // Check for remove personal data action. if ( 'remove_personal_data' === $action ) { foreach ( $order_ids as $order_id ) { $order_post = wc_get_order( $order_id ); // Get editor link. $edit_link = self::get_editor_link( $order_post ); // Log event. Alert_Manager::trigger_event_if( 9040, array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order_id ) ), 'OrderStatus' => \wc_get_order_status_name( $order_post->get_status() ), 'OrderStatusSlug' => $order_post->get_status(), 'PostStatus' => 'wc-' . $order_post->get_status(), $edit_link['name'] => $edit_link['value'], ), array( __CLASS__, 'must_not_contain_refund_or_modification' ) ); } } return $order_ids; } /** * WooCommerce Order Refunded. * * @since 3.3.1 * * @param integer $order_id – Order ID. * @param integer $refund_id – Refund ID. */ public static function event_order_refunded( $order_id, $refund_id ) { // Get order post object. $order_obj = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_obj ); $order = wc_get_order( $order_id ); $customer_user_id = $order->get_user_id(); $username = esc_html__( 'Guest', 'wp-security-audit-log' ); if ( 0 !== $customer_user_id ) { $user = get_user_by( 'id', $customer_user_id ); $username = $user->user_login; } $date = $order->get_date_created(); $date_format = get_option( 'date_format' ); $time_format = get_option( 'time_format' ); $created_date = $date->date( $date_format . ' ' . $time_format ); $refund_amount = ''; $refund_reason = ''; $currency = get_woocommerce_currency_symbol( $order->get_currency() ); $refunds = $order->get_refunds(); foreach ( $refunds as $refund_object ) { $id = $refund_object->get_id(); if ( $id === $refund_id ) { $amount = $refund_object->get_amount(); $refund_amount = ( $amount ) ? $amount : '0.00'; $reason = $refund_object->get_reason(); $refund_reason = ( $reason ) ? $reason : esc_html__( 'None supplied', 'wp-security-audit-log' ); } } Alert_Manager::trigger_event( 9041, array( 'OrderID' => esc_attr( $order_id ), 'RefundID' => esc_attr( $refund_id ), 'CustomerUser' => $username, 'RefundedAmount' => $currency . $refund_amount, 'Reason' => $refund_reason, 'OrderDate' => $created_date, 'OrderTitle' => sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order_id ) ), 'OrderStatus' => \wc_get_order_status_name( $order_obj->get_status() ), 'OrderStatusSlug' => $order_obj->get_status(), 'PostStatus' => 'wc-' . $order_obj->get_status(), $edit_link['name'] => $edit_link['value'], ) ); } /** * WooCommerce Order Refunded. * * @since 3.3.1 * * @param integer $refund_id – Refund ID. * @param integer $order_id – Order ID. */ public static function event_order_refund_removed( $refund_id, $order_id ) { // Get order post object. $order_obj = wc_get_order( $order_id ); $edit_link = self::get_editor_link( $order_obj ); $order = wc_get_order( $order_id ); $customer_user_id = $order->get_user_id(); $username = esc_html__( 'Guest', 'wp-security-audit-log' ); if ( 0 !== $customer_user_id ) { $user = get_user_by( 'id', $customer_user_id ); $username = $user->user_login; } $date = $order->get_date_created(); $date_format = get_option( 'date_format' ); $time_format = get_option( 'time_format' ); $created_date = $date->date( $date_format . ' ' . $time_format ); Alert_Manager::trigger_event( 9136, array( 'OrderID' => esc_attr( $order_id ), 'RefundID' => esc_attr( $refund_id ), 'CustomerUser' => $username, 'OrderDate' => $created_date, 'OrderTitle' => sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order_id ) ), 'OrderStatus' => \wc_get_order_status_name( $order_obj->post_status ), 'OrderStatusSlug' => sanitize_text_field( $order_obj->post_status ), 'PostStatus' => 'wc-' . $order_obj->get_status(), $edit_link['name'] => $edit_link['value'], ) ); } /** * WooCommerce New Attribute Event. * * @since 3.3.1 * * @param int $attr_id - Attribute ID. * @param array $attr_data - Attribute data array. */ public static function event_attribute_added( $attr_id, $attr_data ) { if ( $attr_id && is_array( $attr_data ) ) { Alert_Manager::trigger_event( 9057, self::get_attribute_event_data( $attr_id, $attr_data ) ); } } /** * WooCommerce Attribute Deleted Event. * * @since 3.3.1 * * @param int $id - Attribute ID. * @param string $name - Attribute name. * @param string $taxonomy - Attribute taxonomy name. */ public static function event_attribute_deleted( $id, $name, $taxonomy ) { // Get the attribute. $attribute = wc_get_attribute( $id ); // Check id and attribute object. if ( $id && ! is_null( $attribute ) ) { Alert_Manager::trigger_event( 9058, array( 'AttributeID' => esc_attr( $id ), 'AttributeName' => isset( $attribute->name ) ? sanitize_text_field( $attribute->name ) : false, 'AttributeSlug' => isset( $attribute->slug ) ? sanitize_text_field( str_replace( 'pa_', '', $attribute->slug ) ) : false, 'AttributeType' => isset( $attribute->type ) ? sanitize_text_field( $attribute->type ) : false, 'AttributeOrderby' => isset( $attribute->order_by ) ? sanitize_text_field( $attribute->order_by ) : false, 'AttributePublic' => isset( $attribute->has_archives ) ? sanitize_text_field( $attribute->has_archives ) : '0', 'Taxonomy' => sanitize_text_field( $taxonomy ), ) ); } } /** * Retrieve Attribute Data before editing. * * @since 3.3.1 */ public static function retrieve_attribute_data() { $save_attribute = isset( $_POST['save_attribute'] ) ? true : false; $post_type = isset( $_GET['post_type'] ) ? sanitize_text_field( wp_unslash( $_GET['post_type'] ) ) : false; $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : false; $attribute_id = isset( $_GET['edit'] ) ? absint( sanitize_text_field( wp_unslash( $_GET['edit'] ) ) ) : false; if ( $save_attribute && ! empty( $post_type ) && ! empty( $page ) && ! empty( $attribute_id ) && 'product' === $post_type && 'product_attributes' === $page ) { // Verify nonce. check_admin_referer( 'woocommerce-save-attribute_' . $attribute_id ); // Get attribute data. WooCommerce_Sensor::set_old_attr_data( wc_get_attribute( $attribute_id ) ); } } /** * WooCommerce Attribute Updated Events. * * @since 3.3.1 * * @param int $id - Added attribute ID. * @param array $data - Attribute data. * @param string $old_slug - Attribute old name. */ public static function event_attribute_updated( $id, $data, $old_slug ) { // Check the attribute slug. if ( isset( $data['attribute_name'] ) && $data['attribute_name'] !== $old_slug ) { $attr_event = self::get_attribute_event_data( $id, $data ); $attr_event['OldSlug'] = $old_slug; $attr_event['NewSlug'] = $data['attribute_name']; Alert_Manager::trigger_event( 9059, $attr_event ); } // Check the attribute name. if ( isset( $data['attribute_label'] ) && isset( WooCommerce_Sensor::get_old_attr_data()->name ) && WooCommerce_Sensor::get_old_attr_data()->name !== $data['attribute_label'] ) { $attr_event = self::get_attribute_event_data( $id, $data ); $attr_event['OldName'] = WooCommerce_Sensor::get_old_attr_data()->name; $attr_event['NewName'] = $data['attribute_label']; Alert_Manager::trigger_event( 9060, $attr_event ); } // Check the attribute orderby. if ( isset( $data['attribute_orderby'] ) && isset( WooCommerce_Sensor::get_old_attr_data()->order_by ) && WooCommerce_Sensor::get_old_attr_data()->order_by !== $data['attribute_orderby'] ) { $attr_event = self::get_attribute_event_data( $id, $data ); // Get user data. if ( ! \is_user_logged_in() ) { $attr_event['Username'] = 'Unregistered user'; $v['CurrentUserID'] = 0; } else { $attr_event['Username'] = User_Helper::get_current_user()->user_login; $attr_event['CurrentUserID'] = User_Helper::get_current_user()->ID; } $attr_event['OldSortOrder'] = WooCommerce_Sensor::get_old_attr_data()->order_by; $attr_event['NewSortOrder'] = $data['attribute_orderby']; Alert_Manager::trigger_event( 9061, $attr_event ); } // Check the attribute archives. if ( isset( $data['attribute_public'] ) && isset( WooCommerce_Sensor::get_old_attr_data()->has_archives ) && (int) WooCommerce_Sensor::get_old_attr_data()->has_archives !== $data['attribute_public'] ) { $attr_event = self::get_attribute_event_data( $id, $data ); $attr_event['EventType'] = 1 === $data['attribute_public'] ? 'enabled' : 'disabled'; $attr_event['Slug'] = $old_slug; Alert_Manager::trigger_event( 9062, $attr_event ); } } /** * Return Attribute Events Data. * * @since 3.3.1 * * @param int $attr_id - Added attribute ID. * @param array $data - Attribute data. * @return array */ public static function get_attribute_event_data( $attr_id, $data ) { return array( 'AttributeID' => $attr_id, 'AttributeName' => isset( $data['attribute_label'] ) ? sanitize_text_field( $data['attribute_label'] ) : false, 'AttributeSlug' => isset( $data['attribute_name'] ) ? sanitize_text_field( $data['attribute_name'] ) : false, 'AttributeType' => isset( $data['attribute_type'] ) ? sanitize_text_field( $data['attribute_type'] ) : false, 'AttributeOrderby' => isset( $data['attribute_orderby'] ) ? sanitize_text_field( $data['attribute_orderby'] ) : false, 'AttributePublic' => isset( $data['attribute_public'] ) ? sanitize_text_field( $data['attribute_public'] ) : '0', ); } /** * Check AJAX changes for WooCommerce. * * @since 3.3.1 */ public static function check_wc_ajax_change_events() { $action = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : false; $is_data = isset( $_POST['data'] ) ? true : false; // WooCommerce order actions. $wc_order_actions = array( 'woocommerce_add_order_item', 'woocommerce_save_order_items', 'woocommerce_remove_order_item', 'woocommerce_add_coupon_discount', 'woocommerce_remove_order_coupon', ); // Check for save attributes action. if ( $is_data && 'woocommerce_save_attributes' === $action ) { // Check nonce. check_ajax_referer( 'save-attributes', 'security' ); $post_id = isset( $_POST['post_id'] ) ? sanitize_text_field( wp_unslash( $_POST['post_id'] ) ) : false; if ( ! $post_id ) { return; } $post = \wc_get_product( $post_id ); if ( ! $post ) { return; } // Get the attributes data. parse_str( $_POST['data'], $data ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized self::check_attributes_change( $post, $data ); } elseif ( 'woocommerce_save_variations' === $action ) { // Check nonce. check_ajax_referer( 'save-variations', 'security' ); $product_id = isset( $_POST['product_id'] ) ? sanitize_text_field( wp_unslash( $_POST['product_id'] ) ) : false; if ( ! $product_id ) { return; } $post = \wc_get_product( $product_id ); if ( ! $post ) { return; } // Grab posted data so we have something to process. $data = wp_unslash( $_POST ); self::check_variations_change( $post, $data ); } elseif ( in_array( $action, $wc_order_actions, true ) ) { // Check nonce. check_ajax_referer( 'order-item', 'security' ); // Get order ID. $order_id = isset( $_POST['order_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) ) : false; if ( ! $order_id ) { return; } // Get order post. $order = wc_get_order( $order_id ); // Get editor link. $edit_link = self::get_editor_link( $order ); // Log event. Alert_Manager::trigger_event_if( 9040, array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order_id ) ), 'OrderStatus' => $order->get_status() ? \wc_get_order_status_name( $order->get_status() ) : false, 'OrderStatusSlug' => $order->get_status(), 'PostStatus' => 'wc-' . $order->get_status(), $edit_link['name'] => $edit_link['value'], ), array( __CLASS__, 'must_not_contain_refund_or_modification' ) ); } } /** * Triggers when post is ipdated, checks if that is an order, and compare it to the old one to detect any changes * * @param int $order_id - The order ID. * @param \WP_Post $order - The actual order object. * * @return void * * @since 4.6.0 */ public static function order_updated( $order_id, $order ) { // Get order post. $order = wc_get_order( $order_id ); if ( isset( $order ) && ! empty( $order ) && ! empty( WooCommerce_Sensor::get_old_meta_data() ) ) { $new_meta = get_post_meta( $order_id, '', false ); $compare_changed_items = array_diff_assoc( array_map( 'serialize', $new_meta ), array_map( 'serialize', WooCommerce_Sensor::get_old_meta_data() ) ); if ( ! empty( $compare_changed_items ) ) { // Get editor link. $edit_link = self::get_editor_link( $order ); // Log event. Alert_Manager::trigger_event_if( 9040, array( 'OrderID' => esc_attr( $order_id ), 'OrderTitle' => sanitize_text_field( Woocommerce_Helper::wsal_woocommerce_extension_get_order_title( $order_id ) ), 'OrderStatus' => $order->get_status() ? \wc_get_order_status_name( $order->get_status() ) : false, 'OrderStatusSlug' => $order->get_status(), 'PostStatus' => 'wc-' . $order->get_status(), $edit_link['name'] => $edit_link['value'], ), array( __CLASS__, 'must_not_contain_refund_or_modification' ) ); } } } /** * Check Product Attributes Change. * * @since 3.3.1 * * @param WP_Post $oldpost - WP Post type object. * @param array $data - Data array. * @return int */ public static function check_attributes_change( $oldpost, $data = false ) { $product_id = ''; $title = ''; $status = ''; if ( isset( $oldpost->ID ) && ! empty( $oldpost->ID ) ) { $post_attributes = get_post_meta( $oldpost->ID, '_product_attributes', true ); // Get post attribute meta. $product_id = $oldpost->ID; $title = $oldpost->post_title; $status = $oldpost->post_status; } else { $post_attributes = get_post_meta( $oldpost->get_id(), '_product_attributes', true ); // Get post attribute meta. $product_id = $oldpost->get_id(); $title = $oldpost->get_title(); $status = $oldpost->get_status(); } $post_attributes = ! $post_attributes ? array() : $post_attributes; if ( ! $data ) { $data = array_map( 'sanitize_text_field', wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } $attribute_names = isset( $data['attribute_names'] ) && ! empty( $data['attribute_names'] ) ? array_map( 'sanitize_text_field', wp_unslash( $data['attribute_names'] ) ) : false; $attribute_position = isset( $data['attribute_position'] ) && ! empty( $data['attribute_position'] ) ? array_map( 'sanitize_text_field', wp_unslash( $data['attribute_position'] ) ) : false; $attribute_visibility = isset( $data['attribute_visibility'] ) && ! empty( $data['attribute_visibility'] ) ? array_map( 'sanitize_text_field', wp_unslash( $data['attribute_visibility'] ) ) : false; $attribute_values = isset( $data['attribute_values'] ) && ! empty( $data['attribute_values'] ) ? array_map( 'sanitize_text_field', wp_unslash( $data['attribute_values'] ) ) : false; if ( ! empty( $attribute_names ) && ! empty( $attribute_values ) ) { $new_attributes = array(); foreach ( $attribute_names as $key => $name ) { $attr_key = self::get_attribute_key( $name ); $new_attributes[ $attr_key ] = array( 'name' => $name, 'value' => isset( $attribute_values[ $key ] ) ? sanitize_text_field( self::get_string_attribute_value( $attribute_values[ $key ] ) ) : false, 'position' => isset( $attribute_position[ $key ] ) ? sanitize_text_field( $attribute_position[ $key ] ) : false, 'is_visible' => isset( $attribute_visibility[ $key ] ) ? sanitize_text_field( $attribute_visibility[ $key ] ) : false, ); } // Compare old and new attributes. $added_attributes = array_diff_key( $new_attributes, $post_attributes ); $deleted_attributes = array_diff_key( $post_attributes, $new_attributes ); $compare_changed_items = array_diff_assoc( array_map( 'serialize', $added_attributes ), array_map( 'serialize', $deleted_attributes ) ); $changed_items = array_map( 'unserialize', $compare_changed_items ); $compare_added = array_diff( array_map( 'serialize', $added_attributes ), array_map( 'serialize', $changed_items ) ); $diff_check = array_map( 'unserialize', $compare_added ); // Get product editor link. $editor_link = self::get_editor_link( $oldpost ); // Result. $result = 0; // Event 9047. if ( ! empty( $added_attributes ) && empty( $diff_check ) ) { foreach ( $added_attributes as $added_attribute ) { $old_name = array( array_search( $added_attribute['value'], array_column( $changed_items, 'value', 'name' ), true ) ); $deleted_name = array( array_search( $added_attribute['value'], array_column( $deleted_attributes, 'value', 'name' ), true ) ); if ( ! empty( $old_name[0] ) && ! empty( $deleted_name[0] ) ) { continue; } if ( $added_attribute && ! empty( $added_attribute['name'] ) && ! empty( $added_attribute['value'] ) ) { Alert_Manager::trigger_event( 9047, array( 'AttributeName' => sanitize_text_field( $added_attribute['name'] ), 'AttributeValue' => sanitize_text_field( $added_attribute['value'] ), 'ProductID' => esc_attr( $product_id ), 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'ProductTitle' => sanitize_text_field( $title ), 'ProductStatus' => sanitize_text_field( $status ), 'PostStatus' => sanitize_text_field( $status ), $editor_link['name'] => $editor_link['value'], ) ); $result = 1; } } } // Event 9050. if ( ! empty( $deleted_attributes ) ) { foreach ( $deleted_attributes as $deleted_attribute ) { $old_name = array( array_search( $deleted_attribute['value'], array_column( $changed_items, 'value', 'name' ), true ) ); if ( ! empty( $old_name[0] ) ) { continue; } Alert_Manager::trigger_event( 9050, array( 'AttributeName' => sanitize_text_field( $deleted_attribute['name'] ), 'AttributeValue' => sanitize_text_field( $deleted_attribute['value'] ), 'ProductID' => esc_attr( $product_id ), 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'ProductTitle' => sanitize_text_field( $title ), 'ProductStatus' => sanitize_text_field( $status ), 'PostStatus' => sanitize_text_field( $status ), 'ProductUrl' => get_permalink( $product_id ), $editor_link['name'] => $editor_link['value'], ) ); $result = 1; } } // Event 9049. if ( ! empty( $changed_items ) ) { foreach ( $changed_items as $attr_key => $new_attr ) { // Get old and new attribute names. $old_name = array( array_search( $new_attr['value'], array_column( $deleted_attributes, 'value', 'name' ), true ) ); if ( ! empty( $old_name ) ) { $old_name = (string) $old_name[0]; } else { $old_name = isset( $post_attributes[ $attr_key ]['name'] ) ? $post_attributes[ $attr_key ]['name'] : false; } $new_name = isset( $new_attr['name'] ) ? $new_attr['name'] : false; if ( ! empty( $old_attributes ) && isset( $old_attributes[ $attr_key ] ) ) { $old_name = $old_attributes[ $attr_key ]['name']; } // Name change. if ( ! empty( $old_name ) && $old_name !== $new_name ) { if ( ! $new_name ) { continue; } Alert_Manager::trigger_event( 9049, array( 'AttributeName' => sanitize_text_field( $new_attr['name'] ), 'OldValue' => sanitize_text_field( $old_name ), 'NewValue' => sanitize_text_field( $new_name ), 'ProductID' => esc_attr( $product_id ), 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'ProductTitle' => sanitize_text_field( $title ), 'ProductStatus' => sanitize_text_field( $status ), 'PostStatus' => sanitize_text_field( $status ), 'ProductUrl' => get_permalink( $product_id ), $editor_link['name'] => $editor_link['value'], ) ); $result = 1; } } } if ( ! empty( $new_attributes ) ) { foreach ( $new_attributes as $attr_key => $new_attr ) { // Get old attribute value. $old_value = ''; if ( false !== strpos( $attr_key, 'pa_' ) ) { $old_value = self::get_wc_product_attributes( $oldpost, $attr_key ); } else { $old_value = isset( $post_attributes[ $attr_key ]['value'] ) ? $post_attributes[ $attr_key ]['value'] : false; } $new_value = isset( $new_attr['value'] ) ? $new_attr['value'] : false; // Get new attribute value. // Get old and new attribute names. $old_name = array( array_search( $new_attr['value'], array_column( $deleted_attributes, 'value', 'name' ), true ) ); if ( ! empty( $old_name ) ) { $old_name = (string) $old_name[0]; } else { $old_name = isset( $post_attributes[ $attr_key ]['name'] ) ? $post_attributes[ $attr_key ]['name'] : false; } $new_name = isset( $new_attr['name'] ) ? $new_attr['name'] : false; // Get old and new attribute visibility. $old_visible = isset( $post_attributes[ $attr_key ]['is_visible'] ) ? (int) $post_attributes[ $attr_key ]['is_visible'] : false; $new_visible = isset( $new_attr['is_visible'] ) ? (int) $new_attr['is_visible'] : false; if ( ! empty( $old_attributes ) && isset( $old_attributes[ $attr_key ] ) ) { $old_value = $old_attributes[ $attr_key ]['value']; $old_name = $old_attributes[ $attr_key ]['name']; $old_visible = $old_attributes[ $attr_key ]['is_visible']; } // Value change. if ( $old_value && $new_value && $old_value !== $new_value ) { Alert_Manager::trigger_event( 9048, array( 'AttributeName' => sanitize_text_field( $new_attr['name'] ), 'OldValue' => sanitize_text_field( $old_value ), 'NewValue' => sanitize_text_field( $new_value ), 'ProductID' => esc_attr( $product_id ), 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'ProductTitle' => sanitize_text_field( $title ), 'ProductStatus' => sanitize_text_field( $status ), 'PostStatus' => sanitize_text_field( $status ), $editor_link['name'] => $editor_link['value'], ) ); $result = 1; } // Visibility change. if ( ! empty( $oldpost ) && ! empty( $new_attr['name'] ) && $old_visible !== $new_visible && ! $result ) { Alert_Manager::trigger_event( 9051, array( 'AttributeName' => sanitize_text_field( $new_attr['name'] ), 'AttributeVisiblilty' => 1 === $new_visible ? __( 'Visible', 'wp-security-audit-log' ) : __( 'Non-Visible', 'wp-security-audit-log' ), 'OldAttributeVisiblilty' => 1 === $old_visible ? __( 'Visible', 'wp-security-audit-log' ) : __( 'Non-Visible', 'wp-security-audit-log' ), 'ProductID' => esc_attr( $product_id ), 'SKU' => WooCommerce_Sensor::get_product_sku( $product_id ), 'ProductTitle' => sanitize_text_field( $title ), 'ProductStatus' => sanitize_text_field( $status ), 'PostStatus' => sanitize_text_field( $status ), $editor_link['name'] => $editor_link['value'], ) ); $result = 1; } } } return $result; } return 0; } /** * Check Product Image Change. * * @param WP_Post $oldpost - WP Post type object. * @param array $data - Data array. * @return int * * @since 4.6.0 */ public static function check_image_change( $oldpost, $data = false ) { if ( ! $data ) { $data = array_map( 'sanitize_text_field', wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing } if ( ! isset( $data['_thumbnail_id'] ) ) { return 0; } // Setup our variables. $thumb_id = get_post_thumbnail_id( $oldpost->ID ); $old_attachment_metadata = Woocommerce_Sensor::get_old_attachment_metadata(); $attachment_metadata = wp_get_attachment_metadata( $data['_thumbnail_id'] ); $get_upload_dir = wp_get_upload_dir(); $event_id = 9095; $editor_link = self::get_editor_link( $oldpost ); $alert_needed = false; // Push editor link into event data early. $event_data = array( $editor_link['name'] => $editor_link['value'], ); $event_data['PostID'] = $oldpost->ID; $event_data['ProductTitle'] = sanitize_text_field( $oldpost->post_title ); $event_data['ProductStatus'] = sanitize_text_field( $oldpost->post_status ); $event_data['PostStatus'] = sanitize_text_field( $oldpost->post_status ); $event_data['SKU'] = WooCommerce_Sensor::get_product_sku( $oldpost->ID ); // Featued image added. if ( empty( $old_attachment_metadata ) && ! empty( $attachment_metadata ) && isset( $attachment_metadata['file'] ) ) { $event_data['EventType'] = 'added'; $event_data['name'] = ( isset( $attachment_metadata['file'] ) && ! empty( $attachment_metadata['file'] ) ) ? basename( $attachment_metadata['file'] ) : __( 'File is missing', 'wp-security-audit-log' ); $event_data['path'] = ( isset( $attachment_metadata['file'] ) && ! empty( $attachment_metadata['file'] ) ) ? $get_upload_dir['basedir'] . DIRECTORY_SEPARATOR . $attachment_metadata['file'] : __( 'File is missing', 'wp-security-audit-log' ); $alert_needed = true; } // Featued image removed. if ( empty( $attachment_metadata ) && ! empty( $old_attachment_metadata ) ) { $event_data['EventType'] = 'deleted'; $event_data['name'] = ( isset( $old_attachment_metadata['file'] ) && ! empty( $old_attachment_metadata['file'] ) ) ? basename( $old_attachment_metadata['file'] ) : __( 'File is missing', 'wp-security-audit-log' ); $event_data['path'] = ( isset( $old_attachment_metadata['file'] ) && ! empty( $old_attachment_metadata['file'] ) ) ? $get_upload_dir['basedir'] . DIRECTORY_SEPARATOR . $old_attachment_metadata['file'] : __( 'File is missing', 'wp-security-audit-log' ); $alert_needed = true; } // Featured image modified. if ( ! empty( $attachment_metadata ) && ! empty( $old_attachment_metadata ) && ( $attachment_metadata !== $old_attachment_metadata ) ) { $event_id = 9096; $event_data['EventType'] = 'modified'; $event_data['old_name'] = ( isset( $old_attachment_metadata['file'] ) && ! empty( $old_attachment_metadata['file'] ) ) ? basename( $old_attachment_metadata['file'] ) : __( 'File is missing', 'wp-security-audit-log' ); $event_data['old_path'] = ( isset( $old_attachment_metadata['file'] ) && ! empty( $old_attachment_metadata['file'] ) ) ? $get_upload_dir['basedir'] . DIRECTORY_SEPARATOR . $old_attachment_metadata['file'] : __( 'File is missing', 'wp-security-audit-log' ); $event_data['name'] = ( isset( $attachment_metadata['file'] ) && ! empty( $attachment_metadata['file'] ) ) ? basename( $attachment_metadata['file'] ) : __( 'File is missing', 'wp-security-audit-log' ); $event_data['path'] = ( isset( $attachment_metadata['file'] ) && ! empty( $attachment_metadata['file'] ) ) ? $get_upload_dir['basedir'] . DIRECTORY_SEPARATOR . $attachment_metadata['file'] : __( 'File is missing', 'wp-security-audit-log' ); $alert_needed = true; } // Its go time. if ( $alert_needed ) { Alert_Manager::trigger_event( $event_id, $event_data ); return 1; } return 0; } /** * Check Product Download Limit Change. * * @param WP_Post $oldpost - WP Post type object. * @param array $data - Data array. * @return int * * @since 4.6.0 */ public static function check_download_limit_change( $oldpost, $data = false ) { if ( ! $data ) { $data = array_map( 'sanitize_text_field', wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing } if ( ! isset( $data['_download_expiry'] ) || ! isset( $data['_download_limit'] ) ) { return 0; } $event_id = false; $editor_link = self::get_editor_link( WooCommerce_Sensor::get_old_post() ); $alert_needed = false; // Push editor link into event data early. $event_data = array( $editor_link['name'] => $editor_link['value'], ); $event_data['new_value'] = ( $data['_download_expiry'] ) ?? ''; $event_data['product_name'] = ( $data['post_title'] ) ?? ''; $event_data['ID'] = ( $data['post_ID'] ) ?? ''; $event_data['ProductStatus'] = ( $data['post_status'] ) ?? ''; $event_data['PostStatus'] = ( $data['post_status'] ) ?? ''; if ( isset( $oldpost['_download_limit'] ) && intval( $oldpost['_download_limit'][0] ) < 0 ) { $oldpost['_download_limit'][0] = __( 'Unlimited', 'wp-security-audit-log' ); } if ( isset( $oldpost['_download_expire'] ) && intval( $oldpost['_download_expiry'][0] ) < 0 ) { $oldpost['_download_expiry'][0] = __( 'Never', 'wp-security-audit-log' ); } if ( ! $data['_download_limit'] ) { $data['_download_limit'] = __( 'Unlimited', 'wp-security-audit-log' ); } if ( ! $data['_download_expiry'] ) { $data['_download_expiry'] = __( 'Never', 'wp-security-audit-log' ); } // Event 9097 (Modified the download limit of the product). if ( isset( $oldpost['_download_limit'] ) && $oldpost['_download_limit'][0] !== $data['_download_limit'] ) { $event_id = 9097; $event_data['previous_value'] = $oldpost['_download_limit'][0]; $event_data['new_value'] = $data['_download_limit']; $event_data['SKU'] = WooCommerce_Sensor::get_product_sku( $data['post_ID'] ); Alert_Manager::trigger_event( $event_id, $event_data ); $alert_needed = true; } // Event 9098 (Modified the download expires of the product). if ( isset( $oldpost['_download_expiry'] ) && $oldpost['_download_expiry'][0] !== $data['_download_expiry'] ) { // The empty value for this is stored as -1, so double check we actually have a change to report. $old_value = ( '-1' === $oldpost['_download_expiry'][0] ) ? 'Never' : $oldpost['_download_expiry'][0]; if ( $old_value === $data['_download_expiry'] ) { return false; } $event_id = 9098; $event_data['previous_value'] = $old_value; $event_data['new_value'] = $data['_download_expiry']; $event_data['SKU'] = WooCommerce_Sensor::get_product_sku( $data['post_ID'] ); Alert_Manager::trigger_event( $event_id, $event_data ); $alert_needed = true; } return $alert_needed; } /** * Trigger alert if change of tax status or class occurs. * * @param WC_Product $product - Old post meta data. * @param array $oldpost - Old post. * @param WC_Product $post - new post. * * @return void * * @since 4.6.0 */ public static function check_tax_status_change( $product, $oldpost, $post ) { if ( ! isset( $oldpost['_tax_status'] ) || ! isset( $post['tax_status'] ) ) { return; } // Tax status. $old_status = $oldpost['_tax_status'][0]; $status = $post['tax_status']; if ( $status !== $old_status ) { $editor_link = self::get_editor_link( $product ); Alert_Manager::trigger_event( 9113, array( 'PostID' => $product->ID, 'SKU' => esc_attr( WooCommerce_Sensor::get_product_sku( $product->ID ) ), 'ProductTitle' => $product->post_title, 'ProductStatus' => $product->post_status, 'PostStatus' => $product->post_status, 'old_tax_status' => $old_status, 'new_tax_status' => $status, $editor_link['name'] => $editor_link['value'], ) ); } // Tax class. $old_class = ( empty( $oldpost['_tax_class'][0] ) ) ? 'standard' : $oldpost['_tax_class'][0]; $class = ( empty( $post['tax_class'] ) ) ? 'standard' : $post['tax_class']; if ( $class !== $old_class ) { $editor_link = self::get_editor_link( $product ); Alert_Manager::trigger_event( 9114, array( 'PostID' => $product->ID, 'SKU' => esc_attr( WooCommerce_Sensor::get_product_sku( $product->ID ) ), 'ProductTitle' => $product->post_title, 'ProductStatus' => $product->post_status, 'PostStatus' => $product->post_status, 'old_tax_class' => $old_class, 'new_tax_class' => $class, $editor_link['name'] => $editor_link['value'], ) ); } } /** * Trigger alert if change of low stock threshold occurs. * * @param \WC_Product $product - Old post meta data. * @param array $oldpost - Old post. * @param \WC_Product $post - new post. * @return void * * @since 4.6.0 * @since 5.6.0 - Fixed false positives when old and new values are the same. This caused event 9119 to trigger unnecessarily. */ public static function check_low_stock_threshold_change( $product, $oldpost, $post ) { // Get raw values, note: empty string means store default. $old_raw = $oldpost['_low_stock_amount'][0] ?? ''; $new_raw = $post['low_stock_amount'] ?? ''; // Normalize for comparison: empty = null (store default), otherwise cast to int. $old_normalized = ( '' === $old_raw || '' === trim( (string) $old_raw ) ) ? null : (int) $old_raw; $new_normalized = ( '' === $new_raw || '' === trim( (string) $new_raw ) ) ? null : (int) $new_raw; // Return early if we don't have a change. if ( $old_normalized === $new_normalized ) { return; } $store_default_label = \esc_html__( 'Store Default', 'wp-security-audit-log' ); $old_status = null === $old_normalized ? $store_default_label : (string) $old_normalized; $new_status = null === $new_normalized ? $store_default_label : (string) $new_normalized; $editor_link = self::get_editor_link( $product ); Alert_Manager::trigger_event( 9119, array( 'PostID' => $product->ID, 'SKU' => \esc_attr( WooCommerce_Sensor::get_product_sku( $product->ID ) ), 'ProductTitle' => $product->post_title, 'ProductStatus' => $product->post_status, 'PostStatus' => $product->post_status, 'old_low_stock_amount' => $old_status, 'new_low_stock_amount' => $new_status, $editor_link['name'] => $editor_link['value'], ) ); } /** * Check Product Variations Change. * * @since 3.3.1.2 * * @param WP_Post $oldpost - WP Post type object. * @param array $data - Data array. * @return int */ public static function check_variations_change( $oldpost, $data = false ) { if ( ! $data ) { $data = array_map( 'sanitize_text_field', wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } if ( ! empty( $data['variable_post_id'] ) ) { foreach ( $data['variable_post_id'] as $key => $post_id ) { $post_id = absint( $post_id ); $variation = new \WC_Product_Variation( $post_id ); // Copy and set the product variation. $product = $oldpost; $product->post_title = $variation->get_name(); $product->post_status = $variation->get_status(); // Check regular price. $old_price = (int) $variation->get_regular_price(); $new_price = isset( $data['variable_regular_price'][ $key ] ) ? (int) sanitize_text_field( wp_unslash( $data['variable_regular_price'][ $key ] ) ) : false; if ( $old_price !== $new_price ) { $result = WooCommerce_Sensor_Helper::event_price( $product, 'Regular price', $old_price, $new_price ); } // Check sale price. $old_sale_price = (int) $variation->get_sale_price(); $new_sale_price = isset( $data['variable_sale_price'][ $key ] ) ? (int) sanitize_text_field( wp_unslash( $data['variable_sale_price'][ $key ] ) ) : false; if ( $old_sale_price !== $new_sale_price ) { $result = WooCommerce_Sensor_Helper::event_price( $product, 'Sale price', $old_sale_price, $new_sale_price ); } // Check product SKU. $old_sku = $variation->get_sku(); $new_sku = isset( $data['variable_sku'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_sku'][ $key ] ) ) : false; if ( $old_sku !== $new_sku ) { $result = WooCommerce_Sensor_Helper::check_sku_change( $product, $old_sku, $new_sku ); } // Check product virtual. $virtual['old'] = $variation->is_virtual(); $virtual['new'] = isset( $data['variable_is_virtual'][ $key ] ) ? true : false; if ( $virtual['old'] !== $virtual['new'] ) { $result = WooCommerce_Sensor_Helper::check_type_change( $product, null, $virtual ); } // Check product downloadable. $download['old'] = $variation->is_downloadable(); $download['new'] = isset( $data['variable_is_downloadable'][ $key ] ) ? true : false; if ( $download['old'] !== $download['new'] ) { $result = WooCommerce_Sensor_Helper::check_type_change( $product, null, false, $download ); } // Check product stock status. $old_stock_status = $variation->get_stock_status(); $new_stock_status = isset( $data['variable_stock_status'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_stock_status'][ $key ] ) ) : false; if ( $old_stock_status !== $new_stock_status ) { $result = WooCommerce_Sensor_Helper::check_stock_status_change( $product, $old_stock_status, $new_stock_status ); } // Check product stock quantity. $old_stock = $variation->get_stock_quantity(); $new_stock = isset( $data['variable_stock'][ $key ] ) ? (int) sanitize_text_field( wp_unslash( $data['variable_stock'][ $key ] ) ) : false; if ( $old_stock !== $new_stock ) { $result = WooCommerce_Sensor_Helper::check_stock_quantity_change( $product, $old_stock, $new_stock ); } // Check product weight. $old_weight = $variation->get_weight(); $new_weight = isset( $data['variable_weight'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_weight'][ $key ] ) ) : false; if ( $old_weight !== $new_weight ) { $result = WooCommerce_Sensor_Helper::check_weight_change( $product, $old_weight, $new_weight ); } // Check product dimensions change. $length['old'] = $variation->get_length(); $length['new'] = isset( $data['variable_length'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_length'][ $key ] ) ) : false; $width['old'] = $variation->get_width(); $width['new'] = isset( $data['variable_width'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_width'][ $key ] ) ) : false; $height['old'] = $variation->get_height(); $height['new'] = isset( $data['variable_height'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_height'][ $key ] ) ) : false; WooCommerce_Sensor_Helper::check_dimensions_change( $product, $length, $width, $height ); // Check product downloads change. $file_names['new'] = isset( $data['_wc_variation_file_names'][ $post_id ] ) ? array_map( 'sanitize_text_field', wp_unslash( $data['_wc_variation_file_names'][ $post_id ] ) ) : array(); $file_urls['new'] = isset( $data['_wc_variation_file_urls'][ $post_id ] ) ? array_map( 'esc_url_raw', wp_unslash( $data['_wc_variation_file_urls'][ $post_id ] ) ) : array(); $file_names['old'] = array(); $file_urls['old'] = array(); // Set product old downloads data. foreach ( $variation->get_downloads() as $download ) { array_push( $file_names['old'], $download->get_name() ); array_push( $file_urls['old'], $download->get_file() ); } WooCommerce_Sensor_Helper::check_downloadable_file_change( $product, $file_names, $file_urls ); // Check backorders change. $old_backorder = $variation->get_backorders(); $new_backorder = isset( $data['variable_backorders'][ $key ] ) ? sanitize_text_field( wp_unslash( $data['variable_backorders'][ $key ] ) ) : false; WooCommerce_Sensor_Helper::check_backorders_setting( $product, $old_backorder, $new_backorder ); } } return 1; } /** * Returns the attribute key using attribute name. * * @since 3.3.1 * * @param string $attribute_name - Attribute name. * @return string */ public static function get_attribute_key( $attribute_name = '' ) { return str_replace( ' ', '-', strtolower( $attribute_name ) ); } /** * Return the attribute value string using WooCommerce attribute value field. * * @since 3.3.1 * * @param string $attribute_value - WooCommerce attribute value field. * @return string */ public static function get_string_attribute_value( $attribute_value = '' ) { // New attribute value. $new_attr_value = ''; // Check if incoming value is string. if ( is_string( $attribute_value ) ) { $new_attr_value = sanitize_text_field( wp_unslash( $attribute_value ) ); } elseif ( is_array( $attribute_value ) ) { // If the incoming value is an array, it is an array of term ids. $term_attr_values = array_map( 'sanitize_text_field', wp_unslash( $attribute_value ) ); $new_attr_value = array(); foreach ( $term_attr_values as $term_id ) { $term = get_term( $term_id ); // Get term. if ( null !== $term && ! is_wp_error( $term ) ) { $new_attr_value[] = $term->name; } } $new_attr_value = implode( ' | ', $new_attr_value ); } return $new_attr_value; } /** * Return product attribute value. * * @since 3.3.1 * * @param WP_Post $product - Product post object. * @param string $taxonomy - Taxonomy. * @return string */ public static function get_wc_product_attributes( $product, $taxonomy ) { $attribute_terms = wp_get_post_terms( $product->ID, $taxonomy ); $product_attrs = array(); if ( ! is_wp_error( $attribute_terms ) ) { foreach ( $attribute_terms as $single_term ) { $product_attrs[] = $single_term->term_id; } } return self::get_string_attribute_value( $product_attrs ); } /** * Check Product Category Updated Events. * * @since 3.3.1 * * @param array $data - Term data to be updated. * @param int $term_id - Term ID. * @param string $taxonomy - Taxonomy slug. * @param array $args - Arguments passed to wp_update_term(). */ public static function event_product_cat_updated( $data, $term_id, $taxonomy, $args ) { // Check if the taxonomy is `product_cat`. if ( 'product_cat' === $taxonomy ) { // Get term data. $new_name = isset( $data['name'] ) ? $data['name'] : false; $new_slug = isset( $data['slug'] ) ? $data['slug'] : false; $new_parent_id = isset( $args['parent'] ) ? $args['parent'] : false; // New parent category. $new_parent_cat = false; if ( 0 !== $new_parent_id ) { $new_parent_cat = get_term( $new_parent_id, $taxonomy ); } // Get old data. $term = get_term( $term_id, $taxonomy ); $old_name = $term->name; $old_slug = $term->slug; // Old parent category. $old_parent_cat = false; if ( $term->parent ) { $old_parent_cat = get_term( $term->parent, $taxonomy ); } // Update if both slugs are not same. if ( $old_slug !== $new_slug ) { Alert_Manager::trigger_event( 9053, array( 'CategoryID' => esc_attr( $term_id ), 'CategoryName' => sanitize_text_field( $new_name ), 'OldSlug' => sanitize_text_field( $old_slug ), 'NewSlug' => sanitize_text_field( $new_slug ), 'ProductCatLink' => Woocommerce_Helper::get_taxonomy_edit_link( $term_id ), ) ); } // Update if both parent categories are not same. if ( $term->parent !== $new_parent_id ) { Alert_Manager::trigger_event( 9054, array( 'CategoryID' => esc_attr( $term_id ), 'CategoryName' => sanitize_text_field( $new_name ), 'CategorySlug' => sanitize_text_field( $term->slug ), 'ProductCatLink' => Woocommerce_Helper::get_taxonomy_edit_link( $term_id ), 'OldParentID' => isset( $old_parent_cat->term_id ) ? esc_attr( $old_parent_cat->term_id ) : false, 'OldParentCat' => isset( $old_parent_cat->name ) ? sanitize_text_field( $old_parent_cat->name ) : false, 'NewParentID' => isset( $new_parent_cat->term_id ) ? esc_attr( $new_parent_cat->term_id ) : false, 'NewParentCat' => isset( $new_parent_cat->name ) ? sanitize_text_field( $new_parent_cat->name ) : false, ) ); } // Update if both names are not same. if ( $old_name !== $new_name ) { Alert_Manager::trigger_event( 9056, array( 'CategoryID' => esc_attr( $term_id ), 'CategoryName' => sanitize_text_field( $new_name ), 'OldName' => sanitize_text_field( $old_name ), 'NewName' => sanitize_text_field( $new_name ), 'CategorySlug' => sanitize_text_field( $term->slug ), 'ProductCatLink' => Woocommerce_Helper::get_taxonomy_edit_link( $term_id ), ) ); } } if ( 'product_tag' === $taxonomy ) { // Get term data. $new_name = isset( $data['name'] ) ? $data['name'] : false; $new_slug = isset( $data['slug'] ) ? $data['slug'] : false; $new_parent_id = isset( $args['parent'] ) ? $args['parent'] : false; // New parent category. $new_parent_cat = false; if ( 0 !== $new_parent_id ) { $new_parent_cat = get_term( $new_parent_id, $taxonomy ); } // Get old data. $term = get_term( $term_id, $taxonomy ); $old_name = $term->name; $old_slug = $term->slug; // Old parent category. $old_parent_cat = false; if ( $term->parent ) { $old_parent_cat = get_term( $term->parent, $taxonomy ); } // Update if both slugs are not same. if ( $old_slug !== $new_slug ) { Alert_Manager::trigger_event( 9104, array( 'TagName' => sanitize_text_field( $new_name ), 'OldSlug' => sanitize_text_field( $old_slug ), 'NewSlug' => sanitize_text_field( $new_slug ), 'ProductTagLink' => Woocommerce_Helper::get_taxonomy_edit_link( $term_id, 'product_tag' ), ) ); } // Update if both names are not same. if ( $old_name !== $new_name ) { Alert_Manager::trigger_event( 9103, array( 'OldName' => sanitize_text_field( $old_name ), 'NewName' => sanitize_text_field( $new_name ), 'Slug' => sanitize_text_field( $term->slug ), 'ProductTagLink' => Woocommerce_Helper::get_taxonomy_edit_link( $term_id, 'product_tag' ), ) ); } } return $data; } /** * Check Product Category Display Type Meta Event. * * @since 3.3.1 * * @param int $meta_id - ID of the metadata entry to update. * @param int $object_id - Object ID. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. */ public static function event_cat_display_updated( $meta_id, $object_id, $meta_key, $meta_value ) { // Check `display_type` meta key. if ( 'display_type' !== $meta_key ) { return; } // Get previous value. $old_display = get_term_meta( $object_id, $meta_key, true ); // Get term. $term = get_term( $object_id, 'product_cat' ); // Check if display type changed. if ( $meta_value !== $old_display ) { Alert_Manager::trigger_event( 9055, array( 'CategoryID' => esc_attr( $object_id ), 'CategoryName' => sanitize_text_field( $term->name ), 'CategorySlug' => sanitize_text_field( $term->slug ), 'OldDisplayType' => sanitize_text_field( $old_display ), 'NewDisplayType' => sanitize_text_field( $meta_value ), 'ProductCatLink' => Woocommerce_Helper::get_taxonomy_edit_link( $object_id ), ) ); } } /** * Check Product Category Deletion Event. * * @since 3.3.1 * * @param int $term_id - Term ID. * @param int $tt_id - Term taxonomy ID. * @param mixed $deleted_term - Copy of the already-deleted term, in the form specified by the parent function. WP_Error otherwise. * @param array $object_ids - List of term object IDs. */ public static function event_product_cat_deleted( $term_id, $tt_id, $deleted_term, $object_ids ) { if ( 'product_cat' === $deleted_term->taxonomy ) { Alert_Manager::trigger_event( 9052, array( 'CategoryID' => esc_attr( $deleted_term->term_id ), 'CategoryName' => sanitize_text_field( $deleted_term->name ), 'CategorySlug' => sanitize_text_field( $deleted_term->slug ), ) ); } } /** * Check Product Tag Deletion Event. * * @since 1.4.0 * * @param int $term_id - Term ID. * @param int $tt_id - Term taxonomy ID. * @param mixed $deleted_term - Copy of the already-deleted term, in the form specified by the parent function. WP_Error otherwise. * @param array $object_ids - List of term object IDs. */ public static function event_product_tag_deleted( $term_id, $tt_id, $deleted_term, $object_ids ) { if ( 'product_tag' === $deleted_term->taxonomy ) { Alert_Manager::trigger_event( 9102, array( 'ID' => esc_attr( $deleted_term->term_id ), 'Name' => sanitize_text_field( $deleted_term->name ), 'Slug' => sanitize_text_field( $deleted_term->slug ), ) ); } } /** * Check Created Events for Coupon Meta. * * @since 3.3.1 * * @param bool $log_event - True if log event 2053 for coupon meta, false if not. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. * @param WP_Post $coupon - Coupon CPT object. * @return bool */ public static function log_coupon_meta_created_event( $log_event, $meta_key, $meta_value, $coupon ) { $usage_restriction_meta = array( 'individual_use', 'product_ids', 'exclude_product_ids', 'product_categories', 'exclude_product_categories', 'exclude_sale_items', 'minimum_amount', 'maximum_amount', 'customer_email' ); if ( ! empty( $meta_key ) && in_array( $meta_key, $usage_restriction_meta, true ) ) { self::log_coupon_meta_update_events( $log_event, $meta_key, $meta_value, false, $coupon ); return false; } if ( ! empty( $meta_key ) && 'shop_coupon' === $coupon->post_type && in_array( $meta_key, Woocommerce_Sensor::get_coupon_meta(), true ) ) { return false; } // Do not report total sales as its added automatically when a product is published. if ( ! empty( $meta_key ) && 'total_sales' === $meta_key ) { return false; } return $log_event; } /** * Check Updated Events for Coupon Meta. * * @since 3.3.1 * * @param bool $log_meta_event - True if log meta events 2054 or 2062, false if not. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. * @param stdClass $old_meta_obj - Old meta value and key object. * @param WP_Post $coupon - Coupon CPT object. * @return bool */ public static function log_coupon_meta_update_events( $log_meta_event, $meta_key, $meta_value, $old_meta_obj, $coupon ) { if ( \is_array( $old_meta_obj ) ) { $old_meta_obj = (object) $old_meta_obj; } // If meta key does not match with any coupon meta key, then return. if ( ! empty( $meta_key ) && ( ! in_array( $meta_key, Woocommerce_Sensor::get_coupon_meta(), true ) || 'shop_coupon' !== $coupon->post_type ) ) { return $log_meta_event; } $ignore_coupon_meta = array( 'usage_count', 'free_shipping' ); // Ignore these meta keys. $usage_restriction_meta = array( 'individual_use', 'product_ids', 'exclude_product_ids', 'product_categories', 'exclude_product_categories', 'exclude_sale_items', 'minimum_amount', 'maximum_amount', 'customer_email' ); // Event 9067. $usage_limits_meta = array( 'usage_limit', 'usage_limit_per_user', 'limit_usage_to_x_items' ); // Event 9068. if ( in_array( $meta_key, $ignore_coupon_meta, true ) && $meta_value !== $old_meta_obj->val ) { return false; } elseif ( isset( $old_meta_obj->val ) && $meta_value !== $old_meta_obj->val || ! isset( $old_meta_obj->val ) ) { // Event id. $event_id = false; // Get coupon event data. $coupon_data = WooCommerce_Sensor::get_coupon_event_data( $coupon ); if ( 'discount_type' === $meta_key ) { // Set coupon discount type data. $coupon_data['OldDiscountType'] = isset( $old_meta_obj->val ) ? $old_meta_obj->val : false; $coupon_data['NewDiscountType'] = $meta_value; $coupon_data['EditorLinkCoupon'] = self::get_editor_link( $coupon )['value']; // Set event id. $event_id = 9064; } elseif ( 'coupon_amount' === $meta_key ) { // Set coupon amount data. $coupon_data['OldAmount'] = isset( $old_meta_obj->val ) ? $old_meta_obj->val : false; $coupon_data['NewAmount'] = $meta_value; // Set event id. $event_id = 9065; } elseif ( 'date_expires' === $meta_key ) { // Set coupon expiry date data. $coupon_data['OldDate'] = isset( $old_meta_obj->val ) && ! empty( $old_meta_obj->val ) ? gmdate( get_option( 'date_format' ), (int) $old_meta_obj->val ) : __( 'Does not expire', 'wp-security-audit-log' ); $coupon_data['NewDate'] = ! empty( $meta_value ) ? gmdate( get_option( 'date_format' ), (int) $meta_value ) : __( 'Does not expire', 'wp-security-audit-log' ); // Set event id. $event_id = 9066; } elseif ( in_array( $meta_key, $usage_restriction_meta, true ) && ( $meta_value || isset( $old_meta_obj->val ) ) ) { $event_type = 'modified'; $event_id = 9067; $meta_key = ucfirst( str_replace( '_', ' ', (string) $meta_key ) ); $previous_value = isset( $old_meta_obj->val ) ? $old_meta_obj->val : '0'; // Set usage restriction meta data. $coupon_data['MetaKey'] = $meta_key; $coupon_data['OldMetaValue'] = $previous_value; $coupon_data['NewMetaValue'] = ! empty( $meta_value ) ? $meta_value : '0'; $coupon_data['EventType'] = $event_type; if ( false === self::$is_9067_logged ) { self::$is_9067_logged = true; } } elseif ( in_array( $meta_key, $usage_limits_meta, true ) ) { // Set usage limits meta data. $coupon_data['MetaKey'] = $meta_key; $coupon_data['OldMetaValue'] = isset( $old_meta_obj->val ) ? $old_meta_obj->val : false; $coupon_data['NewMetaValue'] = $meta_value; if ( false === self::$is_9068_logged ) { // Set event id. $event_id = 9068; self::$is_9068_logged = true; } } if ( $event_id && ! empty( $coupon_data ) ) { // Log the event. Alert_Manager::trigger_event_if( $event_id, $coupon_data, array( __CLASS__, 'must_not_be_new_coupon' ) ); } } return false; } /** * Check if is a new coupon or not. * * @return bool - If is duplicate or not. * * @since 4.6.0 */ public static function must_not_be_new_coupon() { if ( Alert_Manager::will_or_has_triggered( 9063 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9124 ) ) { return false; } return true; } /** * Check Created Events for Coupon Meta. * * @since 3.3.1 * * @param bool $log_event - True if log event 2055 for coupon meta, false if not. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. * @param WP_Post $coupon - Coupon CPT object. * @return bool */ public static function log_coupon_meta_delete_event( $log_event, $meta_key, $meta_value, $coupon ) { $usage_restriction_meta = array( 'individual_use', 'product_ids', 'exclude_product_ids', 'product_categories', 'exclude_product_categories', 'exclude_sale_items', 'minimum_amount', 'maximum_amount', 'customer_email' ); if ( ! empty( $meta_key ) && in_array( $meta_key, $usage_restriction_meta, true ) ) { $post_attributes = array(); $post_attributes['val'] = isset( WooCommerce_Sensor::get_old_meta_data()[ $meta_key ][0] ) ? WooCommerce_Sensor::get_old_meta_data()[ $meta_key ][0] : false; $post_attributes = (object) $post_attributes; self::log_coupon_meta_update_events( $log_event, $meta_key, $meta_value, $post_attributes, $coupon ); return false; } if ( ! empty( $meta_key ) && 'shop_coupon' === $coupon->post_type && in_array( $meta_key, Woocommerce_Sensor::get_coupon_meta(), true ) ) { return false; } return $log_event; } /** * Get WC User Meta Data before updating. * * @since 3.4 * * @param integer $meta_id - Meta id. * @param integer $user_id - User id. * @param string $meta_key - Meta key. */ public static function before_wc_user_meta_update( $meta_id, $user_id, $meta_key ) { if ( ! Woocommerce_Helper::is_woocommerce_user_meta( $meta_key ) ) { return; } Woocommerce_Sensor::set_wc_user_meta( $meta_id, (object) array( 'key' => $meta_key, 'value' => get_user_meta( $user_id, $meta_key, true ), ) ); } /** * WC User Meta data updated. * * @since 3.4 * * @param integer $meta_id - Meta id. * @param integer $user_id - User id. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. */ public static function wc_user_meta_updated( $meta_id, $user_id, $meta_key, $meta_value ) { if ( ! Woocommerce_Helper::is_woocommerce_user_meta( $meta_key ) ) { return; } $is_first_edit = false; if ( ! isset( Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ] ) || ! is_object( Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ] ) ) { Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ] = (object) array( 'key' => $meta_key, 'value' => 'None supplied', // Not translatable as its internal only, we use a translatable string for display later on. ); $is_first_edit = true; } if ( isset( Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ] ) ) { $current_value = get_user_meta( $user_id, Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ]->key, true ); } if ( isset( Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ] ) && Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ]->value !== $current_value ) { if ( Woocommerce_Sensor::get_wc_user_meta()[ $meta_id ]->value !== $meta_value ) { // Event id. $event_id = false; if ( false !== strpos( (string) $meta_key, 'billing_' ) ) { $event_id = 9083; $billing_address_fields = array( 'billing_first_name', 'billing_last_name', 'billing_company', 'billing_country', 'billing_address_1', 'billing_address_2', 'billing_city', 'billing_state', 'billing_postcode', 'billing_phone' ); // We will fill this is as needed below. $new_address_array = array(); $old_address_array = array(); foreach ( $billing_address_fields as $field ) { $field_value = get_user_meta( $user_id, $field, true ); $new_address_array[ $field ] = ( $meta_key === $field ) ? $meta_value : $field_value; $old_address_array[ $field ] = $field_value; } // Replace old values we already have stored. foreach ( Woocommerce_Sensor::get_wc_user_meta() as $user_meta ) { $old_address_array[ $user_meta->key ] = $user_meta->value; } $new_address_array = array_filter( $new_address_array ); $old_address_array = array_filter( $old_address_array ); // Combine name fields to avoid weird comma. if ( isset( $new_address_array['billing_first_name'] ) && isset( $new_address_array['billing_last_name'] ) ) { $new_address_array['billing_first_name'] = $new_address_array['billing_first_name'] . ' ' . $new_address_array['billing_last_name']; unset( $new_address_array['billing_last_name'] ); } if ( isset( $old_address_array['billing_first_name'] ) && isset( $old_address_array['billing_last_name'] ) ) { $old_address_array['billing_first_name'] = $old_address_array['billing_first_name'] . ' ' . $old_address_array['billing_last_name']; unset( $old_address_array['billing_last_name'] ); } // Turn them into a nice string. $new_address = implode( ', ', $new_address_array ); $old_address = implode( ', ', $old_address_array ); if ( $event_id ) { $user = get_user_by( 'ID', $user_id ); if ( 9083 === $event_id ) { // Add 1 to our changed fields counter. ++self::$updated_field_count; Alert_Manager::trigger_event_if( $event_id, array( 'TargetUsername' => $user ? $user->user_login : false, 'NewValue' => sanitize_text_field( $new_address ), 'OldValue' => ( $is_first_edit ) ? esc_html__( 'Not supplied', 'wp-security-audit-log' ) : sanitize_text_field( $old_address ), 'EditUserLink' => add_query_arg( 'user_id', $user_id, \network_admin_url( 'user-edit.php' ) ), 'Roles' => is_array( $user->roles ) ? implode( ', ', $user->roles ) : $user->roles, ), array( __CLASS__, 'must_not_repeat_billing', ) ); } } } elseif ( false !== strpos( (string) $meta_key, 'shipping_' ) ) { $event_id = 9084; $shipping_address_fields = array( 'shipping_first_name', 'shipping_last_name', 'shipping_company', 'shipping_country', 'shipping_address_1', 'shipping_address_2', 'shipping_city', 'shipping_state', 'shipping_postcode', 'shipping_phone' ); // We will fill this is as needed below. $new_address_array = array(); foreach ( $shipping_address_fields as $field ) { $field_value = get_user_meta( $user_id, $field, true ); $new_address_array[ $field ] = ( $meta_key === $field ) ? $meta_value : $field_value; $old_address_array[ $field ] = $field_value; } // Replace old values we already have stored. foreach ( Woocommerce_Sensor::get_wc_user_meta() as $user_meta ) { $old_address_array[ $user_meta->key ] = $user_meta->value; } $new_address_array = array_filter( $new_address_array ); $old_address_array = array_filter( $old_address_array ); // Combine name fields to avoid weird comma. if ( isset( $new_address_array['shipping_first_name'] ) && isset( $new_address_array['shipping_last_name'] ) ) { $new_address_array['shipping_first_name'] = $new_address_array['shipping_first_name'] . ' ' . $new_address_array['shipping_last_name']; unset( $new_address_array['shipping_last_name'] ); } if ( isset( $old_address_array['shipping_first_name'] ) && isset( $old_address_array['shipping_last_name'] ) ) { $old_address_array['shipping_first_name'] = $old_address_array['shipping_first_name'] . ' ' . $old_address_array['shipping_last_name']; unset( $old_address_array['shipping_last_name'] ); } // Turn them into a nice string. $new_address = implode( ', ', $new_address_array ); $old_address = implode( ', ', $old_address_array ); if ( $event_id ) { $user = get_user_by( 'ID', $user_id ); if ( 9084 === $event_id ) { ++self::$updated_shipping_field_count; Alert_Manager::trigger_event_if( $event_id, array( 'TargetUsername' => $user ? $user->user_login : false, 'NewValue' => sanitize_text_field( $new_address ), 'OldValue' => ( $is_first_edit ) ? esc_html__( 'Not supplied', 'wp-security-audit-log' ) : sanitize_text_field( $old_address ), 'EditUserLink' => add_query_arg( 'user_id', $user_id, \network_admin_url( 'user-edit.php' ) ), 'Roles' => is_array( $user->roles ) ? implode( ', ', $user->roles ) : $user->roles, ), array( __CLASS__, 'must_not_repeat_shipping', ) ); } } } } } } /** * Check if is a repeated billing alert. * * @return bool - If was repeat or not. * * @since 4.6.0 */ public static function must_not_repeat_billing() { --self::$updated_field_count; if ( 1 === self::$updated_field_count ) { return true; } return false; } /** * Check if is a repeated shipping alert. * * @return bool - If was repeat or not. * * @since 4.6.0 */ public static function must_not_repeat_shipping() { --self::$updated_shipping_field_count; if ( 1 === self::$updated_shipping_field_count ) { return true; } return false; } /** * Trigger 9106 (stock level change by 3rd party). * * @since 3.3.1 * * @param int $meta_id - ID of the metadata entry to update. * @param int $object_id - Post ID. * @param string $meta_key - Meta key. * @param mixed $meta_value - Meta value. This will be a PHP-serialized string representation of the value if the value is an array, an object, or itself a PHP-serialized string. */ public static function detect_stock_level_change( $meta_id, $object_id, $meta_key, $meta_value ) { if ( '_stock' === $meta_key ) { $post = get_post( $object_id ); if ( ! $post || ! isset( $post->ID ) ) { return; } $editor_link = self::get_editor_link( $post ); $old_stock = get_post_meta( $object_id, '_stock', true ); $product_status = get_post_meta( $object_id, '_stock_status', true ); $new_stock = ( empty( $meta_value ) ) ? 0 : $meta_value; $old_stock = ( ! empty( $old_stock ) ) ? $old_stock : 0; Alert_Manager::trigger_event_if( 9106, array( 'PostID' => $post->ID, 'SKU' => esc_attr( WooCommerce_Sensor::get_product_sku( $post->ID ) ), 'ProductTitle' => $post->post_title ?? \esc_html__( 'No product title found', 'wp-security-audit-log' ), 'ProductStatus' => ! $product_status ? $post->post_status : $product_status, 'PostStatus' => ! $product_status ? $post->post_status : $product_status, 'OldValue' => $old_stock, 'NewValue' => $meta_value, $editor_link['name'] => $editor_link['value'], ), array( __CLASS__, 'must_not_edit_or_order' ) ); } } /** * Check WooCommerce product changes before save. * * Support for Admin Columns Pro plugin and its add-on. * * @param WC_Product $product - WooCommerce product object. * @param array $data_store - Data store array. * * @since 4.6.0 */ public static function check_product_changes_before_save( $product, $data_store ) { // If we reach here without any POST data, the change is beong done directly so lets update the old item data in case anything changes. if ( ! isset( $_POST['action'] ) ) { // Update held data. $product_id = $product->get_id(); WooCommerce_Sensor::set_old_product( wc_get_product( $product_id ) ); WooCommerce_Sensor::set_old_post( get_post( $product_id ) ); WooCommerce_Sensor::set_old_data( self::get_product_data( WooCommerce_Sensor::get_old_product() ) ); WooCommerce_Sensor::set_old_meta_data( get_post_meta( $product_id, '', false ) ); } if ( isset( $_POST['_ajax_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_ajax_nonce'] ) ), 'ac-ajax' ) || isset( $_REQUEST['rest_route'] ) && ( '/wc/v3/products/batch' === $_REQUEST['rest_route'] ) ) { // Get product data. $product_id = $product->get_id(); $old_product = wc_get_product( $product_id ); $old_product_post = get_post( $product_id ); // Check for stock quantity changes. WooCommerce_Sensor_Helper::check_stock_quantity_change( $old_product_post, $old_product->get_stock_quantity(), $product->get_stock_quantity() ); } if ( isset( $_REQUEST['action'] ) && ( 'woocommerce_feature_product' === $_REQUEST['action'] ) && check_admin_referer( 'woocommerce-feature-product' ) ) { $product_id = $product->get_id(); $product_post = get_post( $product_id ); $editor_link = self::get_editor_link( $product_post ); Alert_Manager::trigger_event( 9043, array( 'PostID' => esc_attr( $product->get_id() ), 'SKU' => esc_attr( WooCommerce_Sensor::get_product_sku( $product_id ) ), 'ProductTitle' => sanitize_text_field( $product_post->post_title ), 'ProductStatus' => sanitize_text_field( $product_post->post_status ), 'PostStatus' => sanitize_text_field( $product_post->post_status ), 'EventType' => $product->get_featured() ? 'enabled' : 'disabled', $editor_link['name'] => $editor_link['value'], ) ); } } /** * Ensure not a repated order event. * * @return bool - If was repeated or not. * * @since 4.6.0 */ public static function must_not_edit_or_order() { if ( Alert_Manager::will_or_has_triggered( 9019 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9018 ) ) { return false; } if ( Alert_Manager::will_or_has_triggered( 9105 ) ) { return false; } return true; } } } /** // Get Order ID and Key $order->get_id(); $order->get_order_key(); // Get Order Totals $0.00 $order->get_formatted_order_total(); $order->get_cart_tax(); $order->get_currency(); $order->get_discount_tax(); $order->get_discount_to_display(); $order->get_discount_total(); $order->get_total_fees(); $order->get_formatted_line_subtotal(); $order->get_shipping_tax(); $order->get_shipping_total(); $order->get_subtotal(); $order->get_subtotal_to_display(); $order->get_tax_location(); $order->get_tax_totals(); $order->get_taxes(); $order->get_total(); $order->get_total_discount(); $order->get_total_tax(); $order->get_total_refunded(); $order->get_total_tax_refunded(); $order->get_total_shipping_refunded(); $order->get_item_count_refunded(); $order->get_total_qty_refunded(); $order->get_qty_refunded_for_item(); $order->get_total_refunded_for_item(); $order->get_tax_refunded_for_item(); $order->get_total_tax_refunded_by_rate_id(); $order->get_remaining_refund_amount(); // Get and Loop Over Order Items foreach ( $order->get_items() as $item_id => $item ) { $product_id = $item->get_product_id(); $variation_id = $item->get_variation_id(); $product = $item->get_product(); // see link above to get $product info $product_name = $item->get_name(); $quantity = $item->get_quantity(); $subtotal = $item->get_subtotal(); $total = $item->get_total(); $tax = $item->get_subtotal_tax(); $tax_class = $item->get_tax_class(); $tax_status = $item->get_tax_status(); $allmeta = $item->get_meta_data(); $somemeta = $item->get_meta( '_whatever', true ); $item_type = $item->get_type(); // e.g. "line_item", "fee" } // Other Secondary Items Stuff $order->get_items_key(); $order->get_items_tax_classes(); $order->get_item_count(); $order->get_item_total(); $order->get_downloadable_items(); $order->get_coupon_codes(); // Get Order Lines $order->get_line_subtotal(); $order->get_line_tax(); $order->get_line_total(); // Get Order Shipping $order->get_shipping_method(); $order->get_shipping_methods(); $order->get_shipping_to_display(); // Get Order Dates $order->get_date_created(); $order->get_date_modified(); $order->get_date_completed(); $order->get_date_paid(); // Get Order User, Billing & Shipping Addresses $order->get_customer_id(); $order->get_user_id(); $order->get_user(); $order->get_customer_ip_address(); $order->get_customer_user_agent(); $order->get_created_via(); $order->get_customer_note(); $order->get_address_prop(); $order->get_billing_first_name(); $order->get_billing_last_name(); $order->get_billing_company(); $order->get_billing_address_1(); $order->get_billing_address_2(); $order->get_billing_city(); $order->get_billing_state(); $order->get_billing_postcode(); $order->get_billing_country(); $order->get_billing_email(); $order->get_billing_phone(); $order->get_shipping_first_name(); $order->get_shipping_last_name(); $order->get_shipping_company(); $order->get_shipping_address_1(); $order->get_shipping_address_2(); $order->get_shipping_city(); $order->get_shipping_state(); $order->get_shipping_postcode(); $order->get_shipping_country(); $order->get_address(); $order->get_shipping_address_map_url(); $order->get_formatted_billing_full_name(); $order->get_formatted_shipping_full_name(); $order->get_formatted_billing_address(); $order->get_formatted_shipping_address(); // Get Order Payment Details $order->get_payment_method(); $order->get_payment_method_title(); $order->get_transaction_id(); // Get Order URLs $order->get_checkout_payment_url(); $order->get_checkout_order_received_url(); $order->get_cancel_order_url(); $order->get_cancel_order_url_raw(); $order->get_cancel_endpoint(); $order->get_view_order_url(); $order->get_edit_order_url(); // Get Order Status $order->get_status(); // Get Thank You Page URL $order->get_checkout_order_received_url(); */