$column_info ) {
$new_columns[ $column_name ] = $column_info;
if ( 'order_status' === $column_name ) {
$new_columns[ $column_name ] = 'Order Status';
$new_columns['fulfillment_status'] = __( 'Fulfillment Status', 'woocommerce' );
$new_columns['shipment_tracking'] = __( 'Shipment Tracking', 'woocommerce' );
$new_columns['shipment_provider'] = __( 'Shipment Provider', 'woocommerce' );
}
}
return $new_columns;
}
/**
* Render the fulfillment column row data for legacy order list support.
*
* @param string $column_name The name of the column.
*/
public function render_fulfillment_column_row_data_legacy( string $column_name ) {
global $the_order;
// This method is kept for legacy support, but the main rendering logic is now in render_fulfillment_column_row_data.
return $this->render_fulfillment_column_row_data( $column_name, $the_order );
}
/**
* Render the fulfillment status column.
*
* @param string $column_name The name of the column.
* @param WC_Order $order The order object.
*/
public function render_fulfillment_column_row_data( string $column_name, WC_Order $order ) {
$fulfillments = $this->maybe_read_fulfillments( $order );
// Render the column data based on the column name.
switch ( $column_name ) {
case 'fulfillment_status':
$this->render_order_fulfillment_status_column_row_data( $order );
break;
case 'shipment_tracking':
$this->render_shipment_tracking_column_row_data( $order, $fulfillments );
break;
case 'shipment_provider':
$this->render_shipment_provider_column_row_data( $order, $fulfillments );
break;
}
}
/**
* Render the fulfillment status column row data.
*
* @param WC_Order $order The order object.
*/
private function render_order_fulfillment_status_column_row_data( WC_Order $order ) {
$order_fulfillment_status = $order->meta_exists( '_fulfillment_status' ) ? $order->get_meta( '_fulfillment_status', true ) : 'no_fulfillments';
echo "
Shipment %1$s was shipped on %2$s', 'woocommerce' ), 'b' ),
intval( $index ) + 1,
esc_html(
gmdate(
'F j, Y',
strtotime(
$fulfillment->get_date_fulfilled() // Get the fulfilled date.
?? $fulfillment->get_date_updated() // Fallback to the updated date if fulfilled date is not set.
)
)
)
);
?>
';
// Get the fulfillment status for the order.
$fulfillments = $this->maybe_read_fulfillments( $order );
$order_fulfillment_status = FulfillmentUtils::calculate_order_fulfillment_status( $order, $fulfillments );
// Render order status badge.
$order_status = $order->get_status();
echo '' . esc_html( wc_get_order_status_name( $order_status ) ) . '';
// Render fulfillment status badge.
$this->render_order_fulfillment_status_badge( $order, $order_fulfillment_status );
echo '';
}
/**
* Loads the fulfillments scripts and styles.
*/
public function load_components() {
if ( ! $this->should_render_fulfillment_drawer() ) {
return;
}
$this->register_fulfillments_assets();
$this->load_fulfillments_js_settings();
}
/**
* Register the fulfillment assets.
*/
protected function register_fulfillments_assets() {
WCAdminAssets::register_style( 'fulfillments', 'style', array( 'wp-components' ) );
WCAdminAssets::register_script( 'wp-admin-scripts', 'fulfillments', true );
}
/**
* Load the fulfillments JS settings.
*
* @return void
*/
protected function load_fulfillments_js_settings() {
$fulfillment_settings = array(
'providers' => FulfillmentUtils::get_shipping_providers_object(),
'currency_symbols' => get_woocommerce_currency_symbols(),
'fulfillment_statuses' => FulfillmentUtils::get_fulfillment_statuses(),
'order_fulfillment_statuses' => FulfillmentUtils::get_order_fulfillment_statuses(),
);
wp_localize_script( 'wc-admin-fulfillments', 'wcFulfillmentSettings', $fulfillment_settings );
}
/**
* Render the fulfillment filters in the orders table.
*/
public function render_fulfillment_filters() {
if ( ! self::should_render_fulfillment_drawer() ) {
return;
}
?>
render_fulfillment_filters();
}
/**
* Apply the fulfillment status filter to the orders list.
*
* @param array $args The query arguments for the orders list.
* @return array The modified query arguments.
*/
public function filter_orders_list_table_query( $args ) {
// This is a read-only filter on the admin orders table, so nonce verification is not required.
// phpcs:ignore WordPress.Security.NonceVerification
if ( isset( $_GET['fulfillment_status'] ) && ! empty( $_GET['fulfillment_status'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification
$fulfillment_status = sanitize_text_field( wp_unslash( $_GET['fulfillment_status'] ) );
// Ensure the fulfillment status is one of the allowed values.
if ( FulfillmentUtils::is_valid_order_fulfillment_status( $fulfillment_status ) ) {
if ( ! isset( $args['meta_query'] ) ) {
$args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
}
if ( 'no_fulfillments' === $fulfillment_status ) {
// If the status is 'no_fulfillments', we need to check for orders that have no fulfillments.
$args['meta_query'][] = array(
'relation' => 'OR',
array(
'key' => '_fulfillment_status',
'compare' => 'NOT EXISTS',
),
);
} else {
$args['meta_query'][] = array(
'key' => '_fulfillment_status',
'value' => $fulfillment_status,
'compare' => '=',
);
}
}
}
return $args;
}
/**
* Filter the legacy orders list query to include fulfillment status.
*
* @param \WP_Query $query The WP_Query object.
*/
public function filter_legacy_orders_list_query( $query ) {
if (
is_admin()
&& $query->is_main_query()
&& $query->get( 'post_type' ) === 'shop_order'
&& isset( $_GET['fulfillment_status'] ) && ! empty( $_GET['fulfillment_status'] ) // phpcs:ignore WordPress.Security.NonceVerification
) {
$status = sanitize_text_field( wp_unslash( $_GET['fulfillment_status'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
// Ensure the fulfillment status is one of the allowed values.
if ( FulfillmentUtils::is_valid_order_fulfillment_status( $status ) ) {
$query->set(
'meta_query',
'no_fulfillments' === $status ?
array(
'relation' => 'OR',
array(
'key' => '_fulfillment_status',
'compare' => 'NOT EXISTS',
),
) :
array(
array(
'key' => '_fulfillment_status',
'value' => $status,
'compare' => '=',
),
)
);
}
}
}
/**
* Check if the fulfillment drawer should be rendered (admin only).
*
* @return bool True if the fulfillment drawer should be rendered, false otherwise.
*/
protected function should_render_fulfillment_drawer(): bool {
if ( ! is_admin() ) {
return false;
}
if ( ! function_exists( 'get_current_screen' ) ) {
return false;
}
$current_screen = get_current_screen();
if ( ! $current_screen || ! $current_screen->id ) {
return false;
}
return 'woocommerce_page_wc-orders' === $current_screen->id // HPOS screen.
|| 'edit-shop_order' === $current_screen->id // Legacy screen.
|| 'shop_order' === $current_screen->id; // Order details screen (legacy).
}
/**
* Fetches the fulfillments for the given order, caching them to avoid multiple fetches.
*
* @param WC_Order $order The order object.
*
* @return array The fulfillments for the order.
*/
private function maybe_read_fulfillments( WC_Order $order ): array {
// Check if we've already fetched the fulfillments for this order.
if ( isset( $this->fulfillments_cache[ $order->get_id() ] ) ) {
return $this->fulfillments_cache[ $order->get_id() ];
}
// If not, fetch them and cache them.
$data_store = wc_get_container()->get( FulfillmentsDataStore::class );
$fulfillments = $data_store->read_fulfillments( WC_Order::class, '' . $order->get_id() );
$this->fulfillments_cache[ $order->get_id() ] = $fulfillments;
return $fulfillments;
}
}