= 7.1.
*
* @var array
*/
private static $post__in_none = array( 0 );
/**
* Constructor
*/
public function __construct() {
// Subscription list table columns and their content
add_filter( 'manage_edit-shop_subscription_columns', array( $this, 'shop_subscription_columns' ) );
add_filter( 'manage_edit-shop_subscription_sortable_columns', array( $this, 'shop_subscription_sortable_columns' ) );
add_action( 'manage_shop_subscription_posts_custom_column', array( $this, 'render_shop_subscription_columns' ), 2 );
// Bulk actions
add_filter( 'bulk_actions-edit-shop_subscription', array( $this, 'remove_bulk_actions' ) );
add_action( 'admin_print_footer_scripts', array( $this, 'print_bulk_actions_script' ) );
add_action( 'load-edit.php', array( $this, 'parse_bulk_actions' ) );
add_action( 'admin_notices', array( $this, 'bulk_admin_notices' ) );
// Subscription order/filter
add_filter( 'request', array( $this, 'request_query' ) );
// Subscription Search
add_filter( 'get_search_query', array( $this, 'shop_subscription_search_label' ) );
add_filter( 'query_vars', array( $this, 'add_custom_query_var' ) );
add_action( 'parse_query', array( $this, 'shop_subscription_search_custom_fields' ) );
add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
add_action( 'restrict_manage_posts', array( $this, 'restrict_by_product' ) );
add_action( 'restrict_manage_posts', array( $this, 'restrict_by_payment_method' ) );
add_action( 'restrict_manage_posts', array( $this, 'restrict_by_customer' ) );
add_action( 'list_table_primary_column', array( $this, 'list_table_primary_column' ), 10, 2 );
add_filter( 'post_row_actions', array( $this, 'shop_subscription_row_actions' ), 10, 2 );
}
/**
* Modifies the actual SQL that is needed to order by last payment date on subscriptions. Data is pulled from related
* but independent posts, so subqueries are needed. That's something we can't get by filtering the request. This is hooked
* in @see WCS_Admin_Post_Types::request_query function.
*
* @param array $pieces all the pieces of the resulting SQL once WordPress has finished parsing it
* @param WP_Query $query the query object that forms the basis of the SQL
* @return array modified pieces of the SQL query
*/
public function posts_clauses( $pieces, $query ) {
global $wpdb;
if ( ! is_admin() || ! isset( $query->query['post_type'] ) || 'shop_subscription' !== $query->query['post_type'] ) {
return $pieces;
}
// Let's check whether we even have the privileges to do the things we want to do
if ( $this->is_db_user_privileged() ) {
$pieces = self::posts_clauses_high_performance( $pieces );
} else {
$pieces = self::posts_clauses_low_performance( $pieces );
}
$order = strtoupper( $query->query['order'] );
// fields and order are identical in both cases
$pieces['fields'] .= ', COALESCE(lp.last_payment, o.post_date_gmt, 0) as lp';
$pieces['orderby'] = "CAST(lp AS DATETIME) {$order}";
return $pieces;
}
/**
* Check is database user is capable of doing high performance things, such as creating temporary tables,
* indexing them, and then dropping them after.
*
* @return bool
*/
public function is_db_user_privileged() {
$permissions = $this->get_special_database_privileges();
return ( in_array( 'CREATE TEMPORARY TABLES', $permissions ) && in_array( 'INDEX', $permissions ) && in_array( 'DROP', $permissions ) );
}
/**
* Return the privileges a database user has out of CREATE TEMPORARY TABLES, INDEX and DROP. This is so we can use
* these discrete values on a debug page.
*
* @return array
*/
public function get_special_database_privileges() {
global $wpdb;
$permissions = $wpdb->get_col( "SELECT PRIVILEGE_TYPE FROM information_schema.user_privileges WHERE GRANTEE = CONCAT( '''', REPLACE( CURRENT_USER(), '@', '''@''' ), '''' ) AND PRIVILEGE_TYPE IN ('CREATE TEMPORARY TABLES', 'INDEX', 'DROP')" );
return $permissions;
}
/**
* Modifies the query for a slightly faster, yet still pretty slow query in case the user does not have
* the necessary privileges to run
*
* @param $pieces
*
* @return mixed
*/
private function posts_clauses_low_performance( $pieces ) {
global $wpdb;
$pieces['join'] .= "LEFT JOIN
(SELECT
MAX( p.post_date_gmt ) as last_payment,
pm.meta_value
FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '_subscription_renewal'
GROUP BY pm.meta_value) lp
ON {$wpdb->posts}.ID = lp.meta_value
LEFT JOIN {$wpdb->posts} o on {$wpdb->posts}.post_parent = o.ID";
return $pieces;
}
/**
* Modifies the query in such a way that makes use of the CREATE TEMPORARY TABLE, DROP and INDEX
* MySQL privileges.
*
* @param array $pieces
*
* @return array $pieces
*/
private function posts_clauses_high_performance( $pieces ) {
global $wpdb;
// in case multiple users sort at the same time
$session = wp_get_session_token();
$table_name = substr( "{$wpdb->prefix}tmp_{$session}_lastpayment", 0, 64 );
// Let's create a temporary table, drop the previous one, because otherwise this query is hella slow
$wpdb->query( "DROP TEMPORARY TABLE IF EXISTS {$table_name}" );
$wpdb->query(
"CREATE TEMPORARY TABLE {$table_name} (id INT, INDEX USING BTREE (id), last_payment DATETIME) AS
SELECT pm.meta_value as id, MAX( p.post_date_gmt ) as last_payment FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '_subscription_renewal'
GROUP BY pm.meta_value" );
// Magic ends here
$pieces['join'] .= "LEFT JOIN {$table_name} lp
ON {$wpdb->posts}.ID = lp.id
LEFT JOIN {$wpdb->posts} o on {$wpdb->posts}.post_parent = o.ID";
return $pieces;
}
/**
* Displays the dropdown for the product filter
* @return string the html dropdown element
*/
public function restrict_by_product() {
global $typenow;
if ( 'shop_subscription' !== $typenow ) {
return;
}
$product_id = '';
$product_string = '';
if ( ! empty( $_GET['_wcs_product'] ) ) {
$product_id = absint( $_GET['_wcs_product'] );
$product_string = wc_get_product( $product_id )->get_formatted_name();
}
WCS_Select2::render( array(
'class' => 'wc-product-search',
'name' => '_wcs_product',
'placeholder' => esc_attr__( 'Search for a product…', 'woocommerce-subscriptions' ),
'action' => 'woocommerce_json_search_products_and_variations',
'selected' => strip_tags( $product_string ),
'value' => $product_id,
'allow_clear' => 'true',
) );
}
/**
* Remove "edit" from the bulk actions.
*
* @param array $actions
* @return array
*/
public function remove_bulk_actions( $actions ) {
if ( isset( $actions['edit'] ) ) {
unset( $actions['edit'] );
}
return $actions;
}
/**
* Add extra options to the bulk actions dropdown
*
* It's only on the All Shop Subscriptions screen.
* Introducing new filter: woocommerce_subscription_bulk_actions. This has to be done through jQuery as the
* 'bulk_actions' filter that WordPress has can only be used to remove bulk actions, not to add them.
*
* This is a filterable array where the key is the action (will become query arg), and the value is a translatable
* string. The same array is used to
*
*/
public function print_bulk_actions_script() {
$post_status = ( isset( $_GET['post_status'] ) ) ? $_GET['post_status'] : '';
if ( 'shop_subscription' !== get_post_type() || in_array( $post_status, array( 'cancelled', 'trash', 'wc-expired' ) ) ) {
return;
}
// Make it filterable in case extensions want to change this
$bulk_actions = apply_filters( 'woocommerce_subscription_bulk_actions', array(
'active' => _x( 'Activate', 'an action on a subscription', 'woocommerce-subscriptions' ),
'on-hold' => _x( 'Put on-hold', 'an action on a subscription', 'woocommerce-subscriptions' ),
'cancelled' => _x( 'Cancel', 'an action on a subscription', 'woocommerce-subscriptions' ),
) );
// No need to display certain bulk actions if we know all the subscriptions on the page have that status already
switch ( $post_status ) {
case 'wc-active' :
unset( $bulk_actions['active'] );
break;
case 'wc-on-hold' :
unset( $bulk_actions['on-hold'] );
break;
}
?>
'shop_subscription',
$report_action => true,
'ids' => join( ',', $subscription_ids ),
'error_count' => 0,
);
foreach ( $subscription_ids as $subscription_id ) {
$subscription = wcs_get_subscription( $subscription_id );
$order_note = _x( 'Subscription status changed by bulk edit:', 'Used in order note. Reason why status changed.', 'woocommerce-subscriptions' );
try {
if ( 'cancelled' == $action ) {
$subscription->cancel_order( $order_note );
} else {
$subscription->update_status( $new_status, $order_note, true );
}
// Fire the action hooks
switch ( $action ) {
case 'active' :
case 'on-hold' :
case 'cancelled' :
case 'trash' :
do_action( 'woocommerce_admin_changed_subscription_to_' . $action, $subscription_id );
break;
}
$changed++;
} catch ( Exception $e ) {
$sendback_args['error'] = urlencode( $e->getMessage() );
$sendback_args['error_count']++;
}
}
$sendback_args['changed'] = $changed;
$sendback = add_query_arg( $sendback_args, wp_get_referer() ? wp_get_referer() : '' );
wp_safe_redirect( esc_url_raw( $sendback ) );
exit();
}
/**
* Show confirmation message that subscription status was changed
*/
public function bulk_admin_notices() {
global $post_type, $pagenow;
// Bail out if not on shop order list page
if ( 'edit.php' !== $pagenow || 'shop_subscription' !== $post_type ) {
return;
}
$subscription_statuses = wcs_get_subscription_statuses();
// Check if any status changes happened
foreach ( $subscription_statuses as $slug => $name ) {
if ( isset( $_REQUEST[ 'marked_' . str_replace( 'wc-', '', $slug ) ] ) ) {
$number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0;
// translators: placeholder is the number of subscriptions updated
$message = sprintf( _n( '%s subscription status changed.', '%s subscription statuses changed.', $number, 'woocommerce-subscriptions' ), number_format_i18n( $number ) );
echo '
' . esc_html( $message ) . '
';
if ( ! empty( $_REQUEST['error_count'] ) ) {
$error_msg = isset( $_REQUEST['error'] ) ? stripslashes( $_REQUEST['error'] ) : '';
$error_count = isset( $_REQUEST['error_count'] ) ? absint( $_REQUEST['error_count'] ) : 0;
// translators: 1$: is the number of subscriptions not updated, 2$: is the error message
$message = sprintf( _n( '%1$s subscription could not be updated: %2$s', '%1$s subscriptions could not be updated: %2$s', $error_count, 'woocommerce-subscriptions' ), number_format_i18n( $error_count ), $error_msg );
echo '
';
return $item_html;
}
/**
* Get the HTML for order item to display on the Subscription list table using a table element
* as the wrapper, which is done for subscriptions with multilpe line items.
*
* @param array $actions
* @param object $post
* @return array
*/
protected static function get_item_display_row( $item, $item_name, $item_meta_html ) {
ob_start();
?>