class-wc-admin-list-table-orders.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. <?php
  2. /**
  3. * List tables: orders.
  4. *
  5. * @package WooCommerce\admin
  6. * @version 3.3.0
  7. */
  8. if ( ! defined( 'ABSPATH' ) ) {
  9. exit;
  10. }
  11. if ( class_exists( 'WC_Admin_List_Table_Orders', false ) ) {
  12. return;
  13. }
  14. if ( ! class_exists( 'WC_Admin_List_Table', false ) ) {
  15. include_once 'abstract-class-wc-admin-list-table.php';
  16. }
  17. /**
  18. * WC_Admin_List_Table_Orders Class.
  19. */
  20. class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
  21. /**
  22. * Post type.
  23. *
  24. * @var string
  25. */
  26. protected $list_table_type = 'shop_order';
  27. /**
  28. * Constructor.
  29. */
  30. public function __construct() {
  31. parent::__construct();
  32. add_action( 'admin_notices', array( $this, 'bulk_admin_notices' ) );
  33. add_action( 'admin_footer', array( $this, 'order_preview_template' ) );
  34. add_filter( 'get_search_query', array( $this, 'search_label' ) );
  35. add_filter( 'query_vars', array( $this, 'add_custom_query_var' ) );
  36. add_action( 'parse_query', array( $this, 'search_custom_fields' ) );
  37. }
  38. /**
  39. * Render blank state.
  40. */
  41. protected function render_blank_state() {
  42. echo '<div class="woocommerce-BlankState">';
  43. echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'When you receive a new order, it will appear here.', 'woocommerce' ) . '</h2>';
  44. echo '<a class="woocommerce-BlankState-cta button-primary button" target="_blank" href="https://docs.woocommerce.com/document/managing-orders/?utm_source=blankslate&utm_medium=product&utm_content=ordersdoc&utm_campaign=woocommerceplugin">' . esc_html__( 'Learn more about orders', 'woocommerce' ) . '</a>';
  45. echo '</div>';
  46. }
  47. /**
  48. * Define primary column.
  49. *
  50. * @return string
  51. */
  52. protected function get_primary_column() {
  53. return 'order_number';
  54. }
  55. /**
  56. * Get row actions to show in the list table.
  57. *
  58. * @param array $actions Array of actions.
  59. * @param WP_Post $post Current post object.
  60. * @return array
  61. */
  62. protected function get_row_actions( $actions, $post ) {
  63. return array();
  64. }
  65. /**
  66. * Define hidden columns.
  67. *
  68. * @return array
  69. */
  70. protected function define_hidden_columns() {
  71. return array(
  72. 'shipping_address',
  73. 'billing_address',
  74. 'wc_actions',
  75. );
  76. }
  77. /**
  78. * Define which columns are sortable.
  79. *
  80. * @param array $columns Existing columns.
  81. * @return array
  82. */
  83. public function define_sortable_columns( $columns ) {
  84. $custom = array(
  85. 'order_number' => 'ID',
  86. 'order_total' => 'order_total',
  87. 'order_date' => 'date',
  88. );
  89. unset( $columns['comments'] );
  90. return wp_parse_args( $custom, $columns );
  91. }
  92. /**
  93. * Define which columns to show on this screen.
  94. *
  95. * @param array $columns Existing columns.
  96. * @return array
  97. */
  98. public function define_columns( $columns ) {
  99. $show_columns = array();
  100. $show_columns['cb'] = $columns['cb'];
  101. $show_columns['order_number'] = __( 'Order', 'woocommerce' );
  102. $show_columns['order_date'] = __( 'Date', 'woocommerce' );
  103. $show_columns['order_status'] = __( 'Status', 'woocommerce' );
  104. $show_columns['billing_address'] = __( 'Billing', 'woocommerce' );
  105. $show_columns['shipping_address'] = __( 'Ship to', 'woocommerce' );
  106. $show_columns['order_total'] = __( 'Total', 'woocommerce' );
  107. $show_columns['wc_actions'] = __( 'Actions', 'woocommerce' );
  108. wp_enqueue_script( 'wc-orders' );
  109. return $show_columns;
  110. }
  111. /**
  112. * Define bulk actions.
  113. *
  114. * @param array $actions Existing actions.
  115. * @return array
  116. */
  117. public function define_bulk_actions( $actions ) {
  118. if ( isset( $actions['edit'] ) ) {
  119. unset( $actions['edit'] );
  120. }
  121. $actions['mark_processing'] = __( 'Change status to processing', 'woocommerce' );
  122. $actions['mark_on-hold'] = __( 'Change status to on-hold', 'woocommerce' );
  123. $actions['mark_completed'] = __( 'Change status to completed', 'woocommerce' );
  124. $actions['remove_personal_data'] = __( 'Remove personal data', 'woocommerce' );
  125. return $actions;
  126. }
  127. /**
  128. * Pre-fetch any data for the row each column has access to it. the_order global is there for bw compat.
  129. *
  130. * @param int $post_id Post ID being shown.
  131. */
  132. protected function prepare_row_data( $post_id ) {
  133. global $the_order;
  134. if ( empty( $this->object ) || $this->object->get_id() !== $post_id ) {
  135. $this->object = wc_get_order( $post_id );
  136. $the_order = $this->object;
  137. }
  138. }
  139. /**
  140. * Render columm: order_number.
  141. */
  142. protected function render_order_number_column() {
  143. $buyer = '';
  144. if ( $this->object->get_billing_first_name() || $this->object->get_billing_last_name() ) {
  145. /* translators: 1: first name 2: last name */
  146. $buyer = trim( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->object->get_billing_first_name(), $this->object->get_billing_last_name() ) );
  147. } elseif ( $this->object->get_billing_company() ) {
  148. $buyer = trim( $this->object->get_billing_company() );
  149. } elseif ( $this->object->get_customer_id() ) {
  150. $user = get_user_by( 'id', $this->object->get_customer_id() );
  151. $buyer = ucwords( $user->display_name );
  152. }
  153. if ( $this->object->get_status() === 'trash' ) {
  154. echo '<strong>#' . esc_attr( $this->object->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong>';
  155. } else {
  156. echo '<a href="#" class="order-preview" data-order-id="' . absint( $this->object->get_id() ) . '" title="' . esc_attr( __( 'Preview', 'woocommerce' ) ) . '">' . esc_html( __( 'Preview', 'woocommerce' ) ) . '</a>';
  157. echo '<a href="' . esc_url( admin_url( 'post.php?post=' . absint( $this->object->get_id() ) ) . '&action=edit' ) . '" class="order-view"><strong>#' . esc_attr( $this->object->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong></a>';
  158. }
  159. }
  160. /**
  161. * Render columm: order_status.
  162. */
  163. protected function render_order_status_column() {
  164. $tooltip = '';
  165. $comment_count = get_comment_count( $this->object->get_id() );
  166. $approved_comments_count = absint( $comment_count['approved'] );
  167. if ( $approved_comments_count ) {
  168. $latest_notes = wc_get_order_notes(
  169. array(
  170. 'order_id' => $this->object->get_id(),
  171. 'limit' => 1,
  172. 'orderby' => 'date_created_gmt',
  173. )
  174. );
  175. $latest_note = current( $latest_notes );
  176. if ( isset( $latest_note->content ) && 1 === $approved_comments_count ) {
  177. $tooltip = wc_sanitize_tooltip( $latest_note->content );
  178. } elseif ( isset( $latest_note->content ) ) {
  179. /* translators: %d: notes count */
  180. $tooltip = wc_sanitize_tooltip( $latest_note->content . '<br/><small style="display:block">' . sprintf( _n( 'Plus %d other note', 'Plus %d other notes', ( $approved_comments_count - 1 ), 'woocommerce' ), $approved_comments_count - 1 ) . '</small>' );
  181. } else {
  182. /* translators: %d: notes count */
  183. $tooltip = wc_sanitize_tooltip( sprintf( _n( '%d note', '%d notes', $approved_comments_count, 'woocommerce' ), $approved_comments_count ) );
  184. }
  185. }
  186. if ( $tooltip ) {
  187. printf( '<mark class="order-status %s tips" data-tip="%s"><span>%s</span></mark>', esc_attr( sanitize_html_class( 'status-' . $this->object->get_status() ) ), wp_kses_post( $tooltip ), esc_html( wc_get_order_status_name( $this->object->get_status() ) ) );
  188. } else {
  189. printf( '<mark class="order-status %s"><span>%s</span></mark>', esc_attr( sanitize_html_class( 'status-' . $this->object->get_status() ) ), esc_html( wc_get_order_status_name( $this->object->get_status() ) ) );
  190. }
  191. }
  192. /**
  193. * Render columm: order_date.
  194. */
  195. protected function render_order_date_column() {
  196. $order_timestamp = $this->object->get_date_created()->getTimestamp();
  197. // Check if the order was created within the last 24 hours, and not in the future.
  198. if ( $order_timestamp > strtotime( '-1 day', current_time( 'timestamp', true ) ) && $order_timestamp <= current_time( 'timestamp', true ) ) {
  199. $show_date = sprintf(
  200. /* translators: %s: human-readable time difference */
  201. _x( '%s ago', '%s = human-readable time difference', 'woocommerce' ),
  202. human_time_diff( $this->object->get_date_created()->getTimestamp(), current_time( 'timestamp', true ) )
  203. );
  204. } else {
  205. $show_date = $this->object->get_date_created()->date_i18n( apply_filters( 'woocommerce_admin_order_date_format', __( 'M j, Y', 'woocommerce' ) ) );
  206. }
  207. printf(
  208. '<time datetime="%1$s" title="%2$s">%3$s</time>',
  209. esc_attr( $this->object->get_date_created()->date( 'c' ) ),
  210. esc_html( $this->object->get_date_created()->date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ),
  211. esc_html( $show_date )
  212. );
  213. }
  214. /**
  215. * Render columm: order_total.
  216. */
  217. protected function render_order_total_column() {
  218. if ( $this->object->get_payment_method_title() ) {
  219. /* translators: %s: method */
  220. echo '<span class="tips" data-tip="' . esc_attr( sprintf( __( 'via %s', 'woocommerce' ), $this->object->get_payment_method_title() ) ) . '">' . wp_kses_post( $this->object->get_formatted_order_total() ) . '</span>';
  221. } else {
  222. echo wp_kses_post( $this->object->get_formatted_order_total() );
  223. }
  224. }
  225. /**
  226. * Render columm: wc_actions.
  227. */
  228. protected function render_wc_actions_column() {
  229. echo '<p>';
  230. do_action( 'woocommerce_admin_order_actions_start', $this->object );
  231. $actions = array();
  232. if ( $this->object->has_status( array( 'pending', 'on-hold' ) ) ) {
  233. $actions['processing'] = array(
  234. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=processing&order_id=' . $this->object->get_id() ), 'woocommerce-mark-order-status' ),
  235. 'name' => __( 'Processing', 'woocommerce' ),
  236. 'action' => 'processing',
  237. );
  238. }
  239. if ( $this->object->has_status( array( 'pending', 'on-hold', 'processing' ) ) ) {
  240. $actions['complete'] = array(
  241. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=completed&order_id=' . $this->object->get_id() ), 'woocommerce-mark-order-status' ),
  242. 'name' => __( 'Complete', 'woocommerce' ),
  243. 'action' => 'complete',
  244. );
  245. }
  246. $actions = apply_filters( 'woocommerce_admin_order_actions', $actions, $this->object );
  247. echo wc_render_action_buttons( $actions ); // WPCS: XSS ok.
  248. do_action( 'woocommerce_admin_order_actions_end', $this->object );
  249. echo '</p>';
  250. }
  251. /**
  252. * Render columm: billing_address.
  253. */
  254. protected function render_billing_address_column() {
  255. $address = $this->object->get_formatted_billing_address();
  256. if ( $address ) {
  257. echo esc_html( preg_replace( '#<br\s*/?>#i', ', ', $address ) );
  258. if ( $this->object->get_payment_method() ) {
  259. /* translators: %s: payment method */
  260. echo '<span class="description">' . sprintf( __( 'via %s', 'woocommerce' ), esc_html( $this->object->get_payment_method_title() ) ) . '</span>'; // WPCS: XSS ok.
  261. }
  262. } else {
  263. echo '&ndash;';
  264. }
  265. }
  266. /**
  267. * Render columm: shipping_address.
  268. */
  269. protected function render_shipping_address_column() {
  270. $address = $this->object->get_formatted_shipping_address();
  271. if ( $address ) {
  272. echo '<a target="_blank" href="' . esc_url( $this->object->get_shipping_address_map_url() ) . '">' . esc_html( preg_replace( '#<br\s*/?>#i', ', ', $address ) ) . '</a>';
  273. if ( $this->object->get_shipping_method() ) {
  274. /* translators: %s: shipping method */
  275. echo '<span class="description">' . sprintf( __( 'via %s', 'woocommerce' ), esc_html( $this->object->get_shipping_method() ) ) . '</span>'; // WPCS: XSS ok.
  276. }
  277. } else {
  278. echo '&ndash;';
  279. }
  280. }
  281. /**
  282. * Template for order preview.
  283. *
  284. * @since 3.3.0
  285. */
  286. public function order_preview_template() {
  287. ?>
  288. <script type="text/template" id="tmpl-wc-modal-view-order">
  289. <div class="wc-backbone-modal wc-order-preview">
  290. <div class="wc-backbone-modal-content">
  291. <section class="wc-backbone-modal-main" role="main">
  292. <header class="wc-backbone-modal-header">
  293. <mark class="order-status status-{{ data.status }}"><span>{{ data.status_name }}</span></mark>
  294. <?php /* translators: %s: order ID */ ?>
  295. <h1><?php echo esc_html( sprintf( __( 'Order #%s', 'woocommerce' ), '{{ data.order_number }}' ) ); ?></h1>
  296. <button class="modal-close modal-close-link dashicons dashicons-no-alt">
  297. <span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
  298. </button>
  299. </header>
  300. <article>
  301. <?php do_action( 'woocommerce_admin_order_preview_start' ); ?>
  302. <div class="wc-order-preview-addresses">
  303. <div class="wc-order-preview-address">
  304. <h2><?php esc_html_e( 'Billing details', 'woocommerce' ); ?></h2>
  305. {{{ data.formatted_billing_address }}}
  306. <# if ( data.data.billing.email ) { #>
  307. <strong><?php esc_html_e( 'Email', 'woocommerce' ); ?></strong>
  308. <a href="mailto:{{ data.data.billing.email }}">{{ data.data.billing.email }}</a>
  309. <# } #>
  310. <# if ( data.data.billing.phone ) { #>
  311. <strong><?php esc_html_e( 'Phone', 'woocommerce' ); ?></strong>
  312. <a href="tel:{{ data.data.billing.phone }}">{{ data.data.billing.phone }}</a>
  313. <# } #>
  314. <# if ( data.payment_via ) { #>
  315. <strong><?php esc_html_e( 'Payment via', 'woocommerce' ); ?></strong>
  316. {{{ data.payment_via }}}
  317. <# } #>
  318. </div>
  319. <# if ( data.needs_shipping ) { #>
  320. <div class="wc-order-preview-address">
  321. <h2><?php esc_html_e( 'Shipping details', 'woocommerce' ); ?></h2>
  322. <# if ( data.ship_to_billing ) { #>
  323. {{{ data.formatted_billing_address }}}
  324. <# } else { #>
  325. <a href="{{ data.shipping_address_map_url }}" target="_blank">{{{ data.formatted_shipping_address }}}</a>
  326. <# } #>
  327. <# if ( data.shipping_via ) { #>
  328. <strong><?php esc_html_e( 'Shipping method', 'woocommerce' ); ?></strong>
  329. {{ data.shipping_via }}
  330. <# } #>
  331. </div>
  332. <# } #>
  333. <# if ( data.data.customer_note ) { #>
  334. <div class="wc-order-preview-note">
  335. <strong><?php esc_html_e( 'Note', 'woocommerce' ); ?></strong>
  336. {{ data.data.customer_note }}
  337. </div>
  338. <# } #>
  339. </div>
  340. {{{ data.item_html }}}
  341. <?php do_action( 'woocommerce_admin_order_preview_end' ); ?>
  342. </article>
  343. <footer>
  344. <div class="inner">
  345. {{{ data.actions_html }}}
  346. <a class="button button-primary button-large" aria-label="<?php esc_attr_e( 'Edit this order', 'woocommerce' ); ?>" href="<?php echo esc_url( admin_url( 'post.php?action=edit' ) ); ?>&post={{ data.data.id }}"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
  347. </div>
  348. </footer>
  349. </section>
  350. </div>
  351. </div>
  352. <div class="wc-backbone-modal-backdrop modal-close"></div>
  353. </script>
  354. <?php
  355. }
  356. /**
  357. * Get items to display in the preview as HTML.
  358. *
  359. * @param WC_Order $order Order object.
  360. * @return string
  361. */
  362. public static function get_order_preview_item_html( $order ) {
  363. $hidden_order_itemmeta = apply_filters(
  364. 'woocommerce_hidden_order_itemmeta', array(
  365. '_qty',
  366. '_tax_class',
  367. '_product_id',
  368. '_variation_id',
  369. '_line_subtotal',
  370. '_line_subtotal_tax',
  371. '_line_total',
  372. '_line_tax',
  373. 'method_id',
  374. 'cost',
  375. )
  376. );
  377. $line_items = apply_filters( 'woocommerce_admin_order_preview_line_items', $order->get_items(), $order );
  378. $columns = apply_filters(
  379. 'woocommerce_admin_order_preview_line_item_columns', array(
  380. 'product' => __( 'Product', 'woocommerce' ),
  381. 'quantity' => __( 'Quantity', 'woocommerce' ),
  382. 'tax' => __( 'Tax', 'woocommerce' ),
  383. 'total' => __( 'Total', 'woocommerce' ),
  384. ), $order
  385. );
  386. if ( ! wc_tax_enabled() ) {
  387. unset( $columns['tax'] );
  388. }
  389. $html = '
  390. <div class="wc-order-preview-table-wrapper">
  391. <table cellspacing="0" class="wc-order-preview-table">
  392. <thead>
  393. <tr>';
  394. foreach ( $columns as $column => $label ) {
  395. $html .= '<th class="wc-order-preview-table__column--' . esc_attr( $column ) . '">' . esc_html( $label ) . '</th>';
  396. }
  397. $html .= '
  398. </tr>
  399. </thead>
  400. <tbody>';
  401. foreach ( $line_items as $item_id => $item ) {
  402. $product_object = is_callable( array( $item, 'get_product' ) ) ? $item->get_product() : null;
  403. $row_class = apply_filters( 'woocommerce_admin_html_order_preview_item_class', '', $item, $order );
  404. $html .= '<tr class="wc-order-preview-table__item wc-order-preview-table__item--' . esc_attr( $item_id ) . ( $row_class ? ' ' . esc_attr( $row_class ) : '' ) . '">';
  405. foreach ( $columns as $column => $label ) {
  406. $html .= '<td class="wc-order-preview-table__column--' . esc_attr( $column ) . '">';
  407. switch ( $column ) {
  408. case 'product':
  409. $html .= wp_kses_post( $item->get_name() );
  410. if ( $product_object ) {
  411. $html .= '<div class="wc-order-item-sku">' . esc_html( $product_object->get_sku() ) . '</div>';
  412. }
  413. $meta_data = $item->get_formatted_meta_data( '' );
  414. if ( $meta_data ) {
  415. $html .= '<table cellspacing="0" class="wc-order-item-meta">';
  416. foreach ( $meta_data as $meta_id => $meta ) {
  417. if ( in_array( $meta->key, $hidden_order_itemmeta, true ) ) {
  418. continue;
  419. }
  420. $html .= '<tr><th>' . wp_kses_post( $meta->display_key ) . ':</th><td>' . wp_kses_post( force_balance_tags( $meta->display_value ) ) . '</td></tr>';
  421. }
  422. $html .= '</table>';
  423. }
  424. break;
  425. case 'quantity':
  426. $html .= esc_html( $item->get_quantity() );
  427. break;
  428. case 'tax':
  429. $html .= wc_price( $item->get_total_tax(), array( 'currency' => $order->get_currency() ) );
  430. break;
  431. case 'total':
  432. $html .= wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) );
  433. break;
  434. default:
  435. $html .= apply_filters( 'woocommerce_admin_order_preview_line_item_column_' . sanitize_key( $column ), '', $item, $item_id, $order );
  436. break;
  437. }
  438. $html .= '</td>';
  439. }
  440. $html .= '</tr>';
  441. }
  442. $html .= '
  443. </tbody>
  444. </table>
  445. </div>';
  446. return $html;
  447. }
  448. /**
  449. * Get actions to display in the preview as HTML.
  450. *
  451. * @param WC_Order $order Order object.
  452. * @return string
  453. */
  454. public static function get_order_preview_actions_html( $order ) {
  455. $actions = array();
  456. $status_actions = array();
  457. if ( $order->has_status( array( 'pending' ) ) ) {
  458. $status_actions['on-hold'] = array(
  459. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=on-hold&order_id=' . $order->get_id() ), 'woocommerce-mark-order-status' ),
  460. 'name' => __( 'On-hold', 'woocommerce' ),
  461. 'title' => __( 'Change order status to on-hold', 'woocommerce' ),
  462. 'action' => 'on-hold',
  463. );
  464. }
  465. if ( $order->has_status( array( 'pending', 'on-hold' ) ) ) {
  466. $status_actions['processing'] = array(
  467. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=processing&order_id=' . $order->get_id() ), 'woocommerce-mark-order-status' ),
  468. 'name' => __( 'Processing', 'woocommerce' ),
  469. 'title' => __( 'Change order status to processing', 'woocommerce' ),
  470. 'action' => 'processing',
  471. );
  472. }
  473. if ( $order->has_status( array( 'pending', 'on-hold', 'processing' ) ) ) {
  474. $status_actions['complete'] = array(
  475. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=completed&order_id=' . $order->get_id() ), 'woocommerce-mark-order-status' ),
  476. 'name' => __( 'Completed', 'woocommerce' ),
  477. 'title' => __( 'Change order status to completed', 'woocommerce' ),
  478. 'action' => 'complete',
  479. );
  480. }
  481. if ( $status_actions ) {
  482. $actions['status'] = array(
  483. 'group' => __( 'Change status: ', 'woocommerce' ),
  484. 'actions' => $status_actions,
  485. );
  486. }
  487. return wc_render_action_buttons( apply_filters( 'woocommerce_admin_order_preview_actions', $actions, $order ) );
  488. }
  489. /**
  490. * Get order details to send to the ajax endpoint for previews.
  491. *
  492. * @param WC_Order $order Order object.
  493. * @return array
  494. */
  495. public static function order_preview_get_order_details( $order ) {
  496. if ( ! $order ) {
  497. return array();
  498. }
  499. $payment_via = $order->get_payment_method_title();
  500. $payment_method = $order->get_payment_method();
  501. $payment_gateways = WC()->payment_gateways() ? WC()->payment_gateways->payment_gateways() : array();
  502. $transaction_id = $order->get_transaction_id();
  503. if ( $transaction_id ) {
  504. $url = isset( $payment_gateways[ $payment_method ] ) ? $payment_gateways[ $payment_method ]->get_transaction_url( $order ) : false;
  505. if ( $url ) {
  506. $payment_via .= ' (<a href="' . esc_url( $url ) . '" target="_blank">' . esc_html( $transaction_id ) . '</a>)';
  507. } else {
  508. $payment_via .= ' (' . esc_html( $transaction_id ) . ')';
  509. }
  510. }
  511. $billing_address = $order->get_formatted_billing_address();
  512. $shipping_address = $order->get_formatted_shipping_address();
  513. return apply_filters(
  514. 'woocommerce_admin_order_preview_get_order_details', array(
  515. 'data' => $order->get_data(),
  516. 'order_number' => $order->get_order_number(),
  517. 'item_html' => WC_Admin_List_Table_Orders::get_order_preview_item_html( $order ),
  518. 'actions_html' => WC_Admin_List_Table_Orders::get_order_preview_actions_html( $order ),
  519. 'ship_to_billing' => wc_ship_to_billing_address_only(),
  520. 'needs_shipping' => $order->needs_shipping_address(),
  521. 'formatted_billing_address' => $billing_address ? $billing_address : __( 'N/A', 'woocommerce' ),
  522. 'formatted_shipping_address' => $shipping_address ? $shipping_address : __( 'N/A', 'woocommerce' ),
  523. 'shipping_address_map_url' => $order->get_shipping_address_map_url(),
  524. 'payment_via' => $payment_via,
  525. 'shipping_via' => $order->get_shipping_method(),
  526. 'status' => $order->get_status(),
  527. 'status_name' => wc_get_order_status_name( $order->get_status() ),
  528. ), $order
  529. );
  530. }
  531. /**
  532. * Handle bulk actions.
  533. *
  534. * @param string $redirect_to URL to redirect to.
  535. * @param string $action Action name.
  536. * @param array $ids List of ids.
  537. * @return string
  538. */
  539. public function handle_bulk_actions( $redirect_to, $action, $ids ) {
  540. $ids = array_map( 'absint', $ids );
  541. $changed = 0;
  542. if ( 'remove_personal_data' === $action ) {
  543. $report_action = 'removed_personal_data';
  544. foreach ( $ids as $id ) {
  545. $order = wc_get_order( $id );
  546. if ( $order ) {
  547. do_action( 'woocommerce_remove_order_personal_data', $order );
  548. $changed++;
  549. }
  550. }
  551. } elseif ( false !== strpos( $action, 'mark_' ) ) {
  552. $order_statuses = wc_get_order_statuses();
  553. $new_status = substr( $action, 5 ); // Get the status name from action.
  554. $report_action = 'marked_' . $new_status;
  555. // Sanity check: bail out if this is actually not a status, or is not a registered status.
  556. if ( isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
  557. // Initialize payment gateways in case order has hooked status transition actions.
  558. wc()->payment_gateways();
  559. foreach ( $ids as $id ) {
  560. $order = wc_get_order( $id );
  561. $order->update_status( $new_status, __( 'Order status changed by bulk edit:', 'woocommerce' ), true );
  562. do_action( 'woocommerce_order_edit_status', $id, $new_status );
  563. $changed++;
  564. }
  565. }
  566. }
  567. if ( $changed ) {
  568. $redirect_to = add_query_arg(
  569. array(
  570. 'post_type' => $this->list_table_type,
  571. 'bulk_action' => $report_action,
  572. 'changed' => $changed,
  573. 'ids' => join( ',', $ids ),
  574. ), $redirect_to
  575. );
  576. }
  577. return esc_url_raw( $redirect_to );
  578. }
  579. /**
  580. * Show confirmation message that order status changed for number of orders.
  581. */
  582. public function bulk_admin_notices() {
  583. global $post_type, $pagenow;
  584. // Bail out if not on shop order list page.
  585. if ( 'edit.php' !== $pagenow || 'shop_order' !== $post_type || ! isset( $_REQUEST['bulk_action'] ) ) { // WPCS: input var ok, CSRF ok.
  586. return;
  587. }
  588. $order_statuses = wc_get_order_statuses();
  589. $number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0; // WPCS: input var ok, CSRF ok.
  590. $bulk_action = wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) ); // WPCS: input var ok, CSRF ok.
  591. // Check if any status changes happened.
  592. foreach ( $order_statuses as $slug => $name ) {
  593. if ( 'marked_' . str_replace( 'wc-', '', $slug ) === $bulk_action ) { // WPCS: input var ok, CSRF ok.
  594. /* translators: %d: orders count */
  595. $message = sprintf( _n( '%d order status changed.', '%d order statuses changed.', $number, 'woocommerce' ), number_format_i18n( $number ) );
  596. echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
  597. break;
  598. }
  599. }
  600. if ( 'removed_personal_data' === $bulk_action ) { // WPCS: input var ok, CSRF ok.
  601. /* translators: %d: orders count */
  602. $message = sprintf( _n( 'Removed personal data from %d order.', 'Removed personal data from %d orders.', $number, 'woocommerce' ), number_format_i18n( $number ) );
  603. echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
  604. }
  605. }
  606. /**
  607. * See if we should render search filters or not.
  608. */
  609. public function restrict_manage_posts() {
  610. global $typenow;
  611. if ( in_array( $typenow, wc_get_order_types( 'order-meta-boxes' ), true ) ) {
  612. $this->render_filters();
  613. }
  614. }
  615. /**
  616. * Render any custom filters and search inputs for the list table.
  617. */
  618. protected function render_filters() {
  619. $user_string = '';
  620. $user_id = '';
  621. if ( ! empty( $_GET['_customer_user'] ) ) { // WPCS: input var ok.
  622. $user_id = absint( $_GET['_customer_user'] ); // WPCS: input var ok, sanitization ok.
  623. $user = get_user_by( 'id', $user_id );
  624. $user_string = sprintf(
  625. /* translators: 1: user display name 2: user ID 3: user email */
  626. esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce' ),
  627. $user->display_name,
  628. absint( $user->ID ),
  629. $user->user_email
  630. );
  631. }
  632. ?>
  633. <select class="wc-customer-search" name="_customer_user" data-placeholder="<?php esc_attr_e( 'Filter by registered customer', 'woocommerce' ); ?>" data-allow_clear="true">
  634. <option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo wp_kses_post( $user_string ); ?><option>
  635. </select>
  636. <?php
  637. }
  638. /**
  639. * Handle any filters.
  640. *
  641. * @param array $query_vars Query vars.
  642. * @return array
  643. */
  644. public function request_query( $query_vars ) {
  645. global $typenow;
  646. if ( in_array( $typenow, wc_get_order_types( 'order-meta-boxes' ), true ) ) {
  647. return $this->query_filters( $query_vars );
  648. }
  649. return $query_vars;
  650. }
  651. /**
  652. * Handle any custom filters.
  653. *
  654. * @param array $query_vars Query vars.
  655. * @return array
  656. */
  657. protected function query_filters( $query_vars ) {
  658. global $wp_post_statuses;
  659. // Filter the orders by the posted customer.
  660. if ( ! empty( $_GET['_customer_user'] ) ) { // WPCS: input var ok.
  661. // @codingStandardsIgnoreStart
  662. $query_vars['meta_query'] = array(
  663. array(
  664. 'key' => '_customer_user',
  665. 'value' => (int) $_GET['_customer_user'], // WPCS: input var ok, sanitization ok.
  666. 'compare' => '=',
  667. ),
  668. );
  669. // @codingStandardsIgnoreEnd
  670. }
  671. // Sorting.
  672. if ( isset( $query_vars['orderby'] ) ) {
  673. if ( 'order_total' === $query_vars['orderby'] ) {
  674. // @codingStandardsIgnoreStart
  675. $query_vars = array_merge( $query_vars, array(
  676. 'meta_key' => '_order_total',
  677. 'orderby' => 'meta_value_num',
  678. ) );
  679. // @codingStandardsIgnoreEnd
  680. }
  681. }
  682. // Status.
  683. if ( ! isset( $query_vars['post_status'] ) ) {
  684. $post_statuses = wc_get_order_statuses();
  685. foreach ( $post_statuses as $status => $value ) {
  686. if ( isset( $wp_post_statuses[ $status ] ) && false === $wp_post_statuses[ $status ]->show_in_admin_all_list ) {
  687. unset( $post_statuses[ $status ] );
  688. }
  689. }
  690. $query_vars['post_status'] = array_keys( $post_statuses );
  691. }
  692. return $query_vars;
  693. }
  694. /**
  695. * Change the label when searching orders.
  696. *
  697. * @param mixed $query Current search query.
  698. * @return string
  699. */
  700. public function search_label( $query ) {
  701. global $pagenow, $typenow;
  702. if ( 'edit.php' !== $pagenow || 'shop_order' !== $typenow || ! get_query_var( 'shop_order_search' ) || ! isset( $_GET['s'] ) ) { // WPCS: input var ok.
  703. return $query;
  704. }
  705. return wc_clean( wp_unslash( $_GET['s'] ) ); // WPCS: input var ok, sanitization ok.
  706. }
  707. /**
  708. * Query vars for custom searches.
  709. *
  710. * @param mixed $public_query_vars Array of query vars.
  711. * @return array
  712. */
  713. public function add_custom_query_var( $public_query_vars ) {
  714. $public_query_vars[] = 'shop_order_search';
  715. return $public_query_vars;
  716. }
  717. /**
  718. * Search custom fields as well as content.
  719. *
  720. * @param WP_Query $wp Query object.
  721. */
  722. public function search_custom_fields( $wp ) {
  723. global $pagenow;
  724. if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] || ! isset( $_GET['s'] ) ) { // WPCS: input var ok.
  725. return;
  726. }
  727. $post_ids = wc_order_search( wc_clean( wp_unslash( $_GET['s'] ) ) ); // WPCS: input var ok, sanitization ok.
  728. if ( ! empty( $post_ids ) ) {
  729. // Remove "s" - we don't want to search order name.
  730. unset( $wp->query_vars['s'] );
  731. // so we know we're doing this.
  732. $wp->query_vars['shop_order_search'] = true;
  733. // Search by found posts.
  734. $wp->query_vars['post__in'] = array_merge( $post_ids, array( 0 ) );
  735. }
  736. }
  737. }