update plugins
This commit is contained in:
@@ -0,0 +1,945 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract Data.
|
||||
*
|
||||
* Handles generic data interaction which is implemented by
|
||||
* the different data store classes.
|
||||
*
|
||||
* @class WC_Data
|
||||
* @version 3.0.0
|
||||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract WC Data Class
|
||||
*
|
||||
* Implemented by classes using the same CRUD(s) pattern.
|
||||
*
|
||||
* @version 2.6.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Data {
|
||||
|
||||
/**
|
||||
* ID for this object.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var int
|
||||
*/
|
||||
protected $id = 0;
|
||||
|
||||
/**
|
||||
* Core data for this object. Name value pairs (name + default value).
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Core data changes for this object.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $changes = array();
|
||||
|
||||
/**
|
||||
* This is false until the object is read from the DB.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var bool
|
||||
*/
|
||||
protected $object_read = false;
|
||||
|
||||
/**
|
||||
* This is the name of this object type.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $object_type = 'data';
|
||||
|
||||
/**
|
||||
* Extra data for this object. Name value pairs (name + default value).
|
||||
* Used as a standard way for sub classes (like product types) to add
|
||||
* additional information to an inherited class.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $extra_data = array();
|
||||
|
||||
/**
|
||||
* Set to _data on construct so we can track and reset data if needed.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $default_data = array();
|
||||
|
||||
/**
|
||||
* Contains a reference to the data store for this class.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var object
|
||||
*/
|
||||
protected $data_store;
|
||||
|
||||
/**
|
||||
* Stores meta in cache for future reads.
|
||||
* A group must be set to to enable caching.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $cache_group = '';
|
||||
|
||||
/**
|
||||
* Stores additional meta data.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $meta_data = null;
|
||||
|
||||
/**
|
||||
* List of properties that were earlier managed by data store. However, since DataStore is a not a stored entity in itself, they used to store data in metadata of the data object.
|
||||
* With custom tables, some of these are moved from metadata to their own columns, but existing code will still try to add them to metadata. This array is used to keep track of such properties.
|
||||
*
|
||||
* Only reason to add a property here is that you are moving properties from DataStore instance to data object. If you are adding a new property, consider adding it to to $data array instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $legacy_datastore_props = array();
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param int|object|array $read ID to load from the DB (optional) or already queried data.
|
||||
*/
|
||||
public function __construct( $read = 0 ) {
|
||||
$this->data = array_merge( $this->data, $this->extra_data );
|
||||
$this->default_data = $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only store the object ID to avoid serializing the data object instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep() {
|
||||
return array( 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-run the constructor with the object ID.
|
||||
*
|
||||
* If the object no longer exists, remove the ID.
|
||||
*/
|
||||
public function __wakeup() {
|
||||
try {
|
||||
$this->__construct( absint( $this->id ) );
|
||||
} catch ( Exception $e ) {
|
||||
$this->set_id( 0 );
|
||||
$this->set_object_read( true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the object is cloned, make sure meta is duplicated correctly.
|
||||
*
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->maybe_read_meta_data();
|
||||
if ( ! empty( $this->meta_data ) ) {
|
||||
foreach ( $this->meta_data as $array_key => $meta ) {
|
||||
$this->meta_data[ $array_key ] = clone $meta;
|
||||
if ( ! empty( $meta->id ) ) {
|
||||
$this->meta_data[ $array_key ]->id = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data store.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @return object
|
||||
*/
|
||||
public function get_data_store() {
|
||||
return $this->data_store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique ID for this object.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object, set the ID to 0, and return result.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param bool $force_delete Should the date be deleted permanently.
|
||||
* @return bool result
|
||||
*/
|
||||
public function delete( $force_delete = false ) {
|
||||
/**
|
||||
* Filters whether an object deletion should take place. Equivalent to `pre_delete_post`.
|
||||
*
|
||||
* @param mixed $check Whether to go ahead with deletion.
|
||||
* @param WC_Data $this The data object being deleted.
|
||||
* @param bool $force_delete Whether to bypass the trash.
|
||||
*
|
||||
* @since 8.1.0.
|
||||
*/
|
||||
$check = apply_filters( "woocommerce_pre_delete_$this->object_type", null, $this, $force_delete );
|
||||
if ( null !== $check ) {
|
||||
return $check;
|
||||
}
|
||||
if ( $this->data_store ) {
|
||||
$this->data_store->delete( $this, array( 'force_delete' => $force_delete ) );
|
||||
$this->set_id( 0 );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save should create or update based on object existence.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return int
|
||||
*/
|
||||
public function save() {
|
||||
if ( ! $this->data_store ) {
|
||||
return $this->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger action before saving to the DB. Allows you to adjust object props before save.
|
||||
*
|
||||
* @param WC_Data $this The object being saved.
|
||||
* @param WC_Data_Store_WP $data_store THe data store persisting the data.
|
||||
*/
|
||||
do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
|
||||
|
||||
if ( $this->get_id() ) {
|
||||
$this->data_store->update( $this );
|
||||
} else {
|
||||
$this->data_store->create( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger action after saving to the DB.
|
||||
*
|
||||
* @param WC_Data $this The object being saved.
|
||||
* @param WC_Data_Store_WP $data_store THe data store persisting the data.
|
||||
*/
|
||||
do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
|
||||
|
||||
return $this->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change data to JSON format.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return string Data in JSON format.
|
||||
*/
|
||||
public function __toString() {
|
||||
return wp_json_encode( $this->get_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all data for this object.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() {
|
||||
return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of expected data keys for this object.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_data_keys() {
|
||||
return array_keys( $this->data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all "extra" data keys for an object (for sub objects like product types).
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_extra_data_keys() {
|
||||
return array_keys( $this->extra_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter null meta values from array.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param mixed $meta Meta value to check.
|
||||
* @return bool
|
||||
*/
|
||||
protected function filter_null_meta( $meta ) {
|
||||
return ! is_null( $meta->value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All Meta Data.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return array of objects.
|
||||
*/
|
||||
public function get_meta_data() {
|
||||
$this->maybe_read_meta_data();
|
||||
return array_values( array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the key is an internal one.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param string $key Key to check.
|
||||
* @return bool true if it's an internal key, false otherwise
|
||||
*/
|
||||
protected function is_internal_meta_key( $key ) {
|
||||
$internal_meta_key = ! empty( $key ) && $this->data_store && in_array( $key, $this->data_store->get_internal_meta_keys(), true );
|
||||
|
||||
if ( ! $internal_meta_key ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$has_setter_or_getter = is_callable( array( $this, 'set_' . ltrim( $key, '_' ) ) ) || is_callable( array( $this, 'get_' . ltrim( $key, '_' ) ) );
|
||||
|
||||
if ( ! $has_setter_or_getter ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( in_array( $key, $this->legacy_datastore_props, true ) ) {
|
||||
return true; // return without warning because we don't want to break legacy code which was calling add/get/update/delete meta.
|
||||
}
|
||||
|
||||
/* translators: %s: $key Key to check */
|
||||
wc_doing_it_wrong( __FUNCTION__, sprintf( __( 'Generic add/update/get meta methods should not be used for internal meta data, including "%s". Use getters and setters.', 'woocommerce' ), $key ), '3.2.0' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Meta Data by Key.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $key Meta Key.
|
||||
* @param bool $single return first found meta with key, or all with $key.
|
||||
* @param string $context What the value is for. Valid values are view and edit.
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_meta( $key = '', $single = true, $context = 'view' ) {
|
||||
if ( $this->is_internal_meta_key( $key ) ) {
|
||||
$function = 'get_' . ltrim( $key, '_' );
|
||||
|
||||
if ( is_callable( array( $this, $function ) ) ) {
|
||||
return $this->{$function}();
|
||||
}
|
||||
}
|
||||
|
||||
$this->maybe_read_meta_data();
|
||||
$meta_data = $this->get_meta_data();
|
||||
$array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key, true );
|
||||
$value = $single ? '' : array();
|
||||
|
||||
if ( ! empty( $array_keys ) ) {
|
||||
// We don't use the $this->meta_data property directly here because we don't want meta with a null value (i.e. meta which has been deleted via $this->delete_meta_data()).
|
||||
if ( $single ) {
|
||||
$value = $meta_data[ current( $array_keys ) ]->value;
|
||||
} else {
|
||||
$value = array_intersect_key( $meta_data, array_flip( $array_keys ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'view' === $context ) {
|
||||
$value = apply_filters( $this->get_hook_prefix() . $key, $value, $this );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if meta data exists, since get_meta always returns a '' or array().
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param string $key Meta Key.
|
||||
* @return boolean
|
||||
*/
|
||||
public function meta_exists( $key = '' ) {
|
||||
$this->maybe_read_meta_data();
|
||||
$array_keys = wp_list_pluck( $this->get_meta_data(), 'key' );
|
||||
return in_array( $key, $array_keys, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all meta data from array.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param array $data Key/Value pairs.
|
||||
*/
|
||||
public function set_meta_data( $data ) {
|
||||
if ( ! empty( $data ) && is_array( $data ) ) {
|
||||
$this->maybe_read_meta_data();
|
||||
foreach ( $data as $meta ) {
|
||||
$meta = (array) $meta;
|
||||
if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) {
|
||||
$this->meta_data[] = new WC_Meta_Data(
|
||||
array(
|
||||
'id' => $meta['id'],
|
||||
'key' => $meta['key'],
|
||||
'value' => $meta['value'],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta data.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*
|
||||
* @param string $key Meta key.
|
||||
* @param string|array $value Meta value.
|
||||
* @param bool $unique Should this be a unique key?.
|
||||
*/
|
||||
public function add_meta_data( $key, $value, $unique = false ) {
|
||||
if ( $this->is_internal_meta_key( $key ) ) {
|
||||
$function = 'set_' . ltrim( $key, '_' );
|
||||
|
||||
if ( is_callable( array( $this, $function ) ) ) {
|
||||
return $this->{$function}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
$this->maybe_read_meta_data();
|
||||
if ( $unique ) {
|
||||
$this->delete_meta_data( $key );
|
||||
}
|
||||
$this->meta_data[] = new WC_Meta_Data(
|
||||
array(
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update meta data by key or ID, if provided.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*
|
||||
* @param string $key Meta key.
|
||||
* @param string|array $value Meta value.
|
||||
* @param int $meta_id Meta ID.
|
||||
*/
|
||||
public function update_meta_data( $key, $value, $meta_id = 0 ) {
|
||||
if ( $this->is_internal_meta_key( $key ) ) {
|
||||
$function = 'set_' . ltrim( $key, '_' );
|
||||
|
||||
if ( is_callable( array( $this, $function ) ) ) {
|
||||
return $this->{$function}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
$this->maybe_read_meta_data();
|
||||
|
||||
$array_key = false;
|
||||
|
||||
if ( $meta_id ) {
|
||||
$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true );
|
||||
$array_key = $array_keys ? current( $array_keys ) : false;
|
||||
} else {
|
||||
// Find matches by key.
|
||||
$matches = array();
|
||||
foreach ( $this->meta_data as $meta_data_array_key => $meta ) {
|
||||
if ( $meta->key === $key ) {
|
||||
$matches[] = $meta_data_array_key;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $matches ) ) {
|
||||
// Set matches to null so only one key gets the new value.
|
||||
foreach ( $matches as $meta_data_array_key ) {
|
||||
$this->meta_data[ $meta_data_array_key ]->value = null;
|
||||
}
|
||||
$array_key = current( $matches );
|
||||
}
|
||||
}
|
||||
|
||||
if ( false !== $array_key ) {
|
||||
$meta = $this->meta_data[ $array_key ];
|
||||
$meta->key = $key;
|
||||
$meta->value = $value;
|
||||
} else {
|
||||
$this->add_meta_data( $key, $value, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta data.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $key Meta key.
|
||||
*/
|
||||
public function delete_meta_data( $key ) {
|
||||
$this->maybe_read_meta_data();
|
||||
$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
|
||||
|
||||
if ( $array_keys ) {
|
||||
foreach ( $array_keys as $array_key ) {
|
||||
$this->meta_data[ $array_key ]->value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta data with a matching value.
|
||||
*
|
||||
* @since 7.7.0
|
||||
* @param string $key Meta key.
|
||||
* @param mixed $value Meta value. Entries will only be removed that match the value.
|
||||
*/
|
||||
public function delete_meta_data_value( $key, $value ) {
|
||||
$this->maybe_read_meta_data();
|
||||
$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key, true );
|
||||
|
||||
if ( $array_keys ) {
|
||||
foreach ( $array_keys as $array_key ) {
|
||||
if ( $value === $this->meta_data[ $array_key ]->value ) {
|
||||
$this->meta_data[ $array_key ]->value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta data.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param int $mid Meta ID.
|
||||
*/
|
||||
public function delete_meta_data_by_mid( $mid ) {
|
||||
$this->maybe_read_meta_data();
|
||||
$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), (int) $mid, true );
|
||||
|
||||
if ( $array_keys ) {
|
||||
foreach ( $array_keys as $array_key ) {
|
||||
$this->meta_data[ $array_key ]->value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read meta data if null.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function maybe_read_meta_data() {
|
||||
if ( is_null( $this->meta_data ) ) {
|
||||
$this->read_meta_data();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to compute meta cache key. Different from WP Meta cache key in that meta data cached using this key also contains meta_id column.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_meta_cache_key() {
|
||||
if ( ! $this->get_id() ) {
|
||||
wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.7.0' );
|
||||
return false;
|
||||
}
|
||||
return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key from id and group.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param int|string $id Object ID.
|
||||
* @param string $cache_group Group name use to store cache. Whole group cache can be invalidated in one go.
|
||||
*
|
||||
* @return string Meta cache key.
|
||||
*/
|
||||
public static function generate_meta_cache_key( $id, $cache_group ) {
|
||||
return WC_Cache_Helper::get_cache_prefix( $cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $id ) . 'object_meta_' . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime caches for raw meta data. This includes meta_id column as well, which is not included by default in WP meta data.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $raw_meta_data_collection Array of objects of { object_id => array( meta_row_1, meta_row_2, ... }.
|
||||
* @param string $cache_group Name of cache group.
|
||||
*/
|
||||
public static function prime_raw_meta_data_cache( $raw_meta_data_collection, $cache_group ) {
|
||||
foreach ( $raw_meta_data_collection as $object_id => $raw_meta_data_array ) {
|
||||
$cache_key = self::generate_meta_cache_key( $object_id, $cache_group );
|
||||
wp_cache_set( $cache_key, $raw_meta_data_array, $cache_group );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Meta Data from the database. Ignore any internal properties.
|
||||
* Uses it's own caches because get_metadata does not provide meta_ids.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param bool $force_read True to force a new DB read (and update cache).
|
||||
*/
|
||||
public function read_meta_data( $force_read = false ) {
|
||||
$this->meta_data = array();
|
||||
$cache_loaded = false;
|
||||
|
||||
if ( ! $this->get_id() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->data_store ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $this->cache_group ) ) {
|
||||
// Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
|
||||
$cache_key = $this->get_meta_cache_key();
|
||||
}
|
||||
|
||||
if ( ! $force_read ) {
|
||||
if ( ! empty( $this->cache_group ) ) {
|
||||
$cached_meta = wp_cache_get( $cache_key, $this->cache_group );
|
||||
$cache_loaded = is_array( $cached_meta );
|
||||
}
|
||||
}
|
||||
|
||||
// We filter the raw meta data again when loading from cache, in case we cached in an earlier version where filter conditions were different.
|
||||
$raw_meta_data = $cache_loaded ? $this->data_store->filter_raw_meta_data( $this, $cached_meta ) : $this->data_store->read_meta( $this );
|
||||
|
||||
if ( is_array( $raw_meta_data ) ) {
|
||||
$this->init_meta_data( $raw_meta_data );
|
||||
if ( ! $cache_loaded && ! empty( $this->cache_group ) ) {
|
||||
wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to initialize metadata entries from filtered raw meta data.
|
||||
*
|
||||
* @param array $filtered_meta_data Filtered metadata fetched from DB.
|
||||
*/
|
||||
public function init_meta_data( array $filtered_meta_data = array() ) {
|
||||
$this->meta_data = array();
|
||||
foreach ( $filtered_meta_data as $meta ) {
|
||||
$this->meta_data[] = new WC_Meta_Data(
|
||||
array(
|
||||
'id' => (int) $meta->meta_id,
|
||||
'key' => $meta->meta_key,
|
||||
'value' => maybe_unserialize( $meta->meta_value ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Meta Data in the database.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public function save_meta_data() {
|
||||
if ( ! $this->data_store || is_null( $this->meta_data ) ) {
|
||||
return;
|
||||
}
|
||||
foreach ( $this->meta_data as $array_key => $meta ) {
|
||||
if ( is_null( $meta->value ) ) {
|
||||
if ( ! empty( $meta->id ) ) {
|
||||
$this->data_store->delete_meta( $this, $meta );
|
||||
/**
|
||||
* Fires immediately after deleting metadata.
|
||||
*
|
||||
* @param int $meta_id ID of deleted metadata entry.
|
||||
* @param int $object_id Object ID.
|
||||
* @param string $meta_key Metadata key.
|
||||
* @param mixed $meta_value Metadata value (will be empty for delete).
|
||||
*/
|
||||
do_action( "deleted_{$this->object_type}_meta", $meta->id, $this->get_id(), $meta->key, $meta->value );
|
||||
|
||||
unset( $this->meta_data[ $array_key ] );
|
||||
}
|
||||
} elseif ( empty( $meta->id ) ) {
|
||||
$meta->id = $this->data_store->add_meta( $this, $meta );
|
||||
/**
|
||||
* Fires immediately after adding metadata.
|
||||
*
|
||||
* @param int $meta_id ID of added metadata entry.
|
||||
* @param int $object_id Object ID.
|
||||
* @param string $meta_key Metadata key.
|
||||
* @param mixed $meta_value Metadata value.
|
||||
*/
|
||||
do_action( "added_{$this->object_type}_meta", $meta->id, $this->get_id(), $meta->key, $meta->value );
|
||||
|
||||
$meta->apply_changes();
|
||||
} else {
|
||||
if ( $meta->get_changes() ) {
|
||||
$this->data_store->update_meta( $this, $meta );
|
||||
/**
|
||||
* Fires immediately after updating metadata.
|
||||
*
|
||||
* @param int $meta_id ID of updated metadata entry.
|
||||
* @param int $object_id Object ID.
|
||||
* @param string $meta_key Metadata key.
|
||||
* @param mixed $meta_value Metadata value.
|
||||
*/
|
||||
do_action( "updated_{$this->object_type}_meta", $meta->id, $this->get_id(), $meta->key, $meta->value );
|
||||
|
||||
$meta->apply_changes();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ! empty( $this->cache_group ) ) {
|
||||
$cache_key = self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
|
||||
wp_cache_delete( $cache_key, $this->cache_group );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param int $id ID.
|
||||
*/
|
||||
public function set_id( $id ) {
|
||||
$this->id = absint( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all props to default values.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function set_defaults() {
|
||||
$this->data = $this->default_data;
|
||||
$this->changes = array();
|
||||
$this->set_object_read( false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set object read property.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param boolean $read Should read?.
|
||||
*/
|
||||
public function set_object_read( $read = true ) {
|
||||
$this->object_read = (bool) $read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object read property.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @return boolean
|
||||
*/
|
||||
public function get_object_read() {
|
||||
return (bool) $this->object_read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a collection of props in one go, collect any errors, and return the result.
|
||||
* Only sets using public methods.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $props Key value pairs to set. Key is the prop and should map to a setter function name.
|
||||
* @param string $context In what context to run this.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function set_props( $props, $context = 'set' ) {
|
||||
$errors = false;
|
||||
|
||||
foreach ( $props as $prop => $value ) {
|
||||
try {
|
||||
/**
|
||||
* Checks if the prop being set is allowed, and the value is not null.
|
||||
*/
|
||||
if ( is_null( $value ) || in_array( $prop, array( 'prop', 'date_prop', 'meta_data' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
$setter = "set_$prop";
|
||||
|
||||
if ( is_callable( array( $this, $setter ) ) ) {
|
||||
$this->{$setter}( $value );
|
||||
}
|
||||
} catch ( WC_Data_Exception $e ) {
|
||||
if ( ! $errors ) {
|
||||
$errors = new WP_Error();
|
||||
}
|
||||
$errors->add( $e->getErrorCode(), $e->getMessage(), array( 'property_name' => $prop ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $errors && count( $errors->get_error_codes() ) ? $errors : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a prop for a setter method.
|
||||
*
|
||||
* This stores changes in a special array so we can track what needs saving
|
||||
* the the DB later.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param string $prop Name of prop to set.
|
||||
* @param mixed $value Value of the prop.
|
||||
*/
|
||||
protected function set_prop( $prop, $value ) {
|
||||
if ( array_key_exists( $prop, $this->data ) ) {
|
||||
if ( true === $this->object_read ) {
|
||||
if ( $value !== $this->data[ $prop ] || array_key_exists( $prop, $this->changes ) ) {
|
||||
$this->changes[ $prop ] = $value;
|
||||
}
|
||||
} else {
|
||||
$this->data[ $prop ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data changes only.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_changes() {
|
||||
return $this->changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge changes with data and clear.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function apply_changes() {
|
||||
$this->data = array_replace_recursive( $this->data, $this->changes ); // @codingStandardsIgnoreLine
|
||||
$this->changes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix for action and filter hooks on data.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @return string
|
||||
*/
|
||||
protected function get_hook_prefix() {
|
||||
return 'woocommerce_' . $this->object_type . '_get_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a prop for a getter method.
|
||||
*
|
||||
* Gets the value from either current pending changes, or the data itself.
|
||||
* Context controls what happens to the value before it's returned.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param string $prop Name of prop to get.
|
||||
* @param string $context What the value is for. Valid values are view and edit.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_prop( $prop, $context = 'view' ) {
|
||||
$value = null;
|
||||
|
||||
if ( array_key_exists( $prop, $this->data ) ) {
|
||||
$value = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : $this->data[ $prop ];
|
||||
|
||||
if ( 'view' === $context ) {
|
||||
$value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this );
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a date prop whilst handling formatting and datetime objects.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param string $prop Name of prop to set.
|
||||
* @param string|integer $value Value of the prop.
|
||||
*/
|
||||
protected function set_date_prop( $prop, $value ) {
|
||||
try {
|
||||
if ( empty( $value ) || '0000-00-00 00:00:00' === $value ) {
|
||||
$this->set_prop( $prop, null );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_a( $value, 'WC_DateTime' ) ) {
|
||||
$datetime = $value;
|
||||
} elseif ( is_numeric( $value ) ) {
|
||||
// Timestamps are handled as UTC timestamps in all cases.
|
||||
$datetime = new WC_DateTime( "@{$value}", new DateTimeZone( 'UTC' ) );
|
||||
} else {
|
||||
// Strings are defined in local WP timezone. Convert to UTC.
|
||||
if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $value, $date_bits ) ) {
|
||||
$offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
|
||||
$timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
|
||||
} else {
|
||||
$timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $value ) ) ) );
|
||||
}
|
||||
$datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) );
|
||||
}
|
||||
|
||||
// Set local timezone or offset.
|
||||
if ( get_option( 'timezone_string' ) ) {
|
||||
$datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
} else {
|
||||
$datetime->set_utc_offset( wc_timezone_offset() );
|
||||
}
|
||||
|
||||
$this->set_prop( $prop, $datetime );
|
||||
} catch ( Exception $e ) {} // @codingStandardsIgnoreLine.
|
||||
}
|
||||
|
||||
/**
|
||||
* When invalid data is found, throw an exception unless reading from the DB.
|
||||
*
|
||||
* @throws WC_Data_Exception Data Exception.
|
||||
* @since 3.0.0
|
||||
* @param string $code Error code.
|
||||
* @param string $message Error message.
|
||||
* @param int $http_status_code HTTP status code.
|
||||
* @param array $data Extra error data.
|
||||
*/
|
||||
protected function error( $code, $message, $http_status_code = 400, $data = array() ) {
|
||||
throw new WC_Data_Exception( $code, $message, $http_status_code, $data );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract deprecated hooks
|
||||
*
|
||||
* @package WooCommerce\Abstracts
|
||||
* @since 3.0.0
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Deprecated_Hooks class maps old actions and filters to new ones. This is the base class for handling those deprecated hooks.
|
||||
*
|
||||
* Based on the WCS_Hook_Deprecator class by Prospress.
|
||||
*/
|
||||
abstract class WC_Deprecated_Hooks {
|
||||
|
||||
/**
|
||||
* Array of deprecated hooks we need to handle.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecated_hooks = array();
|
||||
|
||||
/**
|
||||
* Array of versions on each hook has been deprecated.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deprecated_version = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$new_hooks = array_keys( $this->deprecated_hooks );
|
||||
array_walk( $new_hooks, array( $this, 'hook_in' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into the new hook so we can handle deprecated hooks once fired.
|
||||
*
|
||||
* @param string $hook_name Hook name.
|
||||
*/
|
||||
abstract public function hook_in( $hook_name );
|
||||
|
||||
/**
|
||||
* Get old hooks to map to new hook.
|
||||
*
|
||||
* @param string $new_hook New hook name.
|
||||
* @return array
|
||||
*/
|
||||
public function get_old_hooks( $new_hook ) {
|
||||
$old_hooks = isset( $this->deprecated_hooks[ $new_hook ] ) ? $this->deprecated_hooks[ $new_hook ] : array();
|
||||
$old_hooks = is_array( $old_hooks ) ? $old_hooks : array( $old_hooks );
|
||||
|
||||
return $old_hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the hook is Deprecated, call the old hooks here.
|
||||
*/
|
||||
public function maybe_handle_deprecated_hook() {
|
||||
$new_hook = current_filter();
|
||||
$old_hooks = $this->get_old_hooks( $new_hook );
|
||||
$new_callback_args = func_get_args();
|
||||
$return_value = $new_callback_args[0];
|
||||
|
||||
foreach ( $old_hooks as $old_hook ) {
|
||||
$return_value = $this->handle_deprecated_hook( $new_hook, $old_hook, $new_callback_args, $return_value );
|
||||
}
|
||||
|
||||
return $return_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the old hook is in-use, trigger it.
|
||||
*
|
||||
* @param string $new_hook New hook name.
|
||||
* @param string $old_hook Old hook name.
|
||||
* @param array $new_callback_args New callback args.
|
||||
* @param mixed $return_value Returned value.
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function handle_deprecated_hook( $new_hook, $old_hook, $new_callback_args, $return_value );
|
||||
|
||||
/**
|
||||
* Get deprecated version.
|
||||
*
|
||||
* @param string $old_hook Old hook name.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_deprecated_version( $old_hook ) {
|
||||
return ! empty( $this->deprecated_version[ $old_hook ] ) ? $this->deprecated_version[ $old_hook ] : Constants::get_constant( 'WC_VERSION' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a deprecated notice for old hooks.
|
||||
*
|
||||
* @param string $old_hook Old hook.
|
||||
* @param string $new_hook New hook.
|
||||
*/
|
||||
protected function display_notice( $old_hook, $new_hook ) {
|
||||
wc_deprecated_hook( esc_html( $old_hook ), esc_html( $this->get_deprecated_version( $old_hook ) ), esc_html( $new_hook ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire off a legacy hook with it's args.
|
||||
*
|
||||
* @param string $old_hook Old hook name.
|
||||
* @param array $new_callback_args New callback args.
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function trigger_hook( $old_hook, $new_callback_args );
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract Integration class
|
||||
*
|
||||
* Extension of the Settings API which in turn gets extended
|
||||
* by individual integrations to offer additional functionality.
|
||||
*
|
||||
* @class WC_Settings_API
|
||||
* @version 2.6.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Integration Class
|
||||
*
|
||||
* Extended by individual integrations to offer additional functionality.
|
||||
*
|
||||
* @class WC_Integration
|
||||
* @extends WC_Settings_API
|
||||
* @version 2.6.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Integration extends WC_Settings_API {
|
||||
|
||||
/**
|
||||
* Yes or no based on whether the integration is enabled.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $enabled = 'yes';
|
||||
|
||||
/**
|
||||
* Integration title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method_title = '';
|
||||
|
||||
/**
|
||||
* Integration description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method_description = '';
|
||||
|
||||
/**
|
||||
* Return the title for admin screens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_method_title() {
|
||||
return apply_filters( 'woocommerce_integration_title', $this->method_title, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for admin screens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_method_description() {
|
||||
return apply_filters( 'woocommerce_integration_description', $this->method_description, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the gateway settings screen.
|
||||
*/
|
||||
public function admin_options() {
|
||||
echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
|
||||
echo wp_kses_post( wpautop( $this->get_method_description() ) );
|
||||
echo '<div><input type="hidden" name="section" value="' . esc_attr( $this->id ) . '" /></div>';
|
||||
parent::admin_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init settings for gateways.
|
||||
*/
|
||||
public function init_settings() {
|
||||
parent::init_settings();
|
||||
$this->enabled = ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'] ? 'yes' : 'no';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Log handling functionality.
|
||||
*
|
||||
* @class WC_Log_Handler
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract WC Log Handler Class
|
||||
*
|
||||
* @version 1.0.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Log_Handler implements WC_Log_Handler_Interface {
|
||||
|
||||
/**
|
||||
* Formats a timestamp for use in log messages.
|
||||
*
|
||||
* @param int $timestamp Log timestamp.
|
||||
* @return string Formatted time for use in log entry.
|
||||
*/
|
||||
protected static function format_time( $timestamp ) {
|
||||
return gmdate( 'c', $timestamp );
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a log entry text from level, timestamp and message.
|
||||
*
|
||||
* @param int $timestamp Log timestamp.
|
||||
* @param string $level emergency|alert|critical|error|warning|notice|info|debug.
|
||||
* @param string $message Log message.
|
||||
* @param array $context Additional information for log handlers.
|
||||
*
|
||||
* @return string Formatted log entry.
|
||||
*/
|
||||
protected static function format_entry( $timestamp, $level, $message, $context ) {
|
||||
$time_string = self::format_time( $timestamp );
|
||||
$level_string = strtoupper( $level );
|
||||
$entry = "{$time_string} {$level_string} {$message}";
|
||||
|
||||
/**
|
||||
* Filter the formatted log entry before it is written.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $entry The formatted entry.
|
||||
* @param array $args The raw data that gets assembled into a log entry.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_format_log_entry',
|
||||
$entry,
|
||||
array(
|
||||
'timestamp' => $timestamp,
|
||||
'level' => $level,
|
||||
'message' => $message,
|
||||
'context' => $context,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a backtrace that shows where the logging function was called.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_backtrace() {
|
||||
// Get the filenames of the logging-related classes so we can ignore them.
|
||||
$ignore_files = array_map(
|
||||
function( $class ) {
|
||||
try {
|
||||
$reflector = new \ReflectionClass( $class );
|
||||
return $reflector->getFileName();
|
||||
} catch ( Exception $exception ) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
array( wc_get_logger(), self::class, static::class )
|
||||
);
|
||||
|
||||
$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
|
||||
|
||||
$filtered_backtrace = array_filter(
|
||||
$backtrace,
|
||||
function( $frame ) use ( $ignore_files ) {
|
||||
$ignore = isset( $frame['file'] ) && in_array( $frame['file'], $ignore_files, true );
|
||||
|
||||
return ! $ignore;
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( $filtered_backtrace );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Query abstraction layer functionality.
|
||||
*
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract WC Object Query Class
|
||||
*
|
||||
* Extended by classes to provide a query abstraction layer for safe object searching.
|
||||
*
|
||||
* @version 3.1.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Object_Query {
|
||||
|
||||
/**
|
||||
* Stores query data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $query_vars = array();
|
||||
|
||||
/**
|
||||
* Create a new query.
|
||||
*
|
||||
* @param array $args Criteria to query on in a format similar to WP_Query.
|
||||
*/
|
||||
public function __construct( $args = array() ) {
|
||||
$this->query_vars = wp_parse_args( $args, $this->get_default_query_vars() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_query_vars() {
|
||||
return $this->query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a query variable.
|
||||
*
|
||||
* @param string $query_var Query variable to get value for.
|
||||
* @param mixed $default Default value if query variable is not set.
|
||||
* @return mixed Query variable value if set, otherwise default.
|
||||
*/
|
||||
public function get( $query_var, $default = '' ) {
|
||||
if ( isset( $this->query_vars[ $query_var ] ) ) {
|
||||
return $this->query_vars[ $query_var ];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a query variable.
|
||||
*
|
||||
* @param string $query_var Query variable to set.
|
||||
* @param mixed $value Value to set for query variable.
|
||||
*/
|
||||
public function set( $query_var, $value ) {
|
||||
$this->query_vars[ $query_var ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default allowed query vars.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_query_vars() {
|
||||
|
||||
return array(
|
||||
'name' => '',
|
||||
'parent' => '',
|
||||
'parent_exclude' => '',
|
||||
'exclude' => '',
|
||||
|
||||
'limit' => get_option( 'posts_per_page' ),
|
||||
'page' => 1,
|
||||
'offset' => '',
|
||||
'paginate' => false,
|
||||
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'date',
|
||||
|
||||
'return' => 'objects',
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,564 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract payment gateway
|
||||
*
|
||||
* Handles generic payment gateway functionality which is extended by individual payment gateways.
|
||||
*
|
||||
* @class WC_Payment_Gateway
|
||||
* @version 2.1.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\Utilities\HtmlSanitizer;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Payment Gateway class.
|
||||
*
|
||||
* Extended by individual payment gateways to handle payments.
|
||||
*
|
||||
* @class WC_Payment_Gateway
|
||||
* @extends WC_Settings_API
|
||||
* @version 2.1.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Payment_Gateway extends WC_Settings_API {
|
||||
|
||||
/**
|
||||
* Set if the place order button should be renamed on selection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $order_button_text;
|
||||
|
||||
/**
|
||||
* Yes or no based on whether the method is enabled.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $enabled = 'yes';
|
||||
|
||||
/**
|
||||
* Payment method title for the frontend.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* Payment method description for the frontend.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* Chosen payment method id.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $chosen;
|
||||
|
||||
/**
|
||||
* Gateway title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method_title = '';
|
||||
|
||||
/**
|
||||
* Gateway description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method_description = '';
|
||||
|
||||
/**
|
||||
* True if the gateway shows fields on the checkout.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $has_fields;
|
||||
|
||||
/**
|
||||
* Countries this gateway is allowed for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $countries;
|
||||
|
||||
/**
|
||||
* Available for all counties or specific.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $availability;
|
||||
|
||||
/**
|
||||
* Icon for the gateway.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $icon;
|
||||
|
||||
/**
|
||||
* Supported features such as 'default_credit_card_form', 'refunds'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $supports = array( 'products' );
|
||||
|
||||
/**
|
||||
* Maximum transaction amount, zero does not define a maximum.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $max_amount = 0;
|
||||
|
||||
/**
|
||||
* Optional URL to view a transaction.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $view_transaction_url = '';
|
||||
|
||||
/**
|
||||
* Optional label to show for "new payment method" in the payment
|
||||
* method/token selection radio selection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $new_method_label = '';
|
||||
|
||||
/**
|
||||
* Pay button ID if supported.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pay_button_id = '';
|
||||
|
||||
/**
|
||||
* Contains a users saved tokens for this gateway.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tokens = array();
|
||||
|
||||
/**
|
||||
* Returns a users saved tokens for this gateway.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_tokens() {
|
||||
if ( count( $this->tokens ) > 0 ) {
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
if ( is_user_logged_in() && $this->supports( 'tokenization' ) ) {
|
||||
$this->tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), $this->id );
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title for admin screens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_method_title() {
|
||||
return apply_filters( 'woocommerce_gateway_method_title', $this->method_title, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for admin screens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_method_description() {
|
||||
return apply_filters( 'woocommerce_gateway_method_description', $this->method_description, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the gateway settings screen.
|
||||
*/
|
||||
public function admin_options() {
|
||||
echo '<h2>' . esc_html( $this->get_method_title() );
|
||||
wc_back_link( __( 'Return to payments', 'woocommerce' ), admin_url( 'admin.php?page=wc-settings&tab=checkout' ) );
|
||||
echo '</h2>';
|
||||
echo wp_kses_post( wpautop( $this->get_method_description() ) );
|
||||
parent::admin_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init settings for gateways.
|
||||
*/
|
||||
public function init_settings() {
|
||||
parent::init_settings();
|
||||
$this->enabled = ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'] ? 'yes' : 'no';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether or not this gateway still requires setup to function.
|
||||
*
|
||||
* When this gateway is toggled on via AJAX, if this returns true a
|
||||
* redirect will occur to the settings page instead.
|
||||
*
|
||||
* @since 3.4.0
|
||||
* @return bool
|
||||
*/
|
||||
public function needs_setup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the return url (thank you page).
|
||||
*
|
||||
* @param WC_Order|null $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
public function get_return_url( $order = null ) {
|
||||
if ( $order ) {
|
||||
$return_url = $order->get_checkout_order_received_url();
|
||||
} else {
|
||||
$return_url = wc_get_endpoint_url( 'order-received', '', wc_get_checkout_url() );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_get_return_url', $return_url, $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a link to the transaction on the 3rd party gateway site (if applicable).
|
||||
*
|
||||
* @param WC_Order $order the order object.
|
||||
* @return string transaction URL, or empty string.
|
||||
*/
|
||||
public function get_transaction_url( $order ) {
|
||||
|
||||
$return_url = '';
|
||||
$transaction_id = $order->get_transaction_id();
|
||||
|
||||
if ( ! empty( $this->view_transaction_url ) && ! empty( $transaction_id ) ) {
|
||||
$return_url = sprintf( $this->view_transaction_url, $transaction_id );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_get_transaction_url', $return_url, $order, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order total in checkout and pay_for_order.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function get_order_total() {
|
||||
|
||||
$total = 0;
|
||||
$order_id = absint( get_query_var( 'order-pay' ) );
|
||||
|
||||
// Gets order total from "pay for order" page.
|
||||
if ( 0 < $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( $order ) {
|
||||
$total = (float) $order->get_total();
|
||||
}
|
||||
|
||||
// Gets order total from cart/checkout.
|
||||
} elseif ( 0 < WC()->cart->total ) {
|
||||
$total = (float) WC()->cart->total;
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the gateway is available for use.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_available() {
|
||||
$is_available = ( 'yes' === $this->enabled );
|
||||
|
||||
if ( WC()->cart && 0 < $this->get_order_total() && 0 < $this->max_amount && $this->max_amount < $this->get_order_total() ) {
|
||||
$is_available = false;
|
||||
}
|
||||
|
||||
return $is_available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the gateway has fields on the checkout.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_fields() {
|
||||
return (bool) $this->has_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the gateway's title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
$title = wc_get_container()->get( HtmlSanitizer::class )->sanitize( (string) $this->title, HtmlSanitizer::LOW_HTML_BALANCED_TAGS_NO_LINKS );
|
||||
return apply_filters( 'woocommerce_gateway_title', $title, $this->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the gateway's description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return apply_filters( 'woocommerce_gateway_description', $this->description, $this->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the gateway's icon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_icon() {
|
||||
|
||||
$icon = $this->icon ? '<img src="' . WC_HTTPS::force_https_url( $this->icon ) . '" alt="' . esc_attr( $this->get_title() ) . '" />' : '';
|
||||
|
||||
return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the gateway's pay button ID.
|
||||
*
|
||||
* @since 3.9.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_pay_button_id() {
|
||||
return sanitize_html_class( $this->pay_button_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as current gateway.
|
||||
*
|
||||
* Set this as the current gateway.
|
||||
*/
|
||||
public function set_current() {
|
||||
$this->chosen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Payment.
|
||||
*
|
||||
* Process the payment. Override this in your gateway. When implemented, this should.
|
||||
* return the success and redirect in an array. e.g:
|
||||
*
|
||||
* return array(
|
||||
* 'result' => 'success',
|
||||
* 'redirect' => $this->get_return_url( $order )
|
||||
* );
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return array
|
||||
*/
|
||||
public function process_payment( $order_id ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process refund.
|
||||
*
|
||||
* If the gateway declares 'refunds' support, this will allow it to refund.
|
||||
* a passed in amount.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @param float|null $amount Refund amount.
|
||||
* @param string $reason Refund reason.
|
||||
* @return bool|\WP_Error True or false based on success, or a WP_Error object.
|
||||
*/
|
||||
public function process_refund( $order_id, $amount = null, $reason = '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate frontend fields.
|
||||
*
|
||||
* Validate payment fields on the frontend.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_fields() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If There are no payment fields show the description if set.
|
||||
* Override this in your gateway if you have some.
|
||||
*/
|
||||
public function payment_fields() {
|
||||
$description = $this->get_description();
|
||||
if ( $description ) {
|
||||
echo wpautop( wptexturize( $description ) ); // @codingStandardsIgnoreLine.
|
||||
}
|
||||
|
||||
if ( $this->supports( 'default_credit_card_form' ) ) {
|
||||
$this->credit_card_form(); // Deprecated, will be removed in a future version.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a gateway supports a given feature.
|
||||
*
|
||||
* Gateways should override this to declare support (or lack of support) for a feature.
|
||||
* For backward compatibility, gateways support 'products' by default, but nothing else.
|
||||
*
|
||||
* @param string $feature string The name of a feature to test support for.
|
||||
* @return bool True if the gateway supports the feature, false otherwise.
|
||||
* @since 1.5.7
|
||||
*/
|
||||
public function supports( $feature ) {
|
||||
return apply_filters( 'woocommerce_payment_gateway_supports', in_array( $feature, $this->supports ), $feature, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the order be refunded via this gateway?
|
||||
*
|
||||
* Should be extended by gateways to do their own checks.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
* @return bool If false, the automatic refund button is hidden in the UI.
|
||||
*/
|
||||
public function can_refund_order( $order ) {
|
||||
return $order && $this->supports( 'refunds' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Core credit card form which gateways can use if needed. Deprecated - inherit WC_Payment_Gateway_CC instead.
|
||||
*
|
||||
* @param array $args Arguments.
|
||||
* @param array $fields Fields.
|
||||
*/
|
||||
public function credit_card_form( $args = array(), $fields = array() ) {
|
||||
wc_deprecated_function( 'credit_card_form', '2.6', 'WC_Payment_Gateway_CC->form' );
|
||||
$cc_form = new WC_Payment_Gateway_CC();
|
||||
$cc_form->id = $this->id;
|
||||
$cc_form->supports = $this->supports;
|
||||
$cc_form->form();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues our tokenization script to handle some of the new form options.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public function tokenization_script() {
|
||||
wp_enqueue_script(
|
||||
'woocommerce-tokenization-form',
|
||||
plugins_url( '/assets/js/frontend/tokenization-form' . ( Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min' ) . '.js', WC_PLUGIN_FILE ),
|
||||
array( 'jquery' ),
|
||||
WC()->version
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'woocommerce-tokenization-form',
|
||||
'wc_tokenization_form_params',
|
||||
array(
|
||||
'is_registration_required' => WC()->checkout()->is_registration_required(),
|
||||
'is_logged_in' => is_user_logged_in(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab and display our saved payment methods.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public function saved_payment_methods() {
|
||||
$html = '<ul class="woocommerce-SavedPaymentMethods wc-saved-payment-methods" data-count="' . esc_attr( count( $this->get_tokens() ) ) . '">';
|
||||
|
||||
foreach ( $this->get_tokens() as $token ) {
|
||||
$html .= $this->get_saved_payment_method_option_html( $token );
|
||||
}
|
||||
|
||||
$html .= $this->get_new_payment_method_option_html();
|
||||
$html .= '</ul>';
|
||||
|
||||
echo apply_filters( 'wc_payment_gateway_form_saved_payment_methods_html', $html, $this ); // @codingStandardsIgnoreLine
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets saved payment method HTML from a token.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param WC_Payment_Token $token Payment Token.
|
||||
* @return string Generated payment method HTML
|
||||
*/
|
||||
public function get_saved_payment_method_option_html( $token ) {
|
||||
$html = sprintf(
|
||||
'<li class="woocommerce-SavedPaymentMethods-token">
|
||||
<input id="wc-%1$s-payment-token-%2$s" type="radio" name="wc-%1$s-payment-token" value="%2$s" style="width:auto;" class="woocommerce-SavedPaymentMethods-tokenInput" %4$s />
|
||||
<label for="wc-%1$s-payment-token-%2$s">%3$s</label>
|
||||
</li>',
|
||||
esc_attr( $this->id ),
|
||||
esc_attr( $token->get_id() ),
|
||||
esc_html( $token->get_display_name() ),
|
||||
checked( $token->is_default(), true, false )
|
||||
);
|
||||
|
||||
return apply_filters( 'woocommerce_payment_gateway_get_saved_payment_method_option_html', $html, $token, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a radio button for entering a new payment method (new CC details) instead of using a saved method.
|
||||
* Only displayed when a gateway supports tokenization.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public function get_new_payment_method_option_html() {
|
||||
$label = apply_filters( 'woocommerce_payment_gateway_get_new_payment_method_option_html_label', $this->new_method_label ? $this->new_method_label : __( 'Use a new payment method', 'woocommerce' ), $this );
|
||||
$html = sprintf(
|
||||
'<li class="woocommerce-SavedPaymentMethods-new">
|
||||
<input id="wc-%1$s-payment-token-new" type="radio" name="wc-%1$s-payment-token" value="new" style="width:auto;" class="woocommerce-SavedPaymentMethods-tokenInput" />
|
||||
<label for="wc-%1$s-payment-token-new">%2$s</label>
|
||||
</li>',
|
||||
esc_attr( $this->id ),
|
||||
esc_html( $label )
|
||||
);
|
||||
|
||||
return apply_filters( 'woocommerce_payment_gateway_get_new_payment_method_option_html', $html, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a checkbox for saving a new payment method to the database.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public function save_payment_method_checkbox() {
|
||||
$html = sprintf(
|
||||
'<p class="form-row woocommerce-SavedPaymentMethods-saveNew">
|
||||
<input id="wc-%1$s-new-payment-method" name="wc-%1$s-new-payment-method" type="checkbox" value="true" style="width:auto;" />
|
||||
<label for="wc-%1$s-new-payment-method" style="display:inline;">%2$s</label>
|
||||
</p>',
|
||||
esc_attr( $this->id ),
|
||||
esc_html__( 'Save to account', 'woocommerce' )
|
||||
);
|
||||
|
||||
echo apply_filters( 'woocommerce_payment_gateway_save_new_payment_method_option_html', $html, $this ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Add payment method via account screen. This should be extended by gateway plugins.
|
||||
*
|
||||
* @since 3.2.0 Included here from 3.2.0, but supported from 3.0.0.
|
||||
* @return array
|
||||
*/
|
||||
public function add_payment_method() {
|
||||
return array(
|
||||
'result' => 'failure',
|
||||
'redirect' => wc_get_endpoint_url( 'payment-methods' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract payment tokens
|
||||
*
|
||||
* Generic payment tokens functionality which can be extended by individual types of payment tokens.
|
||||
*
|
||||
* @class WC_Payment_Token
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php';
|
||||
|
||||
/**
|
||||
* WooCommerce Payment Token.
|
||||
*
|
||||
* Representation of a general payment token to be extended by individuals types of tokens
|
||||
* examples: Credit Card, eCheck.
|
||||
*
|
||||
* @class WC_Payment_Token
|
||||
* @version 3.0.0
|
||||
* @since 2.6.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Payment_Token extends WC_Legacy_Payment_Token {
|
||||
|
||||
/**
|
||||
* Token Data (stored in the payment_tokens table).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array(
|
||||
'gateway_id' => '',
|
||||
'token' => '',
|
||||
'is_default' => false,
|
||||
'user_id' => 0,
|
||||
'type' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* Token Type (CC, eCheck, or a custom type added by an extension).
|
||||
* Set by child classes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = '';
|
||||
|
||||
/**
|
||||
* Initialize a payment token.
|
||||
*
|
||||
* These fields are accepted by all payment tokens:
|
||||
* is_default - boolean Optional - Indicates this is the default payment token for a user
|
||||
* token - string Required - The actual token to store
|
||||
* gateway_id - string Required - Identifier for the gateway this token is associated with
|
||||
* user_id - int Optional - ID for the user this token is associated with. 0 if this token is not associated with a user
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param mixed $token Token.
|
||||
*/
|
||||
public function __construct( $token = '' ) {
|
||||
parent::__construct( $token );
|
||||
|
||||
if ( is_numeric( $token ) ) {
|
||||
$this->set_id( $token );
|
||||
} elseif ( is_object( $token ) ) {
|
||||
$token_id = $token->get_id();
|
||||
if ( ! empty( $token_id ) ) {
|
||||
$this->set_id( $token->get_id() );
|
||||
}
|
||||
} else {
|
||||
$this->set_object_read( true );
|
||||
}
|
||||
|
||||
$this->data_store = WC_Data_Store::load( 'payment-token' );
|
||||
if ( $this->get_id() > 0 ) {
|
||||
$this->data_store->read( $this );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------------
|
||||
* Getters
|
||||
*--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the raw payment token.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $context Context in which to call this.
|
||||
* @return string Raw token
|
||||
*/
|
||||
public function get_token( $context = 'view' ) {
|
||||
return $this->get_prop( 'token', $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this payment token (CC, eCheck, or something else).
|
||||
* Overwritten by child classes.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $deprecated Deprecated since WooCommerce 3.0.
|
||||
* @return string Payment Token Type (CC, eCheck)
|
||||
*/
|
||||
public function get_type( $deprecated = '' ) {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type to display to user.
|
||||
* Get's overwritten by child classes.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $deprecated Deprecated since WooCommerce 3.0.
|
||||
* @return string
|
||||
*/
|
||||
public function get_display_name( $deprecated = '' ) {
|
||||
return $this->get_type();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user ID associated with the token or false if this token is not associated.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $context In what context to execute this.
|
||||
* @return int User ID if this token is associated with a user or 0 if no user is associated
|
||||
*/
|
||||
public function get_user_id( $context = 'view' ) {
|
||||
return $this->get_prop( 'user_id', $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the gateway associated with this payment token.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $context In what context to execute this.
|
||||
* @return string Gateway ID
|
||||
*/
|
||||
public function get_gateway_id( $context = 'view' ) {
|
||||
return $this->get_prop( 'gateway_id', $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the gateway associated with this payment token.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $context In what context to execute this.
|
||||
* @return string Gateway ID
|
||||
*/
|
||||
public function get_is_default( $context = 'view' ) {
|
||||
return $this->get_prop( 'is_default', $context );
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Setters
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the raw payment token.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $token Payment token.
|
||||
*/
|
||||
public function set_token( $token ) {
|
||||
$this->set_prop( 'token', $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user ID for the user associated with this order.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param int $user_id User ID.
|
||||
*/
|
||||
public function set_user_id( $user_id ) {
|
||||
$this->set_prop( 'user_id', absint( $user_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gateway ID.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $gateway_id Gateway ID.
|
||||
*/
|
||||
public function set_gateway_id( $gateway_id ) {
|
||||
$this->set_prop( 'gateway_id', $gateway_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the payment as default or non-default.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param boolean $is_default True or false.
|
||||
*/
|
||||
public function set_default( $is_default ) {
|
||||
$this->set_prop( 'is_default', (bool) $is_default );
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Other Methods
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns if the token is marked as default.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return boolean True if the token is default
|
||||
*/
|
||||
public function is_default() {
|
||||
return (bool) $this->get_prop( 'is_default', 'view' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate basic token info (token and type are required).
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return boolean True if the passed data is valid
|
||||
*/
|
||||
public function validate() {
|
||||
$token = $this->get_prop( 'token', 'edit' );
|
||||
if ( empty( $token ) ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce abstract privacy class.
|
||||
*
|
||||
* @since 3.4.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Abstract class that is intended to be extended by
|
||||
* specific privacy class. It handles the display
|
||||
* of the privacy message of the privacy id to the admin,
|
||||
* privacy data to be exported and privacy data to be deleted.
|
||||
*
|
||||
* @version 3.4.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Abstract_Privacy {
|
||||
/**
|
||||
* This is the name of this object type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* This is a list of exporters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $exporters = array();
|
||||
|
||||
/**
|
||||
* This is a list of erasers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $erasers = array();
|
||||
|
||||
/**
|
||||
* This is a priority for the wp_privacy_personal_data_exporters filter
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $export_priority;
|
||||
|
||||
/**
|
||||
* This is a priority for the wp_privacy_personal_data_erasers filter
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $erase_priority;
|
||||
|
||||
/**
|
||||
* WC_Abstract_Privacy Constructor.
|
||||
*
|
||||
* @param string $name Plugin identifier.
|
||||
* @param int $export_priority Export priority.
|
||||
* @param int $erase_priority Erase priority.
|
||||
*/
|
||||
public function __construct( $name = '', $export_priority = 5, $erase_priority = 10 ) {
|
||||
$this->name = $name;
|
||||
$this->export_priority = $export_priority;
|
||||
$this->erase_priority = $erase_priority;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook in events.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'admin_init', array( $this, 'add_privacy_message' ) );
|
||||
// We set priority to 5 to help WooCommerce's findings appear before those from extensions in exported items.
|
||||
add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_exporters' ), $this->export_priority );
|
||||
add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_erasers' ), $this->erase_priority );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the privacy message on WC privacy page.
|
||||
*/
|
||||
public function add_privacy_message() {
|
||||
if ( function_exists( 'wp_add_privacy_policy_content' ) ) {
|
||||
$content = $this->get_privacy_message();
|
||||
|
||||
if ( $content ) {
|
||||
wp_add_privacy_policy_content( $this->name, $this->get_privacy_message() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message of the privacy to display.
|
||||
* To be overloaded by the implementor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_privacy_message() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrate this exporter implementation within the WordPress core exporters.
|
||||
*
|
||||
* @param array $exporters List of exporter callbacks.
|
||||
* @return array
|
||||
*/
|
||||
public function register_exporters( $exporters = array() ) {
|
||||
foreach ( $this->exporters as $id => $exporter ) {
|
||||
$exporters[ $id ] = $exporter;
|
||||
}
|
||||
return $exporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrate this eraser implementation within the WordPress core erasers.
|
||||
*
|
||||
* @param array $erasers List of eraser callbacks.
|
||||
* @return array
|
||||
*/
|
||||
public function register_erasers( $erasers = array() ) {
|
||||
foreach ( $this->erasers as $id => $eraser ) {
|
||||
$erasers[ $id ] = $eraser;
|
||||
}
|
||||
return $erasers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add exporter to list of exporters.
|
||||
*
|
||||
* @param string $id ID of the Exporter.
|
||||
* @param string $name Exporter name.
|
||||
* @param string|array $callback Exporter callback.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_exporter( $id, $name, $callback ) {
|
||||
$this->exporters[ $id ] = array(
|
||||
'exporter_friendly_name' => $name,
|
||||
'callback' => $callback,
|
||||
);
|
||||
return $this->exporters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add eraser to list of erasers.
|
||||
*
|
||||
* @param string $id ID of the Eraser.
|
||||
* @param string $name Exporter name.
|
||||
* @param string|array $callback Exporter callback.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_eraser( $id, $name, $callback ) {
|
||||
$this->erasers[ $id ] = array(
|
||||
'eraser_friendly_name' => $name,
|
||||
'callback' => $callback,
|
||||
);
|
||||
return $this->erasers;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* Handle data for the current customers session
|
||||
*
|
||||
* @class WC_Session
|
||||
* @version 2.0.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Session
|
||||
*/
|
||||
abstract class WC_Session {
|
||||
|
||||
/**
|
||||
* Customer ID.
|
||||
*
|
||||
* @var int $_customer_id Customer ID.
|
||||
*/
|
||||
protected $_customer_id;
|
||||
|
||||
/**
|
||||
* Session Data.
|
||||
*
|
||||
* @var array $_data Data array.
|
||||
*/
|
||||
protected $_data = array();
|
||||
|
||||
/**
|
||||
* Dirty when the session needs saving.
|
||||
*
|
||||
* @var bool $_dirty When something changes
|
||||
*/
|
||||
protected $_dirty = false;
|
||||
|
||||
/**
|
||||
* Init hooks and session data. Extended by child classes.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function init() {}
|
||||
|
||||
/**
|
||||
* Cleanup session data. Extended by child classes.
|
||||
*/
|
||||
public function cleanup_sessions() {}
|
||||
|
||||
/**
|
||||
* Magic get method.
|
||||
*
|
||||
* @param mixed $key Key to get.
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get( $key ) {
|
||||
return $this->get( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic set method.
|
||||
*
|
||||
* @param mixed $key Key to set.
|
||||
* @param mixed $value Value to set.
|
||||
*/
|
||||
public function __set( $key, $value ) {
|
||||
$this->set( $key, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic isset method.
|
||||
*
|
||||
* @param mixed $key Key to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset( $key ) {
|
||||
return isset( $this->_data[ sanitize_title( $key ) ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic unset method.
|
||||
*
|
||||
* @param mixed $key Key to unset.
|
||||
*/
|
||||
public function __unset( $key ) {
|
||||
if ( isset( $this->_data[ $key ] ) ) {
|
||||
unset( $this->_data[ $key ] );
|
||||
$this->_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a session variable.
|
||||
*
|
||||
* @param string $key Key to get.
|
||||
* @param mixed $default used if the session variable isn't set.
|
||||
* @return array|string value of session variable
|
||||
*/
|
||||
public function get( $key, $default = null ) {
|
||||
$key = sanitize_key( $key );
|
||||
return isset( $this->_data[ $key ] ) ? maybe_unserialize( $this->_data[ $key ] ) : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a session variable.
|
||||
*
|
||||
* @param string $key Key to set.
|
||||
* @param mixed $value Value to set.
|
||||
*/
|
||||
public function set( $key, $value ) {
|
||||
if ( $value !== $this->get( $key ) ) {
|
||||
$this->_data[ sanitize_key( $key ) ] = maybe_serialize( $value );
|
||||
$this->_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_customer_id() {
|
||||
return $this->_customer_id;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,593 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract shipping method
|
||||
*
|
||||
* @class WC_Shipping_Method
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Shipping Method Class.
|
||||
*
|
||||
* Extended by shipping methods to handle shipping calculations etc.
|
||||
*
|
||||
* @class WC_Shipping_Method
|
||||
* @version 3.0.0
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
abstract class WC_Shipping_Method extends WC_Settings_API {
|
||||
|
||||
/**
|
||||
* Features this method supports. Possible features used by core:
|
||||
* - shipping-zones Shipping zone functionality + instances
|
||||
* - instance-settings Instance settings screens.
|
||||
* - settings Non-instance settings screens. Enabled by default for BW compatibility with methods before instances existed.
|
||||
* - instance-settings-modal Allows the instance settings to be loaded within a modal in the zones UI.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $supports = array( 'settings' );
|
||||
|
||||
/**
|
||||
* Unique ID for the shipping method - must be set.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id = '';
|
||||
|
||||
/**
|
||||
* Method title.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method_title = '';
|
||||
|
||||
/**
|
||||
* Method description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method_description = '';
|
||||
|
||||
/**
|
||||
* Yes or no based on whether the method is enabled.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $enabled = 'yes';
|
||||
|
||||
/**
|
||||
* Shipping method title for the frontend.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* This is an array of rates - methods must populate this array to register shipping costs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $rates = array();
|
||||
|
||||
/**
|
||||
* If 'taxable' tax will be charged for this method (if applicable).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $tax_status = 'taxable';
|
||||
|
||||
/**
|
||||
* Fee for the method (if applicable).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $fee = null;
|
||||
|
||||
/**
|
||||
* Minimum fee for the method (if applicable).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $minimum_fee = null;
|
||||
|
||||
/**
|
||||
* Instance ID if used.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $instance_id = 0;
|
||||
|
||||
/**
|
||||
* Instance form fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $instance_form_fields = array();
|
||||
|
||||
/**
|
||||
* Instance settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $instance_settings = array();
|
||||
|
||||
/**
|
||||
* Availability - legacy. Used for method Availability.
|
||||
* No longer useful for instance based shipping methods.
|
||||
*
|
||||
* @deprecated 2.6.0
|
||||
* @var string
|
||||
*/
|
||||
public $availability;
|
||||
|
||||
/**
|
||||
* Availability countries - legacy. Used for method Availability.
|
||||
* No longer useful for instance based shipping methods.
|
||||
*
|
||||
* @deprecated 2.6.0
|
||||
* @var array
|
||||
*/
|
||||
public $countries = array();
|
||||
|
||||
/**
|
||||
* Shipping method order.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $method_order;
|
||||
|
||||
/**
|
||||
* Whether the shipping method has settings or not. Preferably, use {@see has_settings()} instead.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $has_settings;
|
||||
|
||||
/**
|
||||
* When the method supports the settings modal, this is the admin settings HTML.
|
||||
* Preferably, use {@see get_admin_options_html()} instead.
|
||||
*
|
||||
* @var string|bool
|
||||
*/
|
||||
public $settings_html;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $instance_id Instance ID.
|
||||
*/
|
||||
public function __construct( $instance_id = 0 ) {
|
||||
$this->instance_id = absint( $instance_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a shipping method supports a given feature.
|
||||
*
|
||||
* Methods should override this to declare support (or lack of support) for a feature.
|
||||
*
|
||||
* @param string $feature The name of a feature to test support for.
|
||||
* @return bool True if the shipping method supports the feature, false otherwise.
|
||||
*/
|
||||
public function supports( $feature ) {
|
||||
return apply_filters( 'woocommerce_shipping_method_supports', in_array( $feature, $this->supports ), $feature, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to calculate shipping rates for this method. Rates can be added using the add_rate() method.
|
||||
*
|
||||
* @param array $package Package array.
|
||||
*/
|
||||
public function calculate_shipping( $package = array() ) {}
|
||||
|
||||
/**
|
||||
* Whether or not we need to calculate tax on top of the shipping rate.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_taxable() {
|
||||
return wc_tax_enabled() && 'taxable' === $this->tax_status && ( WC()->customer && ! WC()->customer->get_is_vat_exempt() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this method is enabled in settings.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return 'yes' === $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the shipping method instance ID.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return int
|
||||
*/
|
||||
public function get_instance_id() {
|
||||
return $this->instance_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the shipping method title.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_method_title() {
|
||||
return apply_filters( 'woocommerce_shipping_method_title', $this->method_title, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the shipping method description.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_method_description() {
|
||||
return apply_filters( 'woocommerce_shipping_method_description', $this->method_description, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the shipping title which is user set.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return apply_filters( 'woocommerce_shipping_method_title', $this->title, $this->id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return calculated rates for a package.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param array $package Package array.
|
||||
* @return array
|
||||
*/
|
||||
public function get_rates_for_package( $package ) {
|
||||
$this->rates = array();
|
||||
if ( $this->is_available( $package ) && ( empty( $package['ship_via'] ) || in_array( $this->id, $package['ship_via'] ) ) ) {
|
||||
$this->calculate_shipping( $package );
|
||||
}
|
||||
return $this->rates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rate ID based on this methods ID and instance, with an optional
|
||||
* suffix if distinguishing between multiple rates.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param string $suffix Suffix.
|
||||
* @return string
|
||||
*/
|
||||
public function get_rate_id( $suffix = '' ) {
|
||||
$rate_id = array( $this->id );
|
||||
|
||||
if ( $this->instance_id ) {
|
||||
$rate_id[] = $this->instance_id;
|
||||
}
|
||||
|
||||
if ( $suffix ) {
|
||||
$rate_id[] = $suffix;
|
||||
}
|
||||
|
||||
return implode( ':', $rate_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shipping rate. If taxes are not set they will be calculated based on cost.
|
||||
*
|
||||
* @param array $args Arguments (default: array()).
|
||||
*/
|
||||
public function add_rate( $args = array() ) {
|
||||
$args = apply_filters(
|
||||
'woocommerce_shipping_method_add_rate_args',
|
||||
wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'id' => $this->get_rate_id(), // ID for the rate. If not passed, this id:instance default will be used.
|
||||
'label' => '', // Label for the rate.
|
||||
'cost' => '0', // Amount or array of costs (per item shipping).
|
||||
'taxes' => '', // Pass taxes, or leave empty to have it calculated for you, or 'false' to disable calculations.
|
||||
'calc_tax' => 'per_order', // Calc tax per_order or per_item. Per item needs an array of costs.
|
||||
'meta_data' => array(), // Array of misc meta data to store along with this rate - key value pairs.
|
||||
'package' => false, // Package array this rate was generated for @since 2.6.0.
|
||||
'price_decimals' => wc_get_price_decimals(),
|
||||
)
|
||||
),
|
||||
$this
|
||||
);
|
||||
|
||||
// ID and label are required.
|
||||
if ( ! $args['id'] || ! $args['label'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Total up the cost.
|
||||
$total_cost = is_array( $args['cost'] ) ? array_sum( $args['cost'] ) : $args['cost'];
|
||||
$taxes = $args['taxes'];
|
||||
|
||||
// Taxes - if not an array and not set to false, calc tax based on cost and passed calc_tax variable. This saves shipping methods having to do complex tax calculations.
|
||||
if ( ! is_array( $taxes ) && false !== $taxes && $total_cost > 0 && $this->is_taxable() ) {
|
||||
$taxes = 'per_item' === $args['calc_tax'] ? $this->get_taxes_per_item( $args['cost'] ) : WC_Tax::calc_shipping_tax( $total_cost, WC_Tax::get_shipping_tax_rates() );
|
||||
}
|
||||
|
||||
// Round the total cost after taxes have been calculated.
|
||||
$total_cost = wc_format_decimal( $total_cost, $args['price_decimals'] );
|
||||
|
||||
// Create rate object.
|
||||
$rate = new WC_Shipping_Rate();
|
||||
$rate->set_id( $args['id'] );
|
||||
$rate->set_method_id( $this->id );
|
||||
$rate->set_instance_id( $this->instance_id );
|
||||
$rate->set_label( $args['label'] );
|
||||
$rate->set_cost( $total_cost );
|
||||
$rate->set_taxes( $taxes );
|
||||
|
||||
if ( ! empty( $args['meta_data'] ) ) {
|
||||
foreach ( $args['meta_data'] as $key => $value ) {
|
||||
$rate->add_meta_data( $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
// Store package data.
|
||||
if ( $args['package'] ) {
|
||||
$items_in_package = array();
|
||||
foreach ( $args['package']['contents'] as $item ) {
|
||||
$product = $item['data'];
|
||||
$items_in_package[] = $product->get_name() . ' × ' . $item['quantity'];
|
||||
}
|
||||
$rate->add_meta_data( __( 'Items', 'woocommerce' ), implode( ', ', $items_in_package ) );
|
||||
}
|
||||
|
||||
$this->rates[ $args['id'] ] = apply_filters( 'woocommerce_shipping_method_add_rate', $rate, $args, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calc taxes per item being shipping in costs array.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param array $costs Costs.
|
||||
* @return array of taxes
|
||||
*/
|
||||
protected function get_taxes_per_item( $costs ) {
|
||||
$taxes = array();
|
||||
|
||||
// If we have an array of costs we can look up each items tax class and add tax accordingly.
|
||||
if ( is_array( $costs ) ) {
|
||||
|
||||
$cart = WC()->cart->get_cart();
|
||||
|
||||
foreach ( $costs as $cost_key => $amount ) {
|
||||
if ( ! isset( $cart[ $cost_key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item_taxes = WC_Tax::calc_shipping_tax( $amount, WC_Tax::get_shipping_tax_rates( $cart[ $cost_key ]['data']->get_tax_class() ) );
|
||||
|
||||
// Sum the item taxes.
|
||||
foreach ( array_keys( $taxes + $item_taxes ) as $key ) {
|
||||
$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
|
||||
}
|
||||
}
|
||||
|
||||
// Add any cost for the order - order costs are in the key 'order'.
|
||||
if ( isset( $costs['order'] ) ) {
|
||||
$item_taxes = WC_Tax::calc_shipping_tax( $costs['order'], WC_Tax::get_shipping_tax_rates() );
|
||||
|
||||
// Sum the item taxes.
|
||||
foreach ( array_keys( $taxes + $item_taxes ) as $key ) {
|
||||
$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $taxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this method available?
|
||||
*
|
||||
* @param array $package Package.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_available( $package ) {
|
||||
$available = $this->is_enabled();
|
||||
|
||||
// Country availability (legacy, for non-zone based methods).
|
||||
if ( ! $this->instance_id && $available ) {
|
||||
$countries = is_array( $this->countries ) ? $this->countries : array();
|
||||
|
||||
switch ( $this->availability ) {
|
||||
case 'specific':
|
||||
case 'including':
|
||||
$available = in_array( $package['destination']['country'], array_intersect( $countries, array_keys( WC()->countries->get_shipping_countries() ) ) );
|
||||
break;
|
||||
case 'excluding':
|
||||
$available = in_array( $package['destination']['country'], array_diff( array_keys( WC()->countries->get_shipping_countries() ), $countries ) );
|
||||
break;
|
||||
default:
|
||||
$available = in_array( $package['destination']['country'], array_keys( WC()->countries->get_shipping_countries() ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $available, $package, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fee to add to shipping cost.
|
||||
*
|
||||
* @param string|float $fee Fee.
|
||||
* @param float $total Total.
|
||||
* @return float
|
||||
*/
|
||||
public function get_fee( $fee, $total ) {
|
||||
if ( strstr( $fee, '%' ) ) {
|
||||
$fee = ( $total / 100 ) * str_replace( '%', '', $fee );
|
||||
}
|
||||
if ( ! empty( $this->minimum_fee ) && $this->minimum_fee > $fee ) {
|
||||
$fee = $this->minimum_fee;
|
||||
}
|
||||
return $fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this method have a settings page?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_settings() {
|
||||
return $this->instance_id ? $this->supports( 'instance-settings' ) : $this->supports( 'settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return admin options as a html string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_admin_options_html() {
|
||||
if ( $this->instance_id ) {
|
||||
$settings_html = $this->generate_settings_html( $this->get_instance_form_fields(), false );
|
||||
} else {
|
||||
$settings_html = $this->generate_settings_html( $this->get_form_fields(), false );
|
||||
}
|
||||
|
||||
return '<table class="form-table">' . $settings_html . '</table>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the shipping settings screen.
|
||||
*/
|
||||
public function admin_options() {
|
||||
if ( ! $this->instance_id ) {
|
||||
echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
|
||||
}
|
||||
echo wp_kses_post( wpautop( $this->get_method_description() ) );
|
||||
echo $this->get_admin_options_html(); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Get_option function.
|
||||
*
|
||||
* Gets and option from the settings API, using defaults if necessary to prevent undefined notices.
|
||||
*
|
||||
* @param string $key Key.
|
||||
* @param mixed $empty_value Empty value.
|
||||
* @return mixed The value specified for the option or a default value for the option.
|
||||
*/
|
||||
public function get_option( $key, $empty_value = null ) {
|
||||
// Instance options take priority over global options.
|
||||
if ( $this->instance_id && array_key_exists( $key, $this->get_instance_form_fields() ) ) {
|
||||
return $this->get_instance_option( $key, $empty_value );
|
||||
}
|
||||
|
||||
// Return global option.
|
||||
$option = apply_filters( 'woocommerce_shipping_' . $this->id . '_option', parent::get_option( $key, $empty_value ), $key, $this );
|
||||
return $option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an option from the settings API, using defaults if necessary to prevent undefined notices.
|
||||
*
|
||||
* @param string $key Key.
|
||||
* @param mixed $empty_value Empty value.
|
||||
* @return mixed The value specified for the option or a default value for the option.
|
||||
*/
|
||||
public function get_instance_option( $key, $empty_value = null ) {
|
||||
if ( empty( $this->instance_settings ) ) {
|
||||
$this->init_instance_settings();
|
||||
}
|
||||
|
||||
// Get option default if unset.
|
||||
if ( ! isset( $this->instance_settings[ $key ] ) ) {
|
||||
$form_fields = $this->get_instance_form_fields();
|
||||
$this->instance_settings[ $key ] = $this->get_field_default( $form_fields[ $key ] );
|
||||
}
|
||||
|
||||
if ( ! is_null( $empty_value ) && '' === $this->instance_settings[ $key ] ) {
|
||||
$this->instance_settings[ $key ] = $empty_value;
|
||||
}
|
||||
|
||||
$instance_option = apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_option', $this->instance_settings[ $key ], $key, $this );
|
||||
return $instance_option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings fields for instances of this shipping method (within zones).
|
||||
* Should be overridden by shipping methods to add options.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_instance_form_fields() {
|
||||
return apply_filters( 'woocommerce_shipping_instance_form_fields_' . $this->id, array_map( array( $this, 'set_defaults' ), $this->instance_form_fields ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the option in the WP DB.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_instance_option_key() {
|
||||
return $this->instance_id ? $this->plugin_id . $this->id . '_' . $this->instance_id . '_settings' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise Settings for instances.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public function init_instance_settings() {
|
||||
$this->instance_settings = get_option( $this->get_instance_option_key(), null );
|
||||
|
||||
// If there are no settings defined, use defaults.
|
||||
if ( ! is_array( $this->instance_settings ) ) {
|
||||
$form_fields = $this->get_instance_form_fields();
|
||||
$this->instance_settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes and saves global shipping method options in the admin area.
|
||||
*
|
||||
* This method is usually attached to woocommerce_update_options_x hooks.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @return bool was anything saved?
|
||||
*/
|
||||
public function process_admin_options() {
|
||||
if ( ! $this->instance_id ) {
|
||||
return parent::process_admin_options();
|
||||
}
|
||||
|
||||
// Check we are processing the correct form for this instance.
|
||||
if ( ! isset( $_REQUEST['instance_id'] ) || absint( $_REQUEST['instance_id'] ) !== $this->instance_id ) { // WPCS: input var ok, CSRF ok.
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->init_instance_settings();
|
||||
|
||||
$post_data = $this->get_post_data();
|
||||
|
||||
foreach ( $this->get_instance_form_fields() as $key => $field ) {
|
||||
if ( 'title' !== $this->get_field_type( $field ) ) {
|
||||
try {
|
||||
$this->instance_settings[ $key ] = $this->get_field_value( $key, $field, $post_data );
|
||||
} catch ( Exception $e ) {
|
||||
$this->add_error( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return update_option( $this->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_settings_values', $this->instance_settings, $this ), 'yes' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract widget class
|
||||
*
|
||||
* @class WC_Widget
|
||||
* @package WooCommerce\Abstracts
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Widget
|
||||
*
|
||||
* @package WooCommerce\Abstracts
|
||||
* @version 2.5.0
|
||||
* @extends WP_Widget
|
||||
*/
|
||||
abstract class WC_Widget extends WP_Widget {
|
||||
|
||||
/**
|
||||
* CSS class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $widget_cssclass;
|
||||
|
||||
/**
|
||||
* Widget description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $widget_description;
|
||||
|
||||
/**
|
||||
* Widget ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $widget_id;
|
||||
|
||||
/**
|
||||
* Widget name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $widget_name;
|
||||
|
||||
/**
|
||||
* Settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$widget_ops = array(
|
||||
'classname' => $this->widget_cssclass,
|
||||
'description' => $this->widget_description,
|
||||
'customize_selective_refresh' => true,
|
||||
'show_instance_in_rest' => true,
|
||||
);
|
||||
|
||||
parent::__construct( $this->widget_id, $this->widget_name, $widget_ops );
|
||||
|
||||
add_action( 'save_post', array( $this, 'flush_widget_cache' ) );
|
||||
add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) );
|
||||
add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached widget.
|
||||
*
|
||||
* @param array $args Arguments.
|
||||
* @return bool true if the widget is cached otherwise false
|
||||
*/
|
||||
public function get_cached_widget( $args ) {
|
||||
// Don't get cache if widget_id doesn't exists.
|
||||
if ( empty( $args['widget_id'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' );
|
||||
|
||||
if ( ! is_array( $cache ) ) {
|
||||
$cache = array();
|
||||
}
|
||||
|
||||
if ( isset( $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] ) ) {
|
||||
echo $cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ]; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the widget.
|
||||
*
|
||||
* @param array $args Arguments.
|
||||
* @param string $content Content.
|
||||
* @return string the content that was cached
|
||||
*/
|
||||
public function cache_widget( $args, $content ) {
|
||||
// Don't set any cache if widget_id doesn't exist.
|
||||
if ( empty( $args['widget_id'] ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$cache = wp_cache_get( $this->get_widget_id_for_cache( $this->widget_id ), 'widget' );
|
||||
|
||||
if ( ! is_array( $cache ) ) {
|
||||
$cache = array();
|
||||
}
|
||||
|
||||
$cache[ $this->get_widget_id_for_cache( $args['widget_id'] ) ] = $content;
|
||||
|
||||
wp_cache_set( $this->get_widget_id_for_cache( $this->widget_id ), $cache, 'widget' );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the cache.
|
||||
*/
|
||||
public function flush_widget_cache() {
|
||||
foreach ( array( 'https', 'http' ) as $scheme ) {
|
||||
wp_cache_delete( $this->get_widget_id_for_cache( $this->widget_id, $scheme ), 'widget' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this widgets title.
|
||||
*
|
||||
* @param array $instance Array of instance options.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_instance_title( $instance ) {
|
||||
if ( isset( $instance['title'] ) ) {
|
||||
return $instance['title'];
|
||||
}
|
||||
|
||||
if ( isset( $this->settings, $this->settings['title'], $this->settings['title']['std'] ) ) {
|
||||
return $this->settings['title']['std'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the html at the start of a widget.
|
||||
*
|
||||
* @param array $args Arguments.
|
||||
* @param array $instance Instance.
|
||||
*/
|
||||
public function widget_start( $args, $instance ) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$title = apply_filters( 'widget_title', $this->get_instance_title( $instance ), $instance, $this->id_base );
|
||||
|
||||
if ( $title ) {
|
||||
echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the html at the end of a widget.
|
||||
*
|
||||
* @param array $args Arguments.
|
||||
*/
|
||||
public function widget_end( $args ) {
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a particular instance of a widget.
|
||||
*
|
||||
* @see WP_Widget->update
|
||||
* @param array $new_instance New instance.
|
||||
* @param array $old_instance Old instance.
|
||||
* @return array
|
||||
*/
|
||||
public function update( $new_instance, $old_instance ) {
|
||||
|
||||
$instance = $old_instance;
|
||||
|
||||
if ( empty( $this->settings ) ) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// Loop settings and get values to save.
|
||||
foreach ( $this->settings as $key => $setting ) {
|
||||
if ( ! isset( $setting['type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Format the value based on settings type.
|
||||
switch ( $setting['type'] ) {
|
||||
case 'number':
|
||||
$instance[ $key ] = absint( $new_instance[ $key ] );
|
||||
|
||||
if ( isset( $setting['min'] ) && '' !== $setting['min'] ) {
|
||||
$instance[ $key ] = max( $instance[ $key ], $setting['min'] );
|
||||
}
|
||||
|
||||
if ( isset( $setting['max'] ) && '' !== $setting['max'] ) {
|
||||
$instance[ $key ] = min( $instance[ $key ], $setting['max'] );
|
||||
}
|
||||
break;
|
||||
case 'textarea':
|
||||
$instance[ $key ] = wp_kses( trim( wp_unslash( $new_instance[ $key ] ) ), wp_kses_allowed_html( 'post' ) );
|
||||
break;
|
||||
case 'checkbox':
|
||||
$instance[ $key ] = empty( $new_instance[ $key ] ) ? 0 : 1;
|
||||
break;
|
||||
default:
|
||||
$instance[ $key ] = isset( $new_instance[ $key ] ) ? sanitize_text_field( $new_instance[ $key ] ) : $setting['std'];
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the value of a setting.
|
||||
*/
|
||||
$instance[ $key ] = apply_filters( 'woocommerce_widget_settings_sanitize_option', $instance[ $key ], $new_instance, $key, $setting );
|
||||
}
|
||||
|
||||
$this->flush_widget_cache();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the settings update form.
|
||||
*
|
||||
* @see WP_Widget->form
|
||||
*
|
||||
* @param array $instance Instance.
|
||||
*/
|
||||
public function form( $instance ) {
|
||||
|
||||
if ( empty( $this->settings ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->settings as $key => $setting ) {
|
||||
|
||||
$class = isset( $setting['class'] ) ? $setting['class'] : '';
|
||||
$value = isset( $instance[ $key ] ) ? $instance[ $key ] : $setting['std'];
|
||||
|
||||
switch ( $setting['type'] ) {
|
||||
|
||||
case 'text':
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo wp_kses_post( $setting['label'] ); ?></label><?php // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="text" value="<?php echo esc_attr( $value ); ?>" />
|
||||
</p>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
|
||||
<input class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="number" step="<?php echo esc_attr( $setting['step'] ); ?>" min="<?php echo esc_attr( $setting['min'] ); ?>" max="<?php echo esc_attr( $setting['max'] ); ?>" value="<?php echo esc_attr( $value ); ?>" />
|
||||
</p>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
|
||||
<select class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>">
|
||||
<?php foreach ( $setting['options'] as $option_key => $option_value ) : ?>
|
||||
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $option_key, $value ); ?>><?php echo esc_html( $option_value ); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'textarea':
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
|
||||
<textarea class="widefat <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" cols="20" rows="3"><?php echo esc_textarea( $value ); ?></textarea>
|
||||
<?php if ( isset( $setting['desc'] ) ) : ?>
|
||||
<small><?php echo esc_html( $setting['desc'] ); ?></small>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'checkbox':
|
||||
?>
|
||||
<p>
|
||||
<input class="checkbox <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="checkbox" value="1" <?php checked( $value, 1 ); ?> />
|
||||
<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo $setting['label']; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?></label>
|
||||
</p>
|
||||
<?php
|
||||
break;
|
||||
|
||||
// Default: run an action.
|
||||
default:
|
||||
do_action( 'woocommerce_widget_field_' . $setting['type'], $key, $value, $setting, $instance );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page URL with various filtering props supported by WC.
|
||||
*
|
||||
* @return string
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected function get_current_page_url() {
|
||||
if ( Constants::is_defined( 'SHOP_IS_ON_FRONT' ) ) {
|
||||
$link = home_url();
|
||||
} elseif ( is_shop() ) {
|
||||
$link = get_permalink( wc_get_page_id( 'shop' ) );
|
||||
} elseif ( is_product_category() ) {
|
||||
$link = get_term_link( get_query_var( 'product_cat' ), 'product_cat' );
|
||||
} elseif ( is_product_tag() ) {
|
||||
$link = get_term_link( get_query_var( 'product_tag' ), 'product_tag' );
|
||||
} else {
|
||||
$queried_object = get_queried_object();
|
||||
$link = get_term_link( $queried_object->slug, $queried_object->taxonomy );
|
||||
}
|
||||
|
||||
// Min/Max.
|
||||
if ( isset( $_GET['min_price'] ) ) {
|
||||
$link = add_query_arg( 'min_price', wc_clean( wp_unslash( $_GET['min_price'] ) ), $link );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['max_price'] ) ) {
|
||||
$link = add_query_arg( 'max_price', wc_clean( wp_unslash( $_GET['max_price'] ) ), $link );
|
||||
}
|
||||
|
||||
// Order by.
|
||||
if ( isset( $_GET['orderby'] ) ) {
|
||||
$link = add_query_arg( 'orderby', wc_clean( wp_unslash( $_GET['orderby'] ) ), $link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Arg.
|
||||
* To support quote characters, first they are decoded from " entities, then URL encoded.
|
||||
*/
|
||||
if ( get_search_query() ) {
|
||||
$link = add_query_arg( 's', rawurlencode( htmlspecialchars_decode( get_search_query() ) ), $link );
|
||||
}
|
||||
|
||||
// Post Type Arg.
|
||||
if ( isset( $_GET['post_type'] ) ) {
|
||||
$link = add_query_arg( 'post_type', wc_clean( wp_unslash( $_GET['post_type'] ) ), $link );
|
||||
|
||||
// Prevent post type and page id when pretty permalinks are disabled.
|
||||
if ( is_shop() ) {
|
||||
$link = remove_query_arg( 'page_id', $link );
|
||||
}
|
||||
}
|
||||
|
||||
// Min Rating Arg.
|
||||
if ( isset( $_GET['rating_filter'] ) ) {
|
||||
$link = add_query_arg( 'rating_filter', wc_clean( wp_unslash( $_GET['rating_filter'] ) ), $link );
|
||||
}
|
||||
|
||||
// All current filters.
|
||||
if ( $_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes() ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure, WordPress.CodeAnalysis.AssignmentInCondition.Found
|
||||
foreach ( $_chosen_attributes as $name => $data ) {
|
||||
$filter_name = wc_attribute_taxonomy_slug( $name );
|
||||
if ( ! empty( $data['terms'] ) ) {
|
||||
$link = add_query_arg( 'filter_' . $filter_name, implode( ',', $data['terms'] ), $link );
|
||||
}
|
||||
if ( 'or' === $data['query_type'] ) {
|
||||
$link = add_query_arg( 'query_type_' . $filter_name, 'or', $link );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_widget_get_current_page_url', $link, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get widget id plus scheme/protocol to prevent serving mixed content from (persistently) cached widgets.
|
||||
*
|
||||
* @since 3.4.0
|
||||
* @param string $widget_id Id of the cached widget.
|
||||
* @param string $scheme Scheme for the widget id.
|
||||
* @return string Widget id including scheme/protocol.
|
||||
*/
|
||||
protected function get_widget_id_for_cache( $widget_id, $scheme = '' ) {
|
||||
if ( $scheme ) {
|
||||
$widget_id_for_cache = $widget_id . '-' . $scheme;
|
||||
} else {
|
||||
$widget_id_for_cache = $widget_id . '-' . ( is_ssl() ? 'https' : 'http' );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_cached_widget_id', $widget_id_for_cache );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract WP_Background_Process class.
|
||||
*
|
||||
* Uses https://github.com/A5hleyRich/wp-background-processing to handle DB
|
||||
* updates in the background.
|
||||
*
|
||||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'WP_Async_Request', false ) ) {
|
||||
include_once dirname( WC_PLUGIN_FILE ) . '/includes/libraries/wp-async-request.php';
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WP_Background_Process', false ) ) {
|
||||
include_once dirname( WC_PLUGIN_FILE ) . '/includes/libraries/wp-background-process.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Background_Process class.
|
||||
*/
|
||||
abstract class WC_Background_Process extends WP_Background_Process {
|
||||
|
||||
/**
|
||||
* Is queue empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_queue_empty() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
}
|
||||
|
||||
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
|
||||
|
||||
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
|
||||
|
||||
return ! ( $count > 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch.
|
||||
*
|
||||
* @return stdClass Return the first batch from the queue.
|
||||
*/
|
||||
protected function get_batch() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
$key_column = 'option_id';
|
||||
$value_column = 'option_value';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
$key_column = 'meta_id';
|
||||
$value_column = 'meta_value';
|
||||
}
|
||||
|
||||
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
|
||||
|
||||
$query = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE {$column} LIKE %s ORDER BY {$key_column} ASC LIMIT 1", $key ) ); // @codingStandardsIgnoreLine.
|
||||
|
||||
$batch = new stdClass();
|
||||
$batch->key = $query->$column;
|
||||
$batch->data = array_filter( (array) maybe_unserialize( $query->$value_column ) );
|
||||
|
||||
return $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the batch limit has been exceeded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function batch_limit_exceeded() {
|
||||
return $this->time_exceeded() || $this->memory_exceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle.
|
||||
*
|
||||
* Pass each queue item to the task handler, while remaining
|
||||
* within server memory and time limit constraints.
|
||||
*/
|
||||
protected function handle() {
|
||||
$this->lock_process();
|
||||
|
||||
do {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
foreach ( $batch->data as $key => $value ) {
|
||||
$task = $this->task( $value );
|
||||
|
||||
if ( false !== $task ) {
|
||||
$batch->data[ $key ] = $task;
|
||||
} else {
|
||||
unset( $batch->data[ $key ] );
|
||||
}
|
||||
|
||||
if ( $this->batch_limit_exceeded() ) {
|
||||
// Batch limits reached.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update or delete current batch.
|
||||
if ( ! empty( $batch->data ) ) {
|
||||
$this->update( $batch->key, $batch->data );
|
||||
} else {
|
||||
$this->delete( $batch->key );
|
||||
}
|
||||
} while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() );
|
||||
|
||||
$this->unlock_process();
|
||||
|
||||
// Start next batch or complete process.
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$this->dispatch();
|
||||
} else {
|
||||
$this->complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory limit.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_memory_limit() {
|
||||
if ( function_exists( 'ini_get' ) ) {
|
||||
$memory_limit = ini_get( 'memory_limit' );
|
||||
} else {
|
||||
// Sensible default.
|
||||
$memory_limit = '128M';
|
||||
}
|
||||
|
||||
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
|
||||
// Unlimited, set to 32GB.
|
||||
$memory_limit = '32G';
|
||||
}
|
||||
|
||||
return wp_convert_hr_to_bytes( $memory_limit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule cron healthcheck.
|
||||
*
|
||||
* @param array $schedules Schedules.
|
||||
* @return array
|
||||
*/
|
||||
public function schedule_cron_healthcheck( $schedules ) {
|
||||
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
|
||||
|
||||
if ( property_exists( $this, 'cron_interval' ) ) {
|
||||
$interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
|
||||
}
|
||||
|
||||
// Adds every 5 minutes to the existing schedules.
|
||||
$schedules[ $this->identifier . '_cron_interval' ] = array(
|
||||
'interval' => MINUTE_IN_SECONDS * $interval,
|
||||
/* translators: %d: interval */
|
||||
'display' => sprintf( __( 'Every %d minutes', 'woocommerce' ), $interval ),
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all batches.
|
||||
*
|
||||
* @return WC_Background_Process
|
||||
*/
|
||||
public function delete_all_batches() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
}
|
||||
|
||||
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
|
||||
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill process.
|
||||
*
|
||||
* Stop processing queue items, clear cronjob and delete all batches.
|
||||
*/
|
||||
public function kill_process() {
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$this->delete_all_batches();
|
||||
wp_clear_scheduled_hook( $this->cron_hook_identifier );
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce API Keys Table List
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.4.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* API Keys table list class.
|
||||
*/
|
||||
class WC_Admin_API_Keys_Table_List extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* Initialize the API key table list.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => 'key',
|
||||
'plural' => 'keys',
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* No items found text.
|
||||
*/
|
||||
public function no_items() {
|
||||
esc_html_e( 'No keys found.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'title' => __( 'Description', 'woocommerce' ),
|
||||
'truncated_key' => __( 'Consumer key ending in', 'woocommerce' ),
|
||||
'user' => __( 'User', 'woocommerce' ),
|
||||
'permissions' => __( 'Permissions', 'woocommerce' ),
|
||||
'last_access' => __( 'Last access', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column cb.
|
||||
*
|
||||
* @param array $key Key data.
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $key ) {
|
||||
return sprintf( '<input type="checkbox" name="key[]" value="%1$s" />', $key['key_id'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return title column.
|
||||
*
|
||||
* @param array $key Key data.
|
||||
* @return string
|
||||
*/
|
||||
public function column_title( $key ) {
|
||||
$url = admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=keys&edit-key=' . $key['key_id'] );
|
||||
$user_id = intval( $key['user_id'] );
|
||||
|
||||
// Check if current user can edit other users or if it's the same user.
|
||||
$can_edit = current_user_can( 'edit_user', $user_id ) || get_current_user_id() === $user_id;
|
||||
|
||||
$output = '<strong>';
|
||||
if ( $can_edit ) {
|
||||
$output .= '<a href="' . esc_url( $url ) . '" class="row-title">';
|
||||
}
|
||||
if ( empty( $key['description'] ) ) {
|
||||
$output .= esc_html__( 'API key', 'woocommerce' );
|
||||
} else {
|
||||
$output .= esc_html( $key['description'] );
|
||||
}
|
||||
if ( $can_edit ) {
|
||||
$output .= '</a>';
|
||||
}
|
||||
$output .= '</strong>';
|
||||
|
||||
// Get actions.
|
||||
$actions = array(
|
||||
/* translators: %s: API key ID. */
|
||||
'id' => sprintf( __( 'ID: %d', 'woocommerce' ), $key['key_id'] ),
|
||||
);
|
||||
|
||||
if ( $can_edit ) {
|
||||
$actions['edit'] = '<a href="' . esc_url( $url ) . '">' . __( 'View/Edit', 'woocommerce' ) . '</a>';
|
||||
$actions['trash'] = '<a class="submitdelete" aria-label="' . esc_attr__( 'Revoke API key', 'woocommerce' ) . '" href="' . esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'revoke-key' => $key['key_id'],
|
||||
),
|
||||
admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=keys' )
|
||||
),
|
||||
'revoke'
|
||||
)
|
||||
) . '">' . esc_html__( 'Revoke', 'woocommerce' ) . '</a>';
|
||||
}
|
||||
|
||||
$row_actions = array();
|
||||
|
||||
foreach ( $actions as $action => $link ) {
|
||||
$row_actions[] = '<span class="' . esc_attr( $action ) . '">' . $link . '</span>';
|
||||
}
|
||||
|
||||
$output .= '<div class="row-actions">' . implode( ' | ', $row_actions ) . '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return truncated consumer key column.
|
||||
*
|
||||
* @param array $key Key data.
|
||||
* @return string
|
||||
*/
|
||||
public function column_truncated_key( $key ) {
|
||||
return '<code>***' . esc_html( $key['truncated_key'] ) . '</code>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user column.
|
||||
*
|
||||
* @param array $key Key data.
|
||||
* @return string
|
||||
*/
|
||||
public function column_user( $key ) {
|
||||
$user = get_user_by( 'id', $key['user_id'] );
|
||||
|
||||
if ( ! $user ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( current_user_can( 'edit_user', $user->ID ) ) {
|
||||
return '<a href="' . esc_url( add_query_arg( array( 'user_id' => $user->ID ), admin_url( 'user-edit.php' ) ) ) . '">' . esc_html( $user->display_name ) . '</a>';
|
||||
}
|
||||
|
||||
return esc_html( $user->display_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return permissions column.
|
||||
*
|
||||
* @param array $key Key data.
|
||||
* @return string
|
||||
*/
|
||||
public function column_permissions( $key ) {
|
||||
$permission_key = $key['permissions'];
|
||||
$permissions = array(
|
||||
'read' => __( 'Read', 'woocommerce' ),
|
||||
'write' => __( 'Write', 'woocommerce' ),
|
||||
'read_write' => __( 'Read/Write', 'woocommerce' ),
|
||||
);
|
||||
|
||||
if ( isset( $permissions[ $permission_key ] ) ) {
|
||||
return esc_html( $permissions[ $permission_key ] );
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return last access column.
|
||||
*
|
||||
* @param array $key Key data.
|
||||
* @return string
|
||||
*/
|
||||
public function column_last_access( $key ) {
|
||||
if ( ! empty( $key['last_access'] ) ) {
|
||||
/* translators: 1: last access date 2: last access time */
|
||||
$date = sprintf( __( '%1$s at %2$s', 'woocommerce' ), date_i18n( wc_date_format(), strtotime( $key['last_access'] ) ), date_i18n( wc_time_format(), strtotime( $key['last_access'] ) ) );
|
||||
|
||||
return apply_filters( 'woocommerce_api_key_last_access_datetime', $date, $key['last_access'] );
|
||||
}
|
||||
|
||||
return __( 'Unknown', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_bulk_actions() {
|
||||
if ( ! current_user_can( 'remove_users' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
'revoke' => __( 'Revoke', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search box.
|
||||
*
|
||||
* @param string $text Button text.
|
||||
* @param string $input_id Input ID.
|
||||
*/
|
||||
public function search_box( $text, $input_id ) {
|
||||
if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { // WPCS: input var okay, CSRF ok.
|
||||
return;
|
||||
}
|
||||
|
||||
$input_id = $input_id . '-search-input';
|
||||
$search_query = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // WPCS: input var okay, CSRF ok.
|
||||
|
||||
echo '<p class="search-box">';
|
||||
echo '<label class="screen-reader-text" for="' . esc_attr( $input_id ) . '">' . esc_html( $text ) . ':</label>';
|
||||
echo '<input type="search" id="' . esc_attr( $input_id ) . '" name="s" value="' . esc_attr( $search_query ) . '" />';
|
||||
submit_button(
|
||||
$text,
|
||||
'',
|
||||
'',
|
||||
false,
|
||||
array(
|
||||
'id' => 'search-submit',
|
||||
)
|
||||
);
|
||||
echo '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare table list items.
|
||||
*/
|
||||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
|
||||
$per_page = $this->get_items_per_page( 'woocommerce_keys_per_page' );
|
||||
$current_page = $this->get_pagenum();
|
||||
|
||||
if ( 1 < $current_page ) {
|
||||
$offset = $per_page * ( $current_page - 1 );
|
||||
} else {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$search = '';
|
||||
|
||||
if ( ! empty( $_REQUEST['s'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$search = "AND description LIKE '%" . esc_sql( $wpdb->esc_like( wc_clean( wp_unslash( $_REQUEST['s'] ) ) ) ) . "%' "; // WPCS: input var okay, CSRF ok.
|
||||
}
|
||||
|
||||
// Get the API keys.
|
||||
$keys = $wpdb->get_results(
|
||||
"SELECT key_id, user_id, description, permissions, truncated_key, last_access FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1 {$search}" .
|
||||
$wpdb->prepare( 'ORDER BY key_id DESC LIMIT %d OFFSET %d;', $per_page, $offset ),
|
||||
ARRAY_A
|
||||
); // WPCS: unprepared SQL ok.
|
||||
|
||||
$count = $wpdb->get_var( "SELECT COUNT(key_id) FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1 {$search};" ); // WPCS: unprepared SQL ok.
|
||||
|
||||
$this->items = $keys;
|
||||
|
||||
// Set the pagination.
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $count,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => ceil( $count / $per_page ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin API Keys Class
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.4.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_API_Keys.
|
||||
*/
|
||||
class WC_Admin_API_Keys {
|
||||
|
||||
/**
|
||||
* Initialize the API Keys admin actions.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_init', array( $this, 'actions' ) );
|
||||
add_action( 'woocommerce_settings_page_init', array( $this, 'screen_option' ) );
|
||||
add_filter( 'woocommerce_save_settings_advanced_keys', array( $this, 'allow_save_settings' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if should allow save settings.
|
||||
* This prevents "Your settings have been saved." notices on the table list.
|
||||
*
|
||||
* @param bool $allow If allow save settings.
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_save_settings( $allow ) {
|
||||
if ( ! isset( $_GET['create-key'], $_GET['edit-key'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
return false;
|
||||
}
|
||||
|
||||
return $allow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is API Keys settings page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_api_keys_settings_page() {
|
||||
return isset( $_GET['page'], $_GET['tab'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && 'advanced' === $_GET['tab'] && 'keys' === $_GET['section']; // WPCS: input var okay, CSRF ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Page output.
|
||||
*/
|
||||
public static function page_output() {
|
||||
// Hide the save button.
|
||||
$GLOBALS['hide_save_button'] = true;
|
||||
|
||||
if ( isset( $_GET['create-key'] ) || isset( $_GET['edit-key'] ) ) {
|
||||
$key_id = isset( $_GET['edit-key'] ) ? absint( $_GET['edit-key'] ) : 0; // WPCS: input var okay, CSRF ok.
|
||||
$key_data = self::get_key_data( $key_id );
|
||||
$user_id = (int) $key_data['user_id'];
|
||||
|
||||
if ( $key_id && $user_id && ! current_user_can( 'edit_user', $user_id ) ) {
|
||||
if ( get_current_user_id() !== $user_id ) {
|
||||
wp_die( esc_html__( 'You do not have permission to edit this API Key', 'woocommerce' ) );
|
||||
}
|
||||
}
|
||||
|
||||
include dirname( __FILE__ ) . '/settings/views/html-keys-edit.php';
|
||||
} else {
|
||||
self::table_list_output();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add screen option.
|
||||
*/
|
||||
public function screen_option() {
|
||||
global $keys_table_list;
|
||||
|
||||
if ( ! isset( $_GET['create-key'] ) && ! isset( $_GET['edit-key'] ) && $this->is_api_keys_settings_page() ) { // WPCS: input var okay, CSRF ok.
|
||||
$keys_table_list = new WC_Admin_API_Keys_Table_List();
|
||||
|
||||
// Add screen option.
|
||||
add_screen_option(
|
||||
'per_page',
|
||||
array(
|
||||
'default' => 10,
|
||||
'option' => 'woocommerce_keys_per_page',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Table list output.
|
||||
*/
|
||||
private static function table_list_output() {
|
||||
global $wpdb, $keys_table_list;
|
||||
|
||||
echo '<h2 class="wc-table-list-header">' . esc_html__( 'REST API', 'woocommerce' ) . ' <a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=keys&create-key=1' ) ) . '" class="page-title-action">' . esc_html__( 'Add key', 'woocommerce' ) . '</a></h2>';
|
||||
|
||||
// Get the API keys count.
|
||||
$count = $wpdb->get_var( "SELECT COUNT(key_id) FROM {$wpdb->prefix}woocommerce_api_keys WHERE 1 = 1;" );
|
||||
|
||||
if ( absint( $count ) && $count > 0 ) {
|
||||
$keys_table_list->prepare_items();
|
||||
|
||||
echo '<input type="hidden" name="page" value="wc-settings" />';
|
||||
echo '<input type="hidden" name="tab" value="advanced" />';
|
||||
echo '<input type="hidden" name="section" value="keys" />';
|
||||
|
||||
$keys_table_list->views();
|
||||
$keys_table_list->search_box( __( 'Search key', 'woocommerce' ), 'key' );
|
||||
$keys_table_list->display();
|
||||
} else {
|
||||
echo '<div class="woocommerce-BlankState woocommerce-BlankState--api">';
|
||||
?>
|
||||
<h2 class="woocommerce-BlankState-message"><?php esc_html_e( 'The WooCommerce REST API allows external apps to view and manage store data. Access is granted only to those with valid API keys.', 'woocommerce' ); ?></h2>
|
||||
<a class="woocommerce-BlankState-cta button-primary button" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=keys&create-key=1' ) ); ?>"><?php esc_html_e( 'Create an API key', 'woocommerce' ); ?></a>
|
||||
<style type="text/css">#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions { display: none; }</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key data.
|
||||
*
|
||||
* @param int $key_id API Key ID.
|
||||
* @return array
|
||||
*/
|
||||
private static function get_key_data( $key_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$empty = array(
|
||||
'key_id' => 0,
|
||||
'user_id' => '',
|
||||
'description' => '',
|
||||
'permissions' => '',
|
||||
'truncated_key' => '',
|
||||
'last_access' => '',
|
||||
);
|
||||
|
||||
if ( 0 === $key_id ) {
|
||||
return $empty;
|
||||
}
|
||||
|
||||
$key = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT key_id, user_id, description, permissions, truncated_key, last_access
|
||||
FROM {$wpdb->prefix}woocommerce_api_keys
|
||||
WHERE key_id = %d",
|
||||
$key_id
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( is_null( $key ) ) {
|
||||
return $empty;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Keys admin actions.
|
||||
*/
|
||||
public function actions() {
|
||||
if ( $this->is_api_keys_settings_page() ) {
|
||||
// Revoke key.
|
||||
if ( isset( $_REQUEST['revoke-key'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$this->revoke_key();
|
||||
}
|
||||
|
||||
// Bulk actions.
|
||||
if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['key'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$this->bulk_actions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notices.
|
||||
*/
|
||||
public static function notices() {
|
||||
if ( isset( $_GET['revoked'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$revoked = absint( $_GET['revoked'] ); // WPCS: input var okay, CSRF ok.
|
||||
|
||||
/* translators: %d: count */
|
||||
WC_Admin_Settings::add_message( sprintf( _n( '%d API key permanently revoked.', '%d API keys permanently revoked.', $revoked, 'woocommerce' ), $revoked ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke key.
|
||||
*/
|
||||
private function revoke_key() {
|
||||
global $wpdb;
|
||||
|
||||
check_admin_referer( 'revoke' );
|
||||
|
||||
if ( isset( $_REQUEST['revoke-key'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$key_id = absint( $_REQUEST['revoke-key'] ); // WPCS: input var okay, CSRF ok.
|
||||
$user_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM {$wpdb->prefix}woocommerce_api_keys WHERE key_id = %d", $key_id ) );
|
||||
|
||||
if ( $key_id && $user_id && ( current_user_can( 'edit_user', $user_id ) || get_current_user_id() === $user_id ) ) {
|
||||
$this->remove_key( $key_id );
|
||||
} else {
|
||||
wp_die( esc_html__( 'You do not have permission to revoke this API Key', 'woocommerce' ) );
|
||||
}
|
||||
}
|
||||
|
||||
wp_safe_redirect( esc_url_raw( add_query_arg( array( 'revoked' => 1 ), admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=keys' ) ) ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk actions.
|
||||
*/
|
||||
private function bulk_actions() {
|
||||
check_admin_referer( 'woocommerce-settings' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to edit API Keys', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( isset( $_REQUEST['action'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ); // WPCS: input var okay, CSRF ok.
|
||||
$keys = isset( $_REQUEST['key'] ) ? array_map( 'absint', (array) $_REQUEST['key'] ) : array(); // WPCS: input var okay, CSRF ok.
|
||||
|
||||
if ( 'revoke' === $action ) {
|
||||
$this->bulk_revoke_key( $keys );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk revoke key.
|
||||
*
|
||||
* @param array $keys API Keys.
|
||||
*/
|
||||
private function bulk_revoke_key( $keys ) {
|
||||
if ( ! current_user_can( 'remove_users' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to revoke API Keys', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$qty = 0;
|
||||
foreach ( $keys as $key_id ) {
|
||||
$result = $this->remove_key( $key_id );
|
||||
|
||||
if ( $result ) {
|
||||
$qty++;
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to webhooks page.
|
||||
wp_safe_redirect( esc_url_raw( add_query_arg( array( 'revoked' => $qty ), admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=keys' ) ) ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove key.
|
||||
*
|
||||
* @param int $key_id API Key ID.
|
||||
* @return bool
|
||||
*/
|
||||
private function remove_key( $key_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$delete = $wpdb->delete( $wpdb->prefix . 'woocommerce_api_keys', array( 'key_id' => $key_id ), array( '%d' ) );
|
||||
|
||||
return $delete;
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_API_Keys();
|
||||
@@ -0,0 +1,685 @@
|
||||
<?php
|
||||
/**
|
||||
* Load assets
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.7.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Internal\Admin\Analytics;
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
|
||||
|
||||
/**
|
||||
* WC_Admin_Assets Class.
|
||||
*/
|
||||
class WC_Admin_Assets {
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_styles' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
||||
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles.
|
||||
*/
|
||||
public function admin_styles() {
|
||||
global $wp_scripts;
|
||||
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
|
||||
// Register admin styles.
|
||||
wp_register_style( 'woocommerce_admin_menu_styles', WC()->plugin_url() . '/assets/css/menu.css', array(), $version );
|
||||
wp_register_style( 'woocommerce_admin_styles', WC()->plugin_url() . '/assets/css/admin.css', array(), $version );
|
||||
wp_register_style( 'jquery-ui-style', WC()->plugin_url() . '/assets/css/jquery-ui/jquery-ui.min.css', array(), $version );
|
||||
wp_register_style( 'woocommerce_admin_dashboard_styles', WC()->plugin_url() . '/assets/css/dashboard.css', array(), $version );
|
||||
wp_register_style( 'woocommerce_admin_print_reports_styles', WC()->plugin_url() . '/assets/css/reports-print.css', array(), $version, 'print' );
|
||||
wp_register_style( 'woocommerce_admin_marketplace_styles', WC()->plugin_url() . '/assets/css/marketplace-suggestions.css', array(), $version );
|
||||
wp_register_style( 'woocommerce_admin_privacy_styles', WC()->plugin_url() . '/assets/css/privacy.css', array(), $version );
|
||||
|
||||
// Add RTL support for admin styles.
|
||||
wp_style_add_data( 'woocommerce_admin_menu_styles', 'rtl', 'replace' );
|
||||
wp_style_add_data( 'woocommerce_admin_styles', 'rtl', 'replace' );
|
||||
wp_style_add_data( 'woocommerce_admin_dashboard_styles', 'rtl', 'replace' );
|
||||
wp_style_add_data( 'woocommerce_admin_print_reports_styles', 'rtl', 'replace' );
|
||||
wp_style_add_data( 'woocommerce_admin_marketplace_styles', 'rtl', 'replace' );
|
||||
wp_style_add_data( 'woocommerce_admin_privacy_styles', 'rtl', 'replace' );
|
||||
|
||||
if ( $screen && $screen->is_block_editor() ) {
|
||||
$styles = WC_Frontend_Scripts::get_styles();
|
||||
|
||||
if ( $styles ) {
|
||||
foreach ( $styles as $handle => $args ) {
|
||||
wp_register_style(
|
||||
$handle,
|
||||
$args['src'],
|
||||
$args['deps'],
|
||||
$args['version'],
|
||||
$args['media']
|
||||
);
|
||||
|
||||
if ( ! isset( $args['has_rtl'] ) ) {
|
||||
wp_style_add_data( $handle, 'rtl', 'replace' );
|
||||
}
|
||||
|
||||
wp_enqueue_style( $handle );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sitewide menu CSS.
|
||||
wp_enqueue_style( 'woocommerce_admin_menu_styles' );
|
||||
|
||||
// Admin styles for WC pages only.
|
||||
if ( in_array( $screen_id, wc_get_screen_ids() ) ) {
|
||||
wp_enqueue_style( 'woocommerce_admin_styles' );
|
||||
wp_enqueue_style( 'jquery-ui-style' );
|
||||
wp_enqueue_style( 'wp-color-picker' );
|
||||
}
|
||||
|
||||
if ( in_array( $screen_id, array( 'dashboard' ) ) ) {
|
||||
wp_enqueue_style( 'woocommerce_admin_dashboard_styles' );
|
||||
}
|
||||
|
||||
if ( in_array( $screen_id, array( 'woocommerce_page_wc-reports', 'toplevel_page_wc-reports' ) ) ) {
|
||||
wp_enqueue_style( 'woocommerce_admin_print_reports_styles' );
|
||||
}
|
||||
|
||||
// Privacy Policy Guide css for back-compat.
|
||||
if ( isset( $_GET['wp-privacy-policy-guide'] ) || in_array( $screen_id, array( 'privacy-policy-guide' ) ) ) {
|
||||
wp_enqueue_style( 'woocommerce_admin_privacy_styles' );
|
||||
}
|
||||
|
||||
// @deprecated 2.3.
|
||||
if ( has_action( 'woocommerce_admin_css' ) ) {
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
do_action( 'woocommerce_admin_css' );
|
||||
/* phpcs: enable */
|
||||
wc_deprecated_function( 'The woocommerce_admin_css action', '2.3', 'admin_enqueue_scripts' );
|
||||
}
|
||||
|
||||
if ( WC_Marketplace_Suggestions::show_suggestions_for_screen( $screen_id ) ) {
|
||||
wp_enqueue_style( 'woocommerce_admin_marketplace_styles' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enqueue scripts.
|
||||
*/
|
||||
public function admin_scripts() {
|
||||
global $wp_query, $post, $theorder;
|
||||
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
$wc_screen_id = 'woocommerce';
|
||||
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
|
||||
// Register scripts.
|
||||
wp_register_script( 'woocommerce_admin', WC()->plugin_url() . '/assets/js/admin/woocommerce_admin' . $suffix . '.js', array( 'jquery', 'jquery-blockui', 'jquery-ui-sortable', 'jquery-ui-widget', 'jquery-ui-core', 'jquery-tiptip' ), $version );
|
||||
wp_register_script( 'jquery-blockui', WC()->plugin_url() . '/assets/js/jquery-blockui/jquery.blockUI' . $suffix . '.js', array( 'jquery' ), '2.70', true );
|
||||
wp_register_script( 'jquery-tiptip', WC()->plugin_url() . '/assets/js/jquery-tiptip/jquery.tipTip' . $suffix . '.js', array( 'jquery' ), $version, true );
|
||||
wp_register_script( 'round', WC()->plugin_url() . '/assets/js/round/round' . $suffix . '.js', array( 'jquery' ), $version );
|
||||
wp_register_script( 'wc-admin-meta-boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes' . $suffix . '.js', array( 'jquery', 'jquery-ui-datepicker', 'jquery-ui-sortable', 'accounting', 'round', 'wc-enhanced-select', 'plupload-all', 'stupidtable', 'jquery-tiptip' ), $version );
|
||||
wp_register_script( 'qrcode', WC()->plugin_url() . '/assets/js/jquery-qrcode/jquery.qrcode' . $suffix . '.js', array( 'jquery' ), $version );
|
||||
wp_register_script( 'stupidtable', WC()->plugin_url() . '/assets/js/stupidtable/stupidtable' . $suffix . '.js', array( 'jquery' ), $version );
|
||||
wp_register_script( 'serializejson', WC()->plugin_url() . '/assets/js/jquery-serializejson/jquery.serializejson' . $suffix . '.js', array( 'jquery' ), '2.8.1' );
|
||||
wp_register_script( 'flot', WC()->plugin_url() . '/assets/js/jquery-flot/jquery.flot' . $suffix . '.js', array( 'jquery' ), $version );
|
||||
wp_register_script( 'flot-resize', WC()->plugin_url() . '/assets/js/jquery-flot/jquery.flot.resize' . $suffix . '.js', array( 'jquery', 'flot' ), $version );
|
||||
wp_register_script( 'flot-time', WC()->plugin_url() . '/assets/js/jquery-flot/jquery.flot.time' . $suffix . '.js', array( 'jquery', 'flot' ), $version );
|
||||
wp_register_script( 'flot-pie', WC()->plugin_url() . '/assets/js/jquery-flot/jquery.flot.pie' . $suffix . '.js', array( 'jquery', 'flot' ), $version );
|
||||
wp_register_script( 'flot-stack', WC()->plugin_url() . '/assets/js/jquery-flot/jquery.flot.stack' . $suffix . '.js', array( 'jquery', 'flot' ), $version );
|
||||
wp_register_script( 'wc-settings-tax', WC()->plugin_url() . '/assets/js/admin/settings-views-html-settings-tax' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'jquery-blockui' ), $version );
|
||||
wp_register_script( 'wc-backbone-modal', WC()->plugin_url() . '/assets/js/admin/backbone-modal' . $suffix . '.js', array( 'underscore', 'backbone', 'wp-util' ), $version );
|
||||
wp_register_script( 'wc-shipping-zones', WC()->plugin_url() . '/assets/js/admin/wc-shipping-zones' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'jquery-ui-sortable', 'wc-enhanced-select', 'wc-backbone-modal' ), $version );
|
||||
wp_register_script( 'wc-shipping-zone-methods', WC()->plugin_url() . '/assets/js/admin/wc-shipping-zone-methods' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'jquery-ui-sortable', 'wc-backbone-modal' ), $version );
|
||||
wp_register_script( 'wc-shipping-classes', WC()->plugin_url() . '/assets/js/admin/wc-shipping-classes' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'wc-backbone-modal' ), $version, array( 'in_footer' => false ) );
|
||||
wp_register_script( 'wc-clipboard', WC()->plugin_url() . '/assets/js/admin/wc-clipboard' . $suffix . '.js', array( 'jquery' ), $version );
|
||||
wp_register_script( 'select2', WC()->plugin_url() . '/assets/js/select2/select2.full' . $suffix . '.js', array( 'jquery' ), '4.0.3' );
|
||||
wp_register_script( 'selectWoo', WC()->plugin_url() . '/assets/js/selectWoo/selectWoo.full' . $suffix . '.js', array( 'jquery' ), '1.0.6' );
|
||||
wp_register_script( 'wc-enhanced-select', WC()->plugin_url() . '/assets/js/admin/wc-enhanced-select' . $suffix . '.js', array( 'jquery', 'selectWoo' ), $version );
|
||||
wp_register_script( 'js-cookie', WC()->plugin_url() . '/assets/js/js-cookie/js.cookie' . $suffix . '.js', array(), '2.1.4', true );
|
||||
|
||||
wp_localize_script(
|
||||
'wc-enhanced-select',
|
||||
'wc_enhanced_select_params',
|
||||
array(
|
||||
'i18n_no_matches' => _x( 'No matches found', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_ajax_error' => _x( 'Loading failed', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_input_too_short_1' => _x( 'Please enter 1 or more characters', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_input_too_short_n' => _x( 'Please enter %qty% or more characters', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_input_too_long_1' => _x( 'Please delete 1 character', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_input_too_long_n' => _x( 'Please delete %qty% characters', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_selection_too_long_1' => _x( 'You can only select 1 item', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_selection_too_long_n' => _x( 'You can only select %qty% items', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_load_more' => _x( 'Loading more results…', 'enhanced select', 'woocommerce' ),
|
||||
'i18n_searching' => _x( 'Searching…', 'enhanced select', 'woocommerce' ),
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'search_products_nonce' => wp_create_nonce( 'search-products' ),
|
||||
'search_customers_nonce' => wp_create_nonce( 'search-customers' ),
|
||||
'search_categories_nonce' => wp_create_nonce( 'search-categories' ),
|
||||
'search_taxonomy_terms_nonce' => wp_create_nonce( 'search-taxonomy-terms' ),
|
||||
'search_product_attributes_nonce' => wp_create_nonce( 'search-product-attributes' ),
|
||||
'search_pages_nonce' => wp_create_nonce( 'search-pages' ),
|
||||
)
|
||||
);
|
||||
|
||||
wp_register_script( 'accounting', WC()->plugin_url() . '/assets/js/accounting/accounting' . $suffix . '.js', array( 'jquery' ), '0.4.2' );
|
||||
wp_localize_script(
|
||||
'accounting',
|
||||
'accounting_params',
|
||||
array(
|
||||
'mon_decimal_point' => wc_get_price_decimal_separator(),
|
||||
)
|
||||
);
|
||||
|
||||
wp_register_script( 'wc-orders', WC()->plugin_url() . '/assets/js/admin/wc-orders' . $suffix . '.js', array( 'jquery', 'wp-util', 'underscore', 'backbone', 'jquery-blockui' ), $version );
|
||||
wp_localize_script(
|
||||
'wc-orders',
|
||||
'wc_orders_params',
|
||||
array(
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'preview_nonce' => wp_create_nonce( 'woocommerce-preview-order' ),
|
||||
)
|
||||
);
|
||||
|
||||
// WooCommerce admin pages.
|
||||
if ( in_array( $screen_id, wc_get_screen_ids() ) ) {
|
||||
wp_enqueue_script( 'iris' );
|
||||
wp_enqueue_script( 'woocommerce_admin' );
|
||||
wp_enqueue_script( 'wc-enhanced-select' );
|
||||
|
||||
wp_enqueue_script( 'jquery-ui-sortable' );
|
||||
wp_enqueue_script( 'jquery-ui-autocomplete' );
|
||||
|
||||
$locale = localeconv();
|
||||
$decimal_point = isset( $locale['decimal_point'] ) ? $locale['decimal_point'] : '.';
|
||||
$decimal = ( ! empty( wc_get_price_decimal_separator() ) ) ? wc_get_price_decimal_separator() : $decimal_point;
|
||||
|
||||
$params = array(
|
||||
/* translators: %s: decimal */
|
||||
'i18n_decimal_error' => sprintf( __( 'Please enter a value with one decimal point (%s) without thousand separators.', 'woocommerce' ), $decimal ),
|
||||
/* translators: %s: price decimal separator */
|
||||
'i18n_mon_decimal_error' => sprintf( __( 'Please enter a value with one monetary decimal point (%s) without thousand separators and currency symbols.', 'woocommerce' ), wc_get_price_decimal_separator() ),
|
||||
'i18n_country_iso_error' => __( 'Please enter in country code with two capital letters.', 'woocommerce' ),
|
||||
'i18n_sale_less_than_regular_error' => __( 'Please enter in a value less than the regular price.', 'woocommerce' ),
|
||||
'i18n_delete_product_notice' => __( 'This product has produced sales and may be linked to existing orders. Are you sure you want to delete it?', 'woocommerce' ),
|
||||
'i18n_remove_personal_data_notice' => __( 'This action cannot be reversed. Are you sure you wish to erase personal data from the selected orders?', 'woocommerce' ),
|
||||
'i18n_confirm_delete' => __( 'Are you sure you wish to delete this item?', 'woocommerce' ),
|
||||
'decimal_point' => $decimal,
|
||||
'mon_decimal_point' => wc_get_price_decimal_separator(),
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'strings' => array(
|
||||
'import_products' => __( 'Import', 'woocommerce' ),
|
||||
'export_products' => __( 'Export', 'woocommerce' ),
|
||||
),
|
||||
'nonces' => array(
|
||||
'gateway_toggle' => current_user_can( 'manage_woocommerce' ) ? wp_create_nonce( 'woocommerce-toggle-payment-gateway-enabled' ) : null,
|
||||
),
|
||||
'urls' => array(
|
||||
'add_product' => Features::is_enabled( 'new-product-management-experience' ) || \Automattic\WooCommerce\Utilities\FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ? esc_url_raw( admin_url( 'admin.php?page=wc-admin&path=/add-product' ) ) : null,
|
||||
'import_products' => current_user_can( 'import' ) ? esc_url_raw( admin_url( 'edit.php?post_type=product&page=product_importer' ) ) : null,
|
||||
'export_products' => current_user_can( 'export' ) ? esc_url_raw( admin_url( 'edit.php?post_type=product&page=product_exporter' ) ) : null,
|
||||
),
|
||||
);
|
||||
|
||||
wp_localize_script( 'woocommerce_admin', 'woocommerce_admin', $params );
|
||||
}
|
||||
|
||||
// Edit product category pages.
|
||||
if ( in_array( $screen_id, array( 'edit-product_cat' ) ) ) {
|
||||
wp_enqueue_media();
|
||||
}
|
||||
|
||||
// Products.
|
||||
if ( in_array( $screen_id, array( 'edit-product' ) ) ) {
|
||||
wp_enqueue_script( 'woocommerce_quick-edit', WC()->plugin_url() . '/assets/js/admin/quick-edit' . $suffix . '.js', array( 'jquery', 'woocommerce_admin' ), $version );
|
||||
|
||||
$params = array(
|
||||
'strings' => array(
|
||||
'allow_reviews' => esc_js( __( 'Enable reviews', 'woocommerce' ) ),
|
||||
),
|
||||
);
|
||||
|
||||
wp_localize_script( 'woocommerce_quick-edit', 'woocommerce_quick_edit', $params );
|
||||
}
|
||||
|
||||
// Product description.
|
||||
if ( in_array( $screen_id, array( 'product' ), true ) ) {
|
||||
wp_enqueue_script( 'wc-admin-product-editor', WC()->plugin_url() . '/assets/js/admin/product-editor' . $suffix . '.js', array( 'jquery' ), $version, false );
|
||||
|
||||
wp_localize_script(
|
||||
'wc-admin-product-editor',
|
||||
'woocommerce_admin_product_editor',
|
||||
array(
|
||||
'i18n_description' => esc_js( __( 'Product description', 'woocommerce' ) ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Meta boxes.
|
||||
/* phpcs:disable */
|
||||
if ( in_array( $screen_id, array( 'product', 'edit-product' ) ) ) {
|
||||
wp_enqueue_media();
|
||||
wp_register_script( 'wc-admin-product-meta-boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes-product' . $suffix . '.js', array( 'wc-admin-meta-boxes', 'media-models' ), $version );
|
||||
wp_register_script( 'wc-admin-variation-meta-boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes-product-variation' . $suffix . '.js', array( 'wc-admin-meta-boxes', 'serializejson', 'media-models', 'backbone', 'jquery-ui-sortable', 'wc-backbone-modal' ), $version );
|
||||
|
||||
wp_enqueue_script( 'wc-admin-product-meta-boxes' );
|
||||
wp_enqueue_script( 'wc-admin-variation-meta-boxes' );
|
||||
|
||||
$params = array(
|
||||
'post_id' => isset( $post->ID ) ? $post->ID : '',
|
||||
'plugin_url' => WC()->plugin_url(),
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'woocommerce_placeholder_img_src' => wc_placeholder_img_src(),
|
||||
'add_variation_nonce' => wp_create_nonce( 'add-variation' ),
|
||||
'link_variation_nonce' => wp_create_nonce( 'link-variations' ),
|
||||
'delete_variations_nonce' => wp_create_nonce( 'delete-variations' ),
|
||||
'load_variations_nonce' => wp_create_nonce( 'load-variations' ),
|
||||
'save_variations_nonce' => wp_create_nonce( 'save-variations' ),
|
||||
'bulk_edit_variations_nonce' => wp_create_nonce( 'bulk-edit-variations' ),
|
||||
/* translators: %d: Number of variations */
|
||||
'i18n_link_all_variations' => esc_js( sprintf( __( 'Do you want to generate all variations? This will create a new variation for each and every possible combination of variation attributes (max %d per run).', 'woocommerce' ), Constants::is_defined( 'WC_MAX_LINKED_VARIATIONS' ) ? Constants::get_constant( 'WC_MAX_LINKED_VARIATIONS' ) : 50 ) ),
|
||||
'i18n_enter_a_value' => esc_js( __( 'Enter a value', 'woocommerce' ) ),
|
||||
'i18n_enter_menu_order' => esc_js( __( 'Variation menu order (determines position in the list of variations)', 'woocommerce' ) ),
|
||||
'i18n_enter_a_value_fixed_or_percent' => esc_js( __( 'Enter a value (fixed or %)', 'woocommerce' ) ),
|
||||
'i18n_delete_all_variations' => esc_js( __( 'Are you sure you want to delete all variations? This cannot be undone.', 'woocommerce' ) ),
|
||||
'i18n_last_warning' => esc_js( __( 'Last warning, are you sure?', 'woocommerce' ) ),
|
||||
'i18n_choose_image' => esc_js( __( 'Choose an image', 'woocommerce' ) ),
|
||||
'i18n_set_image' => esc_js( __( 'Set variation image', 'woocommerce' ) ),
|
||||
'i18n_variation_added' => esc_js( __( '1 variation added', 'woocommerce' ) ),
|
||||
'i18n_variations_added' => esc_js( __( '%qty% variations added', 'woocommerce' ) ),
|
||||
'i18n_remove_variation' => esc_js( __( 'Are you sure you want to remove this variation?', 'woocommerce' ) ),
|
||||
'i18n_scheduled_sale_start' => esc_js( __( 'Sale start date (YYYY-MM-DD format or leave blank)', 'woocommerce' ) ),
|
||||
'i18n_scheduled_sale_end' => esc_js( __( 'Sale end date (YYYY-MM-DD format or leave blank)', 'woocommerce' ) ),
|
||||
'i18n_edited_variations' => esc_js( __( 'Save changes before changing page?', 'woocommerce' ) ),
|
||||
'i18n_variation_count_single' => esc_js( __( '1 variation', 'woocommerce' ) ),
|
||||
'i18n_variation_count_plural' => esc_js( __( '%qty% variations', 'woocommerce' ) ),
|
||||
'variations_per_page' => absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) ),
|
||||
);
|
||||
|
||||
wp_localize_script( 'wc-admin-variation-meta-boxes', 'woocommerce_admin_meta_boxes_variations', $params );
|
||||
}
|
||||
/* phpcs: enable */
|
||||
if ( $this->is_order_meta_box_screen( $screen_id ) ) {
|
||||
$default_location = wc_get_customer_default_location();
|
||||
|
||||
wp_enqueue_script( 'wc-admin-order-meta-boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes-order' . $suffix . '.js', array( 'wc-admin-meta-boxes', 'wc-backbone-modal', 'selectWoo', 'wc-clipboard' ), $version );
|
||||
wp_localize_script(
|
||||
'wc-admin-order-meta-boxes',
|
||||
'woocommerce_admin_meta_boxes_order',
|
||||
array(
|
||||
'countries' => wp_json_encode( array_merge( WC()->countries->get_allowed_country_states(), WC()->countries->get_shipping_country_states() ) ),
|
||||
'i18n_select_state_text' => esc_attr__( 'Select an option…', 'woocommerce' ),
|
||||
'default_country' => isset( $default_location['country'] ) ? $default_location['country'] : '',
|
||||
'default_state' => isset( $default_location['state'] ) ? $default_location['state'] : '',
|
||||
'placeholder_name' => esc_attr__( 'Name (required)', 'woocommerce' ),
|
||||
'placeholder_value' => esc_attr__( 'Value (required)', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
if ( in_array( $screen_id, array( 'shop_coupon', 'edit-shop_coupon' ) ) ) {
|
||||
wp_enqueue_script( 'wc-admin-coupon-meta-boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes-coupon' . $suffix . '.js', array( 'wc-admin-meta-boxes' ), $version );
|
||||
wp_localize_script(
|
||||
'wc-admin-coupon-meta-boxes',
|
||||
'woocommerce_admin_meta_boxes_coupon',
|
||||
array(
|
||||
'generate_button_text' => esc_html__( 'Generate coupon code', 'woocommerce' ),
|
||||
'characters' => apply_filters( 'woocommerce_coupon_code_generator_characters', 'ABCDEFGHJKMNPQRSTUVWXYZ23456789' ),
|
||||
'char_length' => apply_filters( 'woocommerce_coupon_code_generator_character_length', 8 ),
|
||||
'prefix' => apply_filters( 'woocommerce_coupon_code_generator_prefix', '' ),
|
||||
'suffix' => apply_filters( 'woocommerce_coupon_code_generator_suffix', '' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
/* phpcs: enable */
|
||||
if ( in_array( str_replace( 'edit-', '', $screen_id ), array( 'shop_coupon', 'product' ), true ) || $this->is_order_meta_box_screen( $screen_id ) ) {
|
||||
$post_id = isset( $post->ID ) ? $post->ID : '';
|
||||
$currency = '';
|
||||
$remove_item_notice = __( 'Are you sure you want to remove the selected items?', 'woocommerce' );
|
||||
$remove_fee_notice = __( 'Are you sure you want to remove the selected fees?', 'woocommerce' );
|
||||
$remove_shipping_notice = __( 'Are you sure you want to remove the selected shipping?', 'woocommerce' );
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
// Eventually this will become wc_data_or_post object as we implement more custom tables.
|
||||
$order_or_post_object = $post;
|
||||
if ( ( $theorder instanceof WC_Order ) && $this->is_order_meta_box_screen( $screen_id ) ) {
|
||||
$order_or_post_object = $theorder;
|
||||
if ( $order_or_post_object ) {
|
||||
$currency = $order_or_post_object->get_currency();
|
||||
|
||||
if ( ! $order_or_post_object->has_status( array( 'pending', 'failed', 'cancelled' ) ) ) {
|
||||
$remove_item_notice = $remove_item_notice . ' ' . __( "You may need to manually restore the item's stock.", 'woocommerce' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'remove_item_notice' => $remove_item_notice,
|
||||
'remove_fee_notice' => $remove_fee_notice,
|
||||
'remove_shipping_notice' => $remove_shipping_notice,
|
||||
'i18n_select_items' => __( 'Please select some items.', 'woocommerce' ),
|
||||
'i18n_do_refund' => __( 'Are you sure you wish to process this refund? This action cannot be undone.', 'woocommerce' ),
|
||||
'i18n_delete_refund' => __( 'Are you sure you wish to delete this refund? This action cannot be undone.', 'woocommerce' ),
|
||||
'i18n_delete_tax' => __( 'Are you sure you wish to delete this tax column? This action cannot be undone.', 'woocommerce' ),
|
||||
'remove_item_meta' => __( 'Remove this item meta?', 'woocommerce' ),
|
||||
'name_label' => __( 'Name', 'woocommerce' ),
|
||||
'remove_label' => __( 'Remove', 'woocommerce' ),
|
||||
'click_to_toggle' => __( 'Click to toggle', 'woocommerce' ),
|
||||
'values_label' => __( 'Value(s)', 'woocommerce' ),
|
||||
'text_attribute_tip' => __( 'Enter some text, or some attributes by pipe (|) separating values.', 'woocommerce' ),
|
||||
'visible_label' => __( 'Visible on the product page', 'woocommerce' ),
|
||||
'used_for_variations_label' => __( 'Used for variations', 'woocommerce' ),
|
||||
'new_attribute_prompt' => __( 'Enter a name for the new attribute term:', 'woocommerce' ),
|
||||
'calc_totals' => __( 'Recalculate totals? This will calculate taxes based on the customers country (or the store base country) and update totals.', 'woocommerce' ),
|
||||
'copy_billing' => __( 'Copy billing information to shipping information? This will remove any currently entered shipping information.', 'woocommerce' ),
|
||||
'load_billing' => __( "Load the customer's billing information? This will remove any currently entered billing information.", 'woocommerce' ),
|
||||
'load_shipping' => __( "Load the customer's shipping information? This will remove any currently entered shipping information.", 'woocommerce' ),
|
||||
'featured_label' => __( 'Featured', 'woocommerce' ),
|
||||
'prices_include_tax' => esc_attr( get_option( 'woocommerce_prices_include_tax' ) ),
|
||||
'tax_based_on' => esc_attr( get_option( 'woocommerce_tax_based_on' ) ),
|
||||
'round_at_subtotal' => esc_attr( get_option( 'woocommerce_tax_round_at_subtotal' ) ),
|
||||
'no_customer_selected' => __( 'No customer selected', 'woocommerce' ),
|
||||
'plugin_url' => WC()->plugin_url(),
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'order_item_nonce' => wp_create_nonce( 'order-item' ),
|
||||
'add_attribute_nonce' => wp_create_nonce( 'add-attribute' ),
|
||||
'save_attributes_nonce' => wp_create_nonce( 'save-attributes' ),
|
||||
'add_attributes_and_variations' => wp_create_nonce( 'add-attributes-and-variations' ),
|
||||
'calc_totals_nonce' => wp_create_nonce( 'calc-totals' ),
|
||||
'get_customer_details_nonce' => wp_create_nonce( 'get-customer-details' ),
|
||||
'search_products_nonce' => wp_create_nonce( 'search-products' ),
|
||||
'grant_access_nonce' => wp_create_nonce( 'grant-access' ),
|
||||
'revoke_access_nonce' => wp_create_nonce( 'revoke-access' ),
|
||||
'add_order_note_nonce' => wp_create_nonce( 'add-order-note' ),
|
||||
'delete_order_note_nonce' => wp_create_nonce( 'delete-order-note' ),
|
||||
'calendar_image' => WC()->plugin_url() . '/assets/images/calendar.png',
|
||||
'post_id' => $this->is_order_meta_box_screen( $screen_id ) && isset( $order_or_post_object ) ? \Automattic\WooCommerce\Utilities\OrderUtil::get_post_or_order_id( $order_or_post_object ) : $post_id,
|
||||
'base_country' => WC()->countries->get_base_country(),
|
||||
'currency_format_num_decimals' => wc_get_price_decimals(),
|
||||
'currency_format_symbol' => get_woocommerce_currency_symbol( $currency ),
|
||||
'currency_format_decimal_sep' => esc_attr( wc_get_price_decimal_separator() ),
|
||||
'currency_format_thousand_sep' => esc_attr( wc_get_price_thousand_separator() ),
|
||||
'currency_format' => esc_attr( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_price_format() ) ), // For accounting JS.
|
||||
'rounding_precision' => wc_get_rounding_precision(),
|
||||
'tax_rounding_mode' => wc_get_tax_rounding_mode(),
|
||||
'product_types' => array_unique( array_merge( array( 'simple', 'grouped', 'variable', 'external' ), array_keys( wc_get_product_types() ) ) ),
|
||||
'i18n_download_permission_fail' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'woocommerce' ),
|
||||
'i18n_permission_revoke' => __( 'Are you sure you want to revoke access to this download?', 'woocommerce' ),
|
||||
'i18n_tax_rate_already_exists' => __( 'You cannot add the same tax rate twice!', 'woocommerce' ),
|
||||
'i18n_delete_note' => __( 'Are you sure you wish to delete this note? This action cannot be undone.', 'woocommerce' ),
|
||||
'i18n_apply_coupon' => __( 'Enter a coupon code to apply. Discounts are applied to line totals, before taxes.', 'woocommerce' ),
|
||||
'i18n_add_fee' => __( 'Enter a fixed amount or percentage to apply as a fee.', 'woocommerce' ),
|
||||
'i18n_attribute_name_placeholder' => __( 'New attribute', 'woocommerce' ),
|
||||
'i18n_product_simple_tip' => __( '<b>Simple –</b> covers the vast majority of any products you may sell. Simple products are shipped and have no options. For example, a book.', 'woocommerce' ),
|
||||
'i18n_product_grouped_tip' => __( '<b>Grouped –</b> a collection of related products that can be purchased individually and only consist of simple products. For example, a set of six drinking glasses.', 'woocommerce' ),
|
||||
'i18n_product_external_tip' => __( '<b>External or Affiliate –</b> one that you list and describe on your website but is sold elsewhere.', 'woocommerce' ),
|
||||
'i18n_product_variable_tip' => __( '<b>Variable –</b> a product with variations, each of which may have a different SKU, price, stock option, etc. For example, a t-shirt available in different colors and/or sizes.', 'woocommerce' ),
|
||||
'i18n_product_other_tip' => __( 'Product types define available product details and attributes, such as downloadable files and variations. They’re also used for analytics and inventory management.', 'woocommerce' ),
|
||||
'i18n_product_description_tip' => __( 'Describe this product. What makes it unique? What are its most important features?', 'woocommerce' ),
|
||||
'i18n_product_short_description_tip' => __( 'Summarize this product in 1-2 short sentences. We’ll show it at the top of the page.', 'woocommerce' ),
|
||||
'i18n_save_attribute_variation_tip' => __( 'Make sure you enter the name and values for each attribute.', 'woocommerce' ),
|
||||
/* translators: %1$s: maximum file size */
|
||||
'i18n_product_image_tip' => sprintf( __( 'For best results, upload JPEG or PNG files that are 1000 by 1000 pixels or larger. Maximum upload file size: %1$s.', 'woocommerce' ), size_format( wp_max_upload_size() ) ),
|
||||
'i18n_remove_used_attribute_confirmation_message' => __( 'If you remove this attribute, customers will no longer be able to purchase some variations of this product.', 'woocommerce' ),
|
||||
'i18n_add_attribute_error_notice' => __( 'Adding new attribute failed.', 'woocommerce' ),
|
||||
/* translators: %s: WC_DELIMITER */
|
||||
'i18n_attributes_default_placeholder' => sprintf( esc_attr__( 'Enter some descriptive text. Use “%s” to separate different values.', 'woocommerce' ), esc_attr( WC_DELIMITER ) ),
|
||||
'i18n_attributes_used_for_variations_placeholder' => sprintf( esc_attr__( 'Enter options for customers to choose from, f.e. “Blue” or “Large”. Use “%s” to separate different options.', 'woocommerce' ), esc_attr( WC_DELIMITER ) )
|
||||
);
|
||||
|
||||
wp_localize_script( 'wc-admin-meta-boxes', 'woocommerce_admin_meta_boxes', $params );
|
||||
}
|
||||
|
||||
// Term ordering - only when sorting by term_order.
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
if ( ( strstr( $screen_id, 'edit-pa_' ) || ( ! empty( $_GET['taxonomy'] ) && in_array( wp_unslash( $_GET['taxonomy'] ), apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) ) ) ) ) && ! isset( $_GET['orderby'] ) ) {
|
||||
|
||||
wp_register_script( 'woocommerce_term_ordering', WC()->plugin_url() . '/assets/js/admin/term-ordering' . $suffix . '.js', array( 'jquery-ui-sortable' ), $version );
|
||||
wp_enqueue_script( 'woocommerce_term_ordering' );
|
||||
|
||||
$taxonomy = isset( $_GET['taxonomy'] ) ? wc_clean( wp_unslash( $_GET['taxonomy'] ) ) : '';
|
||||
|
||||
$woocommerce_term_order_params = array(
|
||||
'taxonomy' => $taxonomy,
|
||||
);
|
||||
|
||||
wp_localize_script( 'woocommerce_term_ordering', 'woocommerce_term_ordering_params', $woocommerce_term_order_params );
|
||||
}
|
||||
/* phpcs: enable */
|
||||
|
||||
// Product sorting - only when sorting by menu order on the products page.
|
||||
if ( current_user_can( 'edit_others_pages' ) && 'edit-product' === $screen_id && isset( $wp_query->query['orderby'] ) && 'menu_order title' === $wp_query->query['orderby'] ) {
|
||||
wp_register_script( 'woocommerce_product_ordering', WC()->plugin_url() . '/assets/js/admin/product-ordering' . $suffix . '.js', array( 'jquery-ui-sortable' ), $version, true );
|
||||
wp_enqueue_script( 'woocommerce_product_ordering' );
|
||||
}
|
||||
|
||||
// Reports Pages.
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
if ( in_array( $screen_id, apply_filters( 'woocommerce_reports_screen_ids', array( $wc_screen_id . '_page_wc-reports', 'toplevel_page_wc-reports', 'dashboard' ) ) ) ) {
|
||||
wp_register_script( 'wc-reports', WC()->plugin_url() . '/assets/js/admin/reports' . $suffix . '.js', array( 'jquery', 'jquery-ui-datepicker' ), $version );
|
||||
|
||||
wp_enqueue_script( 'wc-reports' );
|
||||
wp_enqueue_script( 'flot' );
|
||||
wp_enqueue_script( 'flot-resize' );
|
||||
wp_enqueue_script( 'flot-time' );
|
||||
wp_enqueue_script( 'flot-pie' );
|
||||
wp_enqueue_script( 'flot-stack' );
|
||||
}
|
||||
/* phpcs: enable */
|
||||
|
||||
// API settings.
|
||||
if ( $wc_screen_id . '_page_wc-settings' === $screen_id && isset( $_GET['section'] ) && 'keys' == $_GET['section'] ) {
|
||||
wp_register_script( 'wc-api-keys', WC()->plugin_url() . '/assets/js/admin/api-keys' . $suffix . '.js', array( 'jquery', 'woocommerce_admin', 'underscore', 'backbone', 'wp-util', 'qrcode', 'wc-clipboard' ), $version, true );
|
||||
wp_enqueue_script( 'wc-api-keys' );
|
||||
wp_localize_script(
|
||||
'wc-api-keys',
|
||||
'woocommerce_admin_api_keys',
|
||||
array(
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'update_api_nonce' => wp_create_nonce( 'update-api-key' ),
|
||||
'clipboard_failed' => esc_html__( 'Copying to clipboard failed. Please press Ctrl/Cmd+C to copy.', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// System status.
|
||||
if ( $wc_screen_id . '_page_wc-status' === $screen_id ) {
|
||||
wp_register_script( 'wc-admin-system-status', WC()->plugin_url() . '/assets/js/admin/system-status' . $suffix . '.js', array( 'wc-clipboard' ), $version );
|
||||
wp_enqueue_script( 'wc-admin-system-status' );
|
||||
wp_localize_script(
|
||||
'wc-admin-system-status',
|
||||
'woocommerce_admin_system_status',
|
||||
array(
|
||||
'delete_log_confirmation' => esc_js( __( 'Are you sure you want to delete this log?', 'woocommerce' ) ),
|
||||
'run_tool_confirmation' => esc_js( __( 'Are you sure you want to run this tool?', 'woocommerce' ) ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( $screen_id, array( 'user-edit', 'profile' ) ) ) {
|
||||
wp_register_script( 'wc-users', WC()->plugin_url() . '/assets/js/admin/users' . $suffix . '.js', array( 'jquery', 'wc-enhanced-select', 'selectWoo' ), $version, true );
|
||||
wp_enqueue_script( 'wc-users' );
|
||||
wp_localize_script(
|
||||
'wc-users',
|
||||
'wc_users_params',
|
||||
array(
|
||||
'countries' => wp_json_encode( array_merge( WC()->countries->get_allowed_country_states(), WC()->countries->get_shipping_country_states() ) ),
|
||||
'i18n_select_state_text' => esc_attr__( 'Select an option…', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( WC_Marketplace_Suggestions::show_suggestions_for_screen( $screen_id ) ) {
|
||||
$active_plugin_slugs = array_map( 'dirname', get_option( 'active_plugins' ) );
|
||||
wp_register_script(
|
||||
'marketplace-suggestions',
|
||||
WC()->plugin_url() . '/assets/js/admin/marketplace-suggestions' . $suffix . '.js',
|
||||
array( 'jquery', 'underscore', 'js-cookie' ),
|
||||
$version,
|
||||
true
|
||||
);
|
||||
wp_localize_script(
|
||||
'marketplace-suggestions',
|
||||
'marketplace_suggestions',
|
||||
array(
|
||||
'dismiss_suggestion_nonce' => wp_create_nonce( 'add_dismissed_marketplace_suggestion' ),
|
||||
'active_plugins' => $active_plugin_slugs,
|
||||
'dismissed_suggestions' => WC_Marketplace_Suggestions::get_dismissed_suggestions(),
|
||||
'suggestions_data' => WC_Marketplace_Suggestions::get_suggestions_api_data(),
|
||||
'manage_suggestions_url' => admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=woocommerce_com' ),
|
||||
'in_app_purchase_params' => WC_Admin_Addons::get_in_app_purchase_url_params(),
|
||||
'i18n_marketplace_suggestions_default_cta'
|
||||
=> esc_html__( 'Learn More', 'woocommerce' ),
|
||||
'i18n_marketplace_suggestions_dismiss_tooltip'
|
||||
=> esc_attr__( 'Dismiss this suggestion', 'woocommerce' ),
|
||||
'i18n_marketplace_suggestions_manage_suggestions'
|
||||
=> esc_html__( 'Manage suggestions', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
wp_enqueue_script( 'marketplace-suggestions' );
|
||||
}
|
||||
|
||||
// Marketplace promotions.
|
||||
if ( in_array( $screen_id, array( 'woocommerce_page_wc-admin' ), true ) ) {
|
||||
|
||||
$promotions = get_transient( WC_Admin_Marketplace_Promotions::TRANSIENT_NAME );
|
||||
|
||||
if ( false === $promotions ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_inline_script(
|
||||
'wc-admin-app',
|
||||
'window.wcMarketplace = ' . wp_json_encode( array( 'promotions' => $promotions ) ),
|
||||
'before'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a script in the block editor.
|
||||
* Similar to `WCAdminAssets::register_script()` but without enqueuing unnecessary dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function enqueue_block_editor_script( $script_path_name, $script_name ) {
|
||||
$script_assets_filename = WCAdminAssets::get_script_asset_filename( $script_path_name, $script_name );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-admin-' . $script_name,
|
||||
WCAdminAssets::get_url( $script_path_name . '/' . $script_name, 'js' ),
|
||||
$script_assets['dependencies'],
|
||||
WCAdminAssets::get_file_version( 'js', $script_assets['version'] ),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue block editor assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_block_editor_assets() {
|
||||
$settings_tabs = apply_filters('woocommerce_settings_tabs_array', []);
|
||||
|
||||
if ( is_array( $settings_tabs ) && count( $settings_tabs ) > 0 ) {
|
||||
$formatted_settings_tabs = array();
|
||||
foreach ($settings_tabs as $key => $label) {
|
||||
if (
|
||||
is_string( $key ) && $key !== "" &&
|
||||
is_string( $label ) && $label !== ""
|
||||
) {
|
||||
$formatted_settings_tabs[] = array(
|
||||
'key' => $key,
|
||||
'label' => wp_strip_all_tags( $label ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self::enqueue_block_editor_script( 'wp-admin-scripts', 'command-palette' );
|
||||
wp_localize_script(
|
||||
'wc-admin-command-palette',
|
||||
'wcCommandPaletteSettings',
|
||||
array(
|
||||
'settingsTabs' => $formatted_settings_tabs,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$admin_features_disabled = apply_filters( 'woocommerce_admin_disabled', false );
|
||||
if ( ! $admin_features_disabled ) {
|
||||
$analytics_reports = Analytics::get_report_pages();
|
||||
if ( is_array( $analytics_reports ) && count( $analytics_reports ) > 0 ) {
|
||||
$formatted_analytics_reports = array_map( function( $report ) {
|
||||
if ( ! is_array( $report ) ) {
|
||||
return null;
|
||||
}
|
||||
$title = array_key_exists( 'title', $report ) ? $report['title'] : '';
|
||||
$path = array_key_exists( 'path', $report ) ? $report['path'] : '';
|
||||
if (
|
||||
is_string( $title ) && $title !== "" &&
|
||||
is_string( $path ) && $path !== ""
|
||||
) {
|
||||
return array(
|
||||
'title' => wp_strip_all_tags( $title ),
|
||||
'path' => $path,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, $analytics_reports );
|
||||
$formatted_analytics_reports = array_filter( $formatted_analytics_reports, 'is_array' );
|
||||
|
||||
self::enqueue_block_editor_script( 'wp-admin-scripts', 'command-palette-analytics' );
|
||||
wp_localize_script(
|
||||
'wc-admin-command-palette-analytics',
|
||||
'wcCommandPaletteAnalytics',
|
||||
array(
|
||||
'reports' => $formatted_analytics_reports,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine whether the current screen is an order edit screen.
|
||||
*
|
||||
* @param string $screen_id Screen ID.
|
||||
*
|
||||
* @return bool Whether the current screen is an order edit screen.
|
||||
*/
|
||||
private function is_order_meta_box_screen( $screen_id ) {
|
||||
$screen_id = str_replace( 'edit-', '', $screen_id );
|
||||
|
||||
$types_with_metaboxes_screen_ids = array_filter(
|
||||
array_map(
|
||||
'wc_get_page_screen_id',
|
||||
wc_get_order_types( 'order-meta-boxes' )
|
||||
)
|
||||
);
|
||||
|
||||
return in_array( $screen_id, $types_with_metaboxes_screen_ids, true );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
return new WC_Admin_Assets();
|
||||
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
/**
|
||||
* Attributes Page
|
||||
*
|
||||
* The attributes section lets users add custom attributes to assign to products - they can also be used in the "Filter Products by Attribute" widget.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.3.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Attributes Class.
|
||||
*/
|
||||
class WC_Admin_Attributes {
|
||||
|
||||
/**
|
||||
* Edited attribute ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $edited_attribute_id;
|
||||
|
||||
/**
|
||||
* Handles output of the attributes page in admin.
|
||||
*
|
||||
* Shows the created attributes and lets you add new ones or edit existing ones.
|
||||
* The added attributes are stored in the database and can be used for layered navigation.
|
||||
*/
|
||||
public static function output() {
|
||||
$result = '';
|
||||
$action = '';
|
||||
|
||||
// Action to perform: add, edit, delete or none.
|
||||
if ( ! empty( $_POST['add_new_attribute'] ) ) { // WPCS: CSRF ok.
|
||||
$action = 'add';
|
||||
} elseif ( ! empty( $_POST['save_attribute'] ) && ! empty( $_GET['edit'] ) ) { // WPCS: CSRF ok.
|
||||
$action = 'edit';
|
||||
} elseif ( ! empty( $_GET['delete'] ) ) {
|
||||
$action = 'delete';
|
||||
}
|
||||
|
||||
switch ( $action ) {
|
||||
case 'add':
|
||||
$result = self::process_add_attribute();
|
||||
break;
|
||||
case 'edit':
|
||||
$result = self::process_edit_attribute();
|
||||
break;
|
||||
case 'delete':
|
||||
$result = self::process_delete_attribute();
|
||||
break;
|
||||
}
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
echo '<div id="woocommerce_errors" class="error"><p>' . wp_kses_post( $result->get_error_message() ) . '</p></div>';
|
||||
}
|
||||
|
||||
// Show admin interface.
|
||||
if ( ! empty( $_GET['edit'] ) ) {
|
||||
self::edit_attribute();
|
||||
} else {
|
||||
self::add_attribute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and sanitize posted attribute data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_posted_attribute() {
|
||||
$attribute = array(
|
||||
'attribute_label' => isset( $_POST['attribute_label'] ) ? wc_clean( wp_unslash( $_POST['attribute_label'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
||||
'attribute_name' => isset( $_POST['attribute_name'] ) ? wc_sanitize_taxonomy_name( wp_unslash( $_POST['attribute_name'] ) ) : '', // WPCS: input var ok, CSRF ok, sanitization ok.
|
||||
'attribute_type' => isset( $_POST['attribute_type'] ) ? wc_clean( wp_unslash( $_POST['attribute_type'] ) ) : 'select', // WPCS: input var ok, CSRF ok.
|
||||
'attribute_orderby' => isset( $_POST['attribute_orderby'] ) ? wc_clean( wp_unslash( $_POST['attribute_orderby'] ) ) : '', // WPCS: input var ok, CSRF ok.
|
||||
'attribute_public' => isset( $_POST['attribute_public'] ) ? 1 : 0, // WPCS: input var ok, CSRF ok.
|
||||
);
|
||||
|
||||
if ( empty( $attribute['attribute_type'] ) ) {
|
||||
$attribute['attribute_type'] = 'select';
|
||||
}
|
||||
if ( empty( $attribute['attribute_label'] ) ) {
|
||||
$attribute['attribute_label'] = ucfirst( $attribute['attribute_name'] );
|
||||
}
|
||||
if ( empty( $attribute['attribute_name'] ) ) {
|
||||
$attribute['attribute_name'] = wc_sanitize_taxonomy_name( $attribute['attribute_label'] );
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attribute.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
private static function process_add_attribute() {
|
||||
check_admin_referer( 'woocommerce-add-new_attribute' );
|
||||
|
||||
$attribute = self::get_posted_attribute();
|
||||
$args = array(
|
||||
'name' => $attribute['attribute_label'],
|
||||
'slug' => $attribute['attribute_name'],
|
||||
'type' => $attribute['attribute_type'],
|
||||
'order_by' => $attribute['attribute_orderby'],
|
||||
'has_archives' => $attribute['attribute_public'],
|
||||
);
|
||||
|
||||
$id = wc_create_attribute( $args );
|
||||
|
||||
if ( is_wp_error( $id ) ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an attribute.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
private static function process_edit_attribute() {
|
||||
$attribute_id = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
|
||||
check_admin_referer( 'woocommerce-save-attribute_' . $attribute_id );
|
||||
|
||||
$attribute = self::get_posted_attribute();
|
||||
$args = array(
|
||||
'name' => $attribute['attribute_label'],
|
||||
'slug' => $attribute['attribute_name'],
|
||||
'type' => $attribute['attribute_type'],
|
||||
'order_by' => $attribute['attribute_orderby'],
|
||||
'has_archives' => $attribute['attribute_public'],
|
||||
);
|
||||
|
||||
$id = wc_update_attribute( $attribute_id, $args );
|
||||
|
||||
if ( is_wp_error( $id ) ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
self::$edited_attribute_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an attribute.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function process_delete_attribute() {
|
||||
$attribute_id = isset( $_GET['delete'] ) ? absint( $_GET['delete'] ) : 0;
|
||||
check_admin_referer( 'woocommerce-delete-attribute_' . $attribute_id );
|
||||
|
||||
return wc_delete_attribute( $attribute_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Attribute admin panel.
|
||||
*
|
||||
* Shows the interface for changing an attributes type between select and text.
|
||||
*/
|
||||
public static function edit_attribute() {
|
||||
global $wpdb;
|
||||
|
||||
$edit = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
|
||||
|
||||
$attribute_to_edit = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT attribute_type, attribute_label, attribute_name, attribute_orderby, attribute_public
|
||||
FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = %d
|
||||
",
|
||||
$edit
|
||||
)
|
||||
);
|
||||
|
||||
?>
|
||||
<div class="wrap woocommerce">
|
||||
<h1><?php esc_html_e( 'Edit attribute', 'woocommerce' ); ?></h1>
|
||||
|
||||
<?php
|
||||
if ( ! $attribute_to_edit ) {
|
||||
echo '<div id="woocommerce_errors" class="error"><p>' . esc_html__( 'Error: non-existing attribute ID.', 'woocommerce' ) . '</p></div>';
|
||||
} else {
|
||||
if ( self::$edited_attribute_id > 0 ) {
|
||||
echo '<div id="message" class="updated"><p>' . esc_html__( 'Attribute updated successfully', 'woocommerce' ) . '</p><p><a href="' . esc_url( admin_url( 'edit.php?post_type=product&page=product_attributes' ) ) . '">' . esc_html__( 'Back to Attributes', 'woocommerce' ) . '</a></p></div>';
|
||||
self::$edited_attribute_id = null;
|
||||
}
|
||||
$att_type = $attribute_to_edit->attribute_type;
|
||||
$att_label = format_to_edit( $attribute_to_edit->attribute_label );
|
||||
$att_name = $attribute_to_edit->attribute_name;
|
||||
$att_orderby = $attribute_to_edit->attribute_orderby;
|
||||
$att_public = $attribute_to_edit->attribute_public;
|
||||
?>
|
||||
<form action="edit.php?post_type=product&page=product_attributes&edit=<?php echo absint( $edit ); ?>" method="post">
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<?php do_action( 'woocommerce_before_edit_attribute_fields' ); ?>
|
||||
<tr class="form-field form-required">
|
||||
<th scope="row" valign="top">
|
||||
<label for="attribute_label"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input name="attribute_label" id="attribute_label" type="text" value="<?php echo esc_attr( $att_label ); ?>" />
|
||||
<p class="description"><?php esc_html_e( 'Name for the attribute (shown on the front-end).', 'woocommerce' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-field form-required">
|
||||
<th scope="row" valign="top">
|
||||
<label for="attribute_name"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input name="attribute_name" id="attribute_name" type="text" value="<?php echo esc_attr( $att_name ); ?>" maxlength="28" />
|
||||
<p class="description"><?php esc_html_e( 'Unique slug/reference for the attribute; must be no more than 28 characters.', 'woocommerce' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-field form-required">
|
||||
<th scope="row" valign="top">
|
||||
<label for="attribute_public"><?php esc_html_e( 'Enable archives?', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input name="attribute_public" id="attribute_public" type="checkbox" value="1" <?php checked( $att_public, 1 ); ?> />
|
||||
<p class="description"><?php esc_html_e( 'Enable this if you want this attribute to have product archives in your store.', 'woocommerce' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
/**
|
||||
* Attribute types can change the way attributes are displayed on the frontend and admin.
|
||||
*
|
||||
* By Default WooCommerce only includes the `select` type. Others can be added with the
|
||||
* `product_attributes_type_selector` filter. If there is only the default type registered,
|
||||
* this setting will be hidden.
|
||||
*/
|
||||
if ( wc_has_custom_attribute_types() ) {
|
||||
?>
|
||||
<tr class="form-field form-required">
|
||||
<th scope="row" valign="top">
|
||||
<label for="attribute_type"><?php esc_html_e( 'Type', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select name="attribute_type" id="attribute_type">
|
||||
<?php foreach ( wc_get_attribute_types() as $key => $value ) : ?>
|
||||
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $att_type, $key ); ?>><?php echo esc_html( $value ); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php
|
||||
/**
|
||||
* Deprecated action in favor of product_attributes_type_selector filter.
|
||||
*
|
||||
* @todo Remove in 4.0.0
|
||||
* @deprecated 2.4.0
|
||||
*/
|
||||
do_action( 'woocommerce_admin_attribute_types' );
|
||||
?>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( "Determines how this attribute's values are displayed.", 'woocommerce' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<tr class="form-field form-required">
|
||||
<th scope="row" valign="top">
|
||||
<label for="attribute_orderby"><?php esc_html_e( 'Default sort order', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select name="attribute_orderby" id="attribute_orderby">
|
||||
<option value="menu_order" <?php selected( $att_orderby, 'menu_order' ); ?>><?php esc_html_e( 'Custom ordering', 'woocommerce' ); ?></option>
|
||||
<option value="name" <?php selected( $att_orderby, 'name' ); ?>><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
|
||||
<option value="name_num" <?php selected( $att_orderby, 'name_num' ); ?>><?php esc_html_e( 'Name (numeric)', 'woocommerce' ); ?></option>
|
||||
<option value="id" <?php selected( $att_orderby, 'id' ); ?>><?php esc_html_e( 'Term ID', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( 'Determines the sort order of the terms on the frontend shop product pages. If using custom ordering, you can drag and drop the terms in this attribute.', 'woocommerce' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php do_action( 'woocommerce_after_edit_attribute_fields' ); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="submit"><button type="submit" name="save_attribute" id="submit" class="button-primary" value="<?php esc_attr_e( 'Update', 'woocommerce' ); ?>"><?php esc_html_e( 'Update', 'woocommerce' ); ?></button></p>
|
||||
<?php wp_nonce_field( 'woocommerce-save-attribute_' . $edit ); ?>
|
||||
</form>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Attribute admin panel.
|
||||
*
|
||||
* Shows the interface for adding new attributes.
|
||||
*/
|
||||
public static function add_attribute() {
|
||||
?>
|
||||
<div class="wrap woocommerce">
|
||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||
|
||||
<br class="clear" />
|
||||
<div id="col-container">
|
||||
<div id="col-right">
|
||||
<div class="col-wrap">
|
||||
<table class="widefat attributes-table wp-list-table ui-sortable" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><?php esc_html_e( 'Name', 'woocommerce' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></th>
|
||||
<?php if ( wc_has_custom_attribute_types() ) : ?>
|
||||
<th scope="col"><?php esc_html_e( 'Type', 'woocommerce' ); ?></th>
|
||||
<?php endif; ?>
|
||||
<th scope="col"><?php esc_html_e( 'Order by', 'woocommerce' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Terms', 'woocommerce' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$attribute_taxonomies = wc_get_attribute_taxonomies();
|
||||
if ( $attribute_taxonomies ) {
|
||||
/**
|
||||
* Filters the maximum number of terms that will be displayed for each taxonomy in the Attributes page.
|
||||
*
|
||||
* @param int @default_max_terms_to_display Default value.
|
||||
* @returns int Actual value to use, may be zero.
|
||||
*
|
||||
* @since 6.9.0
|
||||
*/
|
||||
$max_terms_to_display = apply_filters( 'woocommerce_max_terms_displayed_in_attributes_page', 100 );
|
||||
foreach ( $attribute_taxonomies as $tax ) :
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><a href="edit-tags.php?taxonomy=<?php echo esc_attr( wc_attribute_taxonomy_name( $tax->attribute_name ) ); ?>&post_type=product"><?php echo esc_html( $tax->attribute_label ); ?></a></strong>
|
||||
|
||||
<div class="row-actions"><span class="edit"><a href="<?php echo esc_url( add_query_arg( 'edit', $tax->attribute_id, 'edit.php?post_type=product&page=product_attributes' ) ); ?>"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a> | </span><span class="delete"><a class="delete" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'delete', $tax->attribute_id, 'edit.php?post_type=product&page=product_attributes' ), 'woocommerce-delete-attribute_' . $tax->attribute_id ) ); ?>"><?php esc_html_e( 'Delete', 'woocommerce' ); ?></a></span></div>
|
||||
</td>
|
||||
<td><?php echo esc_html( $tax->attribute_name ); ?></td>
|
||||
<?php if ( wc_has_custom_attribute_types() ) : ?>
|
||||
<td><?php echo esc_html( wc_get_attribute_type_label( $tax->attribute_type ) ); ?> <?php echo $tax->attribute_public ? esc_html__( '(Public)', 'woocommerce' ) : ''; ?></td>
|
||||
<?php endif; ?>
|
||||
<td>
|
||||
<?php
|
||||
switch ( $tax->attribute_orderby ) {
|
||||
case 'name':
|
||||
esc_html_e( 'Name', 'woocommerce' );
|
||||
break;
|
||||
case 'name_num':
|
||||
esc_html_e( 'Name (numeric)', 'woocommerce' );
|
||||
break;
|
||||
case 'id':
|
||||
esc_html_e( 'Term ID', 'woocommerce' );
|
||||
break;
|
||||
default:
|
||||
esc_html_e( 'Custom ordering', 'woocommerce' );
|
||||
break;
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td class="attribute-terms">
|
||||
<?php
|
||||
$taxonomy = wc_attribute_taxonomy_name( $tax->attribute_name );
|
||||
|
||||
if ( taxonomy_exists( $taxonomy ) ) {
|
||||
$total_count = (int) get_terms(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'fields' => 'count',
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
if ( 0 === $total_count ) {
|
||||
echo '<span class="na">–</span>';
|
||||
} elseif ( $max_terms_to_display > 0 ) {
|
||||
$terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'number' => $max_terms_to_display,
|
||||
'fields' => 'names',
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
$terms_string = implode( ', ', $terms );
|
||||
if ( $total_count > $max_terms_to_display ) {
|
||||
$remaining = $total_count - $max_terms_to_display;
|
||||
/* translators: 1: Comma-separated terms list, 2: how many terms are hidden */
|
||||
$terms_string = sprintf( __( '%1$s... (%2$s more)', 'woocommerce' ), $terms_string, $remaining );
|
||||
}
|
||||
echo esc_html( $terms_string );
|
||||
} elseif ( 1 === $total_count ) {
|
||||
echo esc_html( __( '1 term', 'woocommerce' ) );
|
||||
} else {
|
||||
/* translators: %s: Total count of terms available for the attribute */
|
||||
echo esc_html( sprintf( __( '%s terms', 'woocommerce' ), $total_count ) );
|
||||
}
|
||||
} else {
|
||||
echo '<span class="na">–</span><br />';
|
||||
}
|
||||
?>
|
||||
<br /><a href="edit-tags.php?taxonomy=<?php echo esc_attr( wc_attribute_taxonomy_name( $tax->attribute_name ) ); ?>&post_type=product" class="configure-terms"><?php esc_html_e( 'Configure terms', 'woocommerce' ); ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
endforeach;
|
||||
} else {
|
||||
?>
|
||||
<tr>
|
||||
<td colspan="6"><?php esc_html_e( 'No attributes currently exist.', 'woocommerce' ); ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="col-left">
|
||||
<div class="col-wrap">
|
||||
<div class="form-wrap">
|
||||
<h2><?php esc_html_e( 'Add new attribute', 'woocommerce' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Attributes let you define extra product data, such as size or color. You can use these attributes in the shop sidebar using the "layered nav" widgets.', 'woocommerce' ); ?></p>
|
||||
<form action="edit.php?post_type=product&page=product_attributes" method="post">
|
||||
<?php do_action( 'woocommerce_before_add_attribute_fields' ); ?>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="attribute_label"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
|
||||
<input name="attribute_label" id="attribute_label" type="text" value="" />
|
||||
<p class="description"><?php esc_html_e( 'Name for the attribute (shown on the front-end).', 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="attribute_name"><?php esc_html_e( 'Slug', 'woocommerce' ); ?></label>
|
||||
<input name="attribute_name" id="attribute_name" type="text" value="" maxlength="28" />
|
||||
<p class="description"><?php esc_html_e( 'Unique slug/reference for the attribute; must be no more than 28 characters.', 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="attribute_public"><input name="attribute_public" id="attribute_public" type="checkbox" value="1" /> <?php esc_html_e( 'Enable Archives?', 'woocommerce' ); ?></label>
|
||||
|
||||
<p class="description"><?php esc_html_e( 'Enable this if you want this attribute to have product archives in your store.', 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Attribute types can change the way attributes are displayed on the frontend and admin.
|
||||
*
|
||||
* By Default WooCommerce only includes the `select` type. Others can be added with the
|
||||
* `product_attributes_type_selector` filter. If there is only the default type registered,
|
||||
* this setting will be hidden.
|
||||
*/
|
||||
if ( wc_has_custom_attribute_types() ) {
|
||||
?>
|
||||
<div class="form-field">
|
||||
<label for="attribute_type"><?php esc_html_e( 'Type', 'woocommerce' ); ?></label>
|
||||
<select name="attribute_type" id="attribute_type">
|
||||
<?php foreach ( wc_get_attribute_types() as $key => $value ) : ?>
|
||||
<option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $value ); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php
|
||||
/**
|
||||
* Deprecated action in favor of product_attributes_type_selector filter.
|
||||
*
|
||||
* @todo Remove in 4.0.0
|
||||
* @deprecated 2.4.0
|
||||
*/
|
||||
do_action( 'woocommerce_admin_attribute_types' );
|
||||
?>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( "Determines how this attribute's values are displayed.", 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="attribute_orderby"><?php esc_html_e( 'Default sort order', 'woocommerce' ); ?></label>
|
||||
<select name="attribute_orderby" id="attribute_orderby">
|
||||
<option value="menu_order"><?php esc_html_e( 'Custom ordering', 'woocommerce' ); ?></option>
|
||||
<option value="name"><?php esc_html_e( 'Name', 'woocommerce' ); ?></option>
|
||||
<option value="name_num"><?php esc_html_e( 'Name (numeric)', 'woocommerce' ); ?></option>
|
||||
<option value="id"><?php esc_html_e( 'Term ID', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e( 'Determines the sort order of the terms on the frontend shop product pages. If using custom ordering, you can drag and drop the terms in this attribute.', 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
|
||||
<?php do_action( 'woocommerce_after_add_attribute_fields' ); ?>
|
||||
|
||||
<p class="submit"><button type="submit" name="add_new_attribute" id="submit" class="button button-primary" value="<?php esc_attr_e( 'Add attribute', 'woocommerce' ); ?>"><?php esc_html_e( 'Add attribute', 'woocommerce' ); ?></button></p>
|
||||
<?php wp_nonce_field( 'woocommerce-add-new_attribute' ); ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
/* <![CDATA[ */
|
||||
|
||||
jQuery( 'a.delete' ).on( 'click', function() {
|
||||
if ( window.confirm( '<?php esc_html_e( 'Are you sure you want to delete this attribute?', 'woocommerce' ); ?>' ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
/* ]]> */
|
||||
</script>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* Setup customize items.
|
||||
*
|
||||
* @package WooCommerce\Admin\Customize
|
||||
* @version 3.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Customize', false ) ) :
|
||||
|
||||
/**
|
||||
* WC_Admin_Customize Class.
|
||||
*/
|
||||
class WC_Admin_Customize {
|
||||
|
||||
/**
|
||||
* Initialize customize actions.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Include custom items to customizer nav menu settings.
|
||||
add_filter( 'customize_nav_menu_available_item_types', array( $this, 'register_customize_nav_menu_item_types' ) );
|
||||
add_filter( 'customize_nav_menu_available_items', array( $this, 'register_customize_nav_menu_items' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register customize new nav menu item types.
|
||||
* This will register WooCommerce account endpoints as a nav menu item type.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $item_types Menu item types.
|
||||
* @return array
|
||||
*/
|
||||
public function register_customize_nav_menu_item_types( $item_types ) {
|
||||
$item_types[] = array(
|
||||
'title' => __( 'WooCommerce Endpoints', 'woocommerce' ),
|
||||
'type_label' => __( 'WooCommerce Endpoint', 'woocommerce' ),
|
||||
'type' => 'woocommerce_nav',
|
||||
'object' => 'woocommerce_endpoint',
|
||||
);
|
||||
|
||||
return $item_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register account endpoints to customize nav menu items.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $items List of nav menu items.
|
||||
* @param string $type Nav menu type.
|
||||
* @param string $object Nav menu object.
|
||||
* @param integer $page Page number.
|
||||
* @return array
|
||||
*/
|
||||
public function register_customize_nav_menu_items( $items = array(), $type = '', $object = '', $page = 0 ) {
|
||||
if ( 'woocommerce_endpoint' !== $object ) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
// Don't allow pagination since all items are loaded at once.
|
||||
if ( 0 < $page ) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
// Get items from account menu.
|
||||
$endpoints = wc_get_account_menu_items();
|
||||
|
||||
// Remove dashboard item.
|
||||
if ( isset( $endpoints['dashboard'] ) ) {
|
||||
unset( $endpoints['dashboard'] );
|
||||
}
|
||||
|
||||
// Include missing lost password.
|
||||
$endpoints['lost-password'] = __( 'Lost password', 'woocommerce' );
|
||||
|
||||
$endpoints = apply_filters( 'woocommerce_custom_nav_menu_items', $endpoints );
|
||||
|
||||
foreach ( $endpoints as $endpoint => $title ) {
|
||||
$items[] = array(
|
||||
'id' => $endpoint,
|
||||
'title' => $title,
|
||||
'type_label' => __( 'Custom Link', 'woocommerce' ),
|
||||
'url' => esc_url_raw( wc_get_account_endpoint_url( $endpoint ) ),
|
||||
);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
return new WC_Admin_Customize();
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Dashboard - Setup
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
|
||||
|
||||
/**
|
||||
* WC_Admin_Dashboard_Setup Class.
|
||||
*/
|
||||
class WC_Admin_Dashboard_Setup {
|
||||
|
||||
/**
|
||||
* Check for task list initialization.
|
||||
*/
|
||||
private $initalized = false;
|
||||
|
||||
/**
|
||||
* The task list.
|
||||
*/
|
||||
private $task_list = null;
|
||||
|
||||
/**
|
||||
* The tasks.
|
||||
*/
|
||||
private $tasks = null;
|
||||
|
||||
/**
|
||||
* # of completed tasks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $completed_tasks_count = 0;
|
||||
|
||||
/**
|
||||
* WC_Admin_Dashboard_Setup constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( $this->should_display_widget() ) {
|
||||
add_meta_box(
|
||||
'wc_admin_dashboard_setup',
|
||||
__( 'WooCommerce Setup', 'woocommerce' ),
|
||||
array( $this, 'render' ),
|
||||
'dashboard',
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render meta box output.
|
||||
*/
|
||||
public function render() {
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
wp_enqueue_style( 'wc-dashboard-setup', WC()->plugin_url() . '/assets/css/dashboard-setup.css', array(), $version );
|
||||
|
||||
$task = $this->get_next_task();
|
||||
if ( ! $task ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$button_link = $this->get_button_link( $task );
|
||||
$completed_tasks_count = $this->get_completed_tasks_count();
|
||||
$step_number = $this->get_completed_tasks_count() + 1;
|
||||
$tasks_count = count( $this->get_tasks() );
|
||||
|
||||
// Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2).
|
||||
$progress_percentage = ( $completed_tasks_count / $tasks_count ) * 100;
|
||||
$circle_r = 6.5;
|
||||
$circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) );
|
||||
|
||||
include __DIR__ . '/views/html-admin-dashboard-setup.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the button link for a given task.
|
||||
*
|
||||
* @param Task $task Task.
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_link( $task ) {
|
||||
$url = (string) $task->get_json()['actionUrl'];
|
||||
|
||||
if ( substr( $url, 0, 4 ) === 'http' ) {
|
||||
return $url;
|
||||
} elseif ( $url ) {
|
||||
return wc_admin_url( '&path=' . $url );
|
||||
}
|
||||
|
||||
return admin_url( 'admin.php?page=wc-admin&task=' . $task->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task list.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_task_list() {
|
||||
if ( $this->task_list || $this->initalized ) {
|
||||
return $this->task_list;
|
||||
}
|
||||
|
||||
$this->set_task_list( TaskLists::get_list( 'setup' ) );
|
||||
$this->initalized = true;
|
||||
return $this->task_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the task list.
|
||||
*/
|
||||
public function set_task_list( $task_list ) {
|
||||
return $this->task_list = $task_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tasks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tasks() {
|
||||
if ( $this->tasks ) {
|
||||
return $this->tasks;
|
||||
}
|
||||
|
||||
$this->tasks = $this->get_task_list()->get_viewable_tasks();
|
||||
return $this->tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return # of completed tasks
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function get_completed_tasks_count() {
|
||||
$completed_tasks = array_filter(
|
||||
$this->get_tasks(),
|
||||
function( $task ) {
|
||||
return $task->is_complete();
|
||||
}
|
||||
);
|
||||
|
||||
return count( $completed_tasks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next task.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function get_next_task() {
|
||||
foreach ( $this->get_tasks() as $task ) {
|
||||
if ( false === $task->is_complete() ) {
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we should display the widget
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_display_widget() {
|
||||
if ( ! class_exists( 'Automattic\WooCommerce\Admin\Features\Features' ) || ! class_exists( 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! Features::is_enabled( 'onboarding' ) || ! WC()->is_wc_admin_active() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->get_task_list() || $this->get_task_list()->is_hidden() || $this->get_task_list()->is_complete() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
return new WC_Admin_Dashboard_Setup();
|
||||
@@ -0,0 +1,593 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Dashboard
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
|
||||
|
||||
/**
|
||||
* WC_Admin_Dashboard Class.
|
||||
*/
|
||||
class WC_Admin_Dashboard {
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Only hook in admin parts if the user has admin access.
|
||||
if ( $this->should_display_widget() ) {
|
||||
// If on network admin, only load the widget that works in that context and skip the rest.
|
||||
if ( is_multisite() && is_network_admin() ) {
|
||||
add_action( 'wp_network_dashboard_setup', array( $this, 'register_network_order_widget' ) );
|
||||
} else {
|
||||
add_action( 'wp_dashboard_setup', array( $this, 'init' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init dashboard widgets.
|
||||
*/
|
||||
public function init() {
|
||||
// Reviews Widget.
|
||||
if ( current_user_can( 'publish_shop_orders' ) && post_type_supports( 'product', 'comments' ) ) {
|
||||
wp_add_dashboard_widget( 'woocommerce_dashboard_recent_reviews', __( 'WooCommerce Recent Reviews', 'woocommerce' ), array( $this, 'recent_reviews' ) );
|
||||
}
|
||||
wp_add_dashboard_widget( 'woocommerce_dashboard_status', __( 'WooCommerce Status', 'woocommerce' ), array( $this, 'status_widget' ) );
|
||||
|
||||
// Network Order Widget.
|
||||
if ( is_multisite() && is_main_site() ) {
|
||||
$this->register_network_order_widget();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the network order dashboard widget.
|
||||
*/
|
||||
public function register_network_order_widget() {
|
||||
wp_add_dashboard_widget( 'woocommerce_network_orders', __( 'WooCommerce Network Orders', 'woocommerce' ), array( $this, 'network_orders' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we should display the widget.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_display_widget() {
|
||||
if ( ! WC()->is_wc_admin_active() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$has_permission = current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' );
|
||||
$task_completed_or_hidden = 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' );
|
||||
return $task_completed_or_hidden && $has_permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top seller from DB.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
private function get_top_seller() {
|
||||
global $wpdb;
|
||||
|
||||
$hpos_enabled = OrderUtil::custom_orders_table_usage_is_enabled();
|
||||
$orders_table = OrderUtil::get_table_for_orders();
|
||||
$orders_column_id = $hpos_enabled ? 'id' : 'ID';
|
||||
$orders_column_type = $hpos_enabled ? 'type' : 'post_type';
|
||||
$orders_column_status = $hpos_enabled ? 'status' : 'post_status';
|
||||
$orders_column_date = $hpos_enabled ? 'date_created_gmt' : 'post_date_gmt';
|
||||
|
||||
$query = array();
|
||||
$query['fields'] = "SELECT SUM( order_item_meta.meta_value ) as qty, order_item_meta_2.meta_value as product_id FROM {$orders_table} AS orders";
|
||||
$query['join'] = "INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON orders.{$orders_column_id} = order_id ";
|
||||
$query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id ";
|
||||
$query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_2 ON order_items.order_item_id = order_item_meta_2.order_item_id ";
|
||||
$query['where'] = "WHERE orders.{$orders_column_type} IN ( '" . implode( "','", wc_get_order_types( 'order-count' ) ) . "' ) ";
|
||||
|
||||
/**
|
||||
* Allows modifying the order statuses used in the top seller query inside the dashboard status widget.
|
||||
*
|
||||
* @since 2.2.0
|
||||
*
|
||||
* @param string[] $order_statuses Order statuses.
|
||||
*/
|
||||
$order_statuses = apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) );
|
||||
$query['where'] .= "AND orders.{$orders_column_status} IN ( 'wc-" . implode( "','wc-", $order_statuses ) . "' ) ";
|
||||
|
||||
$query['where'] .= "AND order_item_meta.meta_key = '_qty' ";
|
||||
$query['where'] .= "AND order_item_meta_2.meta_key = '_product_id' ";
|
||||
$query['where'] .= "AND orders.{$orders_column_date} >= '" . gmdate( 'Y-m-01', current_time( 'timestamp' ) ) . "' "; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
|
||||
$query['where'] .= "AND orders.{$orders_column_date} <= '" . gmdate( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) . "' "; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
|
||||
$query['groupby'] = 'GROUP BY product_id';
|
||||
$query['orderby'] = 'ORDER BY qty DESC';
|
||||
$query['limits'] = 'LIMIT 1';
|
||||
|
||||
/**
|
||||
* Allows modification of the query to determine the top seller product in the dashboard status widget.
|
||||
*
|
||||
* @since 2.2.0
|
||||
*
|
||||
* @param array $query SQL query parts.
|
||||
*/
|
||||
$query = apply_filters( 'woocommerce_dashboard_status_widget_top_seller_query', $query );
|
||||
|
||||
$sql = implode( ' ', $query );
|
||||
return $wpdb->get_row( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sales report data.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
private function get_sales_report_data() {
|
||||
include_once dirname( __FILE__ ) . '/reports/class-wc-report-sales-by-date.php';
|
||||
|
||||
$sales_by_date = new WC_Report_Sales_By_Date();
|
||||
$sales_by_date->start_date = strtotime( gmdate( 'Y-m-01', current_time( 'timestamp' ) ) );
|
||||
$sales_by_date->end_date = strtotime( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) );
|
||||
$sales_by_date->chart_groupby = 'day';
|
||||
$sales_by_date->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)';
|
||||
|
||||
return $sales_by_date->get_report_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show status widget.
|
||||
*/
|
||||
public function status_widget() {
|
||||
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
|
||||
wp_enqueue_script( 'wc-status-widget', WC()->plugin_url() . '/assets/js/admin/wc-status-widget' . $suffix . '.js', array( 'jquery' ), $version, true );
|
||||
|
||||
include_once dirname( __FILE__ ) . '/reports/class-wc-admin-report.php';
|
||||
|
||||
//phpcs:ignore
|
||||
$is_wc_admin_disabled = apply_filters( 'woocommerce_admin_disabled', false ) || ! Features::is_enabled( 'analytics' );
|
||||
|
||||
$reports = new WC_Admin_Report();
|
||||
|
||||
$net_sales_link = 'admin.php?page=wc-reports&tab=orders&range=month';
|
||||
$top_seller_link = 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids=';
|
||||
$report_data = $is_wc_admin_disabled ? $this->get_sales_report_data() : $this->get_wc_admin_performance_data();
|
||||
if ( ! $is_wc_admin_disabled ) {
|
||||
$net_sales_link = 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period';
|
||||
$top_seller_link = 'admin.php?page=wc-admin&filter=single_product&path=%2Fanalytics%2Fproducts&products=';
|
||||
}
|
||||
|
||||
echo '<ul class="wc_status_list">';
|
||||
|
||||
if ( current_user_can( 'view_woocommerce_reports' ) ) {
|
||||
|
||||
if ( $report_data ) {
|
||||
?>
|
||||
<li class="sales-this-month">
|
||||
<a href="<?php echo esc_url( admin_url( $net_sales_link ) ); ?>">
|
||||
<?php echo $this->sales_sparkline( $reports, $is_wc_admin_disabled, '' ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: net sales */
|
||||
esc_html__( '%s net sales this month', 'woocommerce' ),
|
||||
'<strong>' . wc_price( $report_data->net_sales ) . '</strong>'
|
||||
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
$top_seller = $this->get_top_seller();
|
||||
if ( $top_seller && $top_seller->qty ) {
|
||||
?>
|
||||
<li class="best-seller-this-month">
|
||||
<a href="<?php echo esc_url( admin_url( $top_seller_link . $top_seller->product_id ) ); ?>">
|
||||
<?php echo $this->sales_sparkline( $reports, $is_wc_admin_disabled, $top_seller->product_id, 'count' ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: top seller product title 2: top seller quantity */
|
||||
esc_html__( '%1$s top seller this month (sold %2$d)', 'woocommerce' ),
|
||||
'<strong>' . get_the_title( $top_seller->product_id ) . '</strong>',
|
||||
$top_seller->qty
|
||||
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
$this->status_widget_order_rows();
|
||||
if ( get_option( 'woocommerce_manage_stock' ) === 'yes' ) {
|
||||
$this->status_widget_stock_rows( $is_wc_admin_disabled );
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_after_dashboard_status_widget', $reports );
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show order data is status widget.
|
||||
*/
|
||||
private function status_widget_order_rows() {
|
||||
if ( ! current_user_can( 'edit_shop_orders' ) ) {
|
||||
return;
|
||||
}
|
||||
$on_hold_count = 0;
|
||||
$processing_count = 0;
|
||||
|
||||
foreach ( wc_get_order_types( 'order-count' ) as $type ) {
|
||||
$counts = OrderUtil::get_count_for_type( $type );
|
||||
$on_hold_count += $counts['wc-on-hold'];
|
||||
$processing_count += $counts['wc-processing'];
|
||||
}
|
||||
?>
|
||||
<li class="processing-orders">
|
||||
<a href="<?php echo esc_url( admin_url( 'edit.php?post_status=wc-processing&post_type=shop_order' ) ); ?>">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: order count */
|
||||
_n( '<strong>%s order</strong> awaiting processing', '<strong>%s orders</strong> awaiting processing', $processing_count, 'woocommerce' ),
|
||||
$processing_count
|
||||
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="on-hold-orders">
|
||||
<a href="<?php echo esc_url( admin_url( 'edit.php?post_status=wc-on-hold&post_type=shop_order' ) ); ?>">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: order count */
|
||||
_n( '<strong>%s order</strong> on-hold', '<strong>%s orders</strong> on-hold', $on_hold_count, 'woocommerce' ),
|
||||
$on_hold_count
|
||||
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Show stock data is status widget.
|
||||
*
|
||||
* @param bool $is_wc_admin_disabled if woocommerce admin is disabled.
|
||||
*/
|
||||
private function status_widget_stock_rows( $is_wc_admin_disabled ) {
|
||||
global $wpdb;
|
||||
|
||||
// Requires lookup table added in 3.6.
|
||||
if ( version_compare( get_option( 'woocommerce_db_version', null ), '3.6', '<' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stock = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
|
||||
$nostock = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
|
||||
|
||||
$transient_name = 'wc_low_stock_count';
|
||||
$lowinstock_count = get_transient( $transient_name );
|
||||
|
||||
if ( false === $lowinstock_count ) {
|
||||
/**
|
||||
* Status widget low in stock count pre query.
|
||||
*
|
||||
* @since 4.3.0
|
||||
* @param null|string $low_in_stock_count Low in stock count, by default null.
|
||||
* @param int $stock Low stock amount.
|
||||
* @param int $nostock No stock amount
|
||||
*/
|
||||
$lowinstock_count = apply_filters( 'woocommerce_status_widget_low_in_stock_count_pre_query', null, $stock, $nostock );
|
||||
|
||||
if ( is_null( $lowinstock_count ) ) {
|
||||
$lowinstock_count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT( product_id )
|
||||
FROM {$wpdb->wc_product_meta_lookup} AS lookup
|
||||
INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID
|
||||
WHERE stock_quantity <= %d
|
||||
AND stock_quantity > %d
|
||||
AND posts.post_status = 'publish'",
|
||||
$stock,
|
||||
$nostock
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
set_transient( $transient_name, (int) $lowinstock_count, DAY_IN_SECONDS * 30 );
|
||||
}
|
||||
|
||||
$transient_name = 'wc_outofstock_count';
|
||||
$outofstock_count = get_transient( $transient_name );
|
||||
$lowstock_link = 'admin.php?page=wc-reports&tab=stock&report=low_in_stock';
|
||||
$outofstock_link = 'admin.php?page=wc-reports&tab=stock&report=out_of_stock';
|
||||
|
||||
if ( false === $is_wc_admin_disabled ) {
|
||||
$lowstock_link = 'admin.php?page=wc-admin&type=lowstock&path=%2Fanalytics%2Fstock';
|
||||
$outofstock_link = 'admin.php?page=wc-admin&type=outofstock&path=%2Fanalytics%2Fstock';
|
||||
}
|
||||
|
||||
if ( false === $outofstock_count ) {
|
||||
/**
|
||||
* Status widget out of stock count pre query.
|
||||
*
|
||||
* @since 4.3.0
|
||||
* @param null|string $outofstock_count Out of stock count, by default null.
|
||||
* @param int $nostock No stock amount
|
||||
*/
|
||||
$outofstock_count = apply_filters( 'woocommerce_status_widget_out_of_stock_count_pre_query', null, $nostock );
|
||||
|
||||
if ( is_null( $outofstock_count ) ) {
|
||||
$outofstock_count = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT( product_id )
|
||||
FROM {$wpdb->wc_product_meta_lookup} AS lookup
|
||||
INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID
|
||||
WHERE stock_quantity <= %d
|
||||
AND posts.post_status = 'publish'",
|
||||
$nostock
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
set_transient( $transient_name, (int) $outofstock_count, DAY_IN_SECONDS * 30 );
|
||||
}
|
||||
?>
|
||||
<li class="low-in-stock">
|
||||
<a href="<?php echo esc_url( admin_url( $lowstock_link ) ); ?>">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: order count */
|
||||
_n( '<strong>%s product</strong> low in stock', '<strong>%s products</strong> low in stock', $lowinstock_count, 'woocommerce' ),
|
||||
$lowinstock_count
|
||||
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<li class="out-of-stock">
|
||||
<a href="<?php echo esc_url( admin_url( $outofstock_link ) ); ?>">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: order count */
|
||||
_n( '<strong>%s product</strong> out of stock', '<strong>%s products</strong> out of stock', $outofstock_count, 'woocommerce' ),
|
||||
$outofstock_count
|
||||
); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</a>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent reviews widget.
|
||||
*/
|
||||
public function recent_reviews() {
|
||||
global $wpdb;
|
||||
|
||||
$query_from = apply_filters(
|
||||
'woocommerce_report_recent_reviews_query_from',
|
||||
"FROM {$wpdb->comments} comments
|
||||
LEFT JOIN {$wpdb->posts} posts ON (comments.comment_post_ID = posts.ID)
|
||||
WHERE comments.comment_approved = '1'
|
||||
AND comments.comment_type = 'review'
|
||||
AND posts.post_password = ''
|
||||
AND posts.post_type = 'product'
|
||||
AND comments.comment_parent = 0
|
||||
ORDER BY comments.comment_date_gmt DESC
|
||||
LIMIT 5"
|
||||
);
|
||||
|
||||
$comments = $wpdb->get_results(
|
||||
"SELECT posts.ID, posts.post_title, comments.comment_author, comments.comment_author_email, comments.comment_ID, comments.comment_content {$query_from};" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
);
|
||||
|
||||
if ( $comments ) {
|
||||
echo '<ul>';
|
||||
foreach ( $comments as $comment ) {
|
||||
|
||||
echo '<li>';
|
||||
|
||||
echo get_avatar( $comment->comment_author_email, '32' );
|
||||
|
||||
$rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );
|
||||
|
||||
/* translators: %s: rating */
|
||||
echo '<div class="star-rating"><span style="width:' . esc_attr( $rating * 20 ) . '%">' . sprintf( esc_html__( '%s out of 5', 'woocommerce' ), esc_html( $rating ) ) . '</span></div>';
|
||||
|
||||
/* translators: %s: review author */
|
||||
echo '<h4 class="meta"><a href="' . esc_url( get_permalink( $comment->ID ) ) . '#comment-' . esc_attr( absint( $comment->comment_ID ) ) . '">' . esc_html( apply_filters( 'woocommerce_admin_dashboard_recent_reviews', $comment->post_title, $comment ) ) . '</a> ' . sprintf( esc_html__( 'reviewed by %s', 'woocommerce' ), esc_html( $comment->comment_author ) ) . '</h4>';
|
||||
echo '<blockquote>' . wp_kses_data( $comment->comment_content ) . '</blockquote></li>';
|
||||
|
||||
}
|
||||
echo '</ul>';
|
||||
} else {
|
||||
echo '<p>' . esc_html__( 'There are no product reviews yet.', 'woocommerce' ) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network orders widget.
|
||||
*/
|
||||
public function network_orders() {
|
||||
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
|
||||
wp_enqueue_style( 'wc-network-orders', WC()->plugin_url() . '/assets/css/network-order-widget.css', array(), $version );
|
||||
|
||||
wp_enqueue_script( 'wc-network-orders', WC()->plugin_url() . '/assets/js/admin/network-orders' . $suffix . '.js', array( 'jquery', 'underscore' ), $version, true );
|
||||
|
||||
$user = wp_get_current_user();
|
||||
$blogs = get_blogs_of_user( $user->ID );
|
||||
$blog_ids = wp_list_pluck( $blogs, 'userblog_id' );
|
||||
|
||||
wp_localize_script(
|
||||
'wc-network-orders',
|
||||
'woocommerce_network_orders',
|
||||
array(
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'sites' => array_values( $blog_ids ),
|
||||
'order_endpoint' => get_rest_url( null, 'wc/v3/orders/network' ),
|
||||
)
|
||||
);
|
||||
?>
|
||||
<div class="post-type-shop_order">
|
||||
<div id="woocommerce-network-order-table-loading" class="woocommerce-network-order-table-loading is-active">
|
||||
<p>
|
||||
<span class="spinner is-active"></span> <?php esc_html_e( 'Loading network orders', 'woocommerce' ); ?>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<table id="woocommerce-network-order-table" class="woocommerce-network-order-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><?php esc_html_e( 'Order', 'woocommerce' ); ?></td>
|
||||
<td><?php esc_html_e( 'Status', 'woocommerce' ); ?></td>
|
||||
<td><?php esc_html_e( 'Total', 'woocommerce' ); ?></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="network-orders-tbody">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="woocommerce-network-orders-no-orders" class="woocommerce-network-orders-no-orders">
|
||||
<p>
|
||||
<?php esc_html_e( 'No orders found', 'woocommerce' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php // @codingStandardsIgnoreStart ?>
|
||||
<script type="text/template" id="network-orders-row-template">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<%- edit_url %>" class="order-view"><strong>#<%- number %> <%- customer %></strong></a>
|
||||
<br>
|
||||
<em>
|
||||
<%- blog.blogname %>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<mark class="order-status status-<%- status %>"><span><%- status_name %></span></mark>
|
||||
</td>
|
||||
<td>
|
||||
<%= formatted_total %>
|
||||
</td>
|
||||
</tr>
|
||||
</script>
|
||||
<?php // @codingStandardsIgnoreEnd ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sales performance data from the new WooAdmin store.
|
||||
*
|
||||
* @return stdClass|WP_Error|WP_REST_Response
|
||||
*/
|
||||
private function get_wc_admin_performance_data() {
|
||||
$request = new \WP_REST_Request( 'GET', '/wc-analytics/reports/performance-indicators' );
|
||||
$start_date = gmdate( 'Y-m-01 00:00:00', current_time( 'timestamp' ) );
|
||||
$end_date = gmdate( 'Y-m-d 23:59:59', current_time( 'timestamp' ) );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'before' => $end_date,
|
||||
'after' => $start_date,
|
||||
'stats' => 'revenue/total_sales,revenue/net_revenue,orders/orders_count,products/items_sold,variations/items_sold',
|
||||
)
|
||||
);
|
||||
$response = rest_do_request( $request );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( 200 !== $response->get_status() ) {
|
||||
return new \WP_Error( 'woocommerce_analytics_performance_indicators_result_failed', __( 'Sorry, fetching performance indicators failed.', 'woocommerce' ) );
|
||||
}
|
||||
$report_keys = array(
|
||||
'net_revenue' => 'net_sales',
|
||||
);
|
||||
$performance_data = new stdClass();
|
||||
foreach ( $response->get_data() as $indicator ) {
|
||||
if ( isset( $indicator['chart'] ) && isset( $indicator['value'] ) ) {
|
||||
$key = isset( $report_keys[ $indicator['chart'] ] ) ? $report_keys[ $indicator['chart'] ] : $indicator['chart'];
|
||||
$performance_data->$key = $indicator['value'];
|
||||
}
|
||||
}
|
||||
return $performance_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the original sparkline to use the new reports data if WooAdmin is enabled.
|
||||
* Prepares a sparkline to show sales in the last X days.
|
||||
*
|
||||
* @param WC_Admin_Report $reports old class for getting reports.
|
||||
* @param bool $is_wc_admin_disabled If WC Admin is disabled or not.
|
||||
* @param int $id ID of the product to show. Blank to get all orders.
|
||||
* @param string $type Type of sparkline to get. Ignored if ID is not set.
|
||||
* @return string
|
||||
*/
|
||||
private function sales_sparkline( $reports, $is_wc_admin_disabled = false, $id = '', $type = 'sales' ) {
|
||||
$days = max( 7, gmdate( 'd', current_time( 'timestamp' ) ) );
|
||||
if ( $is_wc_admin_disabled ) {
|
||||
return $reports->sales_sparkline( $id, $days, $type );
|
||||
}
|
||||
$sales_endpoint = '/wc-analytics/reports/revenue/stats';
|
||||
$start_date = gmdate( 'Y-m-d 00:00:00', current_time( 'timestamp' ) - ( ( $days - 1 ) * DAY_IN_SECONDS ) );
|
||||
$end_date = gmdate( 'Y-m-d 23:59:59', current_time( 'timestamp' ) );
|
||||
$meta_key = 'net_revenue';
|
||||
$params = array(
|
||||
'order' => 'asc',
|
||||
'interval' => 'day',
|
||||
'per_page' => 100,
|
||||
'before' => $end_date,
|
||||
'after' => $start_date,
|
||||
);
|
||||
if ( $id ) {
|
||||
$sales_endpoint = '/wc-analytics/reports/products/stats';
|
||||
$meta_key = ( 'sales' === $type ) ? 'net_revenue' : 'items_sold';
|
||||
$params['products'] = $id;
|
||||
}
|
||||
$request = new \WP_REST_Request( 'GET', $sales_endpoint );
|
||||
$params['fields'] = array( $meta_key );
|
||||
$request->set_query_params( $params );
|
||||
|
||||
$response = rest_do_request( $request );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$resp_data = $response->get_data();
|
||||
$data = $resp_data['intervals'];
|
||||
|
||||
$sparkline_data = array();
|
||||
$total = 0;
|
||||
foreach ( $data as $d ) {
|
||||
$total += $d['subtotals']->$meta_key;
|
||||
array_push( $sparkline_data, array( strval( strtotime( $d['interval'] ) * 1000 ), $d['subtotals']->$meta_key ) );
|
||||
}
|
||||
|
||||
if ( 'sales' === $type ) {
|
||||
/* translators: 1: total income 2: days */
|
||||
$tooltip = sprintf( __( 'Sold %1$s worth in the last %2$d days', 'woocommerce' ), strip_tags( wc_price( $total ) ), $days );
|
||||
} else {
|
||||
/* translators: 1: total items sold 2: days */
|
||||
$tooltip = sprintf( _n( 'Sold %1$d item in the last %2$d days', 'Sold %1$d items in the last %2$d days', $total, 'woocommerce' ), $total, $days );
|
||||
}
|
||||
|
||||
return '<span class="wc_sparkline ' . ( ( 'sales' === $type ) ? 'lines' : 'bars' ) . ' tips" data-color="#777" data-tip="' . esc_attr( $tooltip ) . '" data-barwidth="' . 60 * 60 * 16 * 1000 . '" data-sparkline="' . wc_esc_json( wp_json_encode( $sparkline_data ) ) . '"></span>';
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
return new WC_Admin_Dashboard();
|
||||
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
/**
|
||||
* Duplicate product functionality
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_Duplicate_Product', false ) ) {
|
||||
return new WC_Admin_Duplicate_Product();
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Duplicate_Product Class.
|
||||
*/
|
||||
class WC_Admin_Duplicate_Product {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_action_duplicate_product', array( $this, 'duplicate_product_action' ) );
|
||||
add_filter( 'post_row_actions', array( $this, 'dupe_link' ), 10, 2 );
|
||||
add_action( 'post_submitbox_start', array( $this, 'dupe_button' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the "Duplicate" link in admin products list.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param WP_Post $post Post object.
|
||||
* @return array
|
||||
*/
|
||||
public function dupe_link( $actions, $post ) {
|
||||
global $the_product;
|
||||
|
||||
if ( ! current_user_can( apply_filters( 'woocommerce_duplicate_product_capability', 'manage_woocommerce' ) ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
if ( 'product' !== $post->post_type ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
// Add Class to Delete Permanently link in row actions.
|
||||
if ( empty( $the_product ) || $the_product->get_id() !== $post->ID ) {
|
||||
$the_product = wc_get_product( $post );
|
||||
}
|
||||
|
||||
if ( 'publish' === $post->post_status && $the_product && 0 < $the_product->get_total_sales() ) {
|
||||
$actions['trash'] = sprintf(
|
||||
'<a href="%s" class="submitdelete trash-product" aria-label="%s">%s</a>',
|
||||
get_delete_post_link( $the_product->get_id(), '', false ),
|
||||
/* translators: %s: post title */
|
||||
esc_attr( sprintf( __( 'Move “%s” to the Trash', 'woocommerce' ), $the_product->get_name() ) ),
|
||||
esc_html__( 'Trash', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$actions['duplicate'] = '<a href="' . wp_nonce_url( admin_url( 'edit.php?post_type=product&action=duplicate_product&post=' . $post->ID ), 'woocommerce-duplicate-product_' . $post->ID ) . '" aria-label="' . esc_attr__( 'Make a duplicate from this product', 'woocommerce' )
|
||||
. '" rel="permalink">' . esc_html__( 'Duplicate', 'woocommerce' ) . '</a>';
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dupe product link in admin.
|
||||
*/
|
||||
public function dupe_button() {
|
||||
global $post;
|
||||
|
||||
if ( ! current_user_can( apply_filters( 'woocommerce_duplicate_product_capability', 'manage_woocommerce' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! is_object( $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'product' !== $post->post_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notify_url = wp_nonce_url( admin_url( 'edit.php?post_type=product&action=duplicate_product&post=' . absint( $post->ID ) ), 'woocommerce-duplicate-product_' . $post->ID );
|
||||
?>
|
||||
<div id="duplicate-action"><a class="submitduplicate duplication" href="<?php echo esc_url( $notify_url ); ?>"><?php esc_html_e( 'Copy to a new draft', 'woocommerce' ); ?></a></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate a product action.
|
||||
*/
|
||||
public function duplicate_product_action() {
|
||||
if ( empty( $_REQUEST['post'] ) ) {
|
||||
wp_die( esc_html__( 'No product to duplicate has been supplied!', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$product_id = isset( $_REQUEST['post'] ) ? absint( $_REQUEST['post'] ) : '';
|
||||
|
||||
check_admin_referer( 'woocommerce-duplicate-product_' . $product_id );
|
||||
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( false === $product ) {
|
||||
/* translators: %s: product id */
|
||||
wp_die( sprintf( esc_html__( 'Product creation failed, could not find original product: %s', 'woocommerce' ), esc_html( $product_id ) ) );
|
||||
}
|
||||
|
||||
$duplicate = $this->product_duplicate( $product );
|
||||
|
||||
// Hook rename to match other woocommerce_product_* hooks, and to move away from depending on a response from the wp_posts table.
|
||||
do_action( 'woocommerce_product_duplicate', $duplicate, $product );
|
||||
wc_do_deprecated_action( 'woocommerce_duplicate_product', array( $duplicate->get_id(), $this->get_product_to_duplicate( $product_id ) ), '3.0', 'Use woocommerce_product_duplicate action instead.' );
|
||||
|
||||
// Redirect to the edit screen for the new draft page.
|
||||
wp_redirect( admin_url( 'post.php?action=edit&post=' . $duplicate->get_id() ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create the duplicate of the product.
|
||||
*
|
||||
* @param WC_Product $product The product to duplicate.
|
||||
* @return WC_Product The duplicate.
|
||||
*/
|
||||
public function product_duplicate( $product ) {
|
||||
/**
|
||||
* Filter to allow us to exclude meta keys from product duplication..
|
||||
*
|
||||
* @param array $exclude_meta The keys to exclude from the duplicate.
|
||||
* @param array $existing_meta_keys The meta keys that the product already has.
|
||||
* @since 2.6
|
||||
*/
|
||||
$meta_to_exclude = array_filter(
|
||||
apply_filters(
|
||||
'woocommerce_duplicate_product_exclude_meta',
|
||||
array(),
|
||||
array_map(
|
||||
function ( $datum ) {
|
||||
return $datum->key;
|
||||
},
|
||||
$product->get_meta_data()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$duplicate = clone $product;
|
||||
$duplicate->set_id( 0 );
|
||||
/* translators: %s contains the name of the original product. */
|
||||
$duplicate->set_name( sprintf( esc_html__( '%s (Copy)', 'woocommerce' ), $duplicate->get_name() ) );
|
||||
$duplicate->set_total_sales( 0 );
|
||||
if ( '' !== $product->get_sku( 'edit' ) ) {
|
||||
$duplicate->set_sku( wc_product_generate_unique_sku( 0, $product->get_sku( 'edit' ) ) );
|
||||
}
|
||||
$duplicate->set_status( 'draft' );
|
||||
$duplicate->set_date_created( null );
|
||||
$duplicate->set_slug( '' );
|
||||
$duplicate->set_rating_counts( 0 );
|
||||
$duplicate->set_average_rating( 0 );
|
||||
$duplicate->set_review_count( 0 );
|
||||
|
||||
foreach ( $meta_to_exclude as $meta_key ) {
|
||||
$duplicate->delete_meta_data( $meta_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* This action can be used to modify the object further before it is created - it will be passed by reference.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
do_action( 'woocommerce_product_duplicate_before_save', $duplicate, $product );
|
||||
|
||||
// Save parent product.
|
||||
$duplicate->save();
|
||||
|
||||
// Duplicate children of a variable product.
|
||||
if ( ! apply_filters( 'woocommerce_duplicate_product_exclude_children', false, $product ) && $product->is_type( 'variable' ) ) {
|
||||
foreach ( $product->get_children() as $child_id ) {
|
||||
$child = wc_get_product( $child_id );
|
||||
$child_duplicate = clone $child;
|
||||
$child_duplicate->set_parent_id( $duplicate->get_id() );
|
||||
$child_duplicate->set_id( 0 );
|
||||
$child_duplicate->set_date_created( null );
|
||||
|
||||
// If we wait and let the insertion generate the slug, we will see extreme performance degradation
|
||||
// in the case where a product is used as a template. Every time the template is duplicated, each
|
||||
// variation will query every consecutive slug until it finds an empty one. To avoid this, we can
|
||||
// optimize the generation ourselves, avoiding the issue altogether.
|
||||
$this->generate_unique_slug( $child_duplicate );
|
||||
|
||||
if ( '' !== $child->get_sku( 'edit' ) ) {
|
||||
$child_duplicate->set_sku( wc_product_generate_unique_sku( 0, $child->get_sku( 'edit' ) ) );
|
||||
}
|
||||
|
||||
foreach ( $meta_to_exclude as $meta_key ) {
|
||||
$child_duplicate->delete_meta_data( $meta_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* This action can be used to modify the object further before it is created - it will be passed by reference.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
do_action( 'woocommerce_product_duplicate_before_save', $child_duplicate, $child );
|
||||
|
||||
$child_duplicate->save();
|
||||
}
|
||||
|
||||
// Get new object to reflect new children.
|
||||
$duplicate = wc_get_product( $duplicate->get_id() );
|
||||
}
|
||||
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a product from the database to duplicate.
|
||||
*
|
||||
* @deprecated 3.0.0
|
||||
* @param mixed $id The ID of the product to duplicate.
|
||||
* @return object|bool
|
||||
* @see duplicate_product
|
||||
*/
|
||||
private function get_product_to_duplicate( $id ) {
|
||||
global $wpdb;
|
||||
|
||||
$id = absint( $id );
|
||||
|
||||
if ( ! $id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post = $wpdb->get_row( $wpdb->prepare( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID = %d", $id ) );
|
||||
|
||||
if ( isset( $post->post_type ) && 'revision' === $post->post_type ) {
|
||||
$id = $post->post_parent;
|
||||
$post = $wpdb->get_row( $wpdb->prepare( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID = %d", $id ) );
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique slug for a given product. We do this so that we can override the
|
||||
* behavior of wp_unique_post_slug(). The normal slug generation will run single
|
||||
* select queries on every non-unique slug, resulting in very bad performance.
|
||||
*
|
||||
* @param WC_Product $product The product to generate a slug for.
|
||||
* @since 3.9.0
|
||||
*/
|
||||
private function generate_unique_slug( $product ) {
|
||||
global $wpdb;
|
||||
|
||||
// We want to remove the suffix from the slug so that we can find the maximum suffix using this root slug.
|
||||
// This will allow us to find the next-highest suffix that is unique. While this does not support gap
|
||||
// filling, this shouldn't matter for our use-case.
|
||||
$root_slug = preg_replace( '/-[0-9]+$/', '', $product->get_slug() );
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare( "SELECT post_name FROM $wpdb->posts WHERE post_name LIKE %s AND post_type IN ( 'product', 'product_variation' )", $root_slug . '%' )
|
||||
);
|
||||
|
||||
// The slug is already unique!
|
||||
if ( empty( $results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the maximum suffix so we can ensure uniqueness.
|
||||
$max_suffix = 1;
|
||||
foreach ( $results as $result ) {
|
||||
// Pull a numerical suffix off the slug after the last hyphen.
|
||||
$suffix = intval( substr( $result->post_name, strrpos( $result->post_name, '-' ) + 1 ) );
|
||||
if ( $suffix > $max_suffix ) {
|
||||
$max_suffix = $suffix;
|
||||
}
|
||||
}
|
||||
|
||||
$product->set_slug( $root_slug . '-' . ( $max_suffix + 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
return new WC_Admin_Duplicate_Product();
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Init WooCommerce data exporters.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.1.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Exporters Class.
|
||||
*/
|
||||
class WC_Admin_Exporters {
|
||||
|
||||
/**
|
||||
* Array of exporter IDs.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $exporters = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! $this->export_allowed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_menu', array( $this, 'add_to_menus' ) );
|
||||
add_action( 'admin_head', array( $this, 'hide_from_menus' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
||||
add_action( 'admin_init', array( $this, 'download_export_file' ) );
|
||||
add_action( 'wp_ajax_woocommerce_do_ajax_product_export', array( $this, 'do_ajax_product_export' ) );
|
||||
|
||||
// Register WooCommerce exporters.
|
||||
$this->exporters['product_exporter'] = array(
|
||||
'menu' => 'edit.php?post_type=product',
|
||||
'name' => __( 'Product Export', 'woocommerce' ),
|
||||
'capability' => 'export',
|
||||
'callback' => array( $this, 'product_exporter' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if WooCommerce export is allowed for current user, false otherwise.
|
||||
*
|
||||
* @return bool Whether current user can perform export.
|
||||
*/
|
||||
protected function export_allowed() {
|
||||
return current_user_can( 'edit_products' ) && current_user_can( 'export' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu items for our custom exporters.
|
||||
*/
|
||||
public function add_to_menus() {
|
||||
foreach ( $this->exporters as $id => $exporter ) {
|
||||
add_submenu_page( $exporter['menu'], $exporter['name'], $exporter['name'], $exporter['capability'], $id, $exporter['callback'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide menu items from view so the pages exist, but the menu items do not.
|
||||
*/
|
||||
public function hide_from_menus() {
|
||||
global $submenu;
|
||||
|
||||
foreach ( $this->exporters as $id => $exporter ) {
|
||||
if ( isset( $submenu[ $exporter['menu'] ] ) ) {
|
||||
foreach ( $submenu[ $exporter['menu'] ] as $key => $menu ) {
|
||||
if ( $id === $menu[2] ) {
|
||||
unset( $submenu[ $exporter['menu'] ][ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts.
|
||||
*/
|
||||
public function admin_scripts() {
|
||||
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
wp_register_script( 'wc-product-export', WC()->plugin_url() . '/assets/js/admin/wc-product-export' . $suffix . '.js', array( 'jquery' ), $version );
|
||||
wp_localize_script(
|
||||
'wc-product-export',
|
||||
'wc_product_export_params',
|
||||
array(
|
||||
'export_nonce' => wp_create_nonce( 'wc-product-export' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export page UI.
|
||||
*/
|
||||
public function product_exporter() {
|
||||
include_once WC_ABSPATH . 'includes/export/class-wc-product-csv-exporter.php';
|
||||
include_once dirname( __FILE__ ) . '/views/html-admin-page-product-export.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve the generated file.
|
||||
*/
|
||||
public function download_export_file() {
|
||||
if ( isset( $_GET['action'], $_GET['nonce'] ) && wp_verify_nonce( wp_unslash( $_GET['nonce'] ), 'product-csv' ) && 'download_product_csv' === wp_unslash( $_GET['action'] ) ) { // WPCS: input var ok, sanitization ok.
|
||||
include_once WC_ABSPATH . 'includes/export/class-wc-product-csv-exporter.php';
|
||||
$exporter = new WC_Product_CSV_Exporter();
|
||||
|
||||
if ( ! empty( $_GET['filename'] ) ) { // WPCS: input var ok.
|
||||
$exporter->set_filename( wp_unslash( $_GET['filename'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
$exporter->export();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback for doing the actual export to the CSV file.
|
||||
*/
|
||||
public function do_ajax_product_export() {
|
||||
check_ajax_referer( 'wc-product-export', 'security' );
|
||||
|
||||
if ( ! $this->export_allowed() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient privileges to export products.', 'woocommerce' ) ) );
|
||||
}
|
||||
|
||||
include_once WC_ABSPATH . 'includes/export/class-wc-product-csv-exporter.php';
|
||||
|
||||
$step = isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 1; // WPCS: input var ok, sanitization ok.
|
||||
$exporter = new WC_Product_CSV_Exporter();
|
||||
|
||||
if ( ! empty( $_POST['columns'] ) ) { // WPCS: input var ok.
|
||||
$exporter->set_column_names( wp_unslash( $_POST['columns'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['selected_columns'] ) ) { // WPCS: input var ok.
|
||||
$exporter->set_columns_to_export( wp_unslash( $_POST['selected_columns'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['export_meta'] ) ) { // WPCS: input var ok.
|
||||
$exporter->enable_meta_export( true );
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['export_types'] ) ) { // WPCS: input var ok.
|
||||
$exporter->set_product_types_to_export( wp_unslash( $_POST['export_types'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['export_category'] ) && is_array( $_POST['export_category'] ) ) {// WPCS: input var ok.
|
||||
$exporter->set_product_category_to_export( wp_unslash( array_values( $_POST['export_category'] ) ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['filename'] ) ) { // WPCS: input var ok.
|
||||
$exporter->set_filename( wp_unslash( $_POST['filename'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
$exporter->set_page( $step );
|
||||
$exporter->generate_file();
|
||||
|
||||
$query_args = apply_filters(
|
||||
'woocommerce_export_get_ajax_query_args',
|
||||
array(
|
||||
'nonce' => wp_create_nonce( 'product-csv' ),
|
||||
'action' => 'download_product_csv',
|
||||
'filename' => $exporter->get_filename(),
|
||||
)
|
||||
);
|
||||
|
||||
if ( 100 === $exporter->get_percent_complete() ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'step' => 'done',
|
||||
'percentage' => 100,
|
||||
'url' => add_query_arg( $query_args, admin_url( 'edit.php?post_type=product&page=product_exporter' ) ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'step' => ++$step,
|
||||
'percentage' => $exporter->get_percent_complete(),
|
||||
'columns' => $exporter->get_column_names(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the product types that can be exported.
|
||||
*
|
||||
* @since 5.1.0
|
||||
* @return array The product types keys and labels.
|
||||
*/
|
||||
public static function get_product_types() {
|
||||
$product_types = wc_get_product_types();
|
||||
$product_types['variation'] = __( 'Product variations', 'woocommerce' );
|
||||
|
||||
/**
|
||||
* Allow third-parties to filter the exportable product types.
|
||||
*
|
||||
* @since 5.1.0
|
||||
* @param array $product_types {
|
||||
* The product type key and label.
|
||||
*
|
||||
* @type string Product type key - eg 'variable', 'simple' etc.
|
||||
* @type string A translated product label which appears in the export product type dropdown.
|
||||
* }
|
||||
*/
|
||||
return apply_filters( 'woocommerce_exporter_product_types', $product_types );
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Exporters();
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Add some content to the help tab
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_Help', false ) ) {
|
||||
return new WC_Admin_Help();
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Help Class.
|
||||
*/
|
||||
class WC_Admin_Help {
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'current_screen', array( $this, 'add_tabs' ), 50 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add help tabs.
|
||||
*/
|
||||
public function add_tabs() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( ! $screen || ! in_array( $screen->id, wc_get_screen_ids() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen->add_help_tab(
|
||||
array(
|
||||
'id' => 'woocommerce_support_tab',
|
||||
'title' => __( 'Help & Support', 'woocommerce' ),
|
||||
'content' =>
|
||||
'<h2>' . __( 'Help & Support', 'woocommerce' ) . '</h2>' .
|
||||
'<p>' . sprintf(
|
||||
/* translators: %s: Documentation URL */
|
||||
__( 'Should you need help understanding, using, or extending WooCommerce, <a href="%s">please read our documentation</a>. You will find all kinds of resources including snippets, tutorials and much more.', 'woocommerce' ),
|
||||
'https://woocommerce.com/documentation/plugins/woocommerce/?utm_source=helptab&utm_medium=product&utm_content=docs&utm_campaign=woocommerceplugin'
|
||||
) . '</p>' .
|
||||
'<p>' . sprintf(
|
||||
/* translators: %s: Forum URL */
|
||||
__( 'For further assistance with WooCommerce core, use the <a href="%1$s">community forum</a>. For help with premium extensions sold on WooCommerce.com, <a href="%2$s">open a support request at WooCommerce.com</a>.', 'woocommerce' ),
|
||||
'https://wordpress.org/support/plugin/woocommerce',
|
||||
'https://woocommerce.com/my-account/create-a-ticket/?utm_source=helptab&utm_medium=product&utm_content=tickets&utm_campaign=woocommerceplugin'
|
||||
) . '</p>' .
|
||||
'<p>' . __( 'Before asking for help, we recommend checking the system status page to identify any problems with your configuration.', 'woocommerce' ) . '</p>' .
|
||||
'<p><a href="' . admin_url( 'admin.php?page=wc-status' ) . '" class="button button-primary">' . __( 'System status', 'woocommerce' ) . '</a> <a href="https://wordpress.org/support/plugin/woocommerce" class="button">' . __( 'Community forum', 'woocommerce' ) . '</a> <a href="https://woocommerce.com/my-account/create-a-ticket/?utm_source=helptab&utm_medium=product&utm_content=tickets&utm_campaign=woocommerceplugin" class="button">' . __( 'WooCommerce.com support', 'woocommerce' ) . '</a></p>',
|
||||
)
|
||||
);
|
||||
|
||||
$screen->add_help_tab(
|
||||
array(
|
||||
'id' => 'woocommerce_bugs_tab',
|
||||
'title' => __( 'Found a bug?', 'woocommerce' ),
|
||||
'content' =>
|
||||
'<h2>' . __( 'Found a bug?', 'woocommerce' ) . '</h2>' .
|
||||
/* translators: 1: GitHub issues URL 2: GitHub contribution guide URL 3: System status report URL */
|
||||
'<p>' . sprintf( __( 'If you find a bug within WooCommerce core you can create a ticket via <a href="%1$s">GitHub issues</a>. Ensure you read the <a href="%2$s">contribution guide</a> prior to submitting your report. To help us solve your issue, please be as descriptive as possible and include your <a href="%3$s">system status report</a>.', 'woocommerce' ), 'https://github.com/woocommerce/woocommerce/issues?state=open', 'https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md', admin_url( 'admin.php?page=wc-status' ) ) . '</p>' .
|
||||
'<p><a href="https://github.com/woocommerce/woocommerce/issues/new?assignees=&labels=&template=1-bug-report.yml" class="button button-primary">' . __( 'Report a bug', 'woocommerce' ) . '</a> <a href="' . admin_url( 'admin.php?page=wc-status' ) . '" class="button">' . __( 'System status', 'woocommerce' ) . '</a></p>',
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
$screen->set_help_sidebar(
|
||||
'<p><strong>' . __( 'For more information:', 'woocommerce' ) . '</strong></p>' .
|
||||
'<p><a href="https://woocommerce.com/?utm_source=helptab&utm_medium=product&utm_content=about&utm_campaign=woocommerceplugin" target="_blank">' . __( 'About WooCommerce', 'woocommerce' ) . '</a></p>' .
|
||||
'<p><a href="https://wordpress.org/plugins/woocommerce/" target="_blank">' . __( 'WordPress.org project', 'woocommerce' ) . '</a></p>' .
|
||||
'<p><a href="https://github.com/woocommerce/woocommerce/" target="_blank">' . __( 'GitHub project', 'woocommerce' ) . '</a></p>' .
|
||||
'<p><a href="https://woocommerce.com/product-category/themes/?utm_source=helptab&utm_medium=product&utm_content=wcthemes&utm_campaign=woocommerceplugin" target="_blank">' . __( 'Official themes', 'woocommerce' ) . '</a></p>' .
|
||||
'<p><a href="https://woocommerce.com/product-category/woocommerce-extensions/?utm_source=helptab&utm_medium=product&utm_content=wcextensions&utm_campaign=woocommerceplugin" target="_blank">' . __( 'Official extensions', 'woocommerce' ) . '</a></p>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new WC_Admin_Help();
|
||||
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* Init WooCommerce data importers.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Importers Class.
|
||||
*/
|
||||
class WC_Admin_Importers {
|
||||
|
||||
/**
|
||||
* Array of importer IDs.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $importers = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! $this->import_allowed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_menu', array( $this, 'add_to_menus' ) );
|
||||
add_action( 'admin_init', array( $this, 'register_importers' ) );
|
||||
add_action( 'admin_head', array( $this, 'hide_from_menus' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
||||
add_action( 'wp_ajax_woocommerce_do_ajax_product_import', array( $this, 'do_ajax_product_import' ) );
|
||||
|
||||
// Register WooCommerce importers.
|
||||
$this->importers['product_importer'] = array(
|
||||
'menu' => 'edit.php?post_type=product',
|
||||
'name' => __( 'Product Import', 'woocommerce' ),
|
||||
'capability' => 'import',
|
||||
'callback' => array( $this, 'product_importer' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if WooCommerce imports are allowed for current user, false otherwise.
|
||||
*
|
||||
* @return bool Whether current user can perform imports.
|
||||
*/
|
||||
protected function import_allowed() {
|
||||
return current_user_can( 'edit_products' ) && current_user_can( 'import' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu items for our custom importers.
|
||||
*/
|
||||
public function add_to_menus() {
|
||||
foreach ( $this->importers as $id => $importer ) {
|
||||
add_submenu_page( $importer['menu'], $importer['name'], $importer['name'], $importer['capability'], $id, $importer['callback'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide menu items from view so the pages exist, but the menu items do not.
|
||||
*/
|
||||
public function hide_from_menus() {
|
||||
global $submenu;
|
||||
|
||||
foreach ( $this->importers as $id => $importer ) {
|
||||
if ( isset( $submenu[ $importer['menu'] ] ) ) {
|
||||
foreach ( $submenu[ $importer['menu'] ] as $key => $menu ) {
|
||||
if ( $id === $menu[2] ) {
|
||||
unset( $submenu[ $importer['menu'] ][ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register importer scripts.
|
||||
*/
|
||||
public function admin_scripts() {
|
||||
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
|
||||
$version = Constants::get_constant( 'WC_VERSION' );
|
||||
wp_register_script( 'wc-product-import', WC()->plugin_url() . '/assets/js/admin/wc-product-import' . $suffix . '.js', array( 'jquery' ), $version, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* The product importer.
|
||||
*
|
||||
* This has a custom screen - the Tools > Import item is a placeholder.
|
||||
* If we're on that screen, redirect to the custom one.
|
||||
*/
|
||||
public function product_importer() {
|
||||
if ( Constants::is_defined( 'WP_LOAD_IMPORTERS' ) ) {
|
||||
wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=product_importer' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php';
|
||||
include_once WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php';
|
||||
|
||||
$importer = new WC_Product_CSV_Importer_Controller();
|
||||
$importer->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress based importers.
|
||||
*/
|
||||
public function register_importers() {
|
||||
if ( Constants::is_defined( 'WP_LOAD_IMPORTERS' ) ) {
|
||||
add_action( 'import_start', array( $this, 'post_importer_compatibility' ) );
|
||||
register_importer( 'woocommerce_product_csv', __( 'WooCommerce products (CSV)', 'woocommerce' ), __( 'Import <strong>products</strong> to your store via a csv file.', 'woocommerce' ), array( $this, 'product_importer' ) );
|
||||
register_importer( 'woocommerce_tax_rate_csv', __( 'WooCommerce tax rates (CSV)', 'woocommerce' ), __( 'Import <strong>tax rates</strong> to your store via a csv file.', 'woocommerce' ), array( $this, 'tax_rates_importer' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The tax rate importer which extends WP_Importer.
|
||||
*/
|
||||
public function tax_rates_importer() {
|
||||
require_once ABSPATH . 'wp-admin/includes/import.php';
|
||||
|
||||
if ( ! class_exists( 'WP_Importer' ) ) {
|
||||
$class_wp_importer = ABSPATH . 'wp-admin/includes/class-wp-importer.php';
|
||||
|
||||
if ( file_exists( $class_wp_importer ) ) {
|
||||
require $class_wp_importer;
|
||||
}
|
||||
}
|
||||
|
||||
require dirname( __FILE__ ) . '/importers/class-wc-tax-rate-importer.php';
|
||||
|
||||
$importer = new WC_Tax_Rate_Importer();
|
||||
$importer->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* When running the WP XML importer, ensure attributes exist.
|
||||
*
|
||||
* WordPress import should work - however, it fails to import custom product attribute taxonomies.
|
||||
* This code grabs the file before it is imported and ensures the taxonomies are created.
|
||||
*/
|
||||
public function post_importer_compatibility() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( empty( $_POST['import_id'] ) || ! class_exists( 'WXR_Parser' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$id = absint( $_POST['import_id'] );
|
||||
$file = get_attached_file( $id );
|
||||
$parser = new WXR_Parser();
|
||||
$import_data = $parser->parse( $file );
|
||||
|
||||
if ( isset( $import_data['posts'] ) && ! empty( $import_data['posts'] ) ) {
|
||||
foreach ( $import_data['posts'] as $post ) {
|
||||
if ( 'product' === $post['post_type'] && ! empty( $post['terms'] ) ) {
|
||||
foreach ( $post['terms'] as $term ) {
|
||||
if ( strstr( $term['domain'], 'pa_' ) ) {
|
||||
if ( ! taxonomy_exists( $term['domain'] ) ) {
|
||||
$attribute_name = wc_attribute_taxonomy_slug( $term['domain'] );
|
||||
|
||||
// Create the taxonomy.
|
||||
if ( ! in_array( $attribute_name, wc_get_attribute_taxonomies(), true ) ) {
|
||||
wc_create_attribute(
|
||||
array(
|
||||
'name' => $attribute_name,
|
||||
'slug' => $attribute_name,
|
||||
'type' => 'select',
|
||||
'order_by' => 'menu_order',
|
||||
'has_archives' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Register the taxonomy now so that the import works!
|
||||
register_taxonomy(
|
||||
$term['domain'],
|
||||
apply_filters( 'woocommerce_taxonomy_objects_' . $term['domain'], array( 'product' ) ),
|
||||
apply_filters(
|
||||
'woocommerce_taxonomy_args_' . $term['domain'],
|
||||
array(
|
||||
'hierarchical' => true,
|
||||
'show_ui' => false,
|
||||
'query_var' => true,
|
||||
'rewrite' => false,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback for importing one batch of products from a CSV.
|
||||
*/
|
||||
public function do_ajax_product_import() {
|
||||
global $wpdb;
|
||||
|
||||
check_ajax_referer( 'wc-product-import', 'security' );
|
||||
|
||||
if ( ! $this->import_allowed() || ! isset( $_POST['file'] ) ) { // PHPCS: input var ok.
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient privileges to import products.', 'woocommerce' ) ) );
|
||||
}
|
||||
|
||||
include_once WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php';
|
||||
include_once WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php';
|
||||
|
||||
$file = wc_clean( wp_unslash( $_POST['file'] ) ); // PHPCS: input var ok.
|
||||
$params = array(
|
||||
'delimiter' => ! empty( $_POST['delimiter'] ) ? wc_clean( wp_unslash( $_POST['delimiter'] ) ) : ',', // PHPCS: input var ok.
|
||||
'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, // PHPCS: input var ok.
|
||||
'mapping' => isset( $_POST['mapping'] ) ? (array) wc_clean( wp_unslash( $_POST['mapping'] ) ) : array(), // PHPCS: input var ok.
|
||||
'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, // PHPCS: input var ok.
|
||||
'character_encoding' => isset( $_POST['character_encoding'] ) ? wc_clean( wp_unslash( $_POST['character_encoding'] ) ) : '',
|
||||
|
||||
/**
|
||||
* Batch size for the product import process.
|
||||
*
|
||||
* @param int $size Batch size.
|
||||
*
|
||||
* @since
|
||||
*/
|
||||
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 30 ),
|
||||
'parse' => true,
|
||||
);
|
||||
|
||||
// Log failures.
|
||||
if ( 0 !== $params['start_pos'] ) {
|
||||
$error_log = array_filter( (array) get_user_option( 'product_import_error_log' ) );
|
||||
} else {
|
||||
$error_log = array();
|
||||
}
|
||||
|
||||
$importer = WC_Product_CSV_Importer_Controller::get_importer( $file, $params );
|
||||
$results = $importer->import();
|
||||
$percent_complete = $importer->get_percent_complete();
|
||||
$error_log = array_merge( $error_log, $results['failed'], $results['skipped'] );
|
||||
|
||||
update_user_option( get_current_user_id(), 'product_import_error_log', $error_log );
|
||||
|
||||
if ( 100 === $percent_complete ) {
|
||||
// @codingStandardsIgnoreStart.
|
||||
$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_original_id' ) );
|
||||
$wpdb->delete( $wpdb->posts, array(
|
||||
'post_type' => 'product',
|
||||
'post_status' => 'importing',
|
||||
) );
|
||||
$wpdb->delete( $wpdb->posts, array(
|
||||
'post_type' => 'product_variation',
|
||||
'post_status' => 'importing',
|
||||
) );
|
||||
// @codingStandardsIgnoreEnd.
|
||||
|
||||
// Clean up orphaned data.
|
||||
$wpdb->query(
|
||||
"
|
||||
DELETE {$wpdb->posts}.* FROM {$wpdb->posts}
|
||||
LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->posts}.post_parent
|
||||
WHERE wp.ID IS NULL AND {$wpdb->posts}.post_type = 'product_variation'
|
||||
"
|
||||
);
|
||||
$wpdb->query(
|
||||
"
|
||||
DELETE {$wpdb->postmeta}.* FROM {$wpdb->postmeta}
|
||||
LEFT JOIN {$wpdb->posts} wp ON wp.ID = {$wpdb->postmeta}.post_id
|
||||
WHERE wp.ID IS NULL
|
||||
"
|
||||
);
|
||||
// @codingStandardsIgnoreStart.
|
||||
$wpdb->query( "
|
||||
DELETE tr.* FROM {$wpdb->term_relationships} tr
|
||||
LEFT JOIN {$wpdb->posts} wp ON wp.ID = tr.object_id
|
||||
LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
||||
WHERE wp.ID IS NULL
|
||||
AND tt.taxonomy IN ( '" . implode( "','", array_map( 'esc_sql', get_object_taxonomies( 'product' ) ) ) . "' )
|
||||
" );
|
||||
// @codingStandardsIgnoreEnd.
|
||||
|
||||
// Send success.
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'position' => 'done',
|
||||
'percentage' => 100,
|
||||
'url' => add_query_arg( array( '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ) ), admin_url( 'edit.php?post_type=product&page=product_importer&step=done' ) ),
|
||||
'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0,
|
||||
'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0,
|
||||
'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0,
|
||||
'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0,
|
||||
'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'position' => $importer->get_file_position(),
|
||||
'percentage' => $percent_complete,
|
||||
'imported' => is_countable( $results['imported'] ) ? count( $results['imported'] ) : 0,
|
||||
'imported_variations' => is_countable( $results['imported_variations'] ) ? count( $results['imported_variations'] ) : 0,
|
||||
'failed' => is_countable( $results['failed'] ) ? count( $results['failed'] ) : 0,
|
||||
'updated' => is_countable( $results['updated'] ) ? count( $results['updated'] ) : 0,
|
||||
'skipped' => is_countable( $results['skipped'] ) ? count( $results['skipped'] ) : 0,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Importers();
|
||||
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Log Table List
|
||||
*
|
||||
* @author WooThemes
|
||||
* @category Admin
|
||||
* @package WooCommerce\Admin
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
class WC_Admin_Log_Table_List extends WP_List_Table {
|
||||
/**
|
||||
* The key for the user option of how many list table items to display per page.
|
||||
*
|
||||
* @const string
|
||||
*/
|
||||
public const PER_PAGE_USER_OPTION_KEY = 'woocommerce_status_log_items_per_page';
|
||||
|
||||
/**
|
||||
* Initialize the log table list.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => 'log',
|
||||
'plural' => 'logs',
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display level dropdown
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*/
|
||||
public function level_dropdown() {
|
||||
$labels = WC_Log_Levels::get_all_level_labels();
|
||||
|
||||
$levels = array_reduce(
|
||||
array_keys( $labels ),
|
||||
function( $carry, $item ) use ( $labels ) {
|
||||
$carry[] = array(
|
||||
'value' => $item,
|
||||
'label' => $labels[ $item ],
|
||||
);
|
||||
|
||||
return $carry;
|
||||
},
|
||||
array()
|
||||
);
|
||||
|
||||
$selected_level = isset( $_REQUEST['level'] ) ? $_REQUEST['level'] : '';
|
||||
?>
|
||||
<label for="filter-by-level" class="screen-reader-text"><?php esc_html_e( 'Filter by level', 'woocommerce' ); ?></label>
|
||||
<select name="level" id="filter-by-level">
|
||||
<option<?php selected( $selected_level, '' ); ?> value=""><?php esc_html_e( 'All levels', 'woocommerce' ); ?></option>
|
||||
<?php
|
||||
foreach ( $levels as $l ) {
|
||||
printf(
|
||||
'<option%1$s value="%2$s">%3$s</option>',
|
||||
selected( $selected_level, $l['value'], false ),
|
||||
esc_attr( $l['value'] ),
|
||||
esc_html( $l['label'] )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the table rows.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_rows() {
|
||||
foreach ( $this->items as $log ) {
|
||||
$this->single_row( $log );
|
||||
if ( ! empty( $log['context'] ) ) {
|
||||
$this->context_row( $log );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the additional table row that contains extra log context data.
|
||||
*
|
||||
* @param array $log Log entry data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function context_row( $log ) {
|
||||
// Maintains alternating row background colors.
|
||||
?>
|
||||
<tr style="display: none"><td></td></tr>
|
||||
<tr id="log-context-<?php echo esc_attr( $log['log_id'] ); ?>" class="log-context">
|
||||
<td colspan="<?php echo esc_attr( $this->get_column_count() ); ?>">
|
||||
<p><strong><?php esc_html_e( 'Additional context', 'woocommerce' ); ?></strong></p>
|
||||
<pre><?php echo esc_html( $log['context'] ); ?></pre>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'timestamp' => __( 'Timestamp', 'woocommerce' ),
|
||||
'level' => __( 'Level', 'woocommerce' ),
|
||||
'message' => __( 'Message', 'woocommerce' ),
|
||||
'source' => __( 'Source', 'woocommerce' ),
|
||||
'context' => __( 'Context', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column cb.
|
||||
*
|
||||
* @param array $log
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $log ) {
|
||||
return sprintf( '<input type="checkbox" name="log[]" value="%1$s" />', esc_attr( $log['log_id'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp column.
|
||||
*
|
||||
* @param array $log
|
||||
* @return string
|
||||
*/
|
||||
public function column_timestamp( $log ) {
|
||||
return esc_html(
|
||||
mysql2date(
|
||||
'Y-m-d H:i:s',
|
||||
$log['timestamp']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Level column.
|
||||
*
|
||||
* @param array $log
|
||||
* @return string
|
||||
*/
|
||||
public function column_level( $log ) {
|
||||
$level_key = WC_Log_Levels::get_severity_level( $log['level'] );
|
||||
$levels = WC_Log_Levels::get_all_level_labels();
|
||||
|
||||
if ( ! isset( $levels[ $level_key ] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$level = $levels[ $level_key ];
|
||||
$level_class = sanitize_html_class( 'log-level--' . $level_key );
|
||||
return '<span class="log-level ' . $level_class . '">' . esc_html( $level ) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Message column.
|
||||
*
|
||||
* @param array $log
|
||||
* @return string
|
||||
*/
|
||||
public function column_message( $log ) {
|
||||
return sprintf(
|
||||
'<pre>%s</pre>',
|
||||
esc_html( $log['message'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Source column.
|
||||
*
|
||||
* @param array $log
|
||||
* @return string
|
||||
*/
|
||||
public function column_source( $log ) {
|
||||
return esc_html( $log['source'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Context column.
|
||||
*
|
||||
* @param array $log Log entry data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_context( $log ) {
|
||||
$content = '';
|
||||
|
||||
if ( ! empty( $log['context'] ) ) {
|
||||
ob_start();
|
||||
?>
|
||||
<button
|
||||
class="log-toggle button button-secondary button-small"
|
||||
data-log-id="<?php echo esc_attr( $log['log_id'] ); ?>"
|
||||
data-toggle-status="off"
|
||||
data-label-show="<?php esc_attr_e( 'Show context', 'woocommerce' ); ?>"
|
||||
data-label-hide="<?php esc_attr_e( 'Hide context', 'woocommerce' ); ?>"
|
||||
>
|
||||
<span class="dashicons dashicons-arrow-down-alt2"></span>
|
||||
<span class="log-toggle-label screen-reader-text"><?php esc_html_e( 'Show context', 'woocommerce' ); ?></span>
|
||||
</button>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_bulk_actions() {
|
||||
return array(
|
||||
'delete' => __( 'Delete', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra controls to be displayed between bulk actions and pagination.
|
||||
*
|
||||
* @param string $which
|
||||
*/
|
||||
protected function extra_tablenav( $which ) {
|
||||
if ( 'top' === $which ) {
|
||||
echo '<div class="alignleft actions">';
|
||||
$this->level_dropdown();
|
||||
$this->source_dropdown();
|
||||
submit_button( __( 'Filter', 'woocommerce' ), '', 'filter-action', false );
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sortable columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_sortable_columns() {
|
||||
return array(
|
||||
'timestamp' => array( 'timestamp', true ),
|
||||
'level' => array( 'level', true ),
|
||||
'source' => array( 'source', true ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display source dropdown
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*/
|
||||
protected function source_dropdown() {
|
||||
global $wpdb;
|
||||
|
||||
$sources = $wpdb->get_col(
|
||||
"SELECT DISTINCT source
|
||||
FROM {$wpdb->prefix}woocommerce_log
|
||||
WHERE source != ''
|
||||
ORDER BY source ASC"
|
||||
);
|
||||
|
||||
if ( ! empty( $sources ) ) {
|
||||
$selected_source = isset( $_REQUEST['source'] ) ? $_REQUEST['source'] : '';
|
||||
?>
|
||||
<label for="filter-by-source" class="screen-reader-text"><?php esc_html_e( 'Filter by source', 'woocommerce' ); ?></label>
|
||||
<select name="source" id="filter-by-source">
|
||||
<option<?php selected( $selected_source, '' ); ?> value=""><?php esc_html_e( 'All sources', 'woocommerce' ); ?></option>
|
||||
<?php
|
||||
foreach ( $sources as $s ) {
|
||||
printf(
|
||||
'<option%1$s value="%2$s">%3$s</option>',
|
||||
selected( $selected_source, $s, false ),
|
||||
esc_attr( $s ),
|
||||
esc_html( $s )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare table list items.
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*/
|
||||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
|
||||
$this->prepare_column_headers();
|
||||
|
||||
$per_page = $this->get_items_per_page(
|
||||
self::PER_PAGE_USER_OPTION_KEY,
|
||||
$this->get_per_page_default()
|
||||
);
|
||||
|
||||
$where = $this->get_items_query_where();
|
||||
$order = $this->get_items_query_order();
|
||||
$limit = $this->get_items_query_limit();
|
||||
$offset = $this->get_items_query_offset();
|
||||
|
||||
$query_items = "
|
||||
SELECT log_id, timestamp, level, message, source, context
|
||||
FROM {$wpdb->prefix}woocommerce_log
|
||||
{$where} {$order} {$limit} {$offset}
|
||||
";
|
||||
|
||||
$this->items = $wpdb->get_results( $query_items, ARRAY_A );
|
||||
|
||||
$query_count = "SELECT COUNT(log_id) FROM {$wpdb->prefix}woocommerce_log {$where}";
|
||||
$total_items = $wpdb->get_var( $query_count );
|
||||
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $total_items,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => ceil( $total_items / $per_page ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared LIMIT clause for items query
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*
|
||||
* @return string Prepared LIMIT clause for items query.
|
||||
*/
|
||||
protected function get_items_query_limit() {
|
||||
global $wpdb;
|
||||
|
||||
$per_page = $this->get_items_per_page(
|
||||
self::PER_PAGE_USER_OPTION_KEY,
|
||||
$this->get_per_page_default()
|
||||
);
|
||||
|
||||
return $wpdb->prepare( 'LIMIT %d', $per_page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared OFFSET clause for items query
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*
|
||||
* @return string Prepared OFFSET clause for items query.
|
||||
*/
|
||||
protected function get_items_query_offset() {
|
||||
global $wpdb;
|
||||
|
||||
$per_page = $this->get_items_per_page(
|
||||
self::PER_PAGE_USER_OPTION_KEY,
|
||||
$this->get_per_page_default()
|
||||
);
|
||||
$current_page = $this->get_pagenum();
|
||||
if ( 1 < $current_page ) {
|
||||
$offset = $per_page * ( $current_page - 1 );
|
||||
} else {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
return $wpdb->prepare( 'OFFSET %d', $offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared ORDER BY clause for items query
|
||||
*
|
||||
* @return string Prepared ORDER BY clause for items query.
|
||||
*/
|
||||
protected function get_items_query_order() {
|
||||
$valid_orders = array( 'level', 'source', 'timestamp' );
|
||||
if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], $valid_orders ) ) {
|
||||
$by = wc_clean( $_REQUEST['orderby'] );
|
||||
} else {
|
||||
$by = 'timestamp';
|
||||
}
|
||||
$by = esc_sql( $by );
|
||||
|
||||
if ( ! empty( $_REQUEST['order'] ) && 'asc' === strtolower( $_REQUEST['order'] ) ) {
|
||||
$order = 'ASC';
|
||||
} else {
|
||||
$order = 'DESC';
|
||||
}
|
||||
|
||||
return "ORDER BY {$by} {$order}, log_id {$order}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared WHERE clause for items query
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*
|
||||
* @return string Prepared WHERE clause for items query.
|
||||
*/
|
||||
protected function get_items_query_where() {
|
||||
global $wpdb;
|
||||
|
||||
$where_conditions = array();
|
||||
$where_values = array();
|
||||
if ( ! empty( $_REQUEST['level'] ) && WC_Log_Levels::is_valid_level( $_REQUEST['level'] ) ) {
|
||||
$where_conditions[] = 'level >= %d';
|
||||
$where_values[] = WC_Log_Levels::get_level_severity( $_REQUEST['level'] );
|
||||
}
|
||||
if ( ! empty( $_REQUEST['source'] ) ) {
|
||||
$where_conditions[] = 'source = %s';
|
||||
$where_values[] = wc_clean( $_REQUEST['source'] );
|
||||
}
|
||||
if ( ! empty( $_REQUEST['s'] ) ) {
|
||||
$where_conditions[] = 'message like %s';
|
||||
$where_values[] = '%' . $wpdb->esc_like( wc_clean( wp_unslash( $_REQUEST['s'] ) ) ) . '%';
|
||||
}
|
||||
|
||||
if ( empty( $where_conditions ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $wpdb->prepare( 'WHERE 1 = 1 AND ' . implode( ' AND ', $where_conditions ), $where_values );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set _column_headers property for table list
|
||||
*/
|
||||
protected function prepare_column_headers() {
|
||||
$this->_column_headers = array(
|
||||
$this->get_columns(),
|
||||
array(),
|
||||
$this->get_sortable_columns(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the default value for the per_page arg.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_per_page_default(): int {
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
/**
|
||||
* Addons Page
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.5.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Marketplace_Promotions class.
|
||||
*/
|
||||
class WC_Admin_Marketplace_Promotions {
|
||||
|
||||
const TRANSIENT_NAME = 'woocommerce_marketplace_promotions';
|
||||
const SCHEDULED_ACTION_HOOK = 'woocommerce_marketplace_fetch_promotions';
|
||||
const PROMOTIONS_API_URL = 'https://woocommerce.com/wp-json/wccom-extensions/3.0/promotions';
|
||||
const SCHEDULED_ACTION_INTERVAL = 12 * HOUR_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* The user's locale, for example en_US.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static string $locale;
|
||||
|
||||
/**
|
||||
* On all admin pages, schedule an action to fetch promotions data.
|
||||
* Shows notice and adds menu badge to WooCommerce Extensions item
|
||||
* if the promotions API requests them.
|
||||
*
|
||||
* WC_Admin calls this method when it is instantiated during
|
||||
* is_admin requests.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
/**
|
||||
* Filter to suppress the requests for and showing of marketplace promotions.
|
||||
*
|
||||
* @since 8.8
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_marketplace_suppress_promotions', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_scheduled_event' ) );
|
||||
|
||||
// Add the callback for our scheduled action.
|
||||
if ( ! has_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) ) ) {
|
||||
add_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) );
|
||||
}
|
||||
|
||||
if ( is_admin() ) {
|
||||
add_action( 'init', array( __CLASS__, 'schedule_promotion_fetch' ), 12 );
|
||||
}
|
||||
|
||||
if (
|
||||
defined( 'DOING_AJAX' ) && DOING_AJAX
|
||||
|| defined( 'DOING_CRON' ) && DOING_CRON
|
||||
|| defined( 'WP_CLI' ) && WP_CLI
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$locale = ( self::$locale ?? get_user_locale() ) ?? 'en_US';
|
||||
self::maybe_show_bubble_promotions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the action to fetch promotions data.
|
||||
*/
|
||||
public static function schedule_promotion_fetch() {
|
||||
// Schedule the action twice a day using Action Scheduler.
|
||||
if (
|
||||
function_exists( 'as_has_scheduled_action' )
|
||||
&& function_exists( 'as_schedule_recurring_action' )
|
||||
&& false === as_has_scheduled_action( self::SCHEDULED_ACTION_HOOK )
|
||||
) {
|
||||
as_schedule_recurring_action( time(), self::SCHEDULED_ACTION_INTERVAL, self::SCHEDULED_ACTION_HOOK );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get promotions to show in the Woo in-app marketplace and load them into a transient
|
||||
* with a 12-hour life. Run as a recurring scheduled action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function fetch_marketplace_promotions() {
|
||||
// Fetch promotions from the API.
|
||||
$fetch_options = array(
|
||||
'auth' => true,
|
||||
'country' => true,
|
||||
);
|
||||
$raw_promotions = WC_Admin_Addons::fetch( self::PROMOTIONS_API_URL, $fetch_options );
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
if ( is_wp_error( $raw_promotions ) ) {
|
||||
/**
|
||||
* Allows connection error to be handled.
|
||||
*
|
||||
* @since 8.7
|
||||
*/
|
||||
do_action( 'woocommerce_page_wc-addons_connection_error', $raw_promotions->get_error_message() );
|
||||
}
|
||||
|
||||
$response_code = (int) wp_remote_retrieve_response_code( $raw_promotions );
|
||||
if ( 200 !== $response_code ) {
|
||||
/**
|
||||
* Allows connection error to be handled.
|
||||
*
|
||||
* @since 8.7
|
||||
*/
|
||||
do_action( 'woocommerce_page_wc-addons_connection_error', $response_code );
|
||||
}
|
||||
|
||||
$promotions = json_decode( wp_remote_retrieve_body( $raw_promotions ), true );
|
||||
if ( ! is_array( $promotions ) ) {
|
||||
$promotions = array();
|
||||
|
||||
/**
|
||||
* Allows connection error to be handled.
|
||||
*
|
||||
* @since 8.7
|
||||
*/
|
||||
do_action( 'woocommerce_page_wc-addons_connection_error', 'Malformed response' );
|
||||
}
|
||||
// phpcs:enable WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
|
||||
// Filter out any expired promotions.
|
||||
$active_promotions = self::get_active_promotions( $promotions );
|
||||
set_transient( self::TRANSIENT_NAME, $active_promotions, 12 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's an active promotion of the format `menu_bubble`,
|
||||
* add a filter to show a bubble on the Extensions item in the
|
||||
* WooCommerce menu.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception If we are unable to create a DateTime from the date_to_gmt.
|
||||
*/
|
||||
private static function maybe_show_bubble_promotions() {
|
||||
$promotions = get_transient( self::TRANSIENT_NAME );
|
||||
if ( ! $promotions ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bubble_promotions = self::get_promotions_of_format( $promotions, 'menu_bubble' );
|
||||
if ( empty( $bubble_promotions ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now_date_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
|
||||
// Let's make absolutely sure the promotion is still active.
|
||||
foreach ( $bubble_promotions as $promotion ) {
|
||||
if ( ! isset( $promotion['date_to_gmt'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$date_to_gmt = new DateTime( $promotion['date_to_gmt'], new DateTimeZone( 'UTC' ) );
|
||||
} catch ( \Exception $ex ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $now_date_time < $date_to_gmt ) {
|
||||
add_filter(
|
||||
'woocommerce_marketplace_menu_items',
|
||||
function ( $marketplace_pages ) use ( $promotion ) {
|
||||
return self::filter_marketplace_menu_items( $marketplace_pages, $promotion );
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From the array of promotions, select those of a given format.
|
||||
*
|
||||
* @param ? array $promotions Array of data about promotions of all formats.
|
||||
* @param ? string $format Format we want to filter for.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_promotions_of_format( $promotions = array(), $format = '' ): array {
|
||||
if ( empty( $promotions ) || empty( $format ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$promotions,
|
||||
function( $promotion ) use ( $format ) {
|
||||
return isset( $promotion['format'] ) && $format === $promotion['format'];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find promotions that are still active – they have a date range that
|
||||
* includes the current date.
|
||||
*
|
||||
* @param ?array $promotions Data about current promotions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_active_promotions( $promotions = array() ) {
|
||||
$now_date_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
$active_promotions = array();
|
||||
|
||||
foreach ( $promotions as $promotion ) {
|
||||
if ( ! isset( $promotion['date_from_gmt'] ) || ! isset( $promotion['date_to_gmt'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$date_from_gmt = new DateTime( $promotion['date_from_gmt'], new DateTimeZone( 'UTC' ) );
|
||||
$date_to_gmt = new DateTime( $promotion['date_to_gmt'], new DateTimeZone( 'UTC' ) );
|
||||
} catch ( \Exception $ex ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $now_date_time >= $date_from_gmt && $now_date_time <= $date_to_gmt ) {
|
||||
$active_promotions[] = $promotion;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort promotions so the ones starting more recently are at the top.
|
||||
usort(
|
||||
$active_promotions,
|
||||
function ( $a, $b ) {
|
||||
return $b['date_from_gmt'] <=> $a['date_from_gmt'];
|
||||
}
|
||||
);
|
||||
|
||||
return $active_promotions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the `woocommerce_marketplace_menu_items` filter
|
||||
* in `Automattic\WooCommerce\Internal\Admin\Marketplace::get_marketplace_pages`.
|
||||
* At the moment, the Extensions page is the only page in `$menu_items`.
|
||||
* Adds a bubble to the menu item.
|
||||
*
|
||||
* @param array $menu_items Arrays representing items in nav menu.
|
||||
* @param ?array $promotion Data about a promotion from the WooCommerce.com API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function filter_marketplace_menu_items( $menu_items, $promotion = array() ): array {
|
||||
if ( ! isset( $promotion['menu_item_id'] ) || ! isset( $promotion['content'] ) ) {
|
||||
return $menu_items;
|
||||
}
|
||||
foreach ( $menu_items as $index => $menu_item ) {
|
||||
if (
|
||||
'woocommerce' === $menu_item['parent']
|
||||
&& $promotion['menu_item_id'] === $menu_item['id']
|
||||
) {
|
||||
$bubble_text = $promotion['content'][ self::$locale ] ?? ( $promotion['content']['en_US'] ?? __( 'Sale', 'woocommerce' ) );
|
||||
$menu_items[ $index ]['title'] = $menu_item['title'] . self::append_bubble( $bubble_text );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $menu_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the markup for a menu item bubble with a given text and optional additional attributes.
|
||||
*
|
||||
* @param string $bubble_text Text of bubble.
|
||||
* @param array $attributes Optional. Additional attributes for the bubble, such as class or style.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function append_bubble( $bubble_text, $attributes = array() ) {
|
||||
$default_attributes = array(
|
||||
'class' => 'awaiting-mod update-plugins remaining-tasks-badge woocommerce-task-list-remaining-tasks-badge',
|
||||
'style' => '',
|
||||
);
|
||||
|
||||
$attributes = wp_parse_args( $attributes, $default_attributes );
|
||||
$class_attr = ! empty( $attributes['class'] ) ? sprintf( 'class="%s"', esc_attr( $attributes['class'] ) ) : '';
|
||||
$style_attr = ! empty( $attributes['style'] ) ? sprintf( 'style="%s"', esc_attr( $attributes['style'] ) ) : '';
|
||||
|
||||
$bubble_html = sprintf( ' <span %s %s>%s</span>', $class_attr, $style_attr, esc_html( $bubble_text ) );
|
||||
|
||||
return $bubble_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* When WooCommerce is deactivated, clear the scheduled action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_scheduled_event() {
|
||||
if ( function_exists( 'as_unschedule_all_actions' ) ) {
|
||||
as_unschedule_all_actions( self::SCHEDULED_ACTION_HOOK );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch list of promotions from WooCommerce.com for WooCommerce admin UI.
|
||||
if ( ! has_action( 'init', array( 'WC_Admin_Marketplace_Promotions', 'init' ) ) ) {
|
||||
add_action( 'init', array( 'WC_Admin_Marketplace_Promotions', 'init' ), 11 );
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
<?php
|
||||
/**
|
||||
* Setup menus in WP admin.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.5.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketplace;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\COTRedirectionController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\PageController as Custom_Orders_PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\PageController as LoggingPageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ FileListTable, SearchListTable };
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Utilities\FeaturesUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( class_exists( 'WC_Admin_Menus', false ) ) {
|
||||
return new WC_Admin_Menus();
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Menus Class.
|
||||
*/
|
||||
class WC_Admin_Menus {
|
||||
|
||||
/**
|
||||
* The CSS classes used to hide the submenu items in navigation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HIDE_CSS_CLASS = 'hide-if-js';
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Add menus.
|
||||
add_action( 'admin_menu', array( $this, 'menu_highlight' ) );
|
||||
add_action( 'admin_menu', array( $this, 'menu_order_count' ) );
|
||||
add_action( 'admin_menu', array( $this, 'maybe_add_new_product_management_experience' ) );
|
||||
add_action( 'admin_menu', array( $this, 'admin_menu' ), 9 );
|
||||
add_action( 'admin_menu', array( $this, 'orders_menu' ), 9 );
|
||||
add_action( 'admin_menu', array( $this, 'reports_menu' ), 20 );
|
||||
add_action( 'admin_menu', array( $this, 'settings_menu' ), 50 );
|
||||
add_action( 'admin_menu', array( $this, 'status_menu' ), 60 );
|
||||
|
||||
/**
|
||||
* Controls whether we add a submenu item for the WooCommerce Addons page.
|
||||
* Woo Express uses this filter.
|
||||
*
|
||||
* @since 8.2.1
|
||||
*
|
||||
* @param bool $show_addons_page If the addons page should be included.
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_show_addons_page', true ) ) {
|
||||
if ( FeaturesUtil::feature_is_enabled( 'marketplace' ) ) {
|
||||
$container = wc_get_container();
|
||||
$container->get( Marketplace::class );
|
||||
|
||||
add_action( 'admin_menu', array( $this, 'addons_my_subscriptions' ), 70 );
|
||||
} else {
|
||||
add_action( 'admin_menu', array( $this, 'addons_menu' ), 70 );
|
||||
}
|
||||
}
|
||||
|
||||
add_filter( 'menu_order', array( $this, 'menu_order' ) );
|
||||
add_filter( 'custom_menu_order', array( $this, 'custom_menu_order' ) );
|
||||
add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 10, 3 );
|
||||
|
||||
// Add endpoints custom URLs in Appearance > Menus > Pages.
|
||||
add_action( 'admin_head-nav-menus.php', array( $this, 'add_nav_menu_meta_boxes' ) );
|
||||
|
||||
// Admin bar menus.
|
||||
if ( apply_filters( 'woocommerce_show_admin_bar_visit_store', true ) ) {
|
||||
add_action( 'admin_bar_menu', array( $this, 'admin_bar_menus' ), 31 );
|
||||
}
|
||||
|
||||
// Handle saving settings earlier than load-{page} hook to avoid race conditions in conditional menus.
|
||||
add_action( 'wp_loaded', array( $this, 'save_settings' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu items.
|
||||
*/
|
||||
public function admin_menu() {
|
||||
global $menu, $admin_page_hooks;
|
||||
|
||||
$woocommerce_icon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGZpbGw9IiNhMmFhYjIiIGQ9Ik02MTIuMTkyIDQyNi4zMzZjMC02Ljg5Ni0zLjEzNi01MS42LTI4LTUxLjYtMzcuMzYgMC00Ni43MDQgNzIuMjU2LTQ2LjcwNCA4Mi42MjQgMCAzLjQwOCAzLjE1MiA1OC40OTYgMjguMDMyIDU4LjQ5NiAzNC4xOTItLjAzMiA0Ni42NzItNzIuMjg4IDQ2LjY3Mi04OS41MnptMjAyLjE5MiAwYzAtNi44OTYtMy4xNTItNTEuNi0yOC4wMzItNTEuNi0zNy4yOCAwLTQ2LjYwOCA3Mi4yNTYtNDYuNjA4IDgyLjYyNCAwIDMuNDA4IDMuMDcyIDU4LjQ5NiAyNy45NTIgNTguNDk2IDM0LjE5Mi0uMDMyIDQ2LjY4OC03Mi4yODggNDYuNjg4LTg5LjUyek0xNDEuMjk2Ljc2OGMtNjguMjI0IDAtMTIzLjUwNCA1NS40ODgtMTIzLjUwNCAxMjMuOTJ2NjUwLjcyYzAgNjguNDMyIDU1LjI5NiAxMjMuOTIgMTIzLjUwNCAxMjMuOTJoMzM5LjgwOGwxMjMuNTA0IDEyMy45MzZWODk5LjMyOGgyNzguMDQ4YzY4LjIyNCAwIDEyMy41Mi01NS40NzIgMTIzLjUyLTEyMy45MnYtNjUwLjcyYzAtNjguNDMyLTU1LjI5Ni0xMjMuOTItMTIzLjUyLTEyMy45MmgtNzQxLjM2em01MjYuODY0IDQyMi4xNmMwIDU1LjA4OC0zMS4wODggMTU0Ljg4LTEwMi42NCAxNTQuODgtNi4yMDggMC0xOC40OTYtMy42MTYtMjUuNDI0LTYuMDE2LTMyLjUxMi0xMS4xNjgtNTAuMTkyLTQ5LjY5Ni01Mi4zNTItNjYuMjU2IDAgMC0zLjA3Mi0xNy43OTItMy4wNzItNDAuNzUyIDAtMjIuOTkyIDMuMDcyLTQ1LjMyOCAzLjA3Mi00NS4zMjggMTUuNTUyLTc1LjcyOCA0My41NTItMTA2LjczNiA5Ni40NDgtMTA2LjczNiA1OS4wNzItLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4ek00ODYuNDk2IDMwMi40YzAgMy4zOTItNDMuNTUyIDE0MS4xNjgtNDMuNTUyIDIxMy40MjR2NzUuNzEyYy0yLjU5MiAxMi4wOC00LjE2IDI0LjE0NC0yMS44MjQgMjQuMTQ0LTQ2LjYwOCAwLTg4Ljg4LTE1MS40NzItOTIuMDE2LTE2MS44NC02LjIwOCA2Ljg5Ni02Mi4yNCAxNjEuODQtOTYuNDQ4IDE2MS44NC0yNC44NjQgMC00My41NTItMTEzLjY0OC00Ni42MDgtMTIzLjkzNkMxNzYuNzA0IDQzNi42NzIgMTYwIDMzNC4yMjQgMTYwIDMyNy4zMjhjMC0yMC42NzIgMS4xNTItMzguNzM2IDI2LjA0OC0zOC43MzYgNi4yMDggMCAyMS42IDYuMDY0IDIzLjcxMiAxNy4xNjggMTEuNjQ4IDYyLjAzMiAxNi42ODggMTIwLjUxMiAyOS4xNjggMTg1Ljk2OCAxLjg1NiAyLjkyOCAxLjUwNCA3LjAwOCA0LjU2IDEwLjQzMiAzLjE1Mi0xMC4yODggNjYuOTI4LTE2OC43ODQgOTQuOTYtMTY4Ljc4NCAyMi41NDQgMCAzMC40IDQ0LjU5MiAzMy41MzYgNjEuODI0IDYuMjA4IDIwLjY1NiAxMy4wODggNTUuMjE2IDIyLjQxNiA4Mi43NTIgMC0xMy43NzYgMTIuNDgtMjAzLjEyIDY1LjM5Mi0yMDMuMTIgMTguNTkyLjAzMiAyNi43MDQgNi45MjggMjYuNzA0IDI3LjU2OHpNODcwLjMyIDQyMi45MjhjMCA1NS4wODgtMzEuMDg4IDE1NC44OC0xMDIuNjQgMTU0Ljg4LTYuMTkyIDAtMTguNDQ4LTMuNjE2LTI1LjQyNC02LjAxNi0zMi40MzItMTEuMTY4LTUwLjE3Ni00OS42OTYtNTIuMjg4LTY2LjI1NiAwIDAtMy44ODgtMTcuOTItMy44ODgtNDAuODk2czMuODg4LTQ1LjE4NCAzLjg4OC00NS4xODRjMTUuNTUyLTc1LjcyOCA0My40ODgtMTA2LjczNiA5Ni4zODQtMTA2LjczNiA1OS4xMDQtLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4eiIvPjwvc3ZnPg==';
|
||||
|
||||
if ( self::can_view_woocommerce_menu_item() ) {
|
||||
$menu[] = array( '', 'read', 'separator-woocommerce', '', 'wp-menu-separator woocommerce' ); // WPCS: override ok.
|
||||
}
|
||||
|
||||
add_menu_page( __( 'WooCommerce', 'woocommerce' ), __( 'WooCommerce', 'woocommerce' ), 'edit_others_shop_orders', 'woocommerce', null, $woocommerce_icon, '55.5' );
|
||||
|
||||
// Work around https://github.com/woocommerce/woocommerce/issues/35677 (and related https://core.trac.wordpress.org/ticket/18857).
|
||||
// Translating the menu item breaks screen IDs and page hooks, so we force the hookname to be untranslated.
|
||||
$admin_page_hooks['woocommerce'] = 'woocommerce'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
|
||||
add_submenu_page( 'edit.php?post_type=product', __( 'Attributes', 'woocommerce' ), __( 'Attributes', 'woocommerce' ), 'manage_product_terms', 'product_attributes', array( $this, 'attributes_page' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu item.
|
||||
*/
|
||||
public function reports_menu() {
|
||||
if ( self::can_view_woocommerce_menu_item() ) {
|
||||
add_submenu_page( 'woocommerce', __( 'Reports', 'woocommerce' ), __( 'Reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ) );
|
||||
} else {
|
||||
add_menu_page( __( 'Sales reports', 'woocommerce' ), __( 'Sales reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ), 'dashicons-chart-bar', '55.6' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu item.
|
||||
*/
|
||||
public function settings_menu() {
|
||||
$settings_page = add_submenu_page( 'woocommerce', __( 'WooCommerce settings', 'woocommerce' ), __( 'Settings', 'woocommerce' ), 'manage_woocommerce', 'wc-settings', array( $this, 'settings_page' ) );
|
||||
|
||||
add_action( 'load-' . $settings_page, array( $this, 'settings_page_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can access the top-level WooCommerce item.
|
||||
*/
|
||||
public static function can_view_woocommerce_menu_item() {
|
||||
return current_user_can( 'edit_others_shop_orders' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads gateways and shipping methods into memory for use within settings.
|
||||
*/
|
||||
public function settings_page_init() {
|
||||
WC()->payment_gateways();
|
||||
WC()->shipping();
|
||||
|
||||
// Include settings pages.
|
||||
WC_Admin_Settings::get_settings_pages();
|
||||
|
||||
// Add any posted messages.
|
||||
if ( ! empty( $_GET['wc_error'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
WC_Admin_Settings::add_error( wp_kses_post( wp_unslash( $_GET['wc_error'] ) ) ); // WPCS: input var okay, CSRF ok.
|
||||
}
|
||||
|
||||
if ( ! empty( $_GET['wc_message'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
WC_Admin_Settings::add_message( wp_kses_post( wp_unslash( $_GET['wc_message'] ) ) ); // WPCS: input var okay, CSRF ok.
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_settings_page_init' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle saving of settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_settings() {
|
||||
global $current_tab, $current_section;
|
||||
|
||||
// We should only save on the settings page.
|
||||
if ( ! is_admin() || ! isset( $_GET['page'] ) || 'wc-settings' !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
// Include settings pages.
|
||||
WC_Admin_Settings::get_settings_pages();
|
||||
|
||||
// Get current tab/section.
|
||||
$current_tab = empty( $_GET['tab'] ) ? 'general' : sanitize_title( wp_unslash( $_GET['tab'] ) ); // WPCS: input var okay, CSRF ok.
|
||||
$current_section = empty( $_REQUEST['section'] ) ? '' : sanitize_title( wp_unslash( $_REQUEST['section'] ) ); // WPCS: input var okay, CSRF ok.
|
||||
|
||||
// Save settings if data has been posted.
|
||||
if ( '' !== $current_section && apply_filters( "woocommerce_save_settings_{$current_tab}_{$current_section}", ! empty( $_POST['save'] ) ) ) { // WPCS: input var okay, CSRF ok.
|
||||
WC_Admin_Settings::save();
|
||||
} elseif ( '' === $current_section && apply_filters( "woocommerce_save_settings_{$current_tab}", ! empty( $_POST['save'] ) ) ) { // WPCS: input var okay, CSRF ok.
|
||||
WC_Admin_Settings::save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu item.
|
||||
*/
|
||||
public function status_menu() {
|
||||
$status_page = add_submenu_page( 'woocommerce', __( 'WooCommerce status', 'woocommerce' ), __( 'Status', 'woocommerce' ), 'manage_woocommerce', 'wc-status', array( $this, 'status_page' ) );
|
||||
|
||||
add_action(
|
||||
'load-' . $status_page,
|
||||
function() {
|
||||
if ( 'logs' === filter_input( INPUT_GET, 'tab' ) ) {
|
||||
// Initialize the logging page controller early so that it can hook into things.
|
||||
wc_get_container()->get( LoggingPageController::class );
|
||||
}
|
||||
},
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Addons menu item.
|
||||
*/
|
||||
public function addons_menu() {
|
||||
$count_html = WC_Helper_Updater::get_updates_count_html();
|
||||
/* translators: %s: extensions count */
|
||||
$menu_title = sprintf( __( 'Extensions %s', 'woocommerce' ), $count_html );
|
||||
add_submenu_page( 'woocommerce', __( 'WooCommerce extensions', 'woocommerce' ), $menu_title, 'manage_woocommerce', 'wc-addons', array( $this, 'addons_page' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the wc-addons page within the WooCommerce menu.
|
||||
* Temporary measure till we convert the whole page to React.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addons_my_subscriptions() {
|
||||
add_submenu_page( 'woocommerce', __( 'WooCommerce extensions', 'woocommerce' ), null, 'manage_woocommerce', 'wc-addons', array( $this, 'addons_page' ) );
|
||||
// Temporarily hide the submenu item we've just added.
|
||||
$this->hide_submenu_page( 'woocommerce', 'wc-addons' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights the correct top level admin menu item for post type add screens.
|
||||
*/
|
||||
public function menu_highlight() {
|
||||
global $parent_file, $submenu_file, $post_type;
|
||||
|
||||
switch ( $post_type ) {
|
||||
case 'shop_order':
|
||||
case 'shop_coupon':
|
||||
$parent_file = 'woocommerce'; // WPCS: override ok.
|
||||
break;
|
||||
case 'product':
|
||||
$screen = get_current_screen();
|
||||
if ( $screen && taxonomy_is_product_attribute( $screen->taxonomy ) ) {
|
||||
$submenu_file = 'product_attributes'; // WPCS: override ok.
|
||||
$parent_file = 'edit.php?post_type=product'; // WPCS: override ok.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the order processing count to the menu.
|
||||
*/
|
||||
public function menu_order_count() {
|
||||
global $submenu;
|
||||
|
||||
if ( isset( $submenu['woocommerce'] ) ) {
|
||||
// Remove 'WooCommerce' sub menu item.
|
||||
unset( $submenu['woocommerce'][0] );
|
||||
|
||||
// Add count if user has access.
|
||||
if ( apply_filters( 'woocommerce_include_processing_order_count_in_menu', true ) && current_user_can( 'edit_others_shop_orders' ) ) {
|
||||
$order_count = apply_filters( 'woocommerce_menu_order_count', wc_processing_order_count() );
|
||||
|
||||
if ( $order_count ) {
|
||||
foreach ( $submenu['woocommerce'] as $key => $menu_item ) {
|
||||
if ( 0 === strpos( $menu_item[0], _x( 'Orders', 'Admin menu name', 'woocommerce' ) ) ) {
|
||||
$submenu['woocommerce'][ $key ][0] .= ' <span class="awaiting-mod update-plugins count-' . esc_attr( $order_count ) . '"><span class="processing-count">' . number_format_i18n( $order_count ) . '</span></span>'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder the WC menu items in admin.
|
||||
*
|
||||
* @param int $menu_order Menu order.
|
||||
* @return array
|
||||
*/
|
||||
public function menu_order( $menu_order ) {
|
||||
// Initialize our custom order array.
|
||||
$woocommerce_menu_order = array();
|
||||
|
||||
// Get the index of our custom separator.
|
||||
$woocommerce_separator = array_search( 'separator-woocommerce', $menu_order, true );
|
||||
|
||||
// Get index of product menu.
|
||||
$woocommerce_product = array_search( 'edit.php?post_type=product', $menu_order, true );
|
||||
|
||||
// Loop through menu order and do some rearranging.
|
||||
foreach ( $menu_order as $index => $item ) {
|
||||
|
||||
if ( 'woocommerce' === $item ) {
|
||||
$woocommerce_menu_order[] = 'separator-woocommerce';
|
||||
$woocommerce_menu_order[] = $item;
|
||||
$woocommerce_menu_order[] = 'edit.php?post_type=product';
|
||||
unset( $menu_order[ $woocommerce_separator ] );
|
||||
unset( $menu_order[ $woocommerce_product ] );
|
||||
} elseif ( ! in_array( $item, array( 'separator-woocommerce' ), true ) ) {
|
||||
$woocommerce_menu_order[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Return order.
|
||||
return $woocommerce_menu_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom menu order.
|
||||
*
|
||||
* @param bool $enabled Whether custom menu ordering is already enabled.
|
||||
* @return bool
|
||||
*/
|
||||
public function custom_menu_order( $enabled ) {
|
||||
return $enabled || self::can_view_woocommerce_menu_item();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate screen options on update.
|
||||
*
|
||||
* @param bool|int $status Screen option value. Default false to skip.
|
||||
* @param string $option The option name.
|
||||
* @param int $value The number of rows to use.
|
||||
*/
|
||||
public function set_screen_option( $status, $option, $value ) {
|
||||
$screen_options = array(
|
||||
'woocommerce_keys_per_page',
|
||||
'woocommerce_webhooks_per_page',
|
||||
FileListTable::PER_PAGE_USER_OPTION_KEY,
|
||||
SearchListTable::PER_PAGE_USER_OPTION_KEY,
|
||||
WC_Admin_Log_Table_List::PER_PAGE_USER_OPTION_KEY,
|
||||
);
|
||||
|
||||
if ( in_array( $option, $screen_options, true ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the reports page.
|
||||
*/
|
||||
public function reports_page() {
|
||||
WC_Admin_Reports::output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the settings page.
|
||||
*/
|
||||
public function settings_page() {
|
||||
WC_Admin_Settings::output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the attributes page.
|
||||
*/
|
||||
public function attributes_page() {
|
||||
WC_Admin_Attributes::output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the status page.
|
||||
*/
|
||||
public function status_page() {
|
||||
WC_Admin_Status::output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the addons page.
|
||||
*/
|
||||
public function addons_page() {
|
||||
WC_Admin_Addons::output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Link to the order admin list table from the main WooCommerce menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function orders_menu(): void {
|
||||
if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
||||
wc_get_container()->get( Custom_Orders_PageController::class )->setup();
|
||||
} else {
|
||||
wc_get_container()->get( COTRedirectionController::class )->setup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom nav meta box.
|
||||
*
|
||||
* Adapted from http://www.johnmorrisonline.com/how-to-add-a-fully-functional-custom-meta-box-to-wordpress-navigation-menus/.
|
||||
*/
|
||||
public function add_nav_menu_meta_boxes() {
|
||||
add_meta_box( 'woocommerce_endpoints_nav_link', __( 'WooCommerce endpoints', 'woocommerce' ), array( $this, 'nav_menu_links' ), 'nav-menus', 'side', 'low' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output menu links.
|
||||
*/
|
||||
public function nav_menu_links() {
|
||||
// Get items from account menu.
|
||||
$endpoints = wc_get_account_menu_items();
|
||||
|
||||
// Remove dashboard item.
|
||||
if ( isset( $endpoints['dashboard'] ) ) {
|
||||
unset( $endpoints['dashboard'] );
|
||||
}
|
||||
|
||||
// Include missing lost password.
|
||||
$endpoints['lost-password'] = __( 'Lost password', 'woocommerce' );
|
||||
|
||||
$endpoints = apply_filters( 'woocommerce_custom_nav_menu_items', $endpoints );
|
||||
|
||||
?>
|
||||
<div id="posttype-woocommerce-endpoints" class="posttypediv">
|
||||
<div id="tabs-panel-woocommerce-endpoints" class="tabs-panel tabs-panel-active">
|
||||
<ul id="woocommerce-endpoints-checklist" class="categorychecklist form-no-clear">
|
||||
<?php
|
||||
$i = -1;
|
||||
foreach ( $endpoints as $key => $value ) :
|
||||
?>
|
||||
<li>
|
||||
<label class="menu-item-title">
|
||||
<input type="checkbox" class="menu-item-checkbox" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-object-id]" value="<?php echo esc_attr( $i ); ?>" /> <?php echo esc_html( $value ); ?>
|
||||
</label>
|
||||
<input type="hidden" class="menu-item-type" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-type]" value="custom" />
|
||||
<input type="hidden" class="menu-item-title" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-title]" value="<?php echo esc_attr( $value ); ?>" />
|
||||
<input type="hidden" class="menu-item-url" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-url]" value="<?php echo esc_url( wc_get_account_endpoint_url( $key ) ); ?>" />
|
||||
<input type="hidden" class="menu-item-classes" name="menu-item[<?php echo esc_attr( $i ); ?>][menu-item-classes]" />
|
||||
</li>
|
||||
<?php
|
||||
$i--;
|
||||
endforeach;
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="button-controls" data-items-type="posttype-woocommerce-endpoints">
|
||||
<span class="list-controls">
|
||||
<label>
|
||||
<input type="checkbox" class="select-all" />
|
||||
<?php esc_html_e( 'Select all', 'woocommerce' ); ?>
|
||||
</label>
|
||||
</span>
|
||||
<span class="add-to-menu">
|
||||
<button type="submit" class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to menu', 'woocommerce' ); ?>" name="add-post-type-menu-item" id="submit-posttype-woocommerce-endpoints"><?php esc_html_e( 'Add to menu', 'woocommerce' ); ?></button>
|
||||
<span class="spinner"></span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the "Visit Store" link in admin bar main menu.
|
||||
*
|
||||
* @since 2.4.0
|
||||
* @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
|
||||
*/
|
||||
public function admin_bar_menus( $wp_admin_bar ) {
|
||||
if ( ! is_admin() || ! is_admin_bar_showing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show only when the user is a member of this site, or they're a super admin.
|
||||
if ( ! is_user_member_of_blog() && ! is_super_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't display when shop page is the same of the page on front.
|
||||
if ( intval( get_option( 'page_on_front' ) ) === wc_get_page_id( 'shop' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add an option to visit the store.
|
||||
$wp_admin_bar->add_node(
|
||||
array(
|
||||
'parent' => 'site-name',
|
||||
'id' => 'view-store',
|
||||
'title' => __( 'Visit Store', 'woocommerce' ),
|
||||
'href' => wc_get_page_permalink( 'shop' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe add new management product experience.
|
||||
*/
|
||||
public function maybe_add_new_product_management_experience() {
|
||||
if ( Features::is_enabled( 'new-product-management-experience' ) || FeaturesUtil::feature_is_enabled( 'product_block_editor' ) ) {
|
||||
global $submenu;
|
||||
if ( isset( $submenu['edit.php?post_type=product'][10] ) ) {
|
||||
// Disable phpcs since we need to override submenu classes.
|
||||
// Note that `phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited` does not work to disable this check.
|
||||
// phpcs:disable
|
||||
$submenu['edit.php?post_type=product'][10][2] = 'admin.php?page=wc-admin&path=/add-product';
|
||||
// phps:enableWordPress.Variables.GlobalVariables.OverrideProhibited
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the submenu page based on slug and return the item that was hidden.
|
||||
*
|
||||
* Borrowed from Jetpack's Base_Admin_Menu class.
|
||||
*
|
||||
* Instead of actually removing the submenu item, a safer approach is to hide it and filter it in the API response.
|
||||
* In this manner we'll avoid breaking third-party plugins depending on items that no longer exist.
|
||||
*
|
||||
* A false|array value is returned to be consistent with remove_submenu_page() function
|
||||
*
|
||||
* @param string $menu_slug The parent menu slug.
|
||||
* @param string $submenu_slug The submenu slug that should be hidden.
|
||||
* @return false|array
|
||||
*/
|
||||
public function hide_submenu_page( $menu_slug, $submenu_slug ) {
|
||||
global $submenu;
|
||||
|
||||
if ( ! isset( $submenu[ $menu_slug ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $submenu[ $menu_slug ] as $i => $item ) {
|
||||
if ( $submenu_slug !== $item[2] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->hide_submenu_element( $i, $menu_slug, $item );
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the hide-if-js CSS class to a submenu item.
|
||||
*
|
||||
* Borrowed from Jetpack's Base_Admin_Menu class.
|
||||
*
|
||||
* @param int $index The position of a submenu item in the submenu array.
|
||||
* @param string $parent_slug The parent slug.
|
||||
* @param array $item The submenu item.
|
||||
*/
|
||||
public function hide_submenu_element( $index, $parent_slug, $item ) {
|
||||
global $submenu;
|
||||
|
||||
$css_classes = empty( $item[4] ) ? self::HIDE_CSS_CLASS : $item[4] . ' ' . self::HIDE_CSS_CLASS;
|
||||
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$submenu [ $parent_slug ][ $index ][4] = $css_classes;
|
||||
}
|
||||
}
|
||||
|
||||
return new WC_Admin_Menus();
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Meta Boxes
|
||||
*
|
||||
* Sets up the write panels used by products and orders (custom post types).
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\Edit as OrderEdit;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Meta_Boxes.
|
||||
*/
|
||||
class WC_Admin_Meta_Boxes {
|
||||
/**
|
||||
* Name of the option used to store errors to be displayed at the next suitable opportunity.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public const ERROR_STORE = 'woocommerce_meta_box_errors';
|
||||
|
||||
/**
|
||||
* Is meta boxes saved once?
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $saved_meta_boxes = false;
|
||||
|
||||
/**
|
||||
* Meta box error messages.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $meta_box_errors = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'add_meta_boxes', array( $this, 'remove_meta_boxes' ), 10 );
|
||||
add_action( 'add_meta_boxes', array( $this, 'rename_meta_boxes' ), 20 );
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 30 );
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_product_boxes_sort_order' ), 40 );
|
||||
add_action( 'save_post', array( $this, 'save_meta_boxes' ), 1, 2 );
|
||||
|
||||
OrderEdit::add_save_meta_boxes();
|
||||
|
||||
// Save Product Meta Boxes.
|
||||
add_action( 'woocommerce_process_product_meta', 'WC_Meta_Box_Product_Data::save', 10, 2 );
|
||||
add_action( 'woocommerce_process_product_meta', 'WC_Meta_Box_Product_Images::save', 20, 2 );
|
||||
|
||||
// Save Coupon Meta Boxes.
|
||||
add_action( 'woocommerce_process_shop_coupon_meta', 'WC_Meta_Box_Coupon_Data::save', 10, 2 );
|
||||
|
||||
// Save Rating Meta Boxes.
|
||||
add_filter( 'wp_update_comment_data', 'WC_Meta_Box_Product_Reviews::save', 1 );
|
||||
|
||||
// Error handling (for showing errors from meta boxes on next page load).
|
||||
add_action( 'admin_notices', array( $this, 'output_errors' ) );
|
||||
add_action( 'shutdown', array( $this, 'append_to_error_store' ) );
|
||||
|
||||
add_filter( 'theme_product_templates', array( $this, 'remove_block_templates' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error message.
|
||||
*
|
||||
* @param string $text Error to add.
|
||||
*/
|
||||
public static function add_error( $text ) {
|
||||
self::$meta_box_errors[] = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save errors to an option.
|
||||
*
|
||||
* Note that calling this will overwrite any errors that have already been stored via the Options API.
|
||||
* Unless you are sure you want this, consider using the append_to_error_store() method instead.
|
||||
*/
|
||||
public function save_errors() {
|
||||
update_option( self::ERROR_STORE, self::$meta_box_errors );
|
||||
}
|
||||
|
||||
/**
|
||||
* If additional errors have been added in the current request (ie, via the add_error() method) then they
|
||||
* will be added to the persistent error store via the Options API.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public function append_to_error_store() {
|
||||
if ( empty( self::$meta_box_errors ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing_errors = get_option( self::ERROR_STORE, array() );
|
||||
update_option( self::ERROR_STORE, array_unique( array_merge( $existing_errors, self::$meta_box_errors ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show any stored error messages.
|
||||
*/
|
||||
public function output_errors() {
|
||||
$errors = array_filter( (array) get_option( self::ERROR_STORE ) );
|
||||
|
||||
if ( ! empty( $errors ) ) {
|
||||
|
||||
echo '<div id="woocommerce_errors" class="error notice is-dismissible">';
|
||||
|
||||
foreach ( $errors as $error ) {
|
||||
echo '<p>' . wp_kses_post( $error ) . '</p>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
// Clear.
|
||||
delete_option( self::ERROR_STORE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WC Meta boxes.
|
||||
*/
|
||||
public function add_meta_boxes() {
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
|
||||
// Products.
|
||||
add_meta_box( 'postexcerpt', __( 'Product short description', 'woocommerce' ), 'WC_Meta_Box_Product_Short_Description::output', 'product', 'normal' );
|
||||
add_meta_box( 'woocommerce-product-data', __( 'Product data', 'woocommerce' ), 'WC_Meta_Box_Product_Data::output', 'product', 'normal', 'high' );
|
||||
add_meta_box( 'woocommerce-product-images', __( 'Product gallery', 'woocommerce' ), 'WC_Meta_Box_Product_Images::output', 'product', 'side', 'low' );
|
||||
|
||||
// Orders.
|
||||
foreach ( wc_get_order_types( 'order-meta-boxes' ) as $type ) {
|
||||
$order_type_object = get_post_type_object( $type );
|
||||
OrderEdit::add_order_meta_boxes( $type, $order_type_object->labels->singular_name );
|
||||
}
|
||||
|
||||
// Coupons.
|
||||
add_meta_box( 'woocommerce-coupon-data', __( 'Coupon data', 'woocommerce' ), 'WC_Meta_Box_Coupon_Data::output', 'shop_coupon', 'normal', 'high' );
|
||||
|
||||
// Comment rating.
|
||||
if ( 'comment' === $screen_id && isset( $_GET['c'] ) && metadata_exists( 'comment', wc_clean( wp_unslash( $_GET['c'] ) ), 'rating' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
add_meta_box( 'woocommerce-rating', __( 'Rating', 'woocommerce' ), 'WC_Meta_Box_Product_Reviews::output', 'comment', 'normal', 'high' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default sort order for meta boxes on product page.
|
||||
*/
|
||||
public function add_product_boxes_sort_order() {
|
||||
$current_value = get_user_meta( get_current_user_id(), 'meta-box-order_product', true );
|
||||
|
||||
if ( $current_value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_user_meta(
|
||||
get_current_user_id(),
|
||||
'meta-box-order_product',
|
||||
array(
|
||||
'side' => 'submitdiv,postimagediv,woocommerce-product-images,product_catdiv,tagsdiv-product_tag',
|
||||
'normal' => 'woocommerce-product-data,postcustom,slugdiv,postexcerpt',
|
||||
'advanced' => '',
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove bloat.
|
||||
*/
|
||||
public function remove_meta_boxes() {
|
||||
remove_meta_box( 'postexcerpt', 'product', 'normal' );
|
||||
remove_meta_box( 'product_shipping_classdiv', 'product', 'side' );
|
||||
remove_meta_box( 'commentsdiv', 'product', 'normal' );
|
||||
remove_meta_box( 'commentstatusdiv', 'product', 'side' );
|
||||
remove_meta_box( 'commentstatusdiv', 'product', 'normal' );
|
||||
remove_meta_box( 'woothemes-settings', 'shop_coupon', 'normal' );
|
||||
remove_meta_box( 'commentstatusdiv', 'shop_coupon', 'normal' );
|
||||
remove_meta_box( 'slugdiv', 'shop_coupon', 'normal' );
|
||||
|
||||
foreach ( wc_get_order_types( 'order-meta-boxes' ) as $type ) {
|
||||
remove_meta_box( 'commentsdiv', $type, 'normal' );
|
||||
remove_meta_box( 'woothemes-settings', $type, 'normal' );
|
||||
remove_meta_box( 'commentstatusdiv', $type, 'normal' );
|
||||
remove_meta_box( 'slugdiv', $type, 'normal' );
|
||||
remove_meta_box( 'submitdiv', $type, 'side' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename core meta boxes.
|
||||
*/
|
||||
public function rename_meta_boxes() {
|
||||
global $post;
|
||||
|
||||
// Comments/Reviews.
|
||||
if ( isset( $post ) && ( 'publish' === $post->post_status || 'private' === $post->post_status ) && post_type_supports( 'product', 'comments' ) ) {
|
||||
remove_meta_box( 'commentsdiv', 'product', 'normal' );
|
||||
add_meta_box( 'commentsdiv', __( 'Reviews', 'woocommerce' ), 'post_comment_meta_box', 'product', 'normal' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're saving, the trigger an action based on the post type.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param object $post Post object.
|
||||
*/
|
||||
public function save_meta_boxes( $post_id, $post ) {
|
||||
$post_id = absint( $post_id );
|
||||
|
||||
// $post_id and $post are required
|
||||
if ( empty( $post_id ) || empty( $post ) || ! is_a( $post, 'WP_Post' ) || self::$saved_meta_boxes ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dont' save meta boxes for revisions or autosaves.
|
||||
if ( Constants::is_true( 'DOING_AUTOSAVE' ) || is_int( wp_is_post_revision( $post ) ) || is_int( wp_is_post_autosave( $post ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the nonce.
|
||||
if ( empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['woocommerce_meta_nonce'] ), 'woocommerce_save_data' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the post being saved == the $post_id to prevent triggering this call for other save_post events.
|
||||
if ( empty( $_POST['post_ID'] ) || absint( $_POST['post_ID'] ) !== $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check user has permission to edit.
|
||||
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need this save event to run once to avoid potential endless loops. This would have been perfect:
|
||||
// remove_action( current_filter(), __METHOD__ );
|
||||
// But cannot be used due to https://github.com/woocommerce/woocommerce/issues/6485
|
||||
// When that is patched in core we can use the above.
|
||||
self::$saved_meta_boxes = true;
|
||||
|
||||
// Check the post type.
|
||||
if ( in_array( $post->post_type, wc_get_order_types( 'order-meta-boxes' ), true ) ) {
|
||||
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta for shop order.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param object $post Post object.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'woocommerce_process_shop_order_meta', $post_id, $post );
|
||||
} elseif ( in_array( $post->post_type, array( 'product', 'shop_coupon' ), true ) ) {
|
||||
/**
|
||||
* Save meta for product.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param object $post Post object.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'woocommerce_process_' . $post->post_type . '_meta', $post_id, $post );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove irrelevant block templates from the list of available templates for products.
|
||||
* This will also remove custom created templates.
|
||||
*
|
||||
* @param string[] $templates Array of template header names keyed by the template file name.
|
||||
*
|
||||
* @return string[] Templates array excluding block-based templates.
|
||||
*/
|
||||
public function remove_block_templates( $templates ) {
|
||||
if ( count( $templates ) === 0 || ! wc_current_theme_is_fse_theme() ) {
|
||||
return $templates;
|
||||
}
|
||||
|
||||
$theme = wp_get_theme()->get_stylesheet();
|
||||
$filtered_templates = array();
|
||||
|
||||
foreach ( $templates as $template_key => $template_name ) {
|
||||
// Filter out the single-product.html template as this is a duplicate of "Default Template".
|
||||
if ( 'single-product' === $template_key ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$block_template = get_block_template( $theme . '//' . $template_key );
|
||||
|
||||
// If the block template has the product post type specified, include it.
|
||||
if ( $block_template && is_array( $block_template->post_types ) && in_array( 'product', $block_template->post_types ) ) {
|
||||
$filtered_templates[ $template_key ] = $template_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered_templates;
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Meta_Boxes();
|
||||
@@ -0,0 +1,779 @@
|
||||
<?php
|
||||
/**
|
||||
* Display notices in admin
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.4.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Internal\Utilities\Users;
|
||||
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Notices Class.
|
||||
*/
|
||||
class WC_Admin_Notices {
|
||||
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* Local notices cache.
|
||||
*
|
||||
* DON'T manipulate this field directly!
|
||||
* Always use get_notices and set_notices instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $notices = array();
|
||||
|
||||
/**
|
||||
* Array of notices - name => callback.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $core_notices = array(
|
||||
'update' => 'update_notice',
|
||||
'template_files' => 'template_file_check_notice',
|
||||
'legacy_shipping' => 'legacy_shipping_notice',
|
||||
'no_shipping_methods' => 'no_shipping_methods_notice',
|
||||
'regenerating_thumbnails' => 'regenerating_thumbnails_notice',
|
||||
'regenerating_lookup_table' => 'regenerating_lookup_table_notice',
|
||||
'no_secure_connection' => 'secure_connection_notice',
|
||||
'maxmind_license_key' => 'maxmind_missing_license_key_notice',
|
||||
'redirect_download_method' => 'redirect_download_method_notice',
|
||||
'uploads_directory_is_unprotected' => 'uploads_directory_is_unprotected_notice',
|
||||
'base_tables_missing' => 'base_tables_missing_notice',
|
||||
'download_directories_sync_complete' => 'download_directories_sync_complete',
|
||||
);
|
||||
|
||||
/**
|
||||
* Stores a flag indicating if the code is running in a multisite setup.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static bool $is_multisite;
|
||||
|
||||
/**
|
||||
* Initializes the class.
|
||||
*/
|
||||
public static function init() {
|
||||
self::$is_multisite = is_multisite();
|
||||
self::set_notices( get_option( 'woocommerce_admin_notices', array() ) );
|
||||
|
||||
add_action( 'switch_theme', array( __CLASS__, 'reset_admin_notices' ) );
|
||||
add_action( 'woocommerce_installed', array( __CLASS__, 'reset_admin_notices' ) );
|
||||
add_action( 'wp_loaded', array( __CLASS__, 'add_redirect_download_method_notice' ) );
|
||||
add_action( 'admin_init', array( __CLASS__, 'hide_notices' ), 20 );
|
||||
self::add_action( 'admin_init', array( __CLASS__, 'maybe_remove_legacy_api_removal_notice' ), 20 );
|
||||
|
||||
// @TODO: This prevents Action Scheduler async jobs from storing empty list of notices during WC installation.
|
||||
// That could lead to OBW not starting and 'Run setup wizard' notice not appearing in WP admin, which we want
|
||||
// to avoid.
|
||||
if ( ! WC_Install::is_new_install() || ! wc_is_running_from_async_action_scheduler() ) {
|
||||
add_action( 'shutdown', array( __CLASS__, 'store_notices' ) );
|
||||
}
|
||||
|
||||
if ( current_user_can( 'manage_woocommerce' ) ) {
|
||||
add_action( 'admin_print_styles', array( __CLASS__, 'add_notices' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses query to create nonces when available.
|
||||
*
|
||||
* @deprecated 5.4.0
|
||||
* @param object $response The WP_REST_Response we're working with.
|
||||
* @return object $response The prepared WP_REST_Response object.
|
||||
*/
|
||||
public static function prepare_note_with_nonce( $response ) {
|
||||
wc_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '5.4.0' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the locally cached notices to DB.
|
||||
*/
|
||||
public static function store_notices() {
|
||||
update_option( 'woocommerce_admin_notices', self::get_notices() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the locally cached notices array for the current site.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_notices() {
|
||||
if ( ! self::$is_multisite ) {
|
||||
return self::$notices;
|
||||
}
|
||||
|
||||
$blog_id = get_current_blog_id();
|
||||
$notices = self::$notices[ $blog_id ] ?? null;
|
||||
if ( ! is_null( $notices ) ) {
|
||||
return $notices;
|
||||
}
|
||||
|
||||
self::$notices[ $blog_id ] = get_option( 'woocommerce_admin_notices', array() );
|
||||
return self::$notices[ $blog_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the locally cached notices array for the current site.
|
||||
*
|
||||
* @param array $notices New value for the locally cached notices array.
|
||||
*/
|
||||
private static function set_notices( array $notices ) {
|
||||
if ( self::$is_multisite ) {
|
||||
self::$notices[ get_current_blog_id() ] = $notices;
|
||||
} else {
|
||||
self::$notices = $notices;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all notices from the locally cached notices array.
|
||||
*/
|
||||
public static function remove_all_notices() {
|
||||
self::set_notices( array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset notices for themes when switched or a new version of WC is installed.
|
||||
*/
|
||||
public static function reset_admin_notices() {
|
||||
if ( ! self::is_ssl() ) {
|
||||
self::add_notice( 'no_secure_connection' );
|
||||
}
|
||||
if ( ! self::is_uploads_directory_protected() ) {
|
||||
self::add_notice( 'uploads_directory_is_unprotected' );
|
||||
}
|
||||
self::add_notice( 'template_files' );
|
||||
self::add_min_version_notice();
|
||||
self::add_maxmind_missing_license_key_notice();
|
||||
self::maybe_add_legacy_api_removal_notice();
|
||||
}
|
||||
|
||||
// phpcs:disable Generic.Commenting.Todo.TaskFound
|
||||
|
||||
/**
|
||||
* Add an admin notice about the removal of the Legacy REST API if the said API is enabled,
|
||||
* and a notice about soon to be unsupported webhooks with Legacy API payload if at least one of these exist.
|
||||
*
|
||||
* TODO: Change this method in WooCommerce 9.0 so that it checks if the Legacy REST API extension is installed, and if not, it points to the extension URL in the WordPress plugins directory.
|
||||
*/
|
||||
private static function maybe_add_legacy_api_removal_notice() {
|
||||
if ( is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_api_enabled' ) ) {
|
||||
self::add_custom_notice(
|
||||
'legacy_api_removed_in_woo_90',
|
||||
sprintf(
|
||||
'%s%s',
|
||||
sprintf(
|
||||
'<h4>%s</h4>',
|
||||
esc_html__( 'The WooCommerce Legacy REST API will be removed soon', 'woocommerce' )
|
||||
),
|
||||
sprintf(
|
||||
// translators: Placeholders are URLs.
|
||||
wpautop( __( 'The WooCommerce Legacy REST API, <a href="%1$s">currently enabled in this site</a>, will be removed in WooCommerce 9.0. <a target="_blank" href="%2$s">A separate WooCommerce extension is available</a> to keep it enabled. <b><a target="_blank" href="%3$s">Learn more about this change.</a></b>', 'woocommerce' ) ),
|
||||
admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=legacy_api' ),
|
||||
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/',
|
||||
'https://developer.woocommerce.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( wc_get_container()->get( WebhookUtil::class )->get_legacy_webhooks_count() > 0 ) {
|
||||
self::add_custom_notice(
|
||||
'legacy_webhooks_unsupported_in_woo_90',
|
||||
sprintf(
|
||||
'%s%s',
|
||||
sprintf(
|
||||
'<h4>%s</h4>',
|
||||
esc_html__( 'WooCommerce webhooks that use the Legacy REST API will be unsupported soon', 'woocommerce' )
|
||||
),
|
||||
sprintf(
|
||||
// translators: Placeholders are URLs.
|
||||
wpautop( __( 'The WooCommerce Legacy REST API will be removed in WooCommerce 9.0, and this will cause <a href="%1$s">webhooks on this site that are configured to use the Legacy REST API</a> to stop working. <a target="_blank" href="%2$s">A separate WooCommerce extension is available</a> to allow these webhooks to keep using the Legacy REST API without interruption. You can also edit these webhooks to use the current REST API version to generate the payload instead. <b><a target="_blank" href="%3$s">Learn more about this change.</a></b>', 'woocommerce' ) ),
|
||||
admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks&legacy=true' ),
|
||||
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/',
|
||||
'https://developer.woocommerce.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the admin notice about the removal of the Legacy REST API if the said API is disabled
|
||||
* or if the Legacy REST API extension is installed, and remove the notice about Legacy webhooks
|
||||
* if no such webhooks exist anymore or if the Legacy REST API extension is installed.
|
||||
*
|
||||
* TODO: Change this method in WooCommerce 9.0 so that the notice gets removed if the Legacy REST API extension is installed and active.
|
||||
*/
|
||||
private static function maybe_remove_legacy_api_removal_notice() {
|
||||
$plugin_is_active = is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' );
|
||||
|
||||
if ( self::has_notice( 'legacy_api_removed_in_woo_90' ) && ( $plugin_is_active || 'yes' !== get_option( 'woocommerce_api_enabled' ) ) ) {
|
||||
self::remove_notice( 'legacy_api_removed_in_woo_90' );
|
||||
}
|
||||
|
||||
if ( self::has_notice( 'legacy_webhooks_unsupported_in_woo_90' ) && ( $plugin_is_active || 0 === wc_get_container()->get( WebhookUtil::class )->get_legacy_webhooks_count() ) ) {
|
||||
self::remove_notice( 'legacy_webhooks_unsupported_in_woo_90' );
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:enable Generic.Commenting.Todo.TaskFound
|
||||
|
||||
/**
|
||||
* Show a notice.
|
||||
*
|
||||
* @param string $name Notice name.
|
||||
* @param bool $force_save Force saving inside this method instead of at the 'shutdown'.
|
||||
*/
|
||||
public static function add_notice( $name, $force_save = false ) {
|
||||
self::set_notices( array_unique( array_merge( self::get_notices(), array( $name ) ) ) );
|
||||
|
||||
if ( $force_save ) {
|
||||
// Adding early save to prevent more race conditions with notices.
|
||||
self::store_notices();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notice from being displayed.
|
||||
*
|
||||
* @param string $name Notice name.
|
||||
* @param bool $force_save Force saving inside this method instead of at the 'shutdown'.
|
||||
*/
|
||||
public static function remove_notice( $name, $force_save = false ) {
|
||||
self::set_notices( array_diff( self::get_notices(), array( $name ) ) );
|
||||
delete_option( 'woocommerce_admin_notice_' . $name );
|
||||
|
||||
if ( $force_save ) {
|
||||
// Adding early save to prevent more race conditions with notices.
|
||||
self::store_notices();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given set of notices.
|
||||
*
|
||||
* An array of notice names or a regular expression string can be passed, in the later case
|
||||
* all the notices whose name matches the regular expression will be removed.
|
||||
*
|
||||
* @param array|string $names_array_or_regex An array of notice names, or a string representing a regular expression.
|
||||
* @param bool $force_save Force saving inside this method instead of at the 'shutdown'.
|
||||
* @return void
|
||||
*/
|
||||
public static function remove_notices( $names_array_or_regex, $force_save = false ) {
|
||||
if ( ! is_array( $names_array_or_regex ) ) {
|
||||
$names_array_or_regex = array_filter( self::get_notices(), fn( $notice_name ) => 1 === preg_match( $names_array_or_regex, $notice_name ) );
|
||||
}
|
||||
self::set_notices( array_diff( self::get_notices(), $names_array_or_regex ) );
|
||||
|
||||
if ( $force_save ) {
|
||||
// Adding early save to prevent more race conditions with notices.
|
||||
self::store_notices();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See if a notice is being shown.
|
||||
*
|
||||
* @param string $name Notice name.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_notice( $name ) {
|
||||
return in_array( $name, self::get_notices(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a notice if the GET variable is set.
|
||||
*/
|
||||
public static function hide_notices() {
|
||||
if ( isset( $_GET['wc-hide-notice'] ) && isset( $_GET['_wc_notice_nonce'] ) ) {
|
||||
if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wc_notice_nonce'] ) ), 'woocommerce_hide_notices_nonce' ) ) {
|
||||
wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$notice_name = sanitize_text_field( wp_unslash( $_GET['wc-hide-notice'] ) );
|
||||
|
||||
/**
|
||||
* Filter the capability required to dismiss a given notice.
|
||||
*
|
||||
* @since 6.7.0
|
||||
*
|
||||
* @param string $default_capability The default required capability.
|
||||
* @param string $notice_name The notice name.
|
||||
*/
|
||||
$required_capability = apply_filters( 'woocommerce_dismiss_admin_notice_capability', 'manage_woocommerce', $notice_name );
|
||||
|
||||
if ( ! current_user_can( $required_capability ) ) {
|
||||
wp_die( esc_html__( 'You don’t have permission to do this.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
self::hide_notice( $notice_name );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a single notice.
|
||||
*
|
||||
* @param string $name Notice name.
|
||||
*/
|
||||
private static function hide_notice( $name ) {
|
||||
self::remove_notice( $name );
|
||||
|
||||
update_user_meta( get_current_user_id(), 'dismissed_' . $name . '_notice', true );
|
||||
|
||||
do_action( 'woocommerce_hide_' . $name . '_notice' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given user has dismissed a given admin notice.
|
||||
*
|
||||
* @since 8.5.0
|
||||
*
|
||||
* @param string $name The name of the admin notice to check.
|
||||
* @param int|null $user_id User id, or null for the current user.
|
||||
* @return bool True if the user has dismissed the notice.
|
||||
*/
|
||||
public static function user_has_dismissed_notice( string $name, ?int $user_id = null ): bool {
|
||||
return (bool) get_user_meta( $user_id ?? get_current_user_id(), "dismissed_{$name}_notice", true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notices + styles if needed.
|
||||
*/
|
||||
public static function add_notices() {
|
||||
$notices = self::get_notices();
|
||||
|
||||
if ( empty( $notices ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once WC_ABSPATH . 'includes/admin/wc-admin-functions.php';
|
||||
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
$show_on_screens = array(
|
||||
'dashboard',
|
||||
'plugins',
|
||||
);
|
||||
|
||||
// Notices should only show on WooCommerce screens, the main dashboard, and on the plugins screen.
|
||||
if ( ! in_array( $screen_id, wc_get_screen_ids(), true ) && ! in_array( $screen_id, $show_on_screens, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style( 'woocommerce-activation', plugins_url( '/assets/css/activation.css', WC_PLUGIN_FILE ), array(), Constants::get_constant( 'WC_VERSION' ) );
|
||||
|
||||
// Add RTL support.
|
||||
wp_style_add_data( 'woocommerce-activation', 'rtl', 'replace' );
|
||||
|
||||
foreach ( $notices as $notice ) {
|
||||
if ( ! empty( self::$core_notices[ $notice ] ) && apply_filters( 'woocommerce_show_admin_notice', true, $notice ) ) {
|
||||
add_action( 'admin_notices', array( __CLASS__, self::$core_notices[ $notice ] ) );
|
||||
} else {
|
||||
add_action( 'admin_notices', array( __CLASS__, 'output_custom_notices' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom notice.
|
||||
*
|
||||
* @param string $name Notice name.
|
||||
* @param string $notice_html Notice HTML.
|
||||
*/
|
||||
public static function add_custom_notice( $name, $notice_html ) {
|
||||
self::add_notice( $name );
|
||||
update_option( 'woocommerce_admin_notice_' . $name, wp_kses_post( $notice_html ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output any stored custom notices.
|
||||
*/
|
||||
public static function output_custom_notices() {
|
||||
$notices = self::get_notices();
|
||||
|
||||
if ( ! empty( $notices ) ) {
|
||||
foreach ( $notices as $notice ) {
|
||||
if ( empty( self::$core_notices[ $notice ] ) ) {
|
||||
$notice_html = get_option( 'woocommerce_admin_notice_' . $notice );
|
||||
|
||||
if ( $notice_html ) {
|
||||
include __DIR__ . '/views/html-notice-custom.php';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we need to update the database, include a message with the DB update button.
|
||||
*/
|
||||
public static function update_notice() {
|
||||
$screen = get_current_screen();
|
||||
$screen_id = $screen ? $screen->id : '';
|
||||
if ( WC()->is_wc_admin_active() && in_array( $screen_id, wc_get_screen_ids(), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WC_Install::needs_db_update() ) {
|
||||
$next_scheduled_date = WC()->queue()->get_next( 'woocommerce_run_update_callback', null, 'woocommerce-db-updates' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( $next_scheduled_date || ! empty( $_GET['do_update_woocommerce'] ) ) {
|
||||
include __DIR__ . '/views/html-notice-updating.php';
|
||||
} else {
|
||||
include __DIR__ . '/views/html-notice-update.php';
|
||||
}
|
||||
} else {
|
||||
include __DIR__ . '/views/html-notice-updated.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have just installed, show a message with the install pages button.
|
||||
*
|
||||
* @deprecated 4.6.0
|
||||
*/
|
||||
public static function install_notice() {
|
||||
_deprecated_function( __CLASS__ . '::' . __FUNCTION__, '4.6.0', esc_html__( 'Onboarding is maintained in WooCommerce Admin.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notice highlighting bad template files.
|
||||
*/
|
||||
public static function template_file_check_notice() {
|
||||
$core_templates = WC_Admin_Status::scan_template_files( WC()->plugin_path() . '/templates' );
|
||||
$outdated = false;
|
||||
|
||||
foreach ( $core_templates as $file ) {
|
||||
|
||||
$theme_file = false;
|
||||
if ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
|
||||
$theme_file = get_stylesheet_directory() . '/' . $file;
|
||||
} elseif ( file_exists( get_stylesheet_directory() . '/' . WC()->template_path() . $file ) ) {
|
||||
$theme_file = get_stylesheet_directory() . '/' . WC()->template_path() . $file;
|
||||
} elseif ( file_exists( get_template_directory() . '/' . $file ) ) {
|
||||
$theme_file = get_template_directory() . '/' . $file;
|
||||
} elseif ( file_exists( get_template_directory() . '/' . WC()->template_path() . $file ) ) {
|
||||
$theme_file = get_template_directory() . '/' . WC()->template_path() . $file;
|
||||
}
|
||||
|
||||
if ( false !== $theme_file ) {
|
||||
$core_version = WC_Admin_Status::get_file_version( WC()->plugin_path() . '/templates/' . $file );
|
||||
$theme_version = WC_Admin_Status::get_file_version( $theme_file );
|
||||
|
||||
if ( $core_version && $theme_version && version_compare( $theme_version, $core_version, '<' ) ) {
|
||||
$outdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $outdated ) {
|
||||
include __DIR__ . '/views/html-notice-template-check.php';
|
||||
} else {
|
||||
self::remove_notice( 'template_files' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notice asking users to convert to shipping zones.
|
||||
*
|
||||
* @todo remove in 4.0.0
|
||||
*/
|
||||
public static function legacy_shipping_notice() {
|
||||
$maybe_load_legacy_methods = array( 'flat_rate', 'free_shipping', 'international_delivery', 'local_delivery', 'local_pickup' );
|
||||
$enabled = false;
|
||||
|
||||
foreach ( $maybe_load_legacy_methods as $method ) {
|
||||
$options = get_option( 'woocommerce_' . $method . '_settings' );
|
||||
if ( $options && isset( $options['enabled'] ) && 'yes' === $options['enabled'] ) {
|
||||
$enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $enabled ) {
|
||||
include __DIR__ . '/views/html-notice-legacy-shipping.php';
|
||||
} else {
|
||||
self::remove_notice( 'template_files' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No shipping methods.
|
||||
*/
|
||||
public static function no_shipping_methods_notice() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( wc_shipping_enabled() && ( empty( $_GET['page'] ) || empty( $_GET['tab'] ) || 'wc-settings' !== $_GET['page'] || 'shipping' !== $_GET['tab'] ) ) {
|
||||
$product_count = wp_count_posts( 'product' );
|
||||
$method_count = wc_get_shipping_method_count();
|
||||
|
||||
if ( $product_count->publish > 0 && 0 === $method_count ) {
|
||||
include __DIR__ . '/views/html-notice-no-shipping-methods.php';
|
||||
}
|
||||
|
||||
if ( $method_count > 0 ) {
|
||||
self::remove_notice( 'no_shipping_methods' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice shown when regenerating thumbnails background process is running.
|
||||
*/
|
||||
public static function regenerating_thumbnails_notice() {
|
||||
include __DIR__ . '/views/html-notice-regenerating-thumbnails.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice about secure connection.
|
||||
*/
|
||||
public static function secure_connection_notice() {
|
||||
if ( self::is_ssl() || get_user_meta( get_current_user_id(), 'dismissed_no_secure_connection_notice', true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-secure-connection.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice shown when regenerating thumbnails background process is running.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public static function regenerating_lookup_table_notice() {
|
||||
// See if this is still relevant.
|
||||
if ( ! wc_update_product_lookup_tables_is_running() ) {
|
||||
self::remove_notice( 'regenerating_lookup_table' );
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-regenerating-lookup-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notice about minimum PHP and WordPress requirement.
|
||||
*
|
||||
* @since 3.6.5
|
||||
*/
|
||||
public static function add_min_version_notice() {
|
||||
if ( version_compare( phpversion(), WC_NOTICE_MIN_PHP_VERSION, '<' ) || version_compare( get_bloginfo( 'version' ), WC_NOTICE_MIN_WP_VERSION, '<' ) ) {
|
||||
self::add_notice( WC_PHP_MIN_REQUIREMENTS_NOTICE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice about WordPress and PHP minimum requirements.
|
||||
*
|
||||
* @deprecated 8.2.0 WordPress and PHP minimum requirements notices are no longer shown.
|
||||
*
|
||||
* @since 3.6.5
|
||||
* @return void
|
||||
*/
|
||||
public static function wp_php_min_requirements_notice() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add MaxMind missing license key notice.
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public static function add_maxmind_missing_license_key_notice() {
|
||||
$default_address = get_option( 'woocommerce_default_customer_address' );
|
||||
|
||||
if ( ! in_array( $default_address, array( 'geolocation', 'geolocation_ajax' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$integration_options = get_option( 'woocommerce_maxmind_geolocation_settings' );
|
||||
if ( empty( $integration_options['license_key'] ) ) {
|
||||
self::add_notice( 'maxmind_license_key' );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notice about Redirect-only download method, nudging user to switch to a different method instead.
|
||||
*/
|
||||
public static function add_redirect_download_method_notice() {
|
||||
if ( 'redirect' === get_option( 'woocommerce_file_download_method' ) ) {
|
||||
self::add_notice( 'redirect_download_method' );
|
||||
} else {
|
||||
self::remove_notice( 'redirect_download_method' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice about the completion of the product downloads sync, with further advice for the site operator.
|
||||
*/
|
||||
public static function download_directories_sync_complete() {
|
||||
$notice_dismissed = apply_filters(
|
||||
'woocommerce_hide_download_directories_sync_complete',
|
||||
get_user_meta( get_current_user_id(), 'download_directories_sync_complete', true )
|
||||
);
|
||||
|
||||
if ( $notice_dismissed ) {
|
||||
self::remove_notice( 'download_directories_sync_complete' );
|
||||
}
|
||||
|
||||
if ( Users::is_site_administrator() ) {
|
||||
include __DIR__ . '/views/html-notice-download-dir-sync-complete.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display MaxMind missing license key notice.
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public static function maxmind_missing_license_key_notice() {
|
||||
$user_dismissed_notice = get_user_meta( get_current_user_id(), 'dismissed_maxmind_license_key_notice', true );
|
||||
$filter_dismissed_notice = ! apply_filters( 'woocommerce_maxmind_geolocation_display_notices', true );
|
||||
|
||||
if ( $user_dismissed_notice || $filter_dismissed_notice ) {
|
||||
self::remove_notice( 'maxmind_license_key' );
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-maxmind-license-key.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice about Redirect-Only download method.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public static function redirect_download_method_notice() {
|
||||
if ( apply_filters( 'woocommerce_hide_redirect_method_nag', get_user_meta( get_current_user_id(), 'dismissed_redirect_download_method_notice', true ) ) ) {
|
||||
self::remove_notice( 'redirect_download_method' );
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-redirect-only-download.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice about uploads directory begin unprotected.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static function uploads_directory_is_unprotected_notice() {
|
||||
if ( get_user_meta( get_current_user_id(), 'dismissed_uploads_directory_is_unprotected_notice', true ) || self::is_uploads_directory_protected() ) {
|
||||
self::remove_notice( 'uploads_directory_is_unprotected' );
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-uploads-directory-is-unprotected.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice about base tables missing.
|
||||
*/
|
||||
public static function base_tables_missing_notice() {
|
||||
$notice_dismissed = apply_filters(
|
||||
'woocommerce_hide_base_tables_missing_nag',
|
||||
get_user_meta( get_current_user_id(), 'dismissed_base_tables_missing_notice', true )
|
||||
);
|
||||
if ( $notice_dismissed ) {
|
||||
self::remove_notice( 'base_tables_missing' );
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-base-table-missing.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the store is running SSL.
|
||||
*
|
||||
* @return bool Flag SSL enabled.
|
||||
* @since 3.5.1
|
||||
*/
|
||||
protected static function is_ssl() {
|
||||
$shop_page = wc_get_page_permalink( 'shop' );
|
||||
|
||||
return ( is_ssl() && 'https' === substr( $shop_page, 0, 5 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for is_plugin_active.
|
||||
*
|
||||
* @param string $plugin Plugin to check.
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function is_plugin_active( $plugin ) {
|
||||
if ( ! function_exists( 'is_plugin_active' ) ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
return is_plugin_active( $plugin );
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify Commerce is no longer in core.
|
||||
*
|
||||
* @deprecated 3.6.0 No longer shown.
|
||||
*/
|
||||
public static function simplify_commerce_notice() {
|
||||
wc_deprecated_function( 'WC_Admin_Notices::simplify_commerce_notice', '3.6.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Theme Check notice.
|
||||
*
|
||||
* @deprecated 3.3.0 No longer shown.
|
||||
*/
|
||||
public static function theme_check_notice() {
|
||||
wc_deprecated_function( 'WC_Admin_Notices::theme_check_notice', '3.3.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if uploads directory is protected.
|
||||
*
|
||||
* @since 4.2.0
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_uploads_directory_protected() {
|
||||
$cache_key = '_woocommerce_upload_directory_status';
|
||||
$status = get_transient( $cache_key );
|
||||
|
||||
// Check for cache.
|
||||
if ( false !== $status ) {
|
||||
return 'protected' === $status;
|
||||
}
|
||||
|
||||
// Get only data from the uploads directory.
|
||||
$uploads = wp_get_upload_dir();
|
||||
|
||||
// Check for the "uploads/woocommerce_uploads" directory.
|
||||
$response = wp_safe_remote_get(
|
||||
esc_url_raw( $uploads['baseurl'] . '/woocommerce_uploads/' ),
|
||||
array(
|
||||
'redirection' => 0,
|
||||
)
|
||||
);
|
||||
$response_code = intval( wp_remote_retrieve_response_code( $response ) );
|
||||
$response_content = wp_remote_retrieve_body( $response );
|
||||
|
||||
// Check if returns 200 with empty content in case can open an index.html file,
|
||||
// and check for non-200 codes in case the directory is protected.
|
||||
$is_protected = ( 200 === $response_code && empty( $response_content ) ) || ( 200 !== $response_code );
|
||||
set_transient( $cache_key, $is_protected ? 'protected' : 'unprotected', 1 * DAY_IN_SECONDS );
|
||||
|
||||
return $is_protected;
|
||||
}
|
||||
}
|
||||
|
||||
WC_Admin_Notices::init();
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* Adds settings to the permalinks admin settings page
|
||||
*
|
||||
* @class WC_Admin_Permalink_Settings
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.3.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_Permalink_Settings', false ) ) {
|
||||
return new WC_Admin_Permalink_Settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Permalink_Settings Class.
|
||||
*/
|
||||
class WC_Admin_Permalink_Settings {
|
||||
|
||||
/**
|
||||
* Permalink settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $permalinks = array();
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->settings_init();
|
||||
$this->settings_save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init our settings.
|
||||
*/
|
||||
public function settings_init() {
|
||||
add_settings_section( 'woocommerce-permalink', __( 'Product permalinks', 'woocommerce' ), array( $this, 'settings' ), 'permalink' );
|
||||
|
||||
add_settings_field(
|
||||
'woocommerce_product_category_slug',
|
||||
__( 'Product category base', 'woocommerce' ),
|
||||
array( $this, 'product_category_slug_input' ),
|
||||
'permalink',
|
||||
'optional'
|
||||
);
|
||||
add_settings_field(
|
||||
'woocommerce_product_tag_slug',
|
||||
__( 'Product tag base', 'woocommerce' ),
|
||||
array( $this, 'product_tag_slug_input' ),
|
||||
'permalink',
|
||||
'optional'
|
||||
);
|
||||
add_settings_field(
|
||||
'woocommerce_product_attribute_slug',
|
||||
__( 'Product attribute base', 'woocommerce' ),
|
||||
array( $this, 'product_attribute_slug_input' ),
|
||||
'permalink',
|
||||
'optional'
|
||||
);
|
||||
|
||||
$this->permalinks = wc_get_permalink_structure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a slug input box.
|
||||
*/
|
||||
public function product_category_slug_input() {
|
||||
?>
|
||||
<input name="woocommerce_product_category_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $this->permalinks['category_base'] ); ?>" placeholder="<?php echo esc_attr_x( 'product-category', 'slug', 'woocommerce' ); ?>" />
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a slug input box.
|
||||
*/
|
||||
public function product_tag_slug_input() {
|
||||
?>
|
||||
<input name="woocommerce_product_tag_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $this->permalinks['tag_base'] ); ?>" placeholder="<?php echo esc_attr_x( 'product-tag', 'slug', 'woocommerce' ); ?>" />
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a slug input box.
|
||||
*/
|
||||
public function product_attribute_slug_input() {
|
||||
?>
|
||||
<input name="woocommerce_product_attribute_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $this->permalinks['attribute_base'] ); ?>" /><code>/attribute-name/attribute/</code>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the settings.
|
||||
*/
|
||||
public function settings() {
|
||||
/* translators: %s: Home URL */
|
||||
echo wp_kses_post( wpautop( sprintf( __( 'If you like, you may enter custom structures for your product URLs here. For example, using <code>shop</code> would make your product links like <code>%sshop/sample-product/</code>. This setting affects product URLs only, not things such as product categories.', 'woocommerce' ), esc_url( home_url( '/' ) ) ) ) );
|
||||
|
||||
$shop_page_id = wc_get_page_id( 'shop' );
|
||||
$base_slug = urldecode( ( $shop_page_id > 0 && get_post( $shop_page_id ) ) ? get_page_uri( $shop_page_id ) : _x( 'shop', 'default-slug', 'woocommerce' ) );
|
||||
$product_base = _x( 'product', 'default-slug', 'woocommerce' );
|
||||
|
||||
$structures = array(
|
||||
0 => '',
|
||||
1 => '/' . trailingslashit( $base_slug ),
|
||||
2 => '/' . trailingslashit( $base_slug ) . trailingslashit( '%product_cat%' ),
|
||||
);
|
||||
?>
|
||||
<table class="form-table wc-permalink-structure">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><label><input name="product_permalink" type="radio" value="<?php echo esc_attr( $structures[0] ); ?>" class="wctog" <?php checked( $structures[0], $this->permalinks['product_base'] ); ?> /> <?php esc_html_e( 'Default', 'woocommerce' ); ?></label></th>
|
||||
<td><code class="default-example"><?php echo esc_html( home_url() ); ?>/?product=sample-product</code> <code class="non-default-example"><?php echo esc_html( home_url() ); ?>/<?php echo esc_html( $product_base ); ?>/sample-product/</code></td>
|
||||
</tr>
|
||||
<?php if ( $shop_page_id ) : ?>
|
||||
<tr>
|
||||
<th><label><input name="product_permalink" type="radio" value="<?php echo esc_attr( $structures[1] ); ?>" class="wctog" <?php checked( $structures[1], $this->permalinks['product_base'] ); ?> /> <?php esc_html_e( 'Shop base', 'woocommerce' ); ?></label></th>
|
||||
<td><code><?php echo esc_html( home_url() ); ?>/<?php echo esc_html( $base_slug ); ?>/sample-product/</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label><input name="product_permalink" type="radio" value="<?php echo esc_attr( $structures[2] ); ?>" class="wctog" <?php checked( $structures[2], $this->permalinks['product_base'] ); ?> /> <?php esc_html_e( 'Shop base with category', 'woocommerce' ); ?></label></th>
|
||||
<td><code><?php echo esc_html( home_url() ); ?>/<?php echo esc_html( $base_slug ); ?>/product-category/sample-product/</code></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<tr>
|
||||
<th><label><input name="product_permalink" id="woocommerce_custom_selection" type="radio" value="custom" class="tog" <?php checked( in_array( $this->permalinks['product_base'], $structures, true ), false ); ?> />
|
||||
<?php esc_html_e( 'Custom base', 'woocommerce' ); ?></label></th>
|
||||
<td>
|
||||
<input name="product_permalink_structure" id="woocommerce_permalink_structure" type="text" value="<?php echo esc_attr( $this->permalinks['product_base'] ? trailingslashit( $this->permalinks['product_base'] ) : '' ); ?>" class="regular-text code"> <span class="description"><?php esc_html_e( 'Enter a custom base to use. A base must be set or WordPress will use default instead.', 'woocommerce' ); ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php wp_nonce_field( 'wc-permalinks', 'wc-permalinks-nonce' ); ?>
|
||||
<script type="text/javascript">
|
||||
jQuery( function() {
|
||||
jQuery('input.wctog').on( 'change', function() {
|
||||
jQuery('#woocommerce_permalink_structure').val( jQuery( this ).val() );
|
||||
});
|
||||
jQuery('.permalink-structure input').on( 'change', function() {
|
||||
jQuery('.wc-permalink-structure').find('code.non-default-example, code.default-example').hide();
|
||||
if ( jQuery(this).val() ) {
|
||||
jQuery('.wc-permalink-structure code.non-default-example').show();
|
||||
jQuery('.wc-permalink-structure input').prop('disabled', false);
|
||||
} else {
|
||||
jQuery('.wc-permalink-structure code.default-example').show();
|
||||
jQuery('.wc-permalink-structure input:eq(0)').trigger( 'click' );
|
||||
jQuery('.wc-permalink-structure input').attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
jQuery('.permalink-structure input:checked').trigger( 'change' );
|
||||
jQuery('#woocommerce_permalink_structure').on( 'focus', function(){
|
||||
jQuery('#woocommerce_custom_selection').trigger( 'click' );
|
||||
} );
|
||||
} );
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the settings.
|
||||
*/
|
||||
public function settings_save() {
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to save the options ourselves; settings api does not trigger save for the permalinks page.
|
||||
if ( isset( $_POST['permalink_structure'], $_POST['wc-permalinks-nonce'], $_POST['woocommerce_product_category_slug'], $_POST['woocommerce_product_tag_slug'], $_POST['woocommerce_product_attribute_slug'] ) && wp_verify_nonce( wp_unslash( $_POST['wc-permalinks-nonce'] ), 'wc-permalinks' ) ) { // WPCS: input var ok, sanitization ok.
|
||||
wc_switch_to_site_locale();
|
||||
|
||||
$permalinks = (array) get_option( 'woocommerce_permalinks', array() );
|
||||
$permalinks['category_base'] = wc_sanitize_permalink( wp_unslash( $_POST['woocommerce_product_category_slug'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
$permalinks['tag_base'] = wc_sanitize_permalink( wp_unslash( $_POST['woocommerce_product_tag_slug'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
$permalinks['attribute_base'] = wc_sanitize_permalink( wp_unslash( $_POST['woocommerce_product_attribute_slug'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
|
||||
// Generate product base.
|
||||
$product_base = isset( $_POST['product_permalink'] ) ? wc_clean( wp_unslash( $_POST['product_permalink'] ) ) : ''; // WPCS: input var ok, sanitization ok.
|
||||
|
||||
if ( 'custom' === $product_base ) {
|
||||
if ( isset( $_POST['product_permalink_structure'] ) ) { // WPCS: input var ok.
|
||||
$product_base = preg_replace( '#/+#', '/', '/' . str_replace( '#', '', trim( wp_unslash( $_POST['product_permalink_structure'] ) ) ) ); // WPCS: input var ok, sanitization ok.
|
||||
} else {
|
||||
$product_base = '/';
|
||||
}
|
||||
|
||||
// This is an invalid base structure and breaks pages.
|
||||
if ( '/%product_cat%/' === trailingslashit( $product_base ) ) {
|
||||
$product_base = '/' . _x( 'product', 'slug', 'woocommerce' ) . $product_base;
|
||||
}
|
||||
} elseif ( empty( $product_base ) ) {
|
||||
$product_base = _x( 'product', 'slug', 'woocommerce' );
|
||||
}
|
||||
|
||||
$permalinks['product_base'] = wc_sanitize_permalink( $product_base );
|
||||
|
||||
// Shop base may require verbose page rules if nesting pages.
|
||||
$shop_page_id = wc_get_page_id( 'shop' );
|
||||
$shop_permalink = ( $shop_page_id > 0 && get_post( $shop_page_id ) ) ? get_page_uri( $shop_page_id ) : _x( 'shop', 'default-slug', 'woocommerce' );
|
||||
|
||||
if ( $shop_page_id && stristr( trim( $permalinks['product_base'], '/' ), $shop_permalink ) ) {
|
||||
$permalinks['use_verbose_page_rules'] = true;
|
||||
}
|
||||
|
||||
update_option( 'woocommerce_permalinks', $permalinks );
|
||||
wc_restore_locale();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new WC_Admin_Permalink_Settings();
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Adds and controls pointers for contextual help/tutorials
|
||||
*
|
||||
* @package WooCommerce\Admin\Pointers
|
||||
* @version 2.4.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\WCAdminAssets;
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Pointers Class.
|
||||
*/
|
||||
class WC_Admin_Pointers {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'setup_pointers_for_screen' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup pointers for screen.
|
||||
*/
|
||||
public function setup_pointers_for_screen() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( ! $screen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $screen->id ) {
|
||||
case 'product':
|
||||
$this->create_product_tutorial();
|
||||
$this->create_variable_product_tutorial();
|
||||
break;
|
||||
case 'woocommerce_page_wc-addons':
|
||||
$this->create_wc_addons_tutorial();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointers for creating a product.
|
||||
*/
|
||||
public function create_product_tutorial() {
|
||||
if ( ! isset( $_GET['tutorial'] ) || ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
global $wp_post_types;
|
||||
|
||||
if ( ! isset( $wp_post_types ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$labels = $wp_post_types['product']->labels;
|
||||
$labels->add_new = __( 'Enable guided mode', 'woocommerce' );
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'product-tour', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointers for creating a variable product.
|
||||
*/
|
||||
public function create_variable_product_tutorial() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'variable-product-tour', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointers for accessing In-App Marketplace.
|
||||
*/
|
||||
public function create_wc_addons_tutorial() {
|
||||
if ( ! isset( $_GET['tutorial'] ) || ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_is_mobile() ) {
|
||||
return; // Permit In-App Marketplace Tour on desktops only.
|
||||
}
|
||||
|
||||
WCAdminAssets::register_script( 'wp-admin-scripts', 'wc-addons-tour', true );
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Pointers();
|
||||
@@ -0,0 +1,933 @@
|
||||
<?php
|
||||
/**
|
||||
* Post Types Admin
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Utilities\NumberUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_Post_Types', false ) ) {
|
||||
new WC_Admin_Post_Types();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Post_Types Class.
|
||||
*
|
||||
* Handles the edit posts views and some functionality on the edit post screen for WC post types.
|
||||
*/
|
||||
class WC_Admin_Post_Types {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
include_once __DIR__ . '/class-wc-admin-meta-boxes.php';
|
||||
|
||||
if ( ! function_exists( 'duplicate_post_plugin_activation' ) ) {
|
||||
include_once __DIR__ . '/class-wc-admin-duplicate-product.php';
|
||||
}
|
||||
|
||||
// Load correct list table classes for current screen.
|
||||
add_action( 'current_screen', array( $this, 'setup_screen' ) );
|
||||
add_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
|
||||
|
||||
// Admin notices.
|
||||
add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
|
||||
add_filter( 'woocommerce_order_updated_messages', array( $this, 'order_updated_messages' ) );
|
||||
add_filter( 'bulk_post_updated_messages', array( $this, 'bulk_post_updated_messages' ), 10, 2 );
|
||||
|
||||
// Disable Auto Save.
|
||||
add_action( 'admin_print_scripts', array( $this, 'disable_autosave' ) );
|
||||
|
||||
// Extra post data and screen elements.
|
||||
add_action( 'edit_form_top', array( $this, 'edit_form_top' ) );
|
||||
add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
|
||||
add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );
|
||||
add_filter( 'default_hidden_meta_boxes', array( $this, 'hidden_meta_boxes' ), 10, 2 );
|
||||
add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ) );
|
||||
|
||||
include_once __DIR__ . '/class-wc-admin-upload-downloadable-product.php';
|
||||
|
||||
// Hide template for CPT archive.
|
||||
add_filter( 'theme_page_templates', array( $this, 'hide_cpt_archive_templates' ), 10, 3 );
|
||||
add_action( 'edit_form_top', array( $this, 'show_cpt_archive_notice' ) );
|
||||
|
||||
// Add a post display state for special WC pages.
|
||||
add_filter( 'display_post_states', array( $this, 'add_display_post_states' ), 10, 2 );
|
||||
|
||||
// Bulk / quick edit.
|
||||
add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
|
||||
add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 );
|
||||
add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
|
||||
add_action( 'woocommerce_product_bulk_and_quick_edit', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the current screen and loads the correct list table handler.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function setup_screen() {
|
||||
global $wc_list_table;
|
||||
|
||||
$request_data = $this->request_data();
|
||||
|
||||
$screen_id = false;
|
||||
|
||||
if ( function_exists( 'get_current_screen' ) ) {
|
||||
$screen = get_current_screen();
|
||||
$screen_id = isset( $screen, $screen->id ) ? $screen->id : '';
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['screen'] ) ) {
|
||||
$screen_id = wc_clean( wp_unslash( $request_data['screen'] ) );
|
||||
}
|
||||
|
||||
switch ( $screen_id ) {
|
||||
case 'edit-shop_order':
|
||||
include_once __DIR__ . '/list-tables/class-wc-admin-list-table-orders.php';
|
||||
$wc_list_table = new WC_Admin_List_Table_Orders();
|
||||
break;
|
||||
case 'edit-shop_coupon':
|
||||
include_once __DIR__ . '/list-tables/class-wc-admin-list-table-coupons.php';
|
||||
$wc_list_table = new WC_Admin_List_Table_Coupons();
|
||||
break;
|
||||
case 'edit-product':
|
||||
include_once __DIR__ . '/list-tables/class-wc-admin-list-table-products.php';
|
||||
$wc_list_table = new WC_Admin_List_Table_Products();
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure the table handler is only loaded once. Prevents multiple loads if a plugin calls check_ajax_referer many times.
|
||||
remove_action( 'current_screen', array( $this, 'setup_screen' ) );
|
||||
remove_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change messages when a post type is updated.
|
||||
*
|
||||
* @param array $messages Array of messages.
|
||||
* @return array
|
||||
*/
|
||||
public function post_updated_messages( $messages ) {
|
||||
global $post;
|
||||
|
||||
$messages['product'] = array(
|
||||
0 => '', // Unused. Messages start at index 1.
|
||||
/* translators: %1$s: Product link opening tag. %2$s: Product link closing tag.*/
|
||||
1 => sprintf( __( 'Product updated. %1$sView Product%2$s', 'woocommerce' ), '<a id="woocommerce-product-updated-message-view-product__link" href="' . esc_url( get_permalink( $post->ID ) ) . '">', '</a>' ),
|
||||
2 => __( 'Custom field updated.', 'woocommerce' ),
|
||||
3 => __( 'Custom field deleted.', 'woocommerce' ),
|
||||
4 => __( 'Product updated.', 'woocommerce' ),
|
||||
5 => __( 'Revision restored.', 'woocommerce' ),
|
||||
/* translators: %1$s: Product link opening tag. %2$s: Product link closing tag.*/
|
||||
6 => sprintf( __( 'Product published. %1$sView Product%2$s', 'woocommerce' ), '<a id="woocommerce-product-updated-message-view-product__link" href="' . esc_url( get_permalink( $post->ID ) ) . '">', '</a>' ),
|
||||
7 => __( 'Product saved.', 'woocommerce' ),
|
||||
/* translators: %s: product url */
|
||||
8 => sprintf( __( 'Product submitted. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),
|
||||
9 => sprintf(
|
||||
/* translators: 1: date 2: product url */
|
||||
__( 'Product scheduled for: %1$s. <a target="_blank" href="%2$s">Preview product</a>', 'woocommerce' ),
|
||||
'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>',
|
||||
esc_url( get_permalink( $post->ID ) )
|
||||
),
|
||||
/* translators: %s: product url */
|
||||
10 => sprintf( __( 'Product draft updated. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),
|
||||
);
|
||||
|
||||
$messages = $this->order_updated_messages( $messages );
|
||||
|
||||
$messages['shop_coupon'] = array(
|
||||
0 => '', // Unused. Messages start at index 1.
|
||||
1 => __( 'Coupon updated.', 'woocommerce' ),
|
||||
2 => __( 'Custom field updated.', 'woocommerce' ),
|
||||
3 => __( 'Custom field deleted.', 'woocommerce' ),
|
||||
4 => __( 'Coupon updated.', 'woocommerce' ),
|
||||
5 => __( 'Revision restored.', 'woocommerce' ),
|
||||
6 => __( 'Coupon updated.', 'woocommerce' ),
|
||||
7 => __( 'Coupon saved.', 'woocommerce' ),
|
||||
8 => __( 'Coupon submitted.', 'woocommerce' ),
|
||||
9 => sprintf(
|
||||
/* translators: %s: date */
|
||||
__( 'Coupon scheduled for: %s.', 'woocommerce' ),
|
||||
'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>'
|
||||
),
|
||||
10 => __( 'Coupon draft updated.', 'woocommerce' ),
|
||||
);
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add messages when an order is updated.
|
||||
*
|
||||
* @param array $messages Array of messages.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function order_updated_messages( array $messages ) {
|
||||
global $post, $theorder;
|
||||
|
||||
if ( ! isset( $theorder ) || ! $theorder instanceof WC_Abstract_Order ) {
|
||||
if ( ! isset( $post ) || 'shop_order' !== $post->post_type ) {
|
||||
return $messages;
|
||||
} else {
|
||||
\Automattic\WooCommerce\Utilities\OrderUtil::init_theorder_object( $post );
|
||||
}
|
||||
}
|
||||
|
||||
$messages['shop_order'] = array(
|
||||
0 => '', // Unused. Messages start at index 1.
|
||||
1 => __( 'Order updated.', 'woocommerce' ),
|
||||
2 => __( 'Custom field updated.', 'woocommerce' ),
|
||||
3 => __( 'Custom field deleted.', 'woocommerce' ),
|
||||
4 => __( 'Order updated.', 'woocommerce' ),
|
||||
5 => __( 'Revision restored.', 'woocommerce' ),
|
||||
6 => __( 'Order updated.', 'woocommerce' ),
|
||||
7 => __( 'Order saved.', 'woocommerce' ),
|
||||
8 => __( 'Order submitted.', 'woocommerce' ),
|
||||
9 => sprintf(
|
||||
/* translators: %s: date */
|
||||
__( 'Order scheduled for: %s.', 'woocommerce' ),
|
||||
'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $theorder->get_date_created() ?? $post->post_date ) ) . '</strong>'
|
||||
),
|
||||
10 => __( 'Order draft updated.', 'woocommerce' ),
|
||||
11 => __( 'Order updated and sent.', 'woocommerce' ),
|
||||
);
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify custom bulk actions messages for different post types.
|
||||
*
|
||||
* @param array $bulk_messages Array of messages.
|
||||
* @param array $bulk_counts Array of how many objects were updated.
|
||||
* @return array
|
||||
*/
|
||||
public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {
|
||||
$bulk_messages['product'] = array(
|
||||
/* translators: %s: product count */
|
||||
'updated' => _n( '%s product updated.', '%s products updated.', $bulk_counts['updated'], 'woocommerce' ),
|
||||
/* translators: %s: product count */
|
||||
'locked' => _n( '%s product not updated, somebody is editing it.', '%s products not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
|
||||
/* translators: %s: product count */
|
||||
'deleted' => _n( '%s product permanently deleted.', '%s products permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
|
||||
/* translators: %s: product count */
|
||||
'trashed' => _n( '%s product moved to the Trash.', '%s products moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
|
||||
/* translators: %s: product count */
|
||||
'untrashed' => _n( '%s product restored from the Trash.', '%s products restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
|
||||
);
|
||||
|
||||
$bulk_messages['shop_order'] = array(
|
||||
/* translators: %s: order count */
|
||||
'updated' => _n( '%s order updated.', '%s orders updated.', $bulk_counts['updated'], 'woocommerce' ),
|
||||
/* translators: %s: order count */
|
||||
'locked' => _n( '%s order not updated, somebody is editing it.', '%s orders not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
|
||||
/* translators: %s: order count */
|
||||
'deleted' => _n( '%s order permanently deleted.', '%s orders permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
|
||||
/* translators: %s: order count */
|
||||
'trashed' => _n( '%s order moved to the Trash.', '%s orders moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
|
||||
/* translators: %s: order count */
|
||||
'untrashed' => _n( '%s order restored from the Trash.', '%s orders restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
|
||||
);
|
||||
|
||||
$bulk_messages['shop_coupon'] = array(
|
||||
/* translators: %s: coupon count */
|
||||
'updated' => _n( '%s coupon updated.', '%s coupons updated.', $bulk_counts['updated'], 'woocommerce' ),
|
||||
/* translators: %s: coupon count */
|
||||
'locked' => _n( '%s coupon not updated, somebody is editing it.', '%s coupons not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
|
||||
/* translators: %s: coupon count */
|
||||
'deleted' => _n( '%s coupon permanently deleted.', '%s coupons permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
|
||||
/* translators: %s: coupon count */
|
||||
'trashed' => _n( '%s coupon moved to the Trash.', '%s coupons moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
|
||||
/* translators: %s: coupon count */
|
||||
'untrashed' => _n( '%s coupon restored from the Trash.', '%s coupons restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
|
||||
);
|
||||
|
||||
return $bulk_messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom bulk edit - form.
|
||||
*
|
||||
* @param string $column_name Column being shown.
|
||||
* @param string $post_type Post type being shown.
|
||||
*/
|
||||
public function bulk_edit( $column_name, $post_type ) {
|
||||
if ( 'price' !== $column_name || 'product' !== $post_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shipping_class = get_terms(
|
||||
'product_shipping_class',
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
|
||||
include WC()->plugin_path() . '/includes/admin/views/html-bulk-edit-product.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom quick edit - form.
|
||||
*
|
||||
* @param string $column_name Column being shown.
|
||||
* @param string $post_type Post type being shown.
|
||||
*/
|
||||
public function quick_edit( $column_name, $post_type ) {
|
||||
if ( 'price' !== $column_name || 'product' !== $post_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$shipping_class = get_terms(
|
||||
'product_shipping_class',
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
)
|
||||
);
|
||||
|
||||
include WC()->plugin_path() . '/includes/admin/views/html-quick-edit-product.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers a way to hook into save post without causing an infinite loop
|
||||
* when quick/bulk saving product info.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param int $post_id Post ID being saved.
|
||||
* @param object $post Post object being saved.
|
||||
*/
|
||||
public function bulk_and_quick_edit_hook( $post_id, $post ) {
|
||||
remove_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ) );
|
||||
do_action( 'woocommerce_product_bulk_and_quick_edit', $post_id, $post );
|
||||
add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick and bulk edit saving.
|
||||
*
|
||||
* @param int $post_id Post ID being saved.
|
||||
* @param object $post Post object being saved.
|
||||
* @return int
|
||||
*/
|
||||
public function bulk_and_quick_edit_save_post( $post_id, $post ) {
|
||||
$request_data = $this->request_data();
|
||||
|
||||
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
|
||||
if ( Constants::is_true( 'DOING_AUTOSAVE' ) ) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
// Don't save revisions and autosaves.
|
||||
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || 'product' !== $post->post_type || ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
// Check nonce.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
if ( ! isset( $request_data['woocommerce_quick_edit_nonce'] ) || ! wp_verify_nonce( $request_data['woocommerce_quick_edit_nonce'], 'woocommerce_quick_edit_nonce' ) ) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
// Get the product and save.
|
||||
$product = wc_get_product( $post );
|
||||
|
||||
if ( ! empty( $request_data['woocommerce_quick_edit'] ) ) { // WPCS: input var ok.
|
||||
$this->quick_edit_save( $post_id, $product );
|
||||
} else {
|
||||
$this->bulk_edit_save( $post_id, $product );
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick edit.
|
||||
*
|
||||
* @param int $post_id Post ID being saved.
|
||||
* @param WC_Product $product Product object.
|
||||
*/
|
||||
private function quick_edit_save( $post_id, $product ) {
|
||||
$request_data = $this->request_data();
|
||||
|
||||
$data_store = $product->get_data_store();
|
||||
$old_regular_price = $product->get_regular_price();
|
||||
$old_sale_price = $product->get_sale_price();
|
||||
$input_to_props = array(
|
||||
'_weight' => 'weight',
|
||||
'_length' => 'length',
|
||||
'_width' => 'width',
|
||||
'_height' => 'height',
|
||||
'_visibility' => 'catalog_visibility',
|
||||
'_tax_class' => 'tax_class',
|
||||
'_tax_status' => 'tax_status',
|
||||
);
|
||||
|
||||
foreach ( $input_to_props as $input_var => $prop ) {
|
||||
if ( isset( $request_data[ $input_var ] ) ) {
|
||||
$product->{"set_{$prop}"}( wc_clean( wp_unslash( $request_data[ $input_var ] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request_data['_sku'] ) ) {
|
||||
$sku = $product->get_sku();
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
$new_sku = (string) wc_clean( $request_data['_sku'] );
|
||||
|
||||
if ( $new_sku !== $sku ) {
|
||||
if ( ! empty( $new_sku ) ) {
|
||||
$unique_sku = wc_product_has_unique_sku( $post_id, $new_sku );
|
||||
if ( $unique_sku ) {
|
||||
$product->set_sku( wc_clean( wp_unslash( $new_sku ) ) );
|
||||
}
|
||||
} else {
|
||||
$product->set_sku( '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_shipping_class'] ) ) {
|
||||
if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
|
||||
$product->set_shipping_class_id( 0 );
|
||||
} else {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
$shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
|
||||
$product->set_shipping_class_id( $shipping_class_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_tax_class'] ) ) {
|
||||
$tax_class = sanitize_title( wp_unslash( $request_data['_tax_class'] ) );
|
||||
if ( 'standard' === $tax_class ) {
|
||||
$tax_class = '';
|
||||
}
|
||||
$product->set_tax_class( $tax_class );
|
||||
}
|
||||
|
||||
$product->set_featured( isset( $request_data['_featured'] ) );
|
||||
|
||||
if ( $product->is_type( 'simple' ) || $product->is_type( 'external' ) ) {
|
||||
|
||||
if ( isset( $request_data['_regular_price'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
$new_regular_price = ( '' === $request_data['_regular_price'] ) ? '' : wc_format_decimal( $request_data['_regular_price'] );
|
||||
$product->set_regular_price( $new_regular_price );
|
||||
} else {
|
||||
$new_regular_price = null;
|
||||
}
|
||||
if ( isset( $request_data['_sale_price'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
$new_sale_price = ( '' === $request_data['_sale_price'] ) ? '' : wc_format_decimal( $request_data['_sale_price'] );
|
||||
$product->set_sale_price( $new_sale_price );
|
||||
} else {
|
||||
$new_sale_price = null;
|
||||
}
|
||||
|
||||
// Handle price - remove dates and set to lowest.
|
||||
$price_changed = false;
|
||||
|
||||
if ( ! is_null( $new_regular_price ) && $new_regular_price !== $old_regular_price ) {
|
||||
$price_changed = true;
|
||||
} elseif ( ! is_null( $new_sale_price ) && $new_sale_price !== $old_sale_price ) {
|
||||
$price_changed = true;
|
||||
}
|
||||
|
||||
if ( $price_changed ) {
|
||||
$product->set_date_on_sale_to( '' );
|
||||
$product->set_date_on_sale_from( '' );
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Stock Data.
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$manage_stock = ! empty( $request_data['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no';
|
||||
$backorders = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : 'no';
|
||||
if ( ! empty( $request_data['_stock_status'] ) ) {
|
||||
$stock_status = wc_clean( $request_data['_stock_status'] );
|
||||
} else {
|
||||
$stock_status = $product->is_type( 'variable' ) ? null : 'instock';
|
||||
}
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
$product->set_manage_stock( $manage_stock );
|
||||
|
||||
if ( 'external' !== $product->get_type() ) {
|
||||
$product->set_backorders( $backorders );
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
|
||||
$stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : '';
|
||||
$product->set_stock_quantity( $stock_amount );
|
||||
}
|
||||
|
||||
$product = $this->maybe_update_stock_status( $product, $stock_status );
|
||||
|
||||
$product->save();
|
||||
|
||||
do_action( 'woocommerce_product_quick_edit_save', $product );
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk edit.
|
||||
*
|
||||
* @param int $post_id Post ID being saved.
|
||||
* @param WC_Product $product Product object.
|
||||
*/
|
||||
public function bulk_edit_save( $post_id, $product ) {
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
|
||||
$request_data = $this->request_data();
|
||||
|
||||
$data_store = $product->get_data_store();
|
||||
|
||||
if ( ! empty( $request_data['change_weight'] ) && isset( $request_data['_weight'] ) ) {
|
||||
$product->set_weight( wc_clean( wp_unslash( $request_data['_weight'] ) ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['change_dimensions'] ) ) {
|
||||
if ( isset( $request_data['_length'] ) ) {
|
||||
$product->set_length( wc_clean( wp_unslash( $request_data['_length'] ) ) );
|
||||
}
|
||||
if ( isset( $request_data['_width'] ) ) {
|
||||
$product->set_width( wc_clean( wp_unslash( $request_data['_width'] ) ) );
|
||||
}
|
||||
if ( isset( $request_data['_height'] ) ) {
|
||||
$product->set_height( wc_clean( wp_unslash( $request_data['_height'] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_tax_status'] ) ) {
|
||||
$product->set_tax_status( wc_clean( $request_data['_tax_status'] ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_tax_class'] ) ) {
|
||||
$tax_class = sanitize_title( wp_unslash( $request_data['_tax_class'] ) );
|
||||
if ( 'standard' === $tax_class ) {
|
||||
$tax_class = '';
|
||||
}
|
||||
$product->set_tax_class( $tax_class );
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_shipping_class'] ) ) {
|
||||
if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
|
||||
$product->set_shipping_class_id( 0 );
|
||||
} else {
|
||||
$shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
|
||||
$product->set_shipping_class_id( $shipping_class_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_visibility'] ) ) {
|
||||
$product->set_catalog_visibility( wc_clean( $request_data['_visibility'] ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_featured'] ) ) {
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$product->set_featured( wp_unslash( $request_data['_featured'] ) );
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
}
|
||||
|
||||
if ( ! empty( $request_data['_sold_individually'] ) ) {
|
||||
if ( 'yes' === $request_data['_sold_individually'] ) {
|
||||
$product->set_sold_individually( 'yes' );
|
||||
} else {
|
||||
$product->set_sold_individually( '' );
|
||||
}
|
||||
}
|
||||
|
||||
// Handle price - remove dates and set to lowest.
|
||||
$change_price_product_types = apply_filters( 'woocommerce_bulk_edit_save_price_product_types', array( 'simple', 'external' ) );
|
||||
$can_product_type_change_price = false;
|
||||
foreach ( $change_price_product_types as $product_type ) {
|
||||
if ( $product->is_type( $product_type ) ) {
|
||||
$can_product_type_change_price = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $can_product_type_change_price ) {
|
||||
$regular_price_changed = $this->set_new_price( $product, 'regular' );
|
||||
$sale_price_changed = $this->set_new_price( $product, 'sale' );
|
||||
|
||||
if ( $regular_price_changed || $sale_price_changed ) {
|
||||
$product->set_date_on_sale_to( '' );
|
||||
$product->set_date_on_sale_from( '' );
|
||||
|
||||
if ( $product->get_regular_price() < $product->get_sale_price() ) {
|
||||
$product->set_sale_price( '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Stock Data.
|
||||
$was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no';
|
||||
$backorders = $product->get_backorders();
|
||||
$backorders = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : $backorders;
|
||||
|
||||
if ( ! empty( $request_data['_manage_stock'] ) ) {
|
||||
$manage_stock = 'yes' === wc_clean( $request_data['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no';
|
||||
} else {
|
||||
$manage_stock = $was_managing_stock;
|
||||
}
|
||||
|
||||
$stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
|
||||
|
||||
$product->set_manage_stock( $manage_stock );
|
||||
|
||||
if ( 'external' !== $product->get_type() ) {
|
||||
$product->set_backorders( $backorders );
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
|
||||
$change_stock = absint( $request_data['change_stock'] );
|
||||
switch ( $change_stock ) {
|
||||
case 2:
|
||||
wc_update_product_stock( $product, $stock_amount, 'increase', true );
|
||||
break;
|
||||
case 3:
|
||||
wc_update_product_stock( $product, $stock_amount, 'decrease', true );
|
||||
break;
|
||||
default:
|
||||
wc_update_product_stock( $product, $stock_amount, 'set', true );
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Reset values if WooCommerce Setting - Manage Stock status is disabled.
|
||||
$product->set_stock_quantity( '' );
|
||||
$product->set_manage_stock( 'no' );
|
||||
}
|
||||
|
||||
$stock_status = empty( $request_data['_stock_status'] ) ? null : wc_clean( $request_data['_stock_status'] );
|
||||
$product = $this->maybe_update_stock_status( $product, $stock_status );
|
||||
|
||||
$product->save();
|
||||
|
||||
do_action( 'woocommerce_product_bulk_edit_save', $product );
|
||||
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the auto-save functionality for Orders.
|
||||
*/
|
||||
public function disable_autosave() {
|
||||
global $post;
|
||||
|
||||
if ( $post instanceof WP_Post && in_array( get_post_type( $post->ID ), wc_get_order_types( 'order-meta-boxes' ), true ) ) {
|
||||
wp_dequeue_script( 'autosave' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output extra data on post forms.
|
||||
*
|
||||
* @param WP_Post $post Current post object.
|
||||
*/
|
||||
public function edit_form_top( $post ) {
|
||||
echo '<input type="hidden" id="original_post_title" name="original_post_title" value="' . esc_attr( $post->post_title ) . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Change title boxes in admin.
|
||||
*
|
||||
* @param string $text Text to shown.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return string
|
||||
*/
|
||||
public function enter_title_here( $text, $post ) {
|
||||
switch ( $post->post_type ) {
|
||||
case 'product':
|
||||
$text = esc_html__( 'Product name', 'woocommerce' );
|
||||
break;
|
||||
case 'shop_coupon':
|
||||
$text = esc_html__( 'Coupon code', 'woocommerce' );
|
||||
break;
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print coupon description textarea field.
|
||||
*
|
||||
* @param WP_Post $post Current post object.
|
||||
*/
|
||||
public function edit_form_after_title( $post ) {
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ( 'shop_coupon' === $post->post_type ) {
|
||||
?>
|
||||
<textarea id="woocommerce-coupon-description" name="excerpt" cols="5" rows="2" placeholder="<?php esc_attr_e( 'Description (optional)', 'woocommerce' ); ?>"><?php echo $post->post_excerpt; ?></textarea>
|
||||
<?php
|
||||
}
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Hidden default Meta-Boxes.
|
||||
*
|
||||
* @param array $hidden Hidden boxes.
|
||||
* @param object $screen Current screen.
|
||||
* @return array
|
||||
*/
|
||||
public function hidden_meta_boxes( $hidden, $screen ) {
|
||||
if ( 'product' === $screen->post_type && 'post' === $screen->base ) {
|
||||
$hidden = array_merge( $hidden, array( 'postcustom' ) );
|
||||
}
|
||||
|
||||
return $hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output product visibility options.
|
||||
*/
|
||||
public function product_data_visibility() {
|
||||
global $post, $thepostid, $product_object;
|
||||
|
||||
if ( 'product' !== $post->post_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$thepostid = $post->ID;
|
||||
$product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
|
||||
$current_visibility = $product_object->get_catalog_visibility();
|
||||
$current_featured = wc_bool_to_string( $product_object->get_featured() );
|
||||
$visibility_options = wc_get_product_visibility_options();
|
||||
?>
|
||||
<div class="misc-pub-section" id="catalog-visibility">
|
||||
<?php esc_html_e( 'Catalog visibility:', 'woocommerce' ); ?>
|
||||
<strong id="catalog-visibility-display">
|
||||
<?php
|
||||
|
||||
echo isset( $visibility_options[ $current_visibility ] ) ? esc_html( $visibility_options[ $current_visibility ] ) : esc_html( $current_visibility );
|
||||
|
||||
if ( 'yes' === $current_featured ) {
|
||||
echo ', ' . esc_html__( 'Featured', 'woocommerce' );
|
||||
}
|
||||
?>
|
||||
</strong>
|
||||
|
||||
<a href="#catalog-visibility" class="edit-catalog-visibility hide-if-no-js"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
|
||||
|
||||
<div id="catalog-visibility-select" class="hide-if-js">
|
||||
|
||||
<input type="hidden" name="current_visibility" id="current_visibility" value="<?php echo esc_attr( $current_visibility ); ?>" />
|
||||
<input type="hidden" name="current_featured" id="current_featured" value="<?php echo esc_attr( $current_featured ); ?>" />
|
||||
|
||||
<?php
|
||||
echo '<p>' . esc_html__( 'This setting determines which shop pages products will be listed on.', 'woocommerce' ) . '</p>';
|
||||
|
||||
foreach ( $visibility_options as $name => $label ) {
|
||||
echo '<input type="radio" name="_visibility" id="_visibility_' . esc_attr( $name ) . '" value="' . esc_attr( $name ) . '" ' . checked( $current_visibility, $name, false ) . ' data-label="' . esc_attr( $label ) . '" /> <label for="_visibility_' . esc_attr( $name ) . '" class="selectit">' . esc_html( $label ) . '</label><br />';
|
||||
}
|
||||
|
||||
echo '<br /><input type="checkbox" name="_featured" id="_featured" ' . checked( $current_featured, 'yes', false ) . ' /> <label for="_featured">' . esc_html__( 'This is a featured product', 'woocommerce' ) . '</label><br />';
|
||||
?>
|
||||
<p>
|
||||
<a href="#catalog-visibility" class="save-post-visibility hide-if-no-js button"><?php esc_html_e( 'OK', 'woocommerce' ); ?></a>
|
||||
<a href="#catalog-visibility" class="cancel-post-visibility hide-if-no-js"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant downloadable file access to any newly added files on any existing.
|
||||
* orders for this product that have previously been granted downloadable file access.
|
||||
*
|
||||
* @param int $product_id product identifier.
|
||||
* @param int $variation_id optional product variation identifier.
|
||||
* @param array $downloadable_files newly set files.
|
||||
* @deprecated 3.3.0 and moved to post-data class.
|
||||
*/
|
||||
public function process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ) {
|
||||
wc_deprecated_function( 'WC_Admin_Post_Types::process_product_file_download_paths', '3.3', '' );
|
||||
WC_Post_Data::process_product_file_download_paths( $product_id, $variation_id, $downloadable_files );
|
||||
}
|
||||
|
||||
/**
|
||||
* When editing the shop page, we should hide templates.
|
||||
*
|
||||
* @param array $page_templates Templates array.
|
||||
* @param string $theme Classname.
|
||||
* @param WP_Post $post The current post object.
|
||||
* @return array
|
||||
*/
|
||||
public function hide_cpt_archive_templates( $page_templates, $theme, $post ) {
|
||||
$shop_page_id = wc_get_page_id( 'shop' );
|
||||
|
||||
if ( $post && absint( $post->ID ) === $shop_page_id ) {
|
||||
$page_templates = array();
|
||||
}
|
||||
|
||||
return $page_templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notice above the CPT archive.
|
||||
*
|
||||
* @param WP_Post $post The current post object.
|
||||
*/
|
||||
public function show_cpt_archive_notice( $post ) {
|
||||
$shop_page_id = wc_get_page_id( 'shop' );
|
||||
|
||||
if ( $post && absint( $post->ID ) === $shop_page_id ) {
|
||||
echo '<div class="notice notice-info">';
|
||||
/* translators: %s: URL to read more about the shop page. */
|
||||
echo '<p>' . sprintf( wp_kses_post( __( 'This is the WooCommerce shop page. The shop page is a special archive that lists your products. <a href="%s">You can read more about this here</a>.', 'woocommerce' ) ), 'https://woocommerce.com/document/woocommerce-pages/#section-4' ) . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a post display state for special WC pages in the page list table.
|
||||
*
|
||||
* @param array $post_states An array of post display states.
|
||||
* @param WP_Post $post The current post object.
|
||||
*/
|
||||
public function add_display_post_states( $post_states, $post ) {
|
||||
if ( wc_get_page_id( 'shop' ) === $post->ID ) {
|
||||
$post_states['wc_page_for_shop'] = __( 'Shop Page', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( wc_get_page_id( 'cart' ) === $post->ID ) {
|
||||
$post_states['wc_page_for_cart'] = __( 'Cart Page', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( wc_get_page_id( 'checkout' ) === $post->ID ) {
|
||||
$post_states['wc_page_for_checkout'] = __( 'Checkout Page', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( wc_get_page_id( 'myaccount' ) === $post->ID ) {
|
||||
$post_states['wc_page_for_myaccount'] = __( 'My Account Page', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( wc_get_page_id( 'terms' ) === $post->ID ) {
|
||||
$post_states['wc_page_for_terms'] = __( 'Terms and Conditions Page', 'woocommerce' );
|
||||
}
|
||||
|
||||
return $post_states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply product type constraints to stock status.
|
||||
*
|
||||
* @param WC_Product $product The product whose stock status will be adjusted.
|
||||
* @param string|null $stock_status The stock status to use for adjustment, or null if no new stock status has been supplied in the request.
|
||||
* @return WC_Product The supplied product, or the synced product if it was a variable product.
|
||||
*/
|
||||
private function maybe_update_stock_status( $product, $stock_status ) {
|
||||
if ( $product->is_type( 'external' ) ) {
|
||||
// External products are always in stock.
|
||||
$product->set_stock_status( 'instock' );
|
||||
} elseif ( isset( $stock_status ) ) {
|
||||
if ( $product->is_type( 'variable' ) && ! $product->get_manage_stock() ) {
|
||||
// Stock status is determined by children.
|
||||
foreach ( $product->get_children() as $child_id ) {
|
||||
$child = wc_get_product( $child_id );
|
||||
if ( ! $product->get_manage_stock() ) {
|
||||
$child->set_stock_status( $stock_status );
|
||||
$child->save();
|
||||
}
|
||||
}
|
||||
$product = WC_Product_Variable::sync( $product, false );
|
||||
} else {
|
||||
$product->set_stock_status( $stock_status );
|
||||
}
|
||||
}
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new regular or sale price if requested.
|
||||
*
|
||||
* @param WC_Product $product The product to set the new price for.
|
||||
* @param string $price_type 'regular' or 'sale'.
|
||||
* @return bool true if a new price has been set, false otherwise.
|
||||
*/
|
||||
private function set_new_price( $product, $price_type ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$request_data = $this->request_data();
|
||||
|
||||
if ( empty( $request_data[ "change_{$price_type}_price" ] ) || ! isset( $request_data[ "_{$price_type}_price" ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$old_price = (float) $product->{"get_{$price_type}_price"}();
|
||||
$price_changed = false;
|
||||
|
||||
$change_price = absint( $request_data[ "change_{$price_type}_price" ] );
|
||||
$raw_price = wc_clean( wp_unslash( $request_data[ "_{$price_type}_price" ] ) );
|
||||
$is_percentage = (bool) strstr( $raw_price, '%' );
|
||||
$price = wc_format_decimal( $raw_price );
|
||||
|
||||
switch ( $change_price ) {
|
||||
case 1:
|
||||
$new_price = $price;
|
||||
break;
|
||||
case 2:
|
||||
if ( $is_percentage ) {
|
||||
$percent = $price / 100;
|
||||
$new_price = $old_price + ( $old_price * $percent );
|
||||
} else {
|
||||
$new_price = $old_price + $price;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if ( $is_percentage ) {
|
||||
$percent = $price / 100;
|
||||
$new_price = max( 0, $old_price - ( $old_price * $percent ) );
|
||||
} else {
|
||||
$new_price = max( 0, $old_price - $price );
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if ( 'sale' !== $price_type ) {
|
||||
break;
|
||||
}
|
||||
$regular_price = $product->get_regular_price();
|
||||
if ( $is_percentage && is_numeric( $regular_price ) ) {
|
||||
$percent = $price / 100;
|
||||
$new_price = max( 0, $regular_price - ( NumberUtil::round( $regular_price * $percent, wc_get_price_decimals() ) ) );
|
||||
} else {
|
||||
$new_price = max( 0, (float) $regular_price - (float) $price );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ( isset( $new_price ) && $new_price !== $old_price ) {
|
||||
$price_changed = true;
|
||||
$new_price = NumberUtil::round( $new_price, wc_get_price_decimals() );
|
||||
$product->{"set_{$price_type}_price"}( $new_price );
|
||||
}
|
||||
|
||||
return $price_changed;
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current request data ($_REQUEST superglobal).
|
||||
* This method is added to ease unit testing.
|
||||
*
|
||||
* @return array The $_REQUEST superglobal.
|
||||
*/
|
||||
protected function request_data() {
|
||||
return $_REQUEST;
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Post_Types();
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* Add extra profile fields for users in admin
|
||||
*
|
||||
* @author WooThemes
|
||||
* @category Admin
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.4.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_Profile', false ) ) :
|
||||
|
||||
/**
|
||||
* WC_Admin_Profile Class.
|
||||
*/
|
||||
class WC_Admin_Profile {
|
||||
|
||||
/**
|
||||
* Hook in tabs.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'show_user_profile', array( $this, 'add_customer_meta_fields' ) );
|
||||
add_action( 'edit_user_profile', array( $this, 'add_customer_meta_fields' ) );
|
||||
|
||||
add_action( 'personal_options_update', array( $this, 'save_customer_meta_fields' ) );
|
||||
add_action( 'edit_user_profile_update', array( $this, 'save_customer_meta_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Address Fields for the edit user pages.
|
||||
*
|
||||
* @return array Fields to display which are filtered through woocommerce_customer_meta_fields before being returned
|
||||
*/
|
||||
public function get_customer_meta_fields() {
|
||||
$show_fields = apply_filters(
|
||||
'woocommerce_customer_meta_fields',
|
||||
array(
|
||||
'billing' => array(
|
||||
'title' => __( 'Customer billing address', 'woocommerce' ),
|
||||
'fields' => array(
|
||||
'billing_first_name' => array(
|
||||
'label' => __( 'First name', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_last_name' => array(
|
||||
'label' => __( 'Last name', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_company' => array(
|
||||
'label' => __( 'Company', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_address_1' => array(
|
||||
'label' => __( 'Address line 1', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_address_2' => array(
|
||||
'label' => __( 'Address line 2', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_city' => array(
|
||||
'label' => __( 'City', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_postcode' => array(
|
||||
'label' => __( 'Postcode / ZIP', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_country' => array(
|
||||
'label' => __( 'Country / Region', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'class' => 'js_field-country',
|
||||
'type' => 'select',
|
||||
'options' => array( '' => __( 'Select a country / region…', 'woocommerce' ) ) + WC()->countries->get_allowed_countries(),
|
||||
),
|
||||
'billing_state' => array(
|
||||
'label' => __( 'State / County', 'woocommerce' ),
|
||||
'description' => __( 'State / County or state code', 'woocommerce' ),
|
||||
'class' => 'js_field-state',
|
||||
),
|
||||
'billing_phone' => array(
|
||||
'label' => __( 'Phone', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'billing_email' => array(
|
||||
'label' => __( 'Email address', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
),
|
||||
),
|
||||
'shipping' => array(
|
||||
'title' => __( 'Customer shipping address', 'woocommerce' ),
|
||||
'fields' => array(
|
||||
'copy_billing' => array(
|
||||
'label' => __( 'Copy from billing address', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'class' => 'js_copy-billing',
|
||||
'type' => 'button',
|
||||
'text' => __( 'Copy', 'woocommerce' ),
|
||||
),
|
||||
'shipping_first_name' => array(
|
||||
'label' => __( 'First name', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_last_name' => array(
|
||||
'label' => __( 'Last name', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_company' => array(
|
||||
'label' => __( 'Company', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_address_1' => array(
|
||||
'label' => __( 'Address line 1', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_address_2' => array(
|
||||
'label' => __( 'Address line 2', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_city' => array(
|
||||
'label' => __( 'City', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_postcode' => array(
|
||||
'label' => __( 'Postcode / ZIP', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
'shipping_country' => array(
|
||||
'label' => __( 'Country / Region', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'class' => 'js_field-country',
|
||||
'type' => 'select',
|
||||
'options' => array( '' => __( 'Select a country / region…', 'woocommerce' ) ) + WC()->countries->get_allowed_countries(),
|
||||
),
|
||||
'shipping_state' => array(
|
||||
'label' => __( 'State / County', 'woocommerce' ),
|
||||
'description' => __( 'State / County or state code', 'woocommerce' ),
|
||||
'class' => 'js_field-state',
|
||||
),
|
||||
'shipping_phone' => array(
|
||||
'label' => __( 'Phone', 'woocommerce' ),
|
||||
'description' => '',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
return $show_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Address Fields on edit user pages.
|
||||
*
|
||||
* @param WP_User $user
|
||||
*/
|
||||
public function add_customer_meta_fields( $user ) {
|
||||
if ( ! apply_filters( 'woocommerce_current_user_can_edit_customer_meta_fields', current_user_can( 'manage_woocommerce' ), $user->ID ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$show_fields = $this->get_customer_meta_fields();
|
||||
|
||||
foreach ( $show_fields as $fieldset_key => $fieldset ) :
|
||||
?>
|
||||
<h2><?php echo $fieldset['title']; ?></h2>
|
||||
<table class="form-table" id="<?php echo esc_attr( 'fieldset-' . $fieldset_key ); ?>">
|
||||
<?php foreach ( $fieldset['fields'] as $key => $field ) : ?>
|
||||
<tr>
|
||||
<th>
|
||||
<label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<?php if ( ! empty( $field['type'] ) && 'select' === $field['type'] ) : ?>
|
||||
<select name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" class="<?php echo esc_attr( $field['class'] ); ?>" style="width: 25em;">
|
||||
<?php
|
||||
$selected = esc_attr( get_user_meta( $user->ID, $key, true ) );
|
||||
foreach ( $field['options'] as $option_key => $option_value ) :
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $selected, $option_key, true ); ?>><?php echo esc_html( $option_value ); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php elseif ( ! empty( $field['type'] ) && 'checkbox' === $field['type'] ) : ?>
|
||||
<input type="checkbox" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="1" class="<?php echo esc_attr( $field['class'] ); ?>" <?php checked( (int) get_user_meta( $user->ID, $key, true ), 1, true ); ?> />
|
||||
<?php elseif ( ! empty( $field['type'] ) && 'button' === $field['type'] ) : ?>
|
||||
<button type="button" id="<?php echo esc_attr( $key ); ?>" class="button <?php echo esc_attr( $field['class'] ); ?>"><?php echo esc_html( $field['text'] ); ?></button>
|
||||
<?php else : ?>
|
||||
<input type="text" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" value="<?php echo esc_attr( $this->get_user_meta( $user->ID, $key ) ); ?>" class="<?php echo ( ! empty( $field['class'] ) ? esc_attr( $field['class'] ) : 'regular-text' ); ?>" />
|
||||
<?php endif; ?>
|
||||
<p class="description"><?php echo wp_kses_post( $field['description'] ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php
|
||||
endforeach;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Address Fields on edit user pages.
|
||||
*
|
||||
* @param int $user_id User ID of the user being saved
|
||||
*/
|
||||
public function save_customer_meta_fields( $user_id ) {
|
||||
if ( ! apply_filters( 'woocommerce_current_user_can_edit_customer_meta_fields', current_user_can( 'manage_woocommerce' ), $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$save_fields = $this->get_customer_meta_fields();
|
||||
|
||||
foreach ( $save_fields as $fieldset_type => $fieldset ) {
|
||||
|
||||
foreach ( $fieldset['fields'] as $key => $field ) {
|
||||
|
||||
if ( isset( $field['type'] ) && 'checkbox' === $field['type'] ) {
|
||||
update_user_meta( $user_id, $key, isset( $_POST[ $key ] ) );
|
||||
} elseif ( isset( $_POST[ $key ] ) ) {
|
||||
update_user_meta( $user_id, $key, wc_clean( $_POST[ $key ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Skip firing the action for any non-internal fieldset types.
|
||||
if ( ! in_array( $fieldset_type, array( 'billing', 'shipping' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fieldset type is an internal address type.
|
||||
$address_type = $fieldset_type;
|
||||
|
||||
/**
|
||||
* Hook: woocommerce_customer_save_address.
|
||||
*
|
||||
* Fires after a customer address has been saved on the user profile admin screen.
|
||||
*
|
||||
* @since 8.5.0
|
||||
* @param int $user_id User ID being saved.
|
||||
* @param string $address_type Type of address; 'billing' or 'shipping'.
|
||||
*/
|
||||
do_action( 'woocommerce_customer_save_address', $user_id, $address_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user meta for a given key, with fallbacks to core user info for pre-existing fields.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param int $user_id User ID of the user being edited
|
||||
* @param string $key Key for user meta field
|
||||
* @return string
|
||||
*/
|
||||
protected function get_user_meta( $user_id, $key ) {
|
||||
$value = get_user_meta( $user_id, $key, true );
|
||||
$existing_fields = array( 'billing_first_name', 'billing_last_name' );
|
||||
if ( ! $value && in_array( $key, $existing_fields ) ) {
|
||||
$value = get_user_meta( $user_id, str_replace( 'billing_', '', $key ), true );
|
||||
} elseif ( ! $value && ( 'billing_email' === $key ) ) {
|
||||
$user = get_userdata( $user_id );
|
||||
$value = $user->user_email;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
|
||||
return new WC_Admin_Profile();
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Reports
|
||||
*
|
||||
* Functions used for displaying sales and customer reports in admin.
|
||||
*
|
||||
* @author WooThemes
|
||||
* @category Admin
|
||||
* @package WooCommerce\Admin\Reports
|
||||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_Reports', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Reports Class.
|
||||
*/
|
||||
class WC_Admin_Reports {
|
||||
|
||||
/**
|
||||
* Handles output of the reports page in admin.
|
||||
*/
|
||||
public static function output() {
|
||||
$reports = self::get_reports();
|
||||
$first_tab = array_keys( $reports );
|
||||
$current_tab = ! empty( $_GET['tab'] ) && array_key_exists( $_GET['tab'], $reports ) ? sanitize_title( $_GET['tab'] ) : $first_tab[0];
|
||||
$current_report = isset( $_GET['report'] ) ? sanitize_title( $_GET['report'] ) : current( array_keys( $reports[ $current_tab ]['reports'] ) );
|
||||
|
||||
include_once dirname( __FILE__ ) . '/reports/class-wc-admin-report.php';
|
||||
include_once dirname( __FILE__ ) . '/views/html-admin-page-reports.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the definitions for the reports to show in admin.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_reports() {
|
||||
$reports = array(
|
||||
'orders' => array(
|
||||
'title' => __( 'Orders', 'woocommerce' ),
|
||||
'reports' => array(
|
||||
'sales_by_date' => array(
|
||||
'title' => __( 'Sales by date', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'sales_by_product' => array(
|
||||
'title' => __( 'Sales by product', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'sales_by_category' => array(
|
||||
'title' => __( 'Sales by category', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'coupon_usage' => array(
|
||||
'title' => __( 'Coupons by date', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'downloads' => array(
|
||||
'title' => __( 'Customer downloads', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'customers' => array(
|
||||
'title' => __( 'Customers', 'woocommerce' ),
|
||||
'reports' => array(
|
||||
'customers' => array(
|
||||
'title' => __( 'Customers vs. guests', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'customer_list' => array(
|
||||
'title' => __( 'Customer list', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'stock' => array(
|
||||
'title' => __( 'Stock', 'woocommerce' ),
|
||||
'reports' => array(
|
||||
'low_in_stock' => array(
|
||||
'title' => __( 'Low in stock', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'out_of_stock' => array(
|
||||
'title' => __( 'Out of stock', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'most_stocked' => array(
|
||||
'title' => __( 'Most stocked', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if ( wc_tax_enabled() ) {
|
||||
$reports['taxes'] = array(
|
||||
'title' => __( 'Taxes', 'woocommerce' ),
|
||||
'reports' => array(
|
||||
'taxes_by_code' => array(
|
||||
'title' => __( 'Taxes by code', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
'taxes_by_date' => array(
|
||||
'title' => __( 'Taxes by date', 'woocommerce' ),
|
||||
'description' => '',
|
||||
'hide_title' => true,
|
||||
'callback' => array( __CLASS__, 'get_report' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$reports = apply_filters( 'woocommerce_admin_reports', $reports );
|
||||
$reports = apply_filters( 'woocommerce_reports_charts', $reports ); // Backwards compatibility.
|
||||
|
||||
foreach ( $reports as $key => $report_group ) {
|
||||
if ( isset( $reports[ $key ]['charts'] ) ) {
|
||||
$reports[ $key ]['reports'] = $reports[ $key ]['charts'];
|
||||
}
|
||||
|
||||
foreach ( $reports[ $key ]['reports'] as $report_key => $report ) {
|
||||
if ( isset( $reports[ $key ]['reports'][ $report_key ]['function'] ) ) {
|
||||
$reports[ $key ]['reports'][ $report_key ]['callback'] = $reports[ $key ]['reports'][ $report_key ]['function'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $reports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a report from our reports subfolder.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public static function get_report( $name ) {
|
||||
$name = sanitize_title( str_replace( '_', '-', $name ) );
|
||||
$class = 'WC_Report_' . str_replace( '-', '_', $name );
|
||||
|
||||
include_once apply_filters( 'wc_admin_reports_path', 'reports/class-wc-report-' . $name . '.php', $name, $class );
|
||||
|
||||
if ( ! class_exists( $class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$report = new $class();
|
||||
$report->output_report();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,466 @@
|
||||
<?php
|
||||
/**
|
||||
* Debug/Status page
|
||||
*
|
||||
* @package WooCommerce\Admin\System Status
|
||||
* @version 2.2.0
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\PageController as LoggingPageController;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Status Class.
|
||||
*/
|
||||
class WC_Admin_Status {
|
||||
/**
|
||||
* An instance of the DB log handler list table.
|
||||
*
|
||||
* @var WC_Admin_Log_Table_List
|
||||
*/
|
||||
private static $db_log_list_table;
|
||||
|
||||
/**
|
||||
* Handles output of the reports page in admin.
|
||||
*/
|
||||
public static function output() {
|
||||
include_once __DIR__ . '/views/html-admin-page-status.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles output of report.
|
||||
*/
|
||||
public static function status_report() {
|
||||
include_once __DIR__ . '/views/html-admin-page-status-report.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles output of tools.
|
||||
*/
|
||||
public static function status_tools() {
|
||||
if ( ! class_exists( 'WC_REST_System_Status_Tools_Controller' ) ) {
|
||||
wp_die( 'Cannot load the REST API to access WC_REST_System_Status_Tools_Controller.' );
|
||||
}
|
||||
|
||||
$tools = self::get_tools();
|
||||
$tool_requires_refresh = false;
|
||||
|
||||
if ( ! empty( $_GET['action'] ) && ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), 'debug_action' ) ) { // WPCS: input var ok, sanitization ok.
|
||||
$tools_controller = new WC_REST_System_Status_Tools_Controller();
|
||||
$action = wc_clean( wp_unslash( $_GET['action'] ) ); // WPCS: input var ok.
|
||||
|
||||
if ( array_key_exists( $action, $tools ) ) {
|
||||
$response = $tools_controller->execute_tool( $action );
|
||||
|
||||
$tool = $tools[ $action ];
|
||||
$tool_requires_refresh = $tool['requires_refresh'] ?? false;
|
||||
$tool = array(
|
||||
'id' => $action,
|
||||
'name' => $tool['name'],
|
||||
'action' => $tool['button'],
|
||||
'description' => $tool['desc'],
|
||||
'disabled' => $tool['disabled'] ?? false,
|
||||
);
|
||||
$tool = array_merge( $tool, $response );
|
||||
|
||||
/**
|
||||
* Fires after a WooCommerce system status tool has been executed.
|
||||
*
|
||||
* @param array $tool Details about the tool that has been executed.
|
||||
*/
|
||||
do_action( 'woocommerce_system_status_tool_executed', $tool );
|
||||
} else {
|
||||
$response = array(
|
||||
'success' => false,
|
||||
'message' => __( 'Tool does not exist.', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( $response['success'] ) {
|
||||
echo '<div class="updated inline"><p>' . esc_html( $response['message'] ) . '</p></div>';
|
||||
} else {
|
||||
echo '<div class="error inline"><p>' . esc_html( $response['message'] ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Display message if settings settings have been saved.
|
||||
if ( isset( $_REQUEST['settings-updated'] ) ) { // WPCS: input var ok.
|
||||
echo '<div class="updated inline"><p>' . esc_html__( 'Your changes have been saved.', 'woocommerce' ) . '</p></div>';
|
||||
}
|
||||
|
||||
if ( $tool_requires_refresh ) {
|
||||
$tools = self::get_tools();
|
||||
}
|
||||
|
||||
include_once __DIR__ . '/views/html-admin-page-status-tools.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools.
|
||||
*
|
||||
* @return array of tools
|
||||
*/
|
||||
public static function get_tools() {
|
||||
$tools_controller = new WC_REST_System_Status_Tools_Controller();
|
||||
return $tools_controller->get_tools();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the logs page.
|
||||
*/
|
||||
public static function status_logs() {
|
||||
wc_get_container()->get( LoggingPageController::class )->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the log page contents for file log handler.
|
||||
*/
|
||||
public static function status_logs_file() {
|
||||
$logs = self::scan_log_files();
|
||||
|
||||
if ( ! empty( $_REQUEST['log_file'] ) && isset( $logs[ sanitize_title( wp_unslash( $_REQUEST['log_file'] ) ) ] ) ) { // WPCS: input var ok, CSRF ok.
|
||||
$viewed_log = $logs[ sanitize_title( wp_unslash( $_REQUEST['log_file'] ) ) ]; // WPCS: input var ok, CSRF ok.
|
||||
} elseif ( ! empty( $logs ) ) {
|
||||
$viewed_log = current( $logs );
|
||||
}
|
||||
|
||||
$handle = ! empty( $viewed_log ) ? self::get_log_file_handle( $viewed_log ) : '';
|
||||
|
||||
if ( ! empty( $_REQUEST['handle'] ) ) { // WPCS: input var ok, CSRF ok.
|
||||
self::remove_log();
|
||||
}
|
||||
|
||||
include_once __DIR__ . '/views/html-admin-page-status-logs.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the log page contents for db log handler.
|
||||
*/
|
||||
public static function status_logs_db() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce handled in flush_db_logs().
|
||||
if ( isset( $_REQUEST['flush-logs'] ) ) {
|
||||
self::flush_db_logs();
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce handled in log_table_bulk_actions().
|
||||
if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['log'] ) ) {
|
||||
self::log_table_bulk_actions();
|
||||
}
|
||||
|
||||
$log_table_list = self::get_db_log_list_table();
|
||||
$log_table_list->prepare_items();
|
||||
|
||||
include_once __DIR__ . '/views/html-admin-page-status-logs-db.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve metadata from a file. Based on WP Core's get_file_data function.
|
||||
*
|
||||
* @since 2.1.1
|
||||
* @param string $file Path to the file.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_file_version( $file ) {
|
||||
|
||||
// Avoid notices if file does not exist.
|
||||
if ( ! file_exists( $file ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// We don't need to write to the file, so just open for reading.
|
||||
$fp = fopen( $file, 'r' ); // @codingStandardsIgnoreLine.
|
||||
|
||||
// Pull only the first 8kiB of the file in.
|
||||
$file_data = fread( $fp, 8192 ); // @codingStandardsIgnoreLine.
|
||||
|
||||
// PHP will close file handle, but we are good citizens.
|
||||
fclose( $fp ); // @codingStandardsIgnoreLine.
|
||||
|
||||
// Make sure we catch CR-only line endings.
|
||||
$file_data = str_replace( "\r", "\n", $file_data );
|
||||
$version = '';
|
||||
|
||||
if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( '@version', '/' ) . '(.*)$/mi', $file_data, $match ) && $match[1] ) {
|
||||
$version = _cleanup_header_comment( $match[1] );
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the log file handle.
|
||||
*
|
||||
* @param string $filename Filename to get the handle for.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_log_file_handle( $filename ) {
|
||||
return substr( $filename, 0, strlen( $filename ) > 48 ? strlen( $filename ) - 48 : strlen( $filename ) - 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the template files.
|
||||
*
|
||||
* @param string $template_path Path to the template directory.
|
||||
* @return array
|
||||
*/
|
||||
public static function scan_template_files( $template_path ) {
|
||||
$files = @scandir( $template_path ); // @codingStandardsIgnoreLine.
|
||||
$result = array();
|
||||
|
||||
if ( ! empty( $files ) ) {
|
||||
|
||||
foreach ( $files as $key => $value ) {
|
||||
|
||||
if ( ! in_array( $value, array( '.', '..' ), true ) ) {
|
||||
|
||||
if ( is_dir( $template_path . DIRECTORY_SEPARATOR . $value ) ) {
|
||||
$sub_files = self::scan_template_files( $template_path . DIRECTORY_SEPARATOR . $value );
|
||||
foreach ( $sub_files as $sub_file ) {
|
||||
$result[] = $value . DIRECTORY_SEPARATOR . $sub_file;
|
||||
}
|
||||
} else {
|
||||
$result[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the log files.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function scan_log_files() {
|
||||
return WC_Log_Handler_File::get_log_files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest version of a theme by slug.
|
||||
*
|
||||
* @param object $theme WP_Theme object.
|
||||
* @return string Version number if found.
|
||||
*/
|
||||
public static function get_latest_theme_version( $theme ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/theme.php';
|
||||
|
||||
$api = themes_api(
|
||||
'theme_information',
|
||||
array(
|
||||
'slug' => $theme->get_stylesheet(),
|
||||
'fields' => array(
|
||||
'sections' => false,
|
||||
'tags' => false,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$update_theme_version = 0;
|
||||
|
||||
// Check .org for updates.
|
||||
if ( is_object( $api ) && ! is_wp_error( $api ) && isset( $api->version ) ) {
|
||||
$update_theme_version = $api->version;
|
||||
} elseif ( strstr( $theme->{'Author URI'}, 'woothemes' ) ) { // Check WooThemes Theme Version.
|
||||
$theme_dir = substr( strtolower( str_replace( ' ', '', $theme->Name ) ), 0, 45 ); // @codingStandardsIgnoreLine.
|
||||
$theme_version_data = get_transient( $theme_dir . '_version_data' );
|
||||
|
||||
if ( false === $theme_version_data ) {
|
||||
$theme_changelog = wp_safe_remote_get( 'http://dzv365zjfbd8v.cloudfront.net/changelogs/' . $theme_dir . '/changelog.txt' );
|
||||
$cl_lines = explode( "\n", wp_remote_retrieve_body( $theme_changelog ) );
|
||||
if ( ! empty( $cl_lines ) ) {
|
||||
foreach ( $cl_lines as $line_num => $cl_line ) {
|
||||
if ( preg_match( '/^[0-9]/', $cl_line ) ) {
|
||||
$theme_date = str_replace( '.', '-', trim( substr( $cl_line, 0, strpos( $cl_line, '-' ) ) ) );
|
||||
$theme_version = preg_replace( '~[^0-9,.]~', '', stristr( $cl_line, 'version' ) );
|
||||
$theme_update = trim( str_replace( '*', '', $cl_lines[ $line_num + 1 ] ) );
|
||||
$theme_version_data = array(
|
||||
'date' => $theme_date,
|
||||
'version' => $theme_version,
|
||||
'update' => $theme_update,
|
||||
'changelog' => $theme_changelog,
|
||||
);
|
||||
set_transient( $theme_dir . '_version_data', $theme_version_data, DAY_IN_SECONDS );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $theme_version_data['version'] ) ) {
|
||||
$update_theme_version = $theme_version_data['version'];
|
||||
}
|
||||
}
|
||||
|
||||
return $update_theme_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove/delete the chosen file.
|
||||
*/
|
||||
public static function remove_log() {
|
||||
if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), 'remove_log' ) ) { // WPCS: input var ok, sanitization ok.
|
||||
wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $_REQUEST['handle'] ) ) { // WPCS: input var ok.
|
||||
$log_handler = new WC_Log_Handler_File();
|
||||
$log_handler->remove( wp_unslash( $_REQUEST['handle'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a stored instance of the DB log list table class.
|
||||
*
|
||||
* @return WC_Admin_Log_Table_List
|
||||
*/
|
||||
public static function get_db_log_list_table() {
|
||||
if ( is_null( self::$db_log_list_table ) ) {
|
||||
self::$db_log_list_table = new WC_Admin_Log_Table_List();
|
||||
}
|
||||
|
||||
return self::$db_log_list_table;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear DB log table.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
private static function flush_db_logs() {
|
||||
check_admin_referer( 'bulk-logs' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to manage log entries.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
WC_Log_Handler_DB::flush();
|
||||
|
||||
$sendback = wp_sanitize_redirect( admin_url( 'admin.php?page=wc-status&tab=logs' ) );
|
||||
|
||||
wp_safe_redirect( $sendback );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk DB log table actions.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
private static function log_table_bulk_actions() {
|
||||
check_admin_referer( 'bulk-logs' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to manage log entries.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$log_ids = (array) filter_input( INPUT_GET, 'log', FILTER_CALLBACK, array( 'options' => 'absint' ) );
|
||||
|
||||
$action = self::get_db_log_list_table()->current_action();
|
||||
|
||||
if ( 'delete' === $action ) {
|
||||
WC_Log_Handler_DB::delete( $log_ids );
|
||||
|
||||
$sendback = remove_query_arg( array( 'action', 'action2', 'log', '_wpnonce', '_wp_http_referer' ), wp_get_referer() );
|
||||
|
||||
wp_safe_redirect( $sendback );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints table info if a base table is not present.
|
||||
*/
|
||||
private static function output_tables_info() {
|
||||
$missing_tables = WC_Install::verify_base_tables( false );
|
||||
if ( 0 === count( $missing_tables ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<br>
|
||||
<strong style="color:#a00;">
|
||||
<span class="dashicons dashicons-warning"></span>
|
||||
<?php
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
// translators: Comma separated list of missing tables.
|
||||
__( 'Missing base tables: %s. Some WooCommerce functionality may not work as expected.', 'woocommerce' ),
|
||||
implode( ', ', $missing_tables )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</strong>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the information about plugins for the system status report.
|
||||
* Used for both active and inactive plugins sections.
|
||||
*
|
||||
* @param array $plugins List of plugins to display.
|
||||
* @param array $untested_plugins List of plugins that haven't been tested with the current WooCommerce version.
|
||||
* @return void
|
||||
*/
|
||||
private static function output_plugins_info( $plugins, $untested_plugins ) {
|
||||
$wc_version = Constants::get_constant( 'WC_VERSION' );
|
||||
|
||||
if ( 'major' === Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
|
||||
// Since we're only testing against major, we don't need to show minor and patch version.
|
||||
$wc_version = $wc_version[0] . '.0';
|
||||
}
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
if ( ! empty( $plugin['name'] ) ) {
|
||||
// Link the plugin name to the plugin url if available.
|
||||
$plugin_name = esc_html( $plugin['name'] );
|
||||
if ( ! empty( $plugin['url'] ) ) {
|
||||
$plugin_name = '<a href="' . esc_url( $plugin['url'] ) . '" aria-label="' . esc_attr__( 'Visit plugin homepage', 'woocommerce' ) . '" target="_blank">' . $plugin_name . '</a>';
|
||||
}
|
||||
|
||||
$has_newer_version = false;
|
||||
$version_string = $plugin['version'];
|
||||
$network_string = '';
|
||||
if ( strstr( $plugin['url'], 'woothemes.com' ) || strstr( $plugin['url'], 'woocommerce.com' ) || strstr( $plugin['url'], 'woo.com' ) ) {
|
||||
if ( ! empty( $plugin['version_latest'] ) && version_compare( $plugin['version_latest'], $plugin['version'], '>' ) ) {
|
||||
/* translators: 1: current version. 2: latest version */
|
||||
$version_string = sprintf( __( '%1$s (update to version %2$s is available)', 'woocommerce' ), $plugin['version'], $plugin['version_latest'] );
|
||||
}
|
||||
|
||||
if ( false !== $plugin['network_activated'] ) {
|
||||
$network_string = ' – <strong style="color: black;">' . esc_html__( 'Network enabled', 'woocommerce' ) . '</strong>';
|
||||
}
|
||||
}
|
||||
$untested_string = '';
|
||||
if ( array_key_exists( $plugin['plugin'], $untested_plugins ) ) {
|
||||
$untested_string = ' – <strong style="color: #a00;">';
|
||||
|
||||
/* translators: %s: version */
|
||||
$untested_string .= esc_html( sprintf( __( 'Installed version not tested with active version of WooCommerce %s', 'woocommerce' ), $wc_version ) );
|
||||
|
||||
$untested_string .= '</strong>';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo wp_kses_post( $plugin_name ); ?></td>
|
||||
<td class="help"> </td>
|
||||
<td>
|
||||
<?php
|
||||
/* translators: %s: plugin author */
|
||||
printf( esc_html__( 'by %s', 'woocommerce' ), esc_html( $plugin['author_name'] ) );
|
||||
echo ' – ' . esc_html( $version_string ) . $untested_string . $network_string; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles taxonomies in admin
|
||||
*
|
||||
* @class WC_Admin_Taxonomies
|
||||
* @version 2.3.10
|
||||
* @package WooCommerce\Admin
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Internal\AssignDefaultCategory;
|
||||
|
||||
/**
|
||||
* WC_Admin_Taxonomies class.
|
||||
*/
|
||||
class WC_Admin_Taxonomies {
|
||||
|
||||
/**
|
||||
* Class instance.
|
||||
*
|
||||
* @var WC_Admin_Taxonomies instance
|
||||
*/
|
||||
protected static $instance = false;
|
||||
|
||||
/**
|
||||
* Default category ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $default_cat_id = 0;
|
||||
|
||||
/**
|
||||
* Get class instance
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Default category ID.
|
||||
$this->default_cat_id = get_option( 'default_product_cat', 0 );
|
||||
|
||||
// Category/term ordering.
|
||||
add_action( 'create_term', array( $this, 'create_term' ), 5, 3 );
|
||||
add_action(
|
||||
'delete_product_cat',
|
||||
function() {
|
||||
wc_get_container()->get( AssignDefaultCategory::class )->schedule_action();
|
||||
}
|
||||
);
|
||||
|
||||
// Add form.
|
||||
add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) );
|
||||
add_action( 'product_cat_edit_form_fields', array( $this, 'edit_category_fields' ), 10 );
|
||||
add_action( 'created_term', array( $this, 'save_category_fields' ), 10, 3 );
|
||||
add_action( 'edit_term', array( $this, 'save_category_fields' ), 10, 3 );
|
||||
|
||||
// Add columns.
|
||||
add_filter( 'manage_edit-product_cat_columns', array( $this, 'product_cat_columns' ) );
|
||||
add_filter( 'manage_product_cat_custom_column', array( $this, 'product_cat_column' ), 10, 3 );
|
||||
|
||||
// Add row actions.
|
||||
add_filter( 'product_cat_row_actions', array( $this, 'product_cat_row_actions' ), 10, 2 );
|
||||
add_filter( 'admin_init', array( $this, 'handle_product_cat_row_actions' ) );
|
||||
|
||||
// Taxonomy page descriptions.
|
||||
add_action( 'product_cat_pre_add_form', array( $this, 'product_cat_description' ) );
|
||||
add_action( 'after-product_cat-table', array( $this, 'product_cat_notes' ) );
|
||||
|
||||
$attribute_taxonomies = wc_get_attribute_taxonomies();
|
||||
|
||||
if ( ! empty( $attribute_taxonomies ) ) {
|
||||
foreach ( $attribute_taxonomies as $attribute ) {
|
||||
add_action( 'pa_' . $attribute->attribute_name . '_pre_add_form', array( $this, 'product_attribute_description' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Maintain hierarchy of terms.
|
||||
add_filter( 'wp_terms_checklist_args', array( $this, 'disable_checked_ontop' ) );
|
||||
|
||||
// Admin footer scripts for this product categories admin screen.
|
||||
add_action( 'admin_footer', array( $this, 'scripts_at_product_cat_screen_footer' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Order term when created (put in position 0).
|
||||
*
|
||||
* @param mixed $term_id Term ID.
|
||||
* @param mixed $tt_id Term taxonomy ID.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function create_term( $term_id, $tt_id = '', $taxonomy = '' ) {
|
||||
if ( 'product_cat' !== $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_term_meta( $term_id, 'order', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* When a term is deleted, delete its meta.
|
||||
*
|
||||
* @deprecated 3.6.0 No longer needed.
|
||||
* @param mixed $term_id Term ID.
|
||||
*/
|
||||
public function delete_term( $term_id ) {
|
||||
wc_deprecated_function( 'delete_term', '3.6' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Category thumbnail fields.
|
||||
*/
|
||||
public function add_category_fields() {
|
||||
?>
|
||||
<div class="form-field term-display-type-wrap">
|
||||
<label for="display_type"><?php esc_html_e( 'Display type', 'woocommerce' ); ?></label>
|
||||
<select id="display_type" name="display_type" class="postform">
|
||||
<option value=""><?php esc_html_e( 'Default', 'woocommerce' ); ?></option>
|
||||
<option value="products"><?php esc_html_e( 'Products', 'woocommerce' ); ?></option>
|
||||
<option value="subcategories"><?php esc_html_e( 'Subcategories', 'woocommerce' ); ?></option>
|
||||
<option value="both"><?php esc_html_e( 'Both', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field term-thumbnail-wrap">
|
||||
<label><?php esc_html_e( 'Thumbnail', 'woocommerce' ); ?></label>
|
||||
<div id="product_cat_thumbnail" style="float: left; margin-right: 10px;"><img src="<?php echo esc_url( wc_placeholder_img_src() ); ?>" width="60px" height="60px" /></div>
|
||||
<div style="line-height: 60px;">
|
||||
<input type="hidden" id="product_cat_thumbnail_id" name="product_cat_thumbnail_id" />
|
||||
<button type="button" class="upload_image_button button"><?php esc_html_e( 'Upload/Add image', 'woocommerce' ); ?></button>
|
||||
<button type="button" class="remove_image_button button"><?php esc_html_e( 'Remove image', 'woocommerce' ); ?></button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
// Only show the "remove image" button when needed
|
||||
if ( ! jQuery( '#product_cat_thumbnail_id' ).val() ) {
|
||||
jQuery( '.remove_image_button' ).hide();
|
||||
}
|
||||
|
||||
// Uploading files
|
||||
var file_frame;
|
||||
|
||||
jQuery( document ).on( 'click', '.upload_image_button', function( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( file_frame ) {
|
||||
file_frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media frame.
|
||||
file_frame = wp.media.frames.downloadable_file = wp.media({
|
||||
title: '<?php esc_html_e( 'Choose an image', 'woocommerce' ); ?>',
|
||||
button: {
|
||||
text: '<?php esc_html_e( 'Use image', 'woocommerce' ); ?>'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
// When an image is selected, run a callback.
|
||||
file_frame.on( 'select', function() {
|
||||
var attachment = file_frame.state().get( 'selection' ).first().toJSON();
|
||||
var attachment_thumbnail = attachment.sizes.thumbnail || attachment.sizes.full;
|
||||
|
||||
jQuery( '#product_cat_thumbnail_id' ).val( attachment.id );
|
||||
jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', attachment_thumbnail.url );
|
||||
jQuery( '.remove_image_button' ).show();
|
||||
});
|
||||
|
||||
// Finally, open the modal.
|
||||
file_frame.open();
|
||||
});
|
||||
|
||||
jQuery( document ).on( 'click', '.remove_image_button', function() {
|
||||
jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>' );
|
||||
jQuery( '#product_cat_thumbnail_id' ).val( '' );
|
||||
jQuery( '.remove_image_button' ).hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
jQuery( document ).ajaxComplete( function( event, request, options ) {
|
||||
if ( request && 4 === request.readyState && 200 === request.status
|
||||
&& options.data && 0 <= options.data.indexOf( 'action=add-tag' ) ) {
|
||||
|
||||
var res = wpAjax.parseAjaxResponse( request.responseXML, 'ajax-response' );
|
||||
if ( ! res || res.errors ) {
|
||||
return;
|
||||
}
|
||||
// Clear Thumbnail fields on submit
|
||||
jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>' );
|
||||
jQuery( '#product_cat_thumbnail_id' ).val( '' );
|
||||
jQuery( '.remove_image_button' ).hide();
|
||||
// Clear Display type field on submit
|
||||
jQuery( '#display_type' ).val( '' );
|
||||
return;
|
||||
}
|
||||
} );
|
||||
|
||||
</script>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit category thumbnail field.
|
||||
*
|
||||
* @param mixed $term Term (category) being edited.
|
||||
*/
|
||||
public function edit_category_fields( $term ) {
|
||||
|
||||
$display_type = get_term_meta( $term->term_id, 'display_type', true );
|
||||
$thumbnail_id = absint( get_term_meta( $term->term_id, 'thumbnail_id', true ) );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
$image = wp_get_attachment_thumb_url( $thumbnail_id );
|
||||
} else {
|
||||
$image = wc_placeholder_img_src();
|
||||
}
|
||||
?>
|
||||
<tr class="form-field term-display-type-wrap">
|
||||
<th scope="row" valign="top"><label><?php esc_html_e( 'Display type', 'woocommerce' ); ?></label></th>
|
||||
<td>
|
||||
<select id="display_type" name="display_type" class="postform">
|
||||
<option value="" <?php selected( '', $display_type ); ?>><?php esc_html_e( 'Default', 'woocommerce' ); ?></option>
|
||||
<option value="products" <?php selected( 'products', $display_type ); ?>><?php esc_html_e( 'Products', 'woocommerce' ); ?></option>
|
||||
<option value="subcategories" <?php selected( 'subcategories', $display_type ); ?>><?php esc_html_e( 'Subcategories', 'woocommerce' ); ?></option>
|
||||
<option value="both" <?php selected( 'both', $display_type ); ?>><?php esc_html_e( 'Both', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-field term-thumbnail-wrap">
|
||||
<th scope="row" valign="top"><label><?php esc_html_e( 'Thumbnail', 'woocommerce' ); ?></label></th>
|
||||
<td>
|
||||
<div id="product_cat_thumbnail" style="float: left; margin-right: 10px;"><img src="<?php echo esc_url( $image ); ?>" width="60px" height="60px" /></div>
|
||||
<div style="line-height: 60px;">
|
||||
<input type="hidden" id="product_cat_thumbnail_id" name="product_cat_thumbnail_id" value="<?php echo esc_attr( $thumbnail_id ); ?>" />
|
||||
<button type="button" class="upload_image_button button"><?php esc_html_e( 'Upload/Add image', 'woocommerce' ); ?></button>
|
||||
<button type="button" class="remove_image_button button"><?php esc_html_e( 'Remove image', 'woocommerce' ); ?></button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
// Only show the "remove image" button when needed
|
||||
if ( '0' === jQuery( '#product_cat_thumbnail_id' ).val() ) {
|
||||
jQuery( '.remove_image_button' ).hide();
|
||||
}
|
||||
|
||||
// Uploading files
|
||||
var file_frame;
|
||||
|
||||
jQuery( document ).on( 'click', '.upload_image_button', function( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( file_frame ) {
|
||||
file_frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the media frame.
|
||||
file_frame = wp.media.frames.downloadable_file = wp.media({
|
||||
title: '<?php esc_html_e( 'Choose an image', 'woocommerce' ); ?>',
|
||||
button: {
|
||||
text: '<?php esc_html_e( 'Use image', 'woocommerce' ); ?>'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
// When an image is selected, run a callback.
|
||||
file_frame.on( 'select', function() {
|
||||
var attachment = file_frame.state().get( 'selection' ).first().toJSON();
|
||||
var attachment_thumbnail = attachment.sizes.thumbnail || attachment.sizes.full;
|
||||
|
||||
jQuery( '#product_cat_thumbnail_id' ).val( attachment.id );
|
||||
jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', attachment_thumbnail.url );
|
||||
jQuery( '.remove_image_button' ).show();
|
||||
});
|
||||
|
||||
// Finally, open the modal.
|
||||
file_frame.open();
|
||||
});
|
||||
|
||||
jQuery( document ).on( 'click', '.remove_image_button', function() {
|
||||
jQuery( '#product_cat_thumbnail' ).find( 'img' ).attr( 'src', '<?php echo esc_js( wc_placeholder_img_src() ); ?>' );
|
||||
jQuery( '#product_cat_thumbnail_id' ).val( '' );
|
||||
jQuery( '.remove_image_button' ).hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
</script>
|
||||
<div class="clear"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save category fields
|
||||
*
|
||||
* @param mixed $term_id Term ID being saved.
|
||||
* @param mixed $tt_id Term taxonomy ID.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
public function save_category_fields( $term_id, $tt_id = '', $taxonomy = '' ) {
|
||||
if ( isset( $_POST['display_type'] ) && 'product_cat' === $taxonomy ) { // WPCS: CSRF ok, input var ok.
|
||||
update_term_meta( $term_id, 'display_type', esc_attr( $_POST['display_type'] ) ); // WPCS: CSRF ok, sanitization ok, input var ok.
|
||||
}
|
||||
if ( isset( $_POST['product_cat_thumbnail_id'] ) && 'product_cat' === $taxonomy ) { // WPCS: CSRF ok, input var ok.
|
||||
update_term_meta( $term_id, 'thumbnail_id', absint( $_POST['product_cat_thumbnail_id'] ) ); // WPCS: CSRF ok, input var ok.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description for product_cat page to aid users.
|
||||
*/
|
||||
public function product_cat_description() {
|
||||
echo wp_kses(
|
||||
wpautop( __( 'Product categories for your store can be managed here. To change the order of categories on the front-end you can drag and drop to sort them. To see more categories listed click the "screen options" link at the top-right of this page.', 'woocommerce' ) ),
|
||||
array( 'p' => array() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some notes to describe the behavior of the default category.
|
||||
*/
|
||||
public function product_cat_notes() {
|
||||
$category_id = get_option( 'default_product_cat', 0 );
|
||||
$category = get_term( $category_id, 'product_cat' );
|
||||
$category_name = ( ! $category || is_wp_error( $category ) ) ? _x( 'Uncategorized', 'Default category slug', 'woocommerce' ) : $category->name;
|
||||
?>
|
||||
<div class="form-wrap edit-term-notes">
|
||||
<p>
|
||||
<strong><?php esc_html_e( 'Note:', 'woocommerce' ); ?></strong><br>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: default category */
|
||||
esc_html__( 'Deleting a category does not delete the products in that category. Instead, products that were only assigned to the deleted category are set to the category %s.', 'woocommerce' ),
|
||||
'<strong>' . esc_html( $category_name ) . '</strong>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Description for shipping class page to aid users.
|
||||
*/
|
||||
public function product_attribute_description() {
|
||||
echo wp_kses(
|
||||
wpautop( __( 'Attribute terms can be assigned to products and variations.<br/><br/><b>Note</b>: Deleting a term will remove it from all products and variations to which it has been assigned. Recreating a term will not automatically assign it back to products.', 'woocommerce' ) ),
|
||||
array( 'p' => array() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thumbnail column added to category admin.
|
||||
*
|
||||
* @param mixed $columns Columns array.
|
||||
* @return array
|
||||
*/
|
||||
public function product_cat_columns( $columns ) {
|
||||
$new_columns = array();
|
||||
|
||||
if ( isset( $columns['cb'] ) ) {
|
||||
$new_columns['cb'] = $columns['cb'];
|
||||
unset( $columns['cb'] );
|
||||
}
|
||||
|
||||
$new_columns['thumb'] = __( 'Image', 'woocommerce' );
|
||||
|
||||
$columns = array_merge( $new_columns, $columns );
|
||||
$columns['handle'] = '';
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust row actions.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param object $term Term object.
|
||||
* @return array
|
||||
*/
|
||||
public function product_cat_row_actions( $actions, $term ) {
|
||||
$default_category_id = absint( get_option( 'default_product_cat', 0 ) );
|
||||
|
||||
if ( $default_category_id !== $term->term_id && current_user_can( 'edit_term', $term->term_id ) ) {
|
||||
$actions['make_default'] = sprintf(
|
||||
'<a href="%s" aria-label="%s">%s</a>',
|
||||
wp_nonce_url( 'edit-tags.php?action=make_default&taxonomy=product_cat&post_type=product&tag_ID=' . absint( $term->term_id ), 'make_default_' . absint( $term->term_id ) ),
|
||||
/* translators: %s: taxonomy term name */
|
||||
esc_attr( sprintf( __( 'Make “%s” the default category', 'woocommerce' ), $term->name ) ),
|
||||
__( 'Make default', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle custom row actions.
|
||||
*/
|
||||
public function handle_product_cat_row_actions() {
|
||||
if ( isset( $_GET['action'], $_GET['tag_ID'], $_GET['_wpnonce'] ) && 'make_default' === $_GET['action'] ) { // WPCS: CSRF ok, input var ok.
|
||||
$make_default_id = absint( $_GET['tag_ID'] ); // WPCS: Input var ok.
|
||||
|
||||
if ( wp_verify_nonce( $_GET['_wpnonce'], 'make_default_' . $make_default_id ) && current_user_can( 'edit_term', $make_default_id ) ) { // WPCS: Sanitization ok, input var ok, CSRF ok.
|
||||
update_option( 'default_product_cat', $make_default_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thumbnail column value added to category admin.
|
||||
*
|
||||
* @param string $columns Column HTML output.
|
||||
* @param string $column Column name.
|
||||
* @param int $id Product ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function product_cat_column( $columns, $column, $id ) {
|
||||
if ( 'thumb' === $column ) {
|
||||
// Prepend tooltip for default category.
|
||||
$default_category_id = absint( get_option( 'default_product_cat', 0 ) );
|
||||
|
||||
if ( $default_category_id === $id ) {
|
||||
$columns .= wc_help_tip( __( 'This is the default category and it cannot be deleted. It will be automatically assigned to products with no category.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$thumbnail_id = get_term_meta( $id, 'thumbnail_id', true );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
$image = wp_get_attachment_thumb_url( $thumbnail_id );
|
||||
} else {
|
||||
$image = wc_placeholder_img_src();
|
||||
}
|
||||
|
||||
// Prevent esc_url from breaking spaces in urls for image embeds. Ref: https://core.trac.wordpress.org/ticket/23605 .
|
||||
$image = str_replace( ' ', '%20', $image );
|
||||
$columns .= '<img src="' . esc_url( $image ) . '" alt="' . esc_attr__( 'Thumbnail', 'woocommerce' ) . '" class="wp-post-image" height="48" width="48" />';
|
||||
}
|
||||
if ( 'handle' === $column ) {
|
||||
$columns .= '<input type="hidden" name="term_id" value="' . esc_attr( $id ) . '" />';
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain term hierarchy when editing a product.
|
||||
*
|
||||
* @param array $args Term checklist args.
|
||||
* @return array
|
||||
*/
|
||||
public function disable_checked_ontop( $args ) {
|
||||
if ( ! empty( $args['taxonomy'] ) && 'product_cat' === $args['taxonomy'] ) {
|
||||
$args['checked_ontop'] = false;
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin footer scripts for the product categories admin screen
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function scripts_at_product_cat_screen_footer() {
|
||||
if ( ! isset( $_GET['taxonomy'] ) || 'product_cat' !== $_GET['taxonomy'] ) { // WPCS: CSRF ok, input var ok.
|
||||
return;
|
||||
}
|
||||
// Ensure the tooltip is displayed when the image column is disabled on product categories.
|
||||
wc_enqueue_js(
|
||||
"(function( $ ) {
|
||||
'use strict';
|
||||
var product_cat = $( 'tr#tag-" . absint( $this->default_cat_id ) . "' );
|
||||
product_cat.find( 'th' ).empty();
|
||||
product_cat.find( 'td.thumb span' ).detach( 'span' ).appendTo( product_cat.find( 'th' ) );
|
||||
})( jQuery );"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$wc_admin_taxonomies = WC_Admin_Taxonomies::get_instance();
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* Add hooks related to uploading downloadable products.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 8.5.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_Upload_Downloadable_Product', false ) ) {
|
||||
return new WC_Admin_Upload_Downloadable_Product();
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_Upload_Downloadable_Product Class.
|
||||
*/
|
||||
class WC_Admin_Upload_Downloadable_Product {
|
||||
/**
|
||||
* Add hooks.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
|
||||
add_filter( 'wp_unique_filename', array( $this, 'update_filename' ), 10, 3 );
|
||||
add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) );
|
||||
}
|
||||
/**
|
||||
* Change upload dir for downloadable files.
|
||||
*
|
||||
* @param array $pathdata Array of paths.
|
||||
* @return array
|
||||
*/
|
||||
public function upload_dir( $pathdata ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
if ( isset( $_POST['type'] ) && 'downloadable_product' === $_POST['type'] ) {
|
||||
|
||||
if ( empty( $pathdata['subdir'] ) ) {
|
||||
$pathdata['path'] = $pathdata['path'] . '/woocommerce_uploads';
|
||||
$pathdata['url'] = $pathdata['url'] . '/woocommerce_uploads';
|
||||
$pathdata['subdir'] = '/woocommerce_uploads';
|
||||
} else {
|
||||
$new_subdir = '/woocommerce_uploads' . $pathdata['subdir'];
|
||||
|
||||
$pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
|
||||
$pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
|
||||
$pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
|
||||
}
|
||||
}
|
||||
return $pathdata;
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Change filename for WooCommerce uploads and prepend unique chars for security.
|
||||
*
|
||||
* @param string $full_filename Original filename.
|
||||
* @param string $ext Extension of file.
|
||||
* @param string $dir Directory path.
|
||||
*
|
||||
* @return string New filename with unique hash.
|
||||
* @since 4.0
|
||||
*/
|
||||
public function update_filename( $full_filename, $ext, $dir ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
if ( ! isset( $_POST['type'] ) || ! 'downloadable_product' === $_POST['type'] ) {
|
||||
return $full_filename;
|
||||
}
|
||||
|
||||
if ( ! strpos( $dir, 'woocommerce_uploads' ) ) {
|
||||
return $full_filename;
|
||||
}
|
||||
|
||||
if ( 'no' === get_option( 'woocommerce_downloads_add_hash_to_filename' ) ) {
|
||||
return $full_filename;
|
||||
}
|
||||
|
||||
return $this->unique_filename( $full_filename, $ext );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Change filename to append random text.
|
||||
*
|
||||
* @param string $full_filename Original filename with extension.
|
||||
* @param string $ext Extension.
|
||||
*
|
||||
* @return string Modified filename.
|
||||
*/
|
||||
public function unique_filename( $full_filename, $ext ) {
|
||||
$ideal_random_char_length = 6; // Not going with a larger length because then downloaded filename will not be pretty.
|
||||
$max_filename_length = 255; // Max file name length for most file systems.
|
||||
$length_to_prepend = min( $ideal_random_char_length, $max_filename_length - strlen( $full_filename ) - 1 );
|
||||
|
||||
if ( 1 > $length_to_prepend ) {
|
||||
return $full_filename;
|
||||
}
|
||||
|
||||
$suffix = strtolower( wp_generate_password( $length_to_prepend, false, false ) );
|
||||
$filename = $full_filename;
|
||||
|
||||
if ( strlen( $ext ) > 0 ) {
|
||||
$filename = substr( $filename, 0, strlen( $filename ) - strlen( $ext ) );
|
||||
}
|
||||
|
||||
$full_filename = str_replace(
|
||||
$filename,
|
||||
"$filename-$suffix",
|
||||
$full_filename
|
||||
);
|
||||
|
||||
return $full_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a filter when uploading a downloadable product.
|
||||
*/
|
||||
public function woocommerce_media_upload_downloadable_product() {
|
||||
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
do_action( 'media_upload_file' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Webhooks Table List
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhooks table list class.
|
||||
*/
|
||||
class WC_Admin_Webhooks_Table_List extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* Initialize the webhook table list.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => 'webhook',
|
||||
'plural' => 'webhooks',
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* No items found text.
|
||||
*/
|
||||
public function no_items() {
|
||||
esc_html_e( 'No webhooks found.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'title' => __( 'Name', 'woocommerce' ),
|
||||
'status' => __( 'Status', 'woocommerce' ),
|
||||
'topic' => __( 'Topic', 'woocommerce' ),
|
||||
'delivery_url' => __( 'Delivery URL', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column cb.
|
||||
*
|
||||
* @param WC_Webhook $webhook Webhook instance.
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $webhook ) {
|
||||
return sprintf( '<input type="checkbox" name="%1$s[]" value="%2$s" />', $this->_args['singular'], $webhook->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return title column.
|
||||
*
|
||||
* @param WC_Webhook $webhook Webhook instance.
|
||||
* @return string
|
||||
*/
|
||||
public function column_title( $webhook ) {
|
||||
$edit_link = admin_url( 'admin.php?page=wc-settings&tab=advanced&section=webhooks&edit-webhook=' . $webhook->get_id() );
|
||||
$output = '';
|
||||
|
||||
// Title.
|
||||
$output .= '<strong><a href="' . esc_url( $edit_link ) . '" class="row-title">' . esc_html( $webhook->get_name() ) . '</a></strong>';
|
||||
|
||||
// Get actions.
|
||||
$actions = array(
|
||||
/* translators: %s: webhook ID. */
|
||||
'id' => sprintf( __( 'ID: %d', 'woocommerce' ), $webhook->get_id() ),
|
||||
'edit' => '<a href="' . esc_url( $edit_link ) . '">' . esc_html__( 'Edit', 'woocommerce' ) . '</a>',
|
||||
/* translators: %s: webhook name */
|
||||
'delete' => '<a class="submitdelete" aria-label="' . esc_attr( sprintf( __( 'Delete "%s" permanently', 'woocommerce' ), $webhook->get_name() ) ) . '" href="' . esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'delete' => $webhook->get_id(),
|
||||
),
|
||||
admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks' )
|
||||
),
|
||||
'delete-webhook'
|
||||
)
|
||||
) . '">' . esc_html__( 'Delete permanently', 'woocommerce' ) . '</a>',
|
||||
);
|
||||
|
||||
$actions = apply_filters( 'webhook_row_actions', $actions, $webhook );
|
||||
$row_actions = array();
|
||||
|
||||
foreach ( $actions as $action => $link ) {
|
||||
$row_actions[] = '<span class="' . esc_attr( $action ) . '">' . $link . '</span>';
|
||||
}
|
||||
|
||||
$output .= '<div class="row-actions">' . implode( ' | ', $row_actions ) . '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return status column.
|
||||
*
|
||||
* @param WC_Webhook $webhook Webhook instance.
|
||||
* @return string
|
||||
*/
|
||||
public function column_status( $webhook ) {
|
||||
return $webhook->get_i18n_status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return topic column.
|
||||
*
|
||||
* @param WC_Webhook $webhook Webhook instance.
|
||||
* @return string
|
||||
*/
|
||||
public function column_topic( $webhook ) {
|
||||
return $webhook->get_topic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return delivery URL column.
|
||||
*
|
||||
* @param WC_Webhook $webhook Webhook instance.
|
||||
* @return string
|
||||
*/
|
||||
public function column_delivery_url( $webhook ) {
|
||||
return $webhook->get_delivery_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status label for webhooks.
|
||||
*
|
||||
* @param string $status_name Status name.
|
||||
* @param int $amount Amount of webhooks.
|
||||
* @return array
|
||||
*/
|
||||
private function get_status_label( $status_name, $amount ) {
|
||||
$statuses = wc_get_webhook_statuses();
|
||||
|
||||
if ( isset( $statuses[ $status_name ] ) ) {
|
||||
return array(
|
||||
'singular' => sprintf( '%s <span class="count">(%s)</span>', esc_html( $statuses[ $status_name ] ), $amount ),
|
||||
'plural' => sprintf( '%s <span class="count">(%s)</span>', esc_html( $statuses[ $status_name ] ), $amount ),
|
||||
'context' => '',
|
||||
'domain' => 'woocommerce',
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'singular' => sprintf( '%s <span class="count">(%s)</span>', esc_html( $status_name ), $amount ),
|
||||
'plural' => sprintf( '%s <span class="count">(%s)</span>', esc_html( $status_name ), $amount ),
|
||||
'context' => '',
|
||||
'domain' => 'woocommerce',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table list views.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_views() {
|
||||
$status_links = array();
|
||||
$data_store = WC_Data_Store::load( 'webhook' );
|
||||
$num_webhooks = $data_store->get_count_webhooks_by_status();
|
||||
$total_webhooks = array_sum( (array) $num_webhooks );
|
||||
$statuses = array_keys( wc_get_webhook_statuses() );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$class = empty( $_REQUEST['status'] ) && empty( $_REQUEST['legacy'] ) ? ' class="current"' : '';
|
||||
|
||||
/* translators: %s: count */
|
||||
$status_links['all'] = "<a href='admin.php?page=wc-settings&tab=advanced&section=webhooks'$class>" . sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_webhooks, 'posts', 'woocommerce' ), number_format_i18n( $total_webhooks ) ) . '</a>';
|
||||
|
||||
foreach ( $statuses as $status_name ) {
|
||||
$class = '';
|
||||
|
||||
if ( empty( $num_webhooks[ $status_name ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_REQUEST['status'] ) && sanitize_key( wp_unslash( $_REQUEST['status'] ) ) === $status_name ) {
|
||||
$class = ' class="current"';
|
||||
}
|
||||
|
||||
$label = $this->get_status_label( $status_name, $num_webhooks[ $status_name ] );
|
||||
|
||||
$status_links[ $status_name ] = "<a href='admin.php?page=wc-settings&tab=advanced&section=webhooks&status=$status_name'$class>" . sprintf( translate_nooped_plural( $label, $num_webhooks[ $status_name ] ), number_format_i18n( $num_webhooks[ $status_name ] ) ) . '</a>';
|
||||
}
|
||||
|
||||
$legacy_webhooks_count = wc_get_container()->get( WebhookUtil::class )->get_legacy_webhooks_count();
|
||||
if ( $legacy_webhooks_count > 0 ) {
|
||||
$class = '';
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( 'true' === sanitize_key( wp_unslash( $_REQUEST['legacy'] ?? '' ) ) ) {
|
||||
$class = ' class="current"';
|
||||
}
|
||||
|
||||
$label = $this->get_status_label( __( 'Legacy', 'woocommerce' ), $legacy_webhooks_count );
|
||||
|
||||
$status_links['legacy'] = "<a href='admin.php?page=wc-settings&tab=advanced&section=webhooks&legacy=true'$class>" . sprintf( translate_nooped_plural( $label, $legacy_webhooks_count ), number_format_i18n( $legacy_webhooks_count ) ) . '</a>';
|
||||
}
|
||||
|
||||
return $status_links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_bulk_actions() {
|
||||
return array(
|
||||
'delete' => __( 'Delete permanently', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process bulk actions.
|
||||
*/
|
||||
public function process_bulk_action() {
|
||||
$action = $this->current_action();
|
||||
$webhooks = isset( $_REQUEST['webhook'] ) ? array_map( 'absint', (array) $_REQUEST['webhook'] ) : array(); // WPCS: input var okay, CSRF ok.
|
||||
|
||||
if ( false !== $action ) {
|
||||
check_admin_referer( 'woocommerce-settings' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to edit Webhooks', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( 'delete' === $action ) {
|
||||
WC_Admin_Webhooks::bulk_delete( $webhooks );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the table navigation above or below the table.
|
||||
* Included to remove extra nonce input.
|
||||
*
|
||||
* @param string $which The location of the extra table nav markup: 'top' or 'bottom'.
|
||||
*/
|
||||
protected function display_tablenav( $which ) {
|
||||
echo '<div class="tablenav ' . esc_attr( $which ) . '">';
|
||||
|
||||
if ( $this->has_items() ) {
|
||||
echo '<div class="alignleft actions bulkactions">';
|
||||
$this->bulk_actions( $which );
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
$this->extra_tablenav( $which );
|
||||
$this->pagination( $which );
|
||||
echo '<br class="clear" />';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Search box.
|
||||
*
|
||||
* @param string $text Button text.
|
||||
* @param string $input_id Input ID.
|
||||
*/
|
||||
public function search_box( $text, $input_id ) {
|
||||
if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { // WPCS: input var okay, CSRF ok.
|
||||
return;
|
||||
}
|
||||
|
||||
$input_id = $input_id . '-search-input';
|
||||
$search_query = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // WPCS: input var okay, CSRF ok.
|
||||
|
||||
echo '<p class="search-box">';
|
||||
echo '<label class="screen-reader-text" for="' . esc_attr( $input_id ) . '">' . esc_html( $text ) . ':</label>';
|
||||
echo '<input type="search" id="' . esc_attr( $input_id ) . '" name="s" value="' . esc_attr( $search_query ) . '" />';
|
||||
submit_button(
|
||||
$text,
|
||||
'',
|
||||
'',
|
||||
false,
|
||||
array(
|
||||
'id' => 'search-submit',
|
||||
)
|
||||
);
|
||||
echo '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare table list items.
|
||||
*/
|
||||
public function prepare_items() {
|
||||
$per_page = $this->get_items_per_page( 'woocommerce_webhooks_per_page' );
|
||||
$current_page = $this->get_pagenum();
|
||||
|
||||
// Query args.
|
||||
$args = array(
|
||||
'limit' => $per_page,
|
||||
'offset' => $per_page * ( $current_page - 1 ),
|
||||
);
|
||||
|
||||
// Handle the status query.
|
||||
if ( ! empty( $_REQUEST['status'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$args['status'] = sanitize_key( wp_unslash( $_REQUEST['status'] ) ); // WPCS: input var okay, CSRF ok.
|
||||
}
|
||||
|
||||
if ( ! empty( $_REQUEST['s'] ) ) { // WPCS: input var okay, CSRF ok.
|
||||
$args['search'] = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ); // WPCS: input var okay, CSRF ok.
|
||||
}
|
||||
|
||||
$args['paginate'] = true;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( 'true' === sanitize_key( wp_unslash( $_REQUEST['legacy'] ?? null ) ) ) {
|
||||
$args['api_version'] = -1;
|
||||
}
|
||||
|
||||
// Get the webhooks.
|
||||
$data_store = WC_Data_Store::load( 'webhook' );
|
||||
$webhooks = $data_store->search_webhooks( $args );
|
||||
$this->items = array_map( 'wc_get_webhook', $webhooks->webhooks );
|
||||
|
||||
// Set the pagination.
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $webhooks->total,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => $webhooks->max_num_pages,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Webhooks Class
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Admin_Webhooks.
|
||||
*/
|
||||
class WC_Admin_Webhooks {
|
||||
|
||||
/**
|
||||
* Initialize the webhooks admin actions.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_init', array( $this, 'actions' ) );
|
||||
add_action( 'woocommerce_settings_page_init', array( $this, 'screen_option' ) );
|
||||
add_filter( 'woocommerce_save_settings_advanced_webhooks', array( $this, 'allow_save_settings' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if should allow save settings.
|
||||
* This prevents "Your settings have been saved." notices on the table list.
|
||||
*
|
||||
* @param bool $allow If allow save settings.
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_save_settings( $allow ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['edit-webhook'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $allow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is webhook settings page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_webhook_settings_page() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return isset( $_GET['page'], $_GET['tab'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && 'advanced' === $_GET['tab'] && 'webhooks' === $_GET['section'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save method.
|
||||
*/
|
||||
private function save() {
|
||||
check_admin_referer( 'woocommerce-settings' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to update Webhooks', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$errors = array();
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$webhook_id = isset( $_POST['webhook_id'] ) ? absint( $_POST['webhook_id'] ) : 0;
|
||||
$webhook = new WC_Webhook( $webhook_id );
|
||||
|
||||
// Name.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_POST['webhook_name'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$name = sanitize_text_field( wp_unslash( $_POST['webhook_name'] ) );
|
||||
} else {
|
||||
$name = sprintf(
|
||||
/* translators: %s: date */
|
||||
__( 'Webhook created on %s', 'woocommerce' ),
|
||||
// @codingStandardsIgnoreStart
|
||||
(new DateTime('now'))->format( _x( 'M d, Y @ h:i A', 'Webhook created on date parsed by DateTime::format', 'woocommerce' ) )
|
||||
// @codingStandardsIgnoreEnd
|
||||
);
|
||||
}
|
||||
|
||||
$webhook->set_name( $name );
|
||||
|
||||
if ( ! $webhook->get_user_id() ) {
|
||||
$webhook->set_user_id( get_current_user_id() );
|
||||
}
|
||||
|
||||
// Status.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$webhook->set_status( ! empty( $_POST['webhook_status'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_status'] ) ) : 'disabled' );
|
||||
|
||||
// Delivery URL.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$delivery_url = ! empty( $_POST['webhook_delivery_url'] ) ? esc_url_raw( wp_unslash( $_POST['webhook_delivery_url'] ) ) : '';
|
||||
|
||||
if ( wc_is_valid_url( $delivery_url ) ) {
|
||||
$webhook->set_delivery_url( $delivery_url );
|
||||
}
|
||||
|
||||
// Secret.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$secret = ! empty( $_POST['webhook_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_secret'] ) ) : wp_generate_password( 50, true, true );
|
||||
$webhook->set_secret( $secret );
|
||||
|
||||
// Topic.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_POST['webhook_topic'] ) ) {
|
||||
$resource = '';
|
||||
$event = '';
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
switch ( $_POST['webhook_topic'] ) {
|
||||
case 'action':
|
||||
$resource = 'action';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$event = ! empty( $_POST['webhook_action_event'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_action_event'] ) ) : '';
|
||||
break;
|
||||
|
||||
default:
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
list( $resource, $event ) = explode( '.', sanitize_text_field( wp_unslash( $_POST['webhook_topic'] ) ) );
|
||||
break;
|
||||
}
|
||||
|
||||
$topic = $resource . '.' . $event;
|
||||
|
||||
if ( wc_is_webhook_valid_topic( $topic ) ) {
|
||||
$webhook->set_topic( $topic );
|
||||
} else {
|
||||
$errors[] = __( 'Webhook topic unknown. Please select a valid topic.', 'woocommerce' );
|
||||
}
|
||||
}
|
||||
|
||||
// API version.
|
||||
$rest_api_versions = wc_get_webhook_rest_api_versions();
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$webhook->set_api_version( ! empty( $_POST['webhook_api_version'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_api_version'] ) ) : end( $rest_api_versions ) );
|
||||
|
||||
$webhook->save();
|
||||
|
||||
// Run actions.
|
||||
do_action( 'woocommerce_webhook_options_save', $webhook->get_id() );
|
||||
if ( $errors ) {
|
||||
// Redirect to webhook edit page to avoid settings save actions.
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks&edit-webhook=' . $webhook->get_id() . '&error=' . rawurlencode( implode( '|', $errors ) ) ) );
|
||||
exit();
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
} elseif ( isset( $_POST['webhook_status'] ) && 'active' === $_POST['webhook_status'] && $webhook->get_pending_delivery() ) {
|
||||
// Ping the webhook at the first time that is activated.
|
||||
$result = $webhook->deliver_ping();
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
// Redirect to webhook edit page to avoid settings save actions.
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks&edit-webhook=' . $webhook->get_id() . '&error=' . rawurlencode( $result->get_error_message() ) ) );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to webhook edit page to avoid settings save actions.
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks&edit-webhook=' . $webhook->get_id() . '&updated=1' ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk delete.
|
||||
*
|
||||
* @param array $webhooks List of webhooks IDs.
|
||||
*/
|
||||
public static function bulk_delete( $webhooks ) {
|
||||
foreach ( $webhooks as $webhook_id ) {
|
||||
$webhook = new WC_Webhook( (int) $webhook_id );
|
||||
$webhook->delete( true );
|
||||
}
|
||||
|
||||
$qty = count( $webhooks );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$status = isset( $_GET['status'] ) ? '&status=' . sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '';
|
||||
|
||||
// Redirect to webhooks page.
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks' . $status . '&deleted=' . $qty ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete webhook.
|
||||
*/
|
||||
private function delete() {
|
||||
check_admin_referer( 'delete-webhook' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['delete'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$webhook_id = absint( $_GET['delete'] );
|
||||
|
||||
if ( $webhook_id ) {
|
||||
self::bulk_delete( array( $webhook_id ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhooks admin actions.
|
||||
*/
|
||||
public function actions() {
|
||||
if ( $this->is_webhook_settings_page() ) {
|
||||
// Save.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( isset( $_POST['save'] ) && isset( $_POST['webhook_id'] ) ) {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
// Delete webhook.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['delete'] ) ) {
|
||||
$this->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Page output.
|
||||
*/
|
||||
public static function page_output() {
|
||||
// Hide the save button.
|
||||
$GLOBALS['hide_save_button'] = true;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['edit-webhook'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$webhook_id = absint( $_GET['edit-webhook'] );
|
||||
$webhook = new WC_Webhook( $webhook_id );
|
||||
|
||||
include __DIR__ . '/settings/views/html-webhooks-edit.php';
|
||||
return;
|
||||
}
|
||||
|
||||
self::table_list_output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notices.
|
||||
*/
|
||||
public static function notices() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['deleted'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$deleted = absint( $_GET['deleted'] );
|
||||
|
||||
/* translators: %d: count */
|
||||
WC_Admin_Settings::add_message( sprintf( _n( '%d webhook permanently deleted.', '%d webhooks permanently deleted.', $deleted, 'woocommerce' ), $deleted ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['updated'] ) ) {
|
||||
WC_Admin_Settings::add_message( __( 'Webhook updated successfully.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['created'] ) ) {
|
||||
WC_Admin_Settings::add_message( __( 'Webhook created successfully.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['error'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
foreach ( explode( '|', sanitize_text_field( wp_unslash( $_GET['error'] ) ) ) as $message ) {
|
||||
WC_Admin_Settings::add_error( trim( $message ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add screen option.
|
||||
*/
|
||||
public function screen_option() {
|
||||
global $webhooks_table_list;
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['edit-webhook'] ) && $this->is_webhook_settings_page() ) {
|
||||
$webhooks_table_list = new WC_Admin_Webhooks_Table_List();
|
||||
|
||||
// Add screen option.
|
||||
add_screen_option(
|
||||
'per_page',
|
||||
array(
|
||||
'default' => 10,
|
||||
'option' => 'woocommerce_webhooks_per_page',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Table list output.
|
||||
*/
|
||||
private static function table_list_output() {
|
||||
global $webhooks_table_list;
|
||||
|
||||
echo '<h2 class="wc-table-list-header">' . esc_html__( 'Webhooks', 'woocommerce' ) . ' <a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks&edit-webhook=0' ) ) . '" class="page-title-action">' . esc_html__( 'Add webhook', 'woocommerce' ) . '</a></h2>';
|
||||
|
||||
// Get the webhooks count.
|
||||
$data_store = WC_Data_Store::load( 'webhook' );
|
||||
$num_webhooks = $data_store->get_count_webhooks_by_status();
|
||||
$count = array_sum( $num_webhooks );
|
||||
|
||||
if ( 0 < $count ) {
|
||||
$webhooks_table_list->process_bulk_action();
|
||||
$webhooks_table_list->prepare_items();
|
||||
|
||||
echo '<input type="hidden" name="page" value="wc-settings" />';
|
||||
echo '<input type="hidden" name="tab" value="advanced" />';
|
||||
echo '<input type="hidden" name="section" value="webhooks" />';
|
||||
|
||||
$webhooks_table_list->views();
|
||||
$webhooks_table_list->search_box( __( 'Search webhooks', 'woocommerce' ), 'webhook' );
|
||||
$webhooks_table_list->display();
|
||||
} else {
|
||||
echo '<div class="woocommerce-BlankState woocommerce-BlankState--webhooks">';
|
||||
?>
|
||||
<h2 class="woocommerce-BlankState-message"><?php esc_html_e( 'Webhooks are event notifications sent to URLs of your choice. They can be used to integrate with third-party services which support them.', 'woocommerce' ); ?></h2>
|
||||
<a class="woocommerce-BlankState-cta button-primary button" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=webhooks&edit-webhook=0' ) ); ?>"><?php esc_html_e( 'Create a new webhook', 'woocommerce' ); ?></a>
|
||||
<style type="text/css">#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions { display: none; }</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs output.
|
||||
*
|
||||
* @deprecated 3.3.0
|
||||
* @param WC_Webhook $webhook Deprecated.
|
||||
*/
|
||||
public static function logs_output( $webhook = 'deprecated' ) {
|
||||
wc_deprecated_function( 'WC_Admin_Webhooks::logs_output', '3.3' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the webhook topic data.
|
||||
*
|
||||
* @param WC_Webhook $webhook Webhook instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_topic_data( $webhook ) {
|
||||
$topic = $webhook->get_topic();
|
||||
$event = '';
|
||||
$resource = '';
|
||||
|
||||
if ( $topic ) {
|
||||
list( $resource, $event ) = explode( '.', $topic );
|
||||
|
||||
if ( 'action' === $resource ) {
|
||||
$topic = 'action';
|
||||
} elseif ( ! in_array( $resource, array( 'coupon', 'customer', 'order', 'product' ), true ) ) {
|
||||
$topic = 'custom';
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'topic' => $topic,
|
||||
'event' => $event,
|
||||
'resource' => $resource,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logs navigation.
|
||||
*
|
||||
* @deprecated 3.3.0
|
||||
* @param int $total Deprecated.
|
||||
* @param WC_Webhook $webhook Deprecated.
|
||||
*/
|
||||
public static function get_logs_navigation( $total, $webhook ) {
|
||||
wc_deprecated_function( 'WC_Admin_Webhooks::get_logs_navigation', '3.3' );
|
||||
}
|
||||
}
|
||||
|
||||
new WC_Admin_Webhooks();
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin
|
||||
*
|
||||
* @class WC_Admin
|
||||
* @package WooCommerce\Admin
|
||||
* @version 2.6.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin class.
|
||||
*/
|
||||
class WC_Admin {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'init', array( $this, 'includes' ) );
|
||||
add_action( 'current_screen', array( $this, 'conditional_includes' ) );
|
||||
add_action( 'admin_init', array( $this, 'buffer' ), 1 );
|
||||
add_action( 'admin_init', array( $this, 'preview_emails' ) );
|
||||
add_action( 'admin_init', array( $this, 'prevent_admin_access' ) );
|
||||
add_action( 'admin_init', array( $this, 'admin_redirects' ) );
|
||||
add_action( 'admin_footer', 'wc_print_js', 25 );
|
||||
add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), 1 );
|
||||
|
||||
// Disable WXR export of schedule action posts.
|
||||
add_filter( 'action_scheduler_post_type_args', array( $this, 'disable_webhook_post_export' ) );
|
||||
|
||||
// Add body class for WP 5.3+ compatibility.
|
||||
add_filter( 'admin_body_class', array( $this, 'include_admin_body_class' ), 9999 );
|
||||
|
||||
// Add body class for Marketplace and My Subscriptions pages.
|
||||
if ( isset( $_GET['page'] ) && 'wc-addons' === $_GET['page'] ) {
|
||||
add_filter( 'admin_body_class', array( 'WC_Admin_Addons', 'filter_admin_body_classes' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output buffering allows admin screens to make redirects later on.
|
||||
*/
|
||||
public function buffer() {
|
||||
ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Include any classes we need within admin.
|
||||
*/
|
||||
public function includes() {
|
||||
include_once __DIR__ . '/wc-admin-functions.php';
|
||||
include_once __DIR__ . '/wc-meta-box-functions.php';
|
||||
include_once __DIR__ . '/class-wc-admin-post-types.php';
|
||||
include_once __DIR__ . '/class-wc-admin-taxonomies.php';
|
||||
include_once __DIR__ . '/class-wc-admin-menus.php';
|
||||
include_once __DIR__ . '/class-wc-admin-customize.php';
|
||||
include_once __DIR__ . '/class-wc-admin-notices.php';
|
||||
include_once __DIR__ . '/class-wc-admin-assets.php';
|
||||
include_once __DIR__ . '/class-wc-admin-api-keys.php';
|
||||
include_once __DIR__ . '/class-wc-admin-webhooks.php';
|
||||
include_once __DIR__ . '/class-wc-admin-pointers.php';
|
||||
include_once __DIR__ . '/class-wc-admin-importers.php';
|
||||
include_once __DIR__ . '/class-wc-admin-exporters.php';
|
||||
|
||||
// Help Tabs.
|
||||
if ( apply_filters( 'woocommerce_enable_admin_help_tab', true ) ) {
|
||||
include_once __DIR__ . '/class-wc-admin-help.php';
|
||||
}
|
||||
|
||||
// Helper.
|
||||
include_once __DIR__ . '/helper/class-wc-helper.php';
|
||||
|
||||
// Marketplace suggestions & related REST API.
|
||||
include_once __DIR__ . '/marketplace-suggestions/class-wc-marketplace-suggestions.php';
|
||||
include_once __DIR__ . '/marketplace-suggestions/class-wc-marketplace-updater.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Include admin files conditionally.
|
||||
*/
|
||||
public function conditional_includes() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( ! $screen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $screen->id ) {
|
||||
case 'dashboard':
|
||||
case 'dashboard-network':
|
||||
include __DIR__ . '/class-wc-admin-dashboard-setup.php';
|
||||
include __DIR__ . '/class-wc-admin-dashboard.php';
|
||||
break;
|
||||
case 'options-permalink':
|
||||
include __DIR__ . '/class-wc-admin-permalink-settings.php';
|
||||
break;
|
||||
case 'plugins':
|
||||
include __DIR__ . '/plugin-updates/class-wc-plugins-screen-updates.php';
|
||||
break;
|
||||
case 'update-core':
|
||||
include __DIR__ . '/plugin-updates/class-wc-updates-screen-updates.php';
|
||||
break;
|
||||
case 'users':
|
||||
case 'user':
|
||||
case 'profile':
|
||||
case 'user-edit':
|
||||
include __DIR__ . '/class-wc-admin-profile.php';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle redirects to setup/welcome page after install and updates.
|
||||
*
|
||||
* The user must have access rights, and we must ignore the network/bulk plugin updaters.
|
||||
*/
|
||||
public function admin_redirects() {
|
||||
// Don't run this fn from Action Scheduler requests, as it would clear _wc_activation_redirect transient.
|
||||
// That means OBW would never be shown.
|
||||
if ( wc_is_running_from_async_action_scheduler() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
// Nonced plugin install redirects.
|
||||
if ( ! empty( $_GET['wc-install-plugin-redirect'] ) ) {
|
||||
$plugin_slug = wc_clean( wp_unslash( $_GET['wc-install-plugin-redirect'] ) );
|
||||
|
||||
if ( current_user_can( 'install_plugins' ) && in_array( $plugin_slug, array( 'woocommerce-gateway-stripe' ), true ) ) {
|
||||
$nonce = wp_create_nonce( 'install-plugin_' . $plugin_slug );
|
||||
$url = self_admin_url( 'update.php?action=install-plugin&plugin=' . $plugin_slug . '&_wpnonce=' . $nonce );
|
||||
} else {
|
||||
$url = admin_url( 'plugin-install.php?tab=search&type=term&s=' . $plugin_slug );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent any user who cannot 'edit_posts' (subscribers, customers etc) from accessing admin.
|
||||
*/
|
||||
public function prevent_admin_access() {
|
||||
$prevent_access = false;
|
||||
|
||||
// Do not interfere with admin-post or admin-ajax requests.
|
||||
$exempted_paths = array( 'admin-post.php', 'admin-ajax.php' );
|
||||
|
||||
if (
|
||||
/**
|
||||
* This filter is documented in ../wc-user-functions.php
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
apply_filters( 'woocommerce_disable_admin_bar', true )
|
||||
&& isset( $_SERVER['SCRIPT_FILENAME'] )
|
||||
&& ! in_array( basename( sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) ), $exempted_paths, true )
|
||||
) {
|
||||
$has_cap = false;
|
||||
$access_caps = array( 'edit_posts', 'manage_woocommerce', 'view_admin_dashboard' );
|
||||
|
||||
foreach ( $access_caps as $access_cap ) {
|
||||
if ( current_user_can( $access_cap ) ) {
|
||||
$has_cap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $has_cap ) {
|
||||
$prevent_access = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( apply_filters( 'woocommerce_prevent_admin_access', $prevent_access ) ) {
|
||||
wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview email template.
|
||||
*/
|
||||
public function preview_emails() {
|
||||
|
||||
if ( isset( $_GET['preview_woocommerce_mail'] ) ) {
|
||||
if ( ! ( isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'preview-mail' ) ) ) {
|
||||
die( 'Security check' );
|
||||
}
|
||||
|
||||
// load the mailer class.
|
||||
$mailer = WC()->mailer();
|
||||
|
||||
// get the preview email subject.
|
||||
$email_heading = __( 'HTML email template', 'woocommerce' );
|
||||
|
||||
// get the preview email content.
|
||||
ob_start();
|
||||
include __DIR__ . '/views/html-email-template-preview.php';
|
||||
$message = ob_get_clean();
|
||||
|
||||
// create a new email.
|
||||
$email = new WC_Email();
|
||||
|
||||
// wrap the content with the email template and then add styles.
|
||||
$message = apply_filters( 'woocommerce_mail_content', $email->style_inline( $mailer->wrap_message( $email_heading, $message ) ) );
|
||||
|
||||
// print the preview email.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput
|
||||
echo $message;
|
||||
// phpcs:enable
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the admin footer text on WooCommerce admin pages.
|
||||
*
|
||||
* @since 2.3
|
||||
* @param string $footer_text text to be rendered in the footer.
|
||||
* @return string
|
||||
*/
|
||||
public function admin_footer_text( $footer_text ) {
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) || ! function_exists( 'wc_get_screen_ids' ) ) {
|
||||
return $footer_text;
|
||||
}
|
||||
$current_screen = get_current_screen();
|
||||
$wc_pages = wc_get_screen_ids();
|
||||
|
||||
// Set only WC pages.
|
||||
$wc_pages = array_diff( $wc_pages, array( 'profile', 'user-edit' ) );
|
||||
|
||||
// Check to make sure we're on a WooCommerce admin page.
|
||||
if ( isset( $current_screen->id ) && apply_filters( 'woocommerce_display_admin_footer_text', in_array( $current_screen->id, $wc_pages, true ) ) ) {
|
||||
// Change the footer text.
|
||||
if ( ! get_option( 'woocommerce_admin_footer_text_rated' ) ) {
|
||||
$footer_text = sprintf(
|
||||
/* translators: 1: WooCommerce 2:: five stars */
|
||||
__( 'If you like %1$s please leave us a %2$s rating. A huge thanks in advance!', 'woocommerce' ),
|
||||
sprintf( '<strong>%s</strong>', esc_html__( 'WooCommerce', 'woocommerce' ) ),
|
||||
'<a href="https://wordpress.org/support/plugin/woocommerce/reviews?rate=5#new-post" target="_blank" class="wc-rating-link" aria-label="' . esc_attr__( 'five star', 'woocommerce' ) . '" data-rated="' . esc_attr__( 'Thanks :)', 'woocommerce' ) . '">★★★★★</a>'
|
||||
);
|
||||
wc_enqueue_js(
|
||||
"jQuery( 'a.wc-rating-link' ).on( 'click', function() {
|
||||
jQuery.post( '" . WC()->ajax_url() . "', { action: 'woocommerce_rated' } );
|
||||
jQuery( this ).parent().text( jQuery( this ).data( 'rated' ) );
|
||||
});"
|
||||
);
|
||||
} else {
|
||||
$footer_text = __( 'Thank you for selling with WooCommerce.', 'woocommerce' );
|
||||
}
|
||||
}
|
||||
|
||||
return $footer_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check on a Jetpack install queued by the Setup Wizard.
|
||||
*
|
||||
* See: WC_Admin_Setup_Wizard::install_jetpack()
|
||||
*/
|
||||
public function setup_wizard_check_jetpack() {
|
||||
$jetpack_active = class_exists( 'Jetpack' );
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'is_active' => $jetpack_active ? 'yes' : 'no',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable WXR export of scheduled action posts.
|
||||
*
|
||||
* @since 3.6.2
|
||||
*
|
||||
* @param array $args Scheduled action post type registration args.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function disable_webhook_post_export( $args ) {
|
||||
$args['can_export'] = false;
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include admin classes.
|
||||
*
|
||||
* @since 4.2.0
|
||||
* @param string $classes Body classes string.
|
||||
* @return string
|
||||
*/
|
||||
public function include_admin_body_class( $classes ) {
|
||||
if ( in_array( array( 'wc-wp-version-gte-53', 'wc-wp-version-gte-55' ), explode( ' ', $classes ), true ) ) {
|
||||
return $classes;
|
||||
}
|
||||
|
||||
$raw_version = get_bloginfo( 'version' );
|
||||
$version_parts = explode( '-', $raw_version );
|
||||
$version = count( $version_parts ) > 1 ? $version_parts[0] : $raw_version;
|
||||
|
||||
// Add WP 5.3+ compatibility class.
|
||||
if ( $raw_version && version_compare( $version, '5.3', '>=' ) ) {
|
||||
$classes .= ' wc-wp-version-gte-53';
|
||||
}
|
||||
|
||||
// Add WP 5.5+ compatibility class.
|
||||
if ( $raw_version && version_compare( $version, '5.5', '>=' ) ) {
|
||||
$classes .= ' wc-wp-version-gte-55';
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
|
||||
return new WC_Admin();
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Helper - React admin interface
|
||||
*
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Marketplace;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper Class
|
||||
*
|
||||
* The main entry-point for all things related to the Helper.
|
||||
* The Helper manages the connection between the store and
|
||||
* an account on WooCommerce.com.
|
||||
*/
|
||||
class WC_Helper_Admin {
|
||||
|
||||
/**
|
||||
* Loads the class, runs on init
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function load() {
|
||||
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) );
|
||||
add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes settings onto the WooCommerce Admin global settings object (wcSettings).
|
||||
*
|
||||
* @param mixed $settings The settings object we're amending.
|
||||
*
|
||||
* @return mixed $settings
|
||||
*/
|
||||
public static function add_marketplace_settings( $settings ) {
|
||||
$auth_user_data = WC_Helper_Options::get( 'auth_user_data', array() );
|
||||
$auth_user_email = isset( $auth_user_data['email'] ) ? $auth_user_data['email'] : '';
|
||||
|
||||
// Get the all installed themes and plugins. Knowing this will help us decide to show Add to Store button on product cards.
|
||||
$installed_products = array_merge( WC_Helper::get_local_plugins(), WC_Helper::get_local_themes() );
|
||||
$installed_products = array_map(
|
||||
function ( $product ) {
|
||||
return $product['slug'];
|
||||
},
|
||||
$installed_products
|
||||
);
|
||||
|
||||
$settings['wccomHelper'] = array(
|
||||
'isConnected' => WC_Helper::is_site_connected(),
|
||||
'connectURL' => self::get_connection_url(),
|
||||
'userEmail' => $auth_user_email,
|
||||
'userAvatar' => get_avatar_url( $auth_user_email, array( 'size' => '48' ) ),
|
||||
'storeCountry' => wc_get_base_location()['country'],
|
||||
'inAppPurchaseURLParams' => WC_Admin_Addons::get_in_app_purchase_url_params(),
|
||||
'installedProducts' => $installed_products,
|
||||
'wooUpdateManagerInstalled' => WC_Woo_Update_Manager_Plugin::is_plugin_installed(),
|
||||
'wooUpdateManagerActive' => WC_Woo_Update_Manager_Plugin::is_plugin_active(),
|
||||
'wooUpdateManagerInstallUrl' => WC_Woo_Update_Manager_Plugin::generate_install_url(),
|
||||
'wooUpdateManagerPluginSlug' => WC_Woo_Update_Manager_Plugin::WOO_UPDATE_MANAGER_SLUG,
|
||||
'wooUpdateCount' => WC_Helper_Updater::get_updates_count_based_on_site_status(),
|
||||
);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the URL for connecting or disconnecting the store to/from WooCommerce.com.
|
||||
* Approach taken from existing helper code that isn't exposed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_connection_url() {
|
||||
global $current_screen;
|
||||
|
||||
$connect_url_args = array(
|
||||
'page' => 'wc-addons',
|
||||
'section' => 'helper',
|
||||
);
|
||||
|
||||
// No active connection.
|
||||
if ( WC_Helper::is_site_connected() ) {
|
||||
$connect_url_args['wc-helper-disconnect'] = 1;
|
||||
$connect_url_args['wc-helper-nonce'] = wp_create_nonce( 'disconnect' );
|
||||
} else {
|
||||
$connect_url_args['wc-helper-connect'] = 1;
|
||||
$connect_url_args['wc-helper-nonce'] = wp_create_nonce( 'connect' );
|
||||
}
|
||||
|
||||
return add_query_arg(
|
||||
$connect_url_args,
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST routes for the featured products endpoint.
|
||||
* This endpoint is used by the WooCommerce > Extensions > Discover
|
||||
* page.
|
||||
*/
|
||||
public static function register_rest_routes() {
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/featured',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( __CLASS__, 'get_featured' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Extensions page can only be accessed by users with the manage_woocommerce
|
||||
* capability. So the API mimics that behavior.
|
||||
*/
|
||||
public static function get_permission() {
|
||||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch featured products from WooCommerce.com and serve them
|
||||
* as JSON.
|
||||
*/
|
||||
public static function get_featured() {
|
||||
$featured = WC_Admin_Addons::fetch_featured();
|
||||
|
||||
if ( is_wp_error( $featured ) ) {
|
||||
wp_send_json_error( array( 'message' => $featured->get_error_message() ) );
|
||||
}
|
||||
|
||||
wp_send_json( $featured );
|
||||
}
|
||||
}
|
||||
|
||||
WC_Helper_Admin::load();
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Helper API
|
||||
*
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_API Class
|
||||
*
|
||||
* Provides a communication interface with the WooCommerce.com Helper API.
|
||||
*/
|
||||
class WC_Helper_API {
|
||||
/**
|
||||
* Base path for API routes.
|
||||
*
|
||||
* @var $api_base
|
||||
*/
|
||||
public static $api_base;
|
||||
|
||||
/**
|
||||
* Load
|
||||
*
|
||||
* Allow devs to point the API base to a local API development or staging server.
|
||||
* Note that sslverify will be turned off for the woocommerce.dev + WP_DEBUG combination.
|
||||
* The URL can be changed on plugins_loaded before priority 10.
|
||||
*/
|
||||
public static function load() {
|
||||
self::$api_base = apply_filters( 'woocommerce_helper_api_base', 'https://woocommerce.com/wp-json/helper/1.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an HTTP request to the Helper API.
|
||||
*
|
||||
* @param string $endpoint The endpoint to request.
|
||||
* @param array $args Additional data for the request. Set authenticated to a truthy value to enable auth.
|
||||
*
|
||||
* @return array|WP_Error The response from wp_safe_remote_request()
|
||||
*/
|
||||
public static function request( $endpoint, $args = array() ) {
|
||||
if ( ! isset( $args['query_string'] ) ) {
|
||||
$args['query_string'] = '';
|
||||
}
|
||||
$url = self::url( $endpoint, $args['query_string'] );
|
||||
|
||||
if ( ! empty( $args['authenticated'] ) ) {
|
||||
if ( ! self::_authenticate( $url, $args ) ) {
|
||||
return new WP_Error( 'authentication', 'Authentication failed.' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $args['user-agent'] ) ) {
|
||||
$args['user-agent'] = 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow developers to filter the request args passed to wp_safe_remote_request().
|
||||
* Useful to remove sslverify when working on a local api dev environment.
|
||||
*/
|
||||
$args = apply_filters( 'woocommerce_helper_api_request_args', $args, $endpoint );
|
||||
|
||||
// TODO: Check response signatures on certain endpoints.
|
||||
return wp_safe_remote_request( $url, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create signature for a request.
|
||||
*
|
||||
* @param string $access_token_secret The access token secret.
|
||||
* @param string $url The URL to add the access token and signature to.
|
||||
* @param string $method The request method.
|
||||
* @param array $body The body of the request.
|
||||
* @return string The signature.
|
||||
*/
|
||||
private static function create_request_signature( string $access_token_secret, string $url, string $method, $body = null ): string {
|
||||
|
||||
$request_uri = wp_parse_url( $url, PHP_URL_PATH );
|
||||
$query_string = wp_parse_url( $url, PHP_URL_QUERY );
|
||||
|
||||
if ( is_string( $query_string ) ) {
|
||||
$request_uri .= '?' . $query_string;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'host' => wp_parse_url( $url, PHP_URL_HOST ),
|
||||
'request_uri' => $request_uri,
|
||||
'method' => $method,
|
||||
);
|
||||
|
||||
if ( ! empty( $body ) ) {
|
||||
$data['body'] = $body;
|
||||
}
|
||||
|
||||
return hash_hmac( 'sha256', wp_json_encode( $data ), $access_token_secret );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the access token and signature to the provided URL.
|
||||
*
|
||||
* @param string $url The URL to add the access token and signature to.
|
||||
* @return string
|
||||
*/
|
||||
public static function add_auth_parameters( string $url ): string {
|
||||
$auth = WC_Helper_Options::get( 'auth' );
|
||||
|
||||
if ( empty( $auth['access_token'] ) || empty( $auth['access_token_secret'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signature = self::create_request_signature( (string) $auth['access_token_secret'], $url, 'GET' );
|
||||
|
||||
return add_query_arg(
|
||||
array(
|
||||
'token' => $auth['access_token'],
|
||||
'signature' => $signature,
|
||||
),
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds authentication headers to an HTTP request.
|
||||
*
|
||||
* @param string $url The request URI.
|
||||
* @param array $args By-ref, the args that will be passed to wp_remote_request().
|
||||
* @return bool Were the headers added?
|
||||
*/
|
||||
private static function _authenticate( &$url, &$args ) {
|
||||
$auth = WC_Helper_Options::get( 'auth' );
|
||||
|
||||
if ( empty( $auth['access_token'] ) || empty( $auth['access_token_secret'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signature = self::create_request_signature(
|
||||
(string) $auth['access_token_secret'],
|
||||
$url,
|
||||
! empty( $args['method'] ) ? $args['method'] : 'GET',
|
||||
$args['body'] ?? null
|
||||
);
|
||||
|
||||
if ( empty( $args['headers'] ) ) {
|
||||
$args['headers'] = array();
|
||||
}
|
||||
|
||||
$headers = array(
|
||||
'Authorization' => 'Bearer ' . $auth['access_token'],
|
||||
'X-Woo-Signature' => $signature,
|
||||
);
|
||||
$args['headers'] = wp_parse_args( $headers, $args['headers'] );
|
||||
|
||||
$url = add_query_arg(
|
||||
array(
|
||||
'token' => $auth['access_token'],
|
||||
'signature' => $signature,
|
||||
),
|
||||
$url
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for self::request().
|
||||
*
|
||||
* @param string $endpoint The helper API endpoint to request.
|
||||
* @param array $args Arguments passed to wp_remote_request().
|
||||
*
|
||||
* @return array The response object from wp_safe_remote_request().
|
||||
*/
|
||||
public static function get( $endpoint, $args = array() ) {
|
||||
$args['method'] = 'GET';
|
||||
return self::request( $endpoint, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for self::request().
|
||||
*
|
||||
* @param string $endpoint The helper API endpoint to request.
|
||||
* @param array $args Arguments passed to wp_remote_request().
|
||||
*
|
||||
* @return array The response object from wp_safe_remote_request().
|
||||
*/
|
||||
public static function post( $endpoint, $args = array() ) {
|
||||
$args['method'] = 'POST';
|
||||
return self::request( $endpoint, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for self::request().
|
||||
*
|
||||
* @param string $endpoint The helper API endpoint to request.
|
||||
* @param array $args Arguments passed to wp_remote_request().
|
||||
*
|
||||
* @return array The response object from wp_safe_remote_request().
|
||||
*/
|
||||
public static function put( $endpoint, $args = array() ) {
|
||||
$args['method'] = 'PUT';
|
||||
return self::request( $endpoint, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the API base, form a request URL from a given endpoint.
|
||||
*
|
||||
* @param string $endpoint The endpoint to request.
|
||||
* @param string $query_string Optional query string to append to the URL.
|
||||
*
|
||||
* @return string The absolute endpoint URL.
|
||||
*/
|
||||
public static function url( $endpoint, $query_string = '' ) {
|
||||
$endpoint = ltrim( $endpoint, '/' );
|
||||
$endpoint = sprintf( '%s/%s/%s', self::$api_base, $endpoint, $query_string );
|
||||
$endpoint = esc_url_raw( $endpoint );
|
||||
$endpoint = rtrim( $endpoint, '/' );
|
||||
return $endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
WC_Helper_API::load();
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Helper Compat
|
||||
*
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_Compat Class
|
||||
*
|
||||
* Some level of compatibility with the legacy WooCommerce Helper plugin.
|
||||
*/
|
||||
class WC_Helper_Compat {
|
||||
|
||||
/**
|
||||
* Loads the class, runs on init.
|
||||
*/
|
||||
public static function load() {
|
||||
add_action( 'woocommerce_helper_loaded', array( __CLASS__, 'helper_loaded' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs during woocommerce_helper_loaded
|
||||
*/
|
||||
public static function helper_loaded() {
|
||||
// Stop the nagging about WooThemes Updater
|
||||
remove_action( 'admin_notices', 'woothemes_updater_notice' );
|
||||
|
||||
// A placeholder dashboard menu for legacy helper users.
|
||||
add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
|
||||
|
||||
if ( empty( $GLOBALS['woothemes_updater'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::remove_actions();
|
||||
self::migrate_connection();
|
||||
self::deactivate_plugin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove legacy helper actions (notices, menus, etc.)
|
||||
*/
|
||||
public static function remove_actions() {
|
||||
// Remove WooThemes Updater notices
|
||||
remove_action( 'network_admin_notices', array( $GLOBALS['woothemes_updater']->admin, 'maybe_display_activation_notice' ) );
|
||||
remove_action( 'admin_notices', array( $GLOBALS['woothemes_updater']->admin, 'maybe_display_activation_notice' ) );
|
||||
remove_action( 'network_admin_menu', array( $GLOBALS['woothemes_updater']->admin, 'register_settings_screen' ) );
|
||||
remove_action( 'admin_menu', array( $GLOBALS['woothemes_updater']->admin, 'register_settings_screen' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to migrate a legacy connection to a new one.
|
||||
*/
|
||||
public static function migrate_connection() {
|
||||
// Don't attempt to migrate if attempted before.
|
||||
if ( WC_Helper_Options::get( 'did-migrate' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$auth = WC_Helper_Options::get( 'auth' );
|
||||
if ( ! empty( $auth ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WC_Helper::log( 'Attempting oauth/migrate' );
|
||||
WC_Helper_Options::update( 'did-migrate', true );
|
||||
|
||||
$master_key = get_option( 'woothemes_helper_master_key' );
|
||||
if ( empty( $master_key ) ) {
|
||||
WC_Helper::log( 'Master key not found, aborting' );
|
||||
return;
|
||||
}
|
||||
|
||||
$request = WC_Helper_API::post(
|
||||
'oauth/migrate',
|
||||
array(
|
||||
'body' => array(
|
||||
'home_url' => home_url(),
|
||||
'master_key' => $master_key,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) !== 200 ) {
|
||||
WC_Helper::log( 'Call to oauth/migrate returned a non-200 response code' );
|
||||
return;
|
||||
}
|
||||
|
||||
$request_token = json_decode( wp_remote_retrieve_body( $request ) );
|
||||
if ( empty( $request_token ) ) {
|
||||
WC_Helper::log( 'Call to oauth/migrate returned an empty token' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtain an access token.
|
||||
$request = WC_Helper_API::post(
|
||||
'oauth/access_token',
|
||||
array(
|
||||
'body' => array(
|
||||
'request_token' => $request_token,
|
||||
'home_url' => home_url(),
|
||||
'migrate' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $request ) || wp_remote_retrieve_response_code( $request ) !== 200 ) {
|
||||
WC_Helper::log( 'Call to oauth/access_token returned a non-200 response code' );
|
||||
return;
|
||||
}
|
||||
|
||||
$access_token = json_decode( wp_remote_retrieve_body( $request ), true );
|
||||
if ( empty( $access_token ) ) {
|
||||
WC_Helper::log( 'Call to oauth/access_token returned an invalid token' );
|
||||
return;
|
||||
}
|
||||
|
||||
WC_Helper_Options::update(
|
||||
'auth',
|
||||
array(
|
||||
'access_token' => $access_token['access_token'],
|
||||
'access_token_secret' => $access_token['access_token_secret'],
|
||||
'site_id' => $access_token['site_id'],
|
||||
'user_id' => null, // Set this later
|
||||
'updated' => time(),
|
||||
)
|
||||
);
|
||||
|
||||
// Obtain the connected user info.
|
||||
if ( ! WC_Helper::_flush_authentication_cache() ) {
|
||||
WC_Helper::log( 'Could not obtain connected user info in migrate_connection' );
|
||||
WC_Helper_Options::update( 'auth', array() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to deactivate the legacy helper plugin.
|
||||
*/
|
||||
public static function deactivate_plugin() {
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
if ( ! function_exists( 'deactivate_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_plugin_active( 'woothemes-updater/woothemes-updater.php' ) ) {
|
||||
deactivate_plugins( 'woothemes-updater/woothemes-updater.php' );
|
||||
|
||||
// Notify the user when the plugin is deactivated.
|
||||
add_action( 'pre_current_active_plugins', array( __CLASS__, 'plugin_deactivation_notice' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display admin notice directing the user where to go.
|
||||
*/
|
||||
public static function plugin_deactivation_notice() {
|
||||
?>
|
||||
<div id="message" class="error is-dismissible">
|
||||
<p><?php printf( __( 'The WooCommerce Helper plugin is no longer needed. <a href="%s">Manage subscriptions</a> from the extensions tab instead.', 'woocommerce' ), esc_url( admin_url( 'admin.php?page=wc-addons§ion=helper' ) ) ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Register menu item.
|
||||
*/
|
||||
public static function admin_menu() {
|
||||
// No additional menu items for users who did not have a connected helper before.
|
||||
$master_key = get_option( 'woothemes_helper_master_key' );
|
||||
if ( empty( $master_key ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not show the menu item if user has already seen the new screen.
|
||||
$auth = WC_Helper_Options::get( 'auth' );
|
||||
if ( ! empty( $auth['user_id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_dashboard_page( __( 'WooCommerce Helper', 'woocommerce' ), __( 'WooCommerce Helper', 'woocommerce' ), 'manage_options', 'woothemes-helper', array( __CLASS__, 'render_compat_menu' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the legacy helper compat view.
|
||||
*/
|
||||
public static function render_compat_menu() {
|
||||
$helper_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-addons',
|
||||
'section' => 'helper',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
include WC_Helper::get_view_filename( 'html-helper-compat.php' );
|
||||
}
|
||||
}
|
||||
|
||||
WC_Helper_Compat::load();
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Helper Options
|
||||
*
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_Options Class
|
||||
*
|
||||
* An interface to the woocommerce_helper_data entry in the wp_options table.
|
||||
*/
|
||||
class WC_Helper_Options {
|
||||
/**
|
||||
* The option name used to store the helper data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $option_name = 'woocommerce_helper_data';
|
||||
|
||||
/**
|
||||
* Update an option by key
|
||||
*
|
||||
* All helper options are grouped in a single options entry. This method
|
||||
* is not thread-safe, use with caution.
|
||||
*
|
||||
* @param string $key The key to update.
|
||||
* @param mixed $value The new option value.
|
||||
*
|
||||
* @return bool True if the option has been updated.
|
||||
*/
|
||||
public static function update( $key, $value ) {
|
||||
$options = get_option( self::$option_name, array() );
|
||||
$options[ $key ] = $value;
|
||||
return update_option( self::$option_name, $options, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an option by key
|
||||
*
|
||||
* @see self::update
|
||||
*
|
||||
* @param string $key The key to fetch.
|
||||
* @param mixed $default The default option to return if the key does not exist.
|
||||
*
|
||||
* @return mixed An option or the default.
|
||||
*/
|
||||
public static function get( $key, $default = false ) {
|
||||
$options = get_option( self::$option_name, array() );
|
||||
if ( is_array( $options ) && array_key_exists( $key, $options ) ) {
|
||||
return $options[ $key ];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Helper - React admin interface
|
||||
*
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_Orders_API
|
||||
*
|
||||
* Pings WooCommerce.com to create an order and pull in the necessary data to start the installation process.
|
||||
*/
|
||||
class WC_Helper_Orders_API {
|
||||
/**
|
||||
* Loads the class, runs on init
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function load() {
|
||||
add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST routes for the Marketplace Orders API.
|
||||
* These endpoints are used by the Marketplace Subscriptions React UI.
|
||||
*/
|
||||
public static function register_rest_routes() {
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/create-order',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( __CLASS__, 'create_order' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
'args' => array(
|
||||
'product_id' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => function( $argument ) {
|
||||
return is_int( $argument );
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Extensions page can only be accessed by users with the manage_woocommerce
|
||||
* capability. So the API mimics that behavior.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function get_permission() {
|
||||
return WC_Helper_Subscriptions_API::get_permission();
|
||||
}
|
||||
|
||||
/**
|
||||
* Core function to create an order on WooCommerce.com. Pings the API and catches the exceptions if any.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function create_order( $request ) {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return new \WP_REST_Response(
|
||||
array(
|
||||
'message' => __( 'You do not have permission to install plugins.', 'woocommerce' ),
|
||||
),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = WC_Helper_API::post(
|
||||
'create-order',
|
||||
array(
|
||||
'authenticated' => true,
|
||||
'body' => http_build_query(
|
||||
array(
|
||||
'product_id' => $request['product_id'],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return new \WP_REST_Response(
|
||||
json_decode( wp_remote_retrieve_body( $response ), true ),
|
||||
wp_remote_retrieve_response_code( $response )
|
||||
);
|
||||
} catch ( Exception $e ) {
|
||||
return new \WP_REST_Response(
|
||||
array(
|
||||
'message' => __( 'Could not start the installation process. Reason: ', 'woocommerce' ) . $e->getMessage(),
|
||||
'code' => 'could-not-install',
|
||||
),
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WC_Helper_Orders_API::load();
|
||||
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin Helper - React admin interface
|
||||
*
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_Subscriptions_API
|
||||
*
|
||||
* The main entry-point for all things related to the Marketplace Subscriptions API.
|
||||
* The Subscriptions API manages WooCommerce.com Subscriptions.
|
||||
*/
|
||||
class WC_Helper_Subscriptions_API {
|
||||
|
||||
/**
|
||||
* Loads the class, runs on init
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function load() {
|
||||
add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST routes for the Marketplace Subscriptions API.
|
||||
* These endpoints are used by the Marketplace Subscriptions React UI.
|
||||
*/
|
||||
public static function register_rest_routes() {
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/refresh',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( __CLASS__, 'refresh' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/subscriptions',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( __CLASS__, 'get_subscriptions' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/subscriptions/connect',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( __CLASS__, 'connect' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
'args' => array(
|
||||
'product_key' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/subscriptions/disconnect',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( __CLASS__, 'disconnect' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
'args' => array(
|
||||
'product_key' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/subscriptions/activate',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( __CLASS__, 'activate' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
'args' => array(
|
||||
'product_key' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'wc/v3',
|
||||
'/marketplace/subscriptions/install-url',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( __CLASS__, 'install_url' ),
|
||||
'permission_callback' => array( __CLASS__, 'get_permission' ),
|
||||
'args' => array(
|
||||
'product_key' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Extensions page can only be accessed by users with the manage_woocommerce
|
||||
* capability. So the API mimics that behavior.
|
||||
*/
|
||||
public static function get_permission() {
|
||||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch subscriptions from WooCommerce.com and serve them
|
||||
* as JSON.
|
||||
*/
|
||||
public static function get_subscriptions() {
|
||||
$subscriptions = WC_Helper::get_subscription_list_data();
|
||||
wp_send_json(
|
||||
array_values(
|
||||
$subscriptions
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh account and subscriptions from WooCommerce.com and serve subscriptions
|
||||
* as JSON.
|
||||
*/
|
||||
public static function refresh() {
|
||||
WC_Helper::refresh_helper_subscriptions();
|
||||
self::get_subscriptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a WooCommerce.com subscription.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
public static function connect( $request ) {
|
||||
$product_key = $request->get_param( 'product_key' );
|
||||
try {
|
||||
$success = WC_Helper::activate_helper_subscription( $product_key );
|
||||
} catch ( Exception $e ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $e->getMessage(),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
if ( $success ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'Your subscription has been connected.', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'There was an error connecting your subscription. Please try again.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a WooCommerce.com subscription.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
public static function disconnect( $request ) {
|
||||
$product_key = $request->get_param( 'product_key' );
|
||||
try {
|
||||
$success = WC_Helper::deactivate_helper_subscription( $product_key );
|
||||
} catch ( Exception $e ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $e->getMessage(),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
if ( $success ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'Your subscription has been disconnected.', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'There was an error disconnecting your subscription. Please try again.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a WooCommerce.com product.
|
||||
* This activates the plugin/theme on the site.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
public static function activate( $request ) {
|
||||
$product_key = $request->get_param( 'product_key' );
|
||||
$subscription = WC_Helper::get_subscription( $product_key );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'We couldn\'t find a subscription for this product.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( true !== $subscription['local']['installed'] || ! isset( $subscription['local']['active'] ) ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'This product is not installed.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( true === $subscription['local']['active'] ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'This product is already active.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'plugin' === $subscription['product_type'] ) {
|
||||
$success = activate_plugin( $subscription['local']['path'] );
|
||||
if ( is_wp_error( $success ) ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'There was an error activating this plugin.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
} elseif ( 'theme' === $subscription['product_type'] ) {
|
||||
switch_theme( $subscription['local']['slug'] );
|
||||
$theme = wp_get_theme();
|
||||
if ( $subscription['local']['slug'] !== $theme->get_stylesheet() ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'There was an error activating this theme.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'This product has been activated.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the install URL for a WooCommerce.com product.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
public static function install_url( $request ) {
|
||||
$product_key = $request->get_param( 'product_key' );
|
||||
$subscription = WC_Helper::get_subscription( $product_key );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'We couldn\'t find a subscription for this product.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( true === $subscription['local']['installed'] ) {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'This product is already installed.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$install_url = WC_Helper::get_subscription_install_url(
|
||||
$subscription['product_key'],
|
||||
$subscription['product_slug']
|
||||
);
|
||||
|
||||
if ( ! $install_url ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => __( 'There was an error getting the install URL for this product.', 'woocommerce' ),
|
||||
),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'url' => $install_url,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WC_Helper_Subscriptions_API::load();
|
||||
@@ -0,0 +1,578 @@
|
||||
<?php
|
||||
/**
|
||||
* The update helper for WooCommerce.com plugins.
|
||||
*
|
||||
* @class WC_Helper_Updater
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_Updater Class
|
||||
*
|
||||
* Contains the logic to fetch available updates and hook into Core's update
|
||||
* routines to serve WooCommerce.com-provided packages.
|
||||
*/
|
||||
class WC_Helper_Updater {
|
||||
|
||||
/**
|
||||
* Loads the class, runs on init.
|
||||
*/
|
||||
public static function load() {
|
||||
add_action( 'pre_set_site_transient_update_plugins', array( __CLASS__, 'transient_update_plugins' ), 21, 1 );
|
||||
add_action( 'pre_set_site_transient_update_themes', array( __CLASS__, 'transient_update_themes' ), 21, 1 );
|
||||
add_action( 'upgrader_process_complete', array( __CLASS__, 'upgrader_process_complete' ) );
|
||||
add_action( 'upgrader_pre_download', array( __CLASS__, 'block_expired_updates' ), 10, 2 );
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'add_hook_for_modifying_update_notices' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the hook for modifying default WPCore update notices on the plugins management page.
|
||||
*/
|
||||
public static function add_hook_for_modifying_update_notices() {
|
||||
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
|
||||
add_action( 'load-plugins.php', array( __CLASS__, 'setup_update_plugins_messages' ), 11 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in a cron thread, or in a visitor thread if triggered
|
||||
* by _maybe_update_plugins(), or in an auto-update thread.
|
||||
*
|
||||
* @param object $transient The update_plugins transient object.
|
||||
*
|
||||
* @return object The same or a modified version of the transient.
|
||||
*/
|
||||
public static function transient_update_plugins( $transient ) {
|
||||
$update_data = self::get_update_data();
|
||||
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
|
||||
if ( empty( $update_data[ $plugin['_product_id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $update_data[ $plugin['_product_id'] ];
|
||||
$filename = $plugin['_filename'];
|
||||
|
||||
$item = array(
|
||||
'id' => 'woocommerce-com-' . $plugin['_product_id'],
|
||||
'slug' => 'woocommerce-com-' . $data['slug'],
|
||||
'plugin' => $filename,
|
||||
'new_version' => $data['version'],
|
||||
'url' => $data['url'],
|
||||
'package' => '',
|
||||
'upgrade_notice' => $data['upgrade_notice'],
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the Woo plugin data before saving it in transient used for updates.
|
||||
*
|
||||
* @since 8.7.0
|
||||
*
|
||||
* @param array $item Plugin item to modify.
|
||||
* @param array $data Subscription data fetched from Helper API for the plugin.
|
||||
* @param int $product_id Woo product id assigned to the plugin.
|
||||
*/
|
||||
$item = apply_filters( 'update_woo_com_subscription_details', $item, $data, $plugin['_product_id'] );
|
||||
|
||||
if ( isset( $data['requires_php'] ) ) {
|
||||
$item['requires_php'] = $data['requires_php'];
|
||||
}
|
||||
|
||||
if ( $transient instanceof stdClass ) {
|
||||
if ( version_compare( $plugin['Version'], $data['version'], '<' ) ) {
|
||||
$transient->response[ $filename ] = (object) $item;
|
||||
unset( $transient->no_update[ $filename ] );
|
||||
} else {
|
||||
$transient->no_update[ $filename ] = (object) $item;
|
||||
unset( $transient->response[ $filename ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $transient instanceof stdClass ) {
|
||||
$translations = self::get_translations_update_data();
|
||||
$transient->translations = array_merge( isset( $transient->translations ) ? $transient->translations : array(), $translations );
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on pre_set_site_transient_update_themes, provides custom
|
||||
* packages for WooCommerce.com-hosted extensions.
|
||||
*
|
||||
* @param object $transient The update_themes transient object.
|
||||
*
|
||||
* @return object The same or a modified version of the transient.
|
||||
*/
|
||||
public static function transient_update_themes( $transient ) {
|
||||
$update_data = self::get_update_data();
|
||||
|
||||
foreach ( WC_Helper::get_local_woo_themes() as $theme ) {
|
||||
if ( empty( $update_data[ $theme['_product_id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $update_data[ $theme['_product_id'] ];
|
||||
$slug = $theme['_stylesheet'];
|
||||
|
||||
$item = array(
|
||||
'theme' => $slug,
|
||||
'new_version' => $data['version'],
|
||||
'url' => $data['url'],
|
||||
'package' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the Woo plugin data before saving it in transient used for updates.
|
||||
*
|
||||
* @since 8.7.0
|
||||
*
|
||||
* @param array $item Plugin item to modify.
|
||||
* @param array $data Subscription data fetched from Helper API for the plugin.
|
||||
* @param int $product_id Woo product id assigned to the plugin.
|
||||
*/
|
||||
$item = apply_filters( 'update_woo_com_subscription_details', $item, $data, $theme['_product_id'] );
|
||||
|
||||
if ( version_compare( $theme['Version'], $data['version'], '<' ) ) {
|
||||
$transient->response[ $slug ] = $item;
|
||||
} else {
|
||||
unset( $transient->response[ $slug ] );
|
||||
$transient->checked[ $slug ] = $data['version'];
|
||||
}
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on load-plugins.php, adds a hook to show a custom plugin update message for WooCommerce.com hosted plugins.
|
||||
*
|
||||
* @return void.
|
||||
*/
|
||||
public static function setup_update_plugins_messages() {
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
|
||||
$filename = $plugin['_filename'];
|
||||
add_action( 'in_plugin_update_message-' . $filename, array( __CLASS__, 'add_install_marketplace_plugin_message' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on in_plugin_update_message-{file-name}, show a message to install the Woo Marketplace plugin, on plugin update notification,
|
||||
* if the Woo Marketplace plugin isn't already installed.
|
||||
*
|
||||
* @param object $plugin_data TAn array of plugin metadata.
|
||||
* @param object $response An object of metadata about the available plugin update.
|
||||
*
|
||||
* @return void.
|
||||
*/
|
||||
public static function add_install_marketplace_plugin_message( $plugin_data, $response ) {
|
||||
if ( ! empty( $response->package ) || WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_installed() ) {
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: 1: Woo Update Manager plugin install URL */
|
||||
__( ' <a href="%1$s">Install WooCommerce.com Update Manager</a> to update.', 'woocommerce' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url( WC_Woo_Update_Manager_Plugin::generate_install_url() ),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
|
||||
echo esc_html_e( ' Activate WooCommerce.com Update Manager to update.', 'woocommerce' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get update data for all plugins.
|
||||
*
|
||||
* @return array Update data {product_id => data}
|
||||
* @see get_update_data
|
||||
*/
|
||||
public static function get_available_extensions_downloads_data() {
|
||||
$payload = array();
|
||||
|
||||
// Scan subscriptions.
|
||||
foreach ( WC_Helper::get_subscriptions() as $subscription ) {
|
||||
$payload[ $subscription['product_id'] ] = array(
|
||||
'product_id' => $subscription['product_id'],
|
||||
'file_id' => '',
|
||||
);
|
||||
}
|
||||
|
||||
// Scan local plugins which may or may not have a subscription.
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $data ) {
|
||||
if ( ! isset( $payload[ $data['_product_id'] ] ) ) {
|
||||
$payload[ $data['_product_id'] ] = array(
|
||||
'product_id' => $data['_product_id'],
|
||||
);
|
||||
}
|
||||
|
||||
$payload[ $data['_product_id'] ]['file_id'] = $data['_file_id'];
|
||||
}
|
||||
|
||||
return self::_update_check( $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get update data for all extensions.
|
||||
*
|
||||
* Scans through all subscriptions for the connected user, as well
|
||||
* as all Woo extensions without a subscription, and obtains update
|
||||
* data for each product.
|
||||
*
|
||||
* @return array Update data {product_id => data}
|
||||
*/
|
||||
public static function get_update_data() {
|
||||
$payload = array();
|
||||
|
||||
// Scan subscriptions.
|
||||
foreach ( WC_Helper::get_subscriptions() as $subscription ) {
|
||||
$payload[ $subscription['product_id'] ] = array(
|
||||
'product_id' => $subscription['product_id'],
|
||||
'file_id' => '',
|
||||
);
|
||||
}
|
||||
|
||||
// Scan local plugins which may or may not have a subscription.
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $data ) {
|
||||
if ( ! isset( $payload[ $data['_product_id'] ] ) ) {
|
||||
$payload[ $data['_product_id'] ] = array(
|
||||
'product_id' => $data['_product_id'],
|
||||
);
|
||||
}
|
||||
|
||||
$payload[ $data['_product_id'] ]['file_id'] = $data['_file_id'];
|
||||
}
|
||||
|
||||
// Scan local themes.
|
||||
foreach ( WC_Helper::get_local_woo_themes() as $data ) {
|
||||
if ( ! isset( $payload[ $data['_product_id'] ] ) ) {
|
||||
$payload[ $data['_product_id'] ] = array(
|
||||
'product_id' => $data['_product_id'],
|
||||
);
|
||||
}
|
||||
|
||||
$payload[ $data['_product_id'] ]['file_id'] = $data['_file_id'];
|
||||
}
|
||||
|
||||
return self::_update_check( $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translations updates information.
|
||||
*
|
||||
* Scans through all subscriptions for the connected user, as well
|
||||
* as all Woo extensions without a subscription, and obtains update
|
||||
* data for each product.
|
||||
*
|
||||
* @return array Update data {product_id => data}
|
||||
*/
|
||||
public static function get_translations_update_data() {
|
||||
$payload = array();
|
||||
|
||||
$installed_translations = wp_get_installed_translations( 'plugins' );
|
||||
|
||||
$locales = array_values( get_available_languages() );
|
||||
/**
|
||||
* Filters the locales requested for plugin translations.
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @since 4.5.0 The default value of the `$locales` parameter changed to include all locales.
|
||||
*
|
||||
* @param array $locales Plugin locales. Default is all available locales of the site.
|
||||
*/
|
||||
$locales = apply_filters( 'plugins_update_check_locales', $locales );
|
||||
$locales = array_unique( $locales );
|
||||
|
||||
// No locales, the response will be empty, we can return now.
|
||||
if ( empty( $locales ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Scan local plugins which may or may not have a subscription.
|
||||
$plugins = WC_Helper::get_local_woo_plugins();
|
||||
$active_woo_plugins = array_intersect( array_keys( $plugins ), get_option( 'active_plugins', array() ) );
|
||||
|
||||
/*
|
||||
* Use only plugins that are subscribed to the automatic translations updates.
|
||||
*/
|
||||
$active_for_translations = array_filter(
|
||||
$active_woo_plugins,
|
||||
function ( $plugin ) use ( $plugins ) {
|
||||
/**
|
||||
* Filters the plugins that are subscribed to the automatic translations updates.
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
return apply_filters( 'woocommerce_translations_updates_for_' . $plugins[ $plugin ]['slug'], false );
|
||||
}
|
||||
);
|
||||
|
||||
// Nothing to check for, exit.
|
||||
if ( empty( $active_for_translations ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( wp_doing_cron() ) {
|
||||
$timeout = 30;
|
||||
} else {
|
||||
// Three seconds, plus one extra second for every 10 plugins.
|
||||
$timeout = 3 + (int) ( count( $active_for_translations ) / 10 );
|
||||
}
|
||||
|
||||
$request_body = array(
|
||||
'locales' => $locales,
|
||||
'plugins' => array(),
|
||||
);
|
||||
|
||||
foreach ( $active_for_translations as $active_plugin ) {
|
||||
$plugin = $plugins[ $active_plugin ];
|
||||
$request_body['plugins'][ $plugin['slug'] ] = array( 'version' => $plugin['Version'] );
|
||||
}
|
||||
|
||||
$raw_response = wp_remote_post(
|
||||
'https://translate.wordpress.com/api/translations-updates/woocommerce',
|
||||
array(
|
||||
'body' => wp_json_encode( $request_body ),
|
||||
'headers' => array( 'Content-Type: application/json' ),
|
||||
'timeout' => $timeout,
|
||||
)
|
||||
);
|
||||
|
||||
// Something wrong happened on the translate server side.
|
||||
$response_code = wp_remote_retrieve_response_code( $raw_response );
|
||||
if ( 200 !== $response_code ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
|
||||
|
||||
// API error, api returned but something was wrong.
|
||||
if ( array_key_exists( 'success', $response ) && false === $response['success'] ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$translations = array();
|
||||
|
||||
foreach ( $response['data'] as $plugin_name => $language_packs ) {
|
||||
foreach ( $language_packs as $language_pack ) {
|
||||
// Maybe we have this language pack already installed so lets check revision date.
|
||||
if ( array_key_exists( $plugin_name, $installed_translations ) && array_key_exists( $language_pack['wp_locale'], $installed_translations[ $plugin_name ] ) ) {
|
||||
$installed_translation_revision_time = new DateTime( $installed_translations[ $plugin_name ][ $language_pack['wp_locale'] ]['PO-Revision-Date'] );
|
||||
$new_translation_revision_time = new DateTime( $language_pack['last_modified'] );
|
||||
// Skip if translation language pack is not newer than what is installed already.
|
||||
if ( $new_translation_revision_time <= $installed_translation_revision_time ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$translations[] = array(
|
||||
'type' => 'plugin',
|
||||
'slug' => $plugin_name,
|
||||
'language' => $language_pack['wp_locale'],
|
||||
'version' => $language_pack['version'],
|
||||
'updated' => $language_pack['last_modified'],
|
||||
'package' => $language_pack['package'],
|
||||
'autoupdate' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update check API call.
|
||||
*
|
||||
* The call is cached based on the payload (product ids, file ids). If
|
||||
* the payload changes, the cache is going to miss.
|
||||
*
|
||||
* @param array $payload Information about the plugin to update.
|
||||
* @return array Update data for each requested product.
|
||||
*/
|
||||
private static function _update_check( $payload ) {
|
||||
ksort( $payload );
|
||||
$hash = md5( wp_json_encode( $payload ) );
|
||||
|
||||
$cache_key = '_woocommerce_helper_updates';
|
||||
$data = get_transient( $cache_key );
|
||||
if ( false !== $data ) {
|
||||
if ( hash_equals( $hash, $data['hash'] ) ) {
|
||||
return $data['products'];
|
||||
}
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'hash' => $hash,
|
||||
'updated' => time(),
|
||||
'products' => array(),
|
||||
'errors' => array(),
|
||||
);
|
||||
|
||||
$request = WC_Helper_API::post(
|
||||
'update-check',
|
||||
array(
|
||||
'body' => wp_json_encode( array( 'products' => $payload ) ),
|
||||
'authenticated' => true,
|
||||
)
|
||||
);
|
||||
|
||||
if ( wp_remote_retrieve_response_code( $request ) !== 200 ) {
|
||||
$data['errors'][] = 'http-error';
|
||||
} else {
|
||||
$data['products'] = json_decode( wp_remote_retrieve_body( $request ), true );
|
||||
}
|
||||
|
||||
set_transient( $cache_key, $data, 12 * HOUR_IN_SECONDS );
|
||||
return $data['products'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of products that have updates.
|
||||
*
|
||||
* @return int The number of products with updates.
|
||||
*/
|
||||
public static function get_updates_count() {
|
||||
$cache_key = '_woocommerce_helper_updates_count';
|
||||
$count = get_transient( $cache_key );
|
||||
if ( false !== $count ) {
|
||||
return $count;
|
||||
}
|
||||
|
||||
// Don't fetch any new data since this function in high-frequency.
|
||||
if ( ! get_transient( '_woocommerce_helper_subscriptions' ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( ! get_transient( '_woocommerce_helper_updates' ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$update_data = self::get_update_data();
|
||||
|
||||
if ( empty( $update_data ) ) {
|
||||
set_transient( $cache_key, $count, 12 * HOUR_IN_SECONDS );
|
||||
return $count;
|
||||
}
|
||||
|
||||
// Scan local plugins.
|
||||
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
|
||||
if ( empty( $update_data[ $plugin['_product_id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( version_compare( $plugin['Version'], $update_data[ $plugin['_product_id'] ]['version'], '<' ) ) {
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
// Scan local themes.
|
||||
foreach ( WC_Helper::get_local_woo_themes() as $theme ) {
|
||||
if ( empty( $update_data[ $theme['_product_id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( version_compare( $theme['Version'], $update_data[ $theme['_product_id'] ]['version'], '<' ) ) {
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
set_transient( $cache_key, $count, 12 * HOUR_IN_SECONDS );
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the update count to based on the status of the site.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_updates_count_based_on_site_status() {
|
||||
if ( ! WC_Helper::is_site_connected() ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$count = self::get_updates_count() ?? 0;
|
||||
if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_installed() || ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) {
|
||||
++$count;
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the updates count markup.
|
||||
*
|
||||
* @return string Updates count markup, empty string if no updates avairable.
|
||||
*/
|
||||
public static function get_updates_count_html() {
|
||||
$count = self::get_updates_count_based_on_site_status();
|
||||
$count_html = sprintf( '<span class="update-plugins count-%d"><span class="update-count">%d</span></span>', $count, number_format_i18n( $count ) );
|
||||
|
||||
return $count_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes cached update data.
|
||||
*/
|
||||
public static function flush_updates_cache() {
|
||||
delete_transient( '_woocommerce_helper_updates' );
|
||||
delete_transient( '_woocommerce_helper_updates_count' );
|
||||
delete_site_transient( 'update_plugins' );
|
||||
delete_site_transient( 'update_themes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when a user successfully updated a theme or a plugin.
|
||||
*/
|
||||
public static function upgrader_process_complete() {
|
||||
delete_transient( '_woocommerce_helper_updates_count' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into the upgrader_pre_download filter in order to better handle error messaging around expired
|
||||
* plugin updates. Initially we were using an empty string, but the error message that no_package
|
||||
* results in does not fit the cause.
|
||||
*
|
||||
* @since 4.1.0
|
||||
* @param bool $reply Holds the current filtered response.
|
||||
* @param string $package The path to the package file for the update.
|
||||
* @return false|WP_Error False to proceed with the update as normal, anything else to be returned instead of updating.
|
||||
*/
|
||||
public static function block_expired_updates( $reply, $package ) {
|
||||
// Don't override a reply that was set already.
|
||||
if ( false !== $reply ) {
|
||||
return $reply;
|
||||
}
|
||||
|
||||
// Only for packages with expired subscriptions.
|
||||
if ( 0 !== strpos( $package, 'woocommerce-com-expired-' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'woocommerce_subscription_expired',
|
||||
sprintf(
|
||||
// translators: %s: URL of WooCommerce.com subscriptions tab.
|
||||
__( 'Please visit the <a href="%s" target="_blank">subscriptions page</a> and renew to continue receiving updates.', 'woocommerce' ),
|
||||
esc_url( admin_url( 'admin.php?page=wc-addons§ion=helper' ) )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WC_Helper_Updater::load();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Updates the Product API response from WP.org.
|
||||
*
|
||||
* @class WC_Plugin_Api_Updater
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Class WC_Plugin_Api_Updater
|
||||
*/
|
||||
class WC_Plugin_Api_Updater {
|
||||
|
||||
/**
|
||||
* Loads the class, runs on init.
|
||||
*/
|
||||
public static function load() {
|
||||
add_filter( 'plugins_api', array( __CLASS__, 'plugins_api' ), 20, 3 );
|
||||
add_filter( 'themes_api', array( __CLASS__, 'themes_api' ), 20, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin information callback for Woo extensions.
|
||||
*
|
||||
* @param object $response The response core needs to display the modal.
|
||||
* @param string $action The requested plugins_api() action.
|
||||
* @param object $args Arguments passed to plugins_api().
|
||||
*
|
||||
* @return object An updated $response.
|
||||
*/
|
||||
public static function plugins_api( $response, $action, $args ) {
|
||||
if ( 'plugin_information' !== $action ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return self::override_products_api_response( $response, $action, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme information callback for Woo themes.
|
||||
*
|
||||
* @param object $response The response core needs to display the modal.
|
||||
* @param string $action The requested themes_api() action.
|
||||
* @param object $args Arguments passed to themes_api().
|
||||
*/
|
||||
public static function themes_api( $response, $action, $args ) {
|
||||
if ( 'theme_information' !== $action ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return self::override_products_api_response( $response, $action, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the products API to fetch data from the Helper API if it's a Woo product.
|
||||
*
|
||||
* @param object $response The response core needs to display the modal.
|
||||
* @param string $action The requested action.
|
||||
* @param object $args Arguments passed to the API.
|
||||
*/
|
||||
public static function override_products_api_response( $response, $action, $args ) {
|
||||
if ( empty( $args->slug ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Only for slugs that start with woocommerce-com-.
|
||||
if ( 0 !== strpos( $args->slug, 'woocommerce-com-' ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$clean_slug = str_replace( 'woocommerce-com-', '', $args->slug );
|
||||
|
||||
// Look through update data by slug.
|
||||
$update_data = WC_Helper_Updater::get_update_data();
|
||||
$products = wp_list_filter( $update_data, array( 'slug' => $clean_slug ) );
|
||||
|
||||
if ( empty( $products ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$product_id = array_keys( $products );
|
||||
$product_id = array_shift( $product_id );
|
||||
|
||||
// Fetch the product information from the Helper API.
|
||||
$request = WC_Helper_API::get(
|
||||
add_query_arg(
|
||||
array(
|
||||
'product_id' => absint( $product_id ),
|
||||
),
|
||||
'info'
|
||||
),
|
||||
array( 'authenticated' => true )
|
||||
);
|
||||
|
||||
$results = json_decode( wp_remote_retrieve_body( $request ), true );
|
||||
if ( ! empty( $results ) ) {
|
||||
$response = (object) $results;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
WC_Plugin_Api_Updater::load();
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* A utility class for Woo Update Manager plugin.
|
||||
*
|
||||
* @class WC_Woo_Update_Manager_Plugin
|
||||
* @package WooCommerce\Admin\Helper
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Helper_Plugin Class
|
||||
*
|
||||
* Contains the logic to manage the Woo Update Manager plugin.
|
||||
*/
|
||||
class WC_Woo_Update_Manager_Plugin {
|
||||
const WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE = 'woo-update-manager/woo-update-manager.php';
|
||||
const WOO_UPDATE_MANAGER_DOWNLOAD_URL = 'https://woocommerce.com/product-download/woo-update-manager';
|
||||
const WOO_UPDATE_MANAGER_SLUG = 'woo-update-manager';
|
||||
|
||||
/**
|
||||
* Loads the class, runs on init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function load(): void {
|
||||
add_action( 'admin_notices', array( __CLASS__, 'show_woo_update_manager_install_notice' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Woo Update Manager plugin is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_plugin_active(): bool {
|
||||
return is_plugin_active_for_network( self::WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE ) || is_plugin_active( self::WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Woo Update Manager plugin is installed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_plugin_installed(): bool {
|
||||
return file_exists( WP_PLUGIN_DIR . '/' . self::WOO_UPDATE_MANAGER_PLUGIN_MAIN_FILE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the URL to install the Woo Update Manager plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate_install_url(): string {
|
||||
$install_url = WC_Helper::get_install_base_url() . self::WOO_UPDATE_MANAGER_SLUG . '/';
|
||||
|
||||
return WC_Helper_API::add_auth_parameters( $install_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the Woo Update Manager plugin.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_plugin_slug(): string {
|
||||
return self::WOO_UPDATE_MANAGER_SLUG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notice on the WC admin pages to install or activate the Woo Update Manager plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function show_woo_update_manager_install_notice(): void {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! WC_Helper::is_site_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! PageController::is_admin_or_embed_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::is_plugin_installed() && self::is_plugin_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! self::is_plugin_installed() ) {
|
||||
|
||||
if ( self::install_admin_notice_dismissed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-woo-updater-not-installed.php';
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::activate_admin_notice_dismissed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-notice-woo-updater-not-activated.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the installation notice has been dismissed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function install_admin_notice_dismissed(): bool {
|
||||
return get_user_meta( get_current_user_id(), 'dismissed_woo_updater_not_installed_notice', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the activation notice has been dismissed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function activate_admin_notice_dismissed(): bool {
|
||||
return get_user_meta( get_current_user_id(), 'dismissed_woo_updater_not_activated_notice', true );
|
||||
}
|
||||
}
|
||||
|
||||
WC_Woo_Update_Manager_Plugin::load();
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php defined( 'ABSPATH' ) or exit(); ?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1><?php _e( 'Looking for the WooCommerce Helper?', 'woocommerce' ); ?></h1>
|
||||
<p><?php printf( __( 'We\'ve made things simpler and easier to manage moving forward. From now on you can manage all your WooCommerce purchases directly from the Extensions menu within the WooCommerce plugin itself. <a href="%s">View and manage</a> your extensions now.', 'woocommerce' ), esc_url( $helper_url ) ); ?></p>
|
||||
</div>
|
||||
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper main view
|
||||
*
|
||||
* @package WooCommerce\Helper
|
||||
*/
|
||||
|
||||
?>
|
||||
<?php defined( 'ABSPATH' ) || exit(); ?>
|
||||
|
||||
<div class="wrap woocommerce wc-subscriptions-wrap wc-helper">
|
||||
<?php require WC_Helper::get_view_filename( 'html-section-nav.php' ); ?>
|
||||
<h1 class="screen-reader-text"><?php esc_html_e( 'WooCommerce Extensions', 'woocommerce' ); ?></h1>
|
||||
|
||||
<?php require WC_Helper::get_view_filename( 'html-section-notices.php' ); ?>
|
||||
|
||||
<div class="subscriptions-header">
|
||||
<h2><?php esc_html_e( 'Subscriptions', 'woocommerce' ); ?></h2>
|
||||
<?php require WC_Helper::get_view_filename( 'html-section-account.php' ); ?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: Introduction to list of WooCommerce.com extensions the merchant has subscriptions for. */
|
||||
__(
|
||||
'Below is a list of extensions available on your WooCommerce.com account. To receive extension updates please make sure the extension is installed, and its subscription activated and connected to your WooCommerce.com account. Extensions can be activated from the <a href="%s">Plugins</a> screen.',
|
||||
'woocommerce'
|
||||
),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url(
|
||||
admin_url( 'plugins.php' )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="subscription-filter">
|
||||
<label><?php esc_html_e( 'Sort by:', 'woocommerce' ); ?> <span class="chevron dashicons dashicons-arrow-up-alt2"></span></label>
|
||||
<?php
|
||||
$filters = array_keys( WC_Helper::get_filters() );
|
||||
$last_filter = array_pop( $filters );
|
||||
$current_filter = WC_Helper::get_current_filter();
|
||||
$counts = WC_Helper::get_filters_counts();
|
||||
?>
|
||||
|
||||
<?php
|
||||
foreach ( WC_Helper::get_filters() as $key => $label ) :
|
||||
// Don't show empty filters.
|
||||
if ( empty( $counts[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = admin_url( 'admin.php?page=wc-addons§ion=helper&filter=' . $key );
|
||||
$class_html = $current_filter === $key ? 'class="current"' : '';
|
||||
?>
|
||||
<li>
|
||||
<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<a <?php echo $class_html; ?> href="<?php echo esc_url( $url ); ?>">
|
||||
<?php echo esc_html( $label ); ?>
|
||||
<span class="count">(<?php echo absint( $counts[ $key ] ); ?>)</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<?php if ( ! empty( $subscriptions ) ) : ?>
|
||||
<?php foreach ( $subscriptions as $subscription ) : ?>
|
||||
<tbody>
|
||||
<tr class="wp-list-table__row is-ext-header">
|
||||
<td class="wp-list-table__ext-details">
|
||||
<div class="wp-list-table__ext-title">
|
||||
<a href="<?php echo esc_url( WC_Helper::add_utm_params_to_url_for_subscription_link( $subscription['product_url'], 'product-name' ) ); ?>" target="_blank">
|
||||
<?php echo esc_html( $subscription['product_name'] ); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="wp-list-table__ext-description">
|
||||
<?php if ( $subscription['lifetime'] ) : ?>
|
||||
<span class="renews">
|
||||
<?php esc_html_e( 'Lifetime Subscription', 'woocommerce' ); ?>
|
||||
</span>
|
||||
<?php elseif ( $subscription['expired'] ) : ?>
|
||||
<span class="renews">
|
||||
<strong><?php esc_html_e( 'Expired :(', 'woocommerce' ); ?></strong>
|
||||
<?php echo esc_html( date_i18n( 'F jS, Y', $subscription['expires'] ) ); ?>
|
||||
</span>
|
||||
<?php elseif ( $subscription['autorenew'] ) : ?>
|
||||
<span class="renews">
|
||||
<?php esc_html_e( 'Auto renews on:', 'woocommerce' ); ?>
|
||||
<?php echo esc_html( date_i18n( 'F jS, Y', $subscription['expires'] ) ); ?>
|
||||
</span>
|
||||
<?php elseif ( $subscription['expiring'] ) : ?>
|
||||
<span class="renews">
|
||||
<strong><?php esc_html_e( 'Expiring soon!', 'woocommerce' ); ?></strong>
|
||||
<?php echo esc_html( date_i18n( 'F jS, Y', $subscription['expires'] ) ); ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
<span class="renews">
|
||||
<?php esc_html_e( 'Expires on:', 'woocommerce' ); ?>
|
||||
<?php echo esc_html( date_i18n( 'F jS, Y', $subscription['expires'] ) ); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<br/>
|
||||
<span class="subscription">
|
||||
<?php
|
||||
if ( ! $subscription['active'] && $subscription['maxed'] ) {
|
||||
/* translators: %1$d: sites active, %2$d max sites active */
|
||||
printf( esc_html__( 'Subscription: Not available - %1$d of %2$d already in use', 'woocommerce' ), absint( $subscription['sites_active'] ), absint( $subscription['sites_max'] ) );
|
||||
} elseif ( $subscription['sites_max'] > 0 ) {
|
||||
/* translators: %1$d: sites active, %2$d max sites active */
|
||||
printf( esc_html__( 'Subscription: Using %1$d of %2$d sites available', 'woocommerce' ), absint( $subscription['sites_active'] ), absint( $subscription['sites_max'] ) );
|
||||
} else {
|
||||
esc_html_e( 'Subscription: Unlimited', 'woocommerce' );
|
||||
}
|
||||
|
||||
// Check shared.
|
||||
if ( ! empty( $subscription['is_shared'] ) && ! empty( $subscription['owner_email'] ) ) {
|
||||
/* translators: Email address of person who shared the subscription. */
|
||||
printf( '</br>' . esc_html__( 'Shared by %s', 'woocommerce' ), esc_html( $subscription['owner_email'] ) );
|
||||
} elseif ( isset( $subscription['master_user_email'] ) ) {
|
||||
/* translators: Email address of person who shared the subscription. */
|
||||
printf( '</br>' . esc_html__( 'Shared by %s', 'woocommerce' ), esc_html( $subscription['master_user_email'] ) );
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wp-list-table__ext-actions">
|
||||
<?php if ( ! $subscription['active'] && $subscription['maxed'] ) : ?>
|
||||
<a class="button" href="https://woocommerce.com/my-account/my-subscriptions/" target="_blank"><?php esc_html_e( 'Upgrade', 'woocommerce' ); ?></a>
|
||||
<?php elseif ( ! $subscription['local']['installed'] && ! $subscription['expired'] ) : ?>
|
||||
<a class="button <?php echo empty( $subscription['download_primary'] ) ? 'button-secondary' : ''; ?>" href="<?php echo esc_url( $subscription['download_url'] ); ?>" target="_blank"><?php esc_html_e( 'Download', 'woocommerce' ); ?></a>
|
||||
<?php elseif ( $subscription['active'] ) : ?>
|
||||
<span class="form-toggle__wrapper">
|
||||
<a href="<?php echo esc_url( $subscription['deactivate_url'] ); ?>" class="form-toggle active is-compact" role="link" aria-checked="true"><?php esc_html_e( 'Active', 'woocommerce' ); ?></a>
|
||||
<label class="form-toggle__label" for="activate-extension">
|
||||
<span class="form-toggle__label-content">
|
||||
<label for="activate-extension"><?php esc_html_e( 'Active', 'woocommerce' ); ?></label>
|
||||
</span>
|
||||
<span class="form-toggle__switch"></span>
|
||||
</label>
|
||||
</span>
|
||||
<?php elseif ( ! $subscription['expired'] ) : ?>
|
||||
<span class="form-toggle__wrapper">
|
||||
<a href="<?php echo esc_url( $subscription['activate_url'] ); ?>" class="form-toggle is-compact" role="link" aria-checked="false"><?php esc_html_e( 'Inactive', 'woocommerce' ); ?></a>
|
||||
<label class="form-toggle__label" for="activate-extension">
|
||||
<span class="form-toggle__label-content">
|
||||
<label for="activate-extension"><?php esc_html_e( 'Inactive', 'woocommerce' ); ?></label>
|
||||
</span>
|
||||
<span class="form-toggle__switch"></span>
|
||||
</label>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
<span class="form-toggle__wrapper">
|
||||
<span class="form-toggle disabled is-compact"><?php esc_html_e( 'Inactive', 'woocommerce' ); ?></span>
|
||||
<label class="form-toggle__label" for="activate-extension">
|
||||
<span class="form-toggle__label-content">
|
||||
<label for="activate-extension"><?php esc_html_e( 'Inactive', 'woocommerce' ); ?></label>
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php foreach ( $subscription['actions'] as $subscription_action ) : ?>
|
||||
<tr class="wp-list-table__row wp-list-table__ext-updates">
|
||||
<td class="wp-list-table__ext-status <?php echo sanitize_html_class( $subscription_action['status'] ); ?>">
|
||||
<p><span class="dashicons <?php echo sanitize_html_class( $subscription_action['icon'] ); ?>"></span>
|
||||
<?php echo wp_kses_post( $subscription_action['message'] ); ?>
|
||||
</p>
|
||||
</td>
|
||||
<td class="wp-list-table__ext-actions">
|
||||
<?php if ( ! empty( $subscription_action['button_label'] ) && ! empty( $subscription_action['button_url'] ) ) : ?>
|
||||
<a class="button <?php echo empty( $subscription_action['primary'] ) ? 'button-secondary' : ''; ?>" href="<?php echo esc_url( $subscription_action['button_url'] ); ?>"><?php echo esc_html( $subscription_action['button_label'] ); ?></a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</tbody>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="3"><em><?php esc_html_e( 'Could not find any subscriptions on your WooCommerce.com account', 'woocommerce' ); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ( ! empty( $no_subscriptions ) ) : ?>
|
||||
<h2><?php esc_html_e( 'Installed Extensions without a Subscription', 'woocommerce' ); ?></h2>
|
||||
<p>Below is a list of WooCommerce.com products available on your site - but are either out-dated or do not have a valid subscription.</p>
|
||||
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<?php /* Extensions without a subscription. */ ?>
|
||||
<?php foreach ( $no_subscriptions as $filename => $data ) : ?>
|
||||
<tbody>
|
||||
<tr class="wp-list-table__row is-ext-header">
|
||||
<td class="wp-list-table__ext-details color-bar autorenews">
|
||||
<div class="wp-list-table__ext-title">
|
||||
<a href="<?php echo esc_url( WC_Helper::add_utm_params_to_url_for_subscription_link( $data['_product_url'], 'product-name' ) ); ?>" target="_blank">
|
||||
<?php echo esc_html( $data['Name'] ); ?>
|
||||
</a>
|
||||
</div>
|
||||
<div class="wp-list-table__ext-description">
|
||||
</div>
|
||||
</td>
|
||||
<td class="wp-list-table__ext-actions">
|
||||
<span class="form-toggle__wrapper">
|
||||
<span class="form-toggle disabled is-compact" ><?php esc_html_e( 'Inactive', 'woocommerce' ); ?></span>
|
||||
<label class="form-toggle__label" for="activate-extension">
|
||||
<span class="form-toggle__label-content">
|
||||
<label for="activate-extension"><?php esc_html_e( 'Inactive', 'woocommerce' ); ?></label>
|
||||
</span>
|
||||
</label>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php foreach ( $data['_actions'] as $subscription_action ) : ?>
|
||||
<tr class="wp-list-table__row wp-list-table__ext-updates">
|
||||
<td class="wp-list-table__ext-status <?php echo sanitize_html_class( $subscription_action['status'] ); ?>">
|
||||
<p><span class="dashicons <?php echo sanitize_html_class( $subscription_action['icon'] ); ?>"></span>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
$subscription_action['message'],
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'title' => array(),
|
||||
),
|
||||
'br' => array(),
|
||||
'em' => array(),
|
||||
'strong' => array(),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</td>
|
||||
<td class="wp-list-table__ext-actions">
|
||||
<a class="button" href="<?php echo esc_url( $subscription_action['button_url'] ); ?>" target="_blank"><?php echo esc_html( $subscription_action['button_label'] ); ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</tbody>
|
||||
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper Admin Notice - Woo Updater Plugin is not activated.
|
||||
*
|
||||
* @package WooCommerce\Views
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
?>
|
||||
<div id="message" class="error woocommerce-message">
|
||||
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'woo_updater_not_activated' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: 1: WP plugin management URL */
|
||||
__(
|
||||
'Please <a href="%1$s">activate the WooCommerce.com Update Manager</a> to continue receiving the updates and streamlined support included in your WooCommerce.com subscriptions.',
|
||||
'woocommerce'
|
||||
),
|
||||
esc_url( admin_url( 'plugins.php' ) ),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper Admin Notice - Woo Updater Plugin is not Installed.
|
||||
*
|
||||
* @package WooCommerce\Views
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
?>
|
||||
<div id="message" class="error woocommerce-message">
|
||||
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'woo_updater_not_installed' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>"><?php esc_html_e( 'Dismiss', 'woocommerce' ); ?></a>
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: 1: Woo Update Manager plugin install URL 2: Woo Update Manager plugin download URL */
|
||||
__(
|
||||
'Please <a href="%1$s">Install the WooCommerce.com Update Manager</a> to continue receiving the updates and streamlined support included in your WooCommerce.com subscriptions. Alternatively, you can <a href="%2$s">download</a> and install it manually.',
|
||||
'woocommerce'
|
||||
),
|
||||
esc_url( WC_Woo_Update_Manager_Plugin::generate_install_url() ),
|
||||
esc_url( WC_Woo_Update_Manager_Plugin::WOO_UPDATE_MANAGER_DOWNLOAD_URL )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin -> WooCommerce -> Extensions -> WooCommerce.com Subscriptions main page.
|
||||
*
|
||||
* @package WooCommerce\Views
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit();
|
||||
|
||||
?>
|
||||
|
||||
<div class="wrap woocommerce wc-addons-wrap wc-helper">
|
||||
<?php require WC_Helper::get_view_filename( 'html-section-nav.php' ); ?>
|
||||
<h1 class="screen-reader-text"><?php esc_html_e( 'WooCommerce Extensions', 'woocommerce' ); ?></h1>
|
||||
<?php require WC_Helper::get_view_filename( 'html-section-notices.php' ); ?>
|
||||
|
||||
<div class="start-container">
|
||||
<div class="text">
|
||||
<img src="<?php echo esc_url( WC()->plugin_url() . '/assets/images/woocommerce_logo.png' ); ?>" alt="<?php esc_attr_e( 'WooCommerce', 'woocommerce' ); ?>" style="width:180px;">
|
||||
|
||||
<?php if ( ! empty( $_GET['wc-helper-status'] ) && 'helper-disconnected' === $_GET['wc-helper-status'] ) : ?>
|
||||
<p><strong><?php esc_html_e( 'Sorry to see you go.', 'woocommerce' ); ?></strong> <?php esc_html_e( 'Feel free to reconnect again using the button below.', 'woocommerce' ); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2><?php esc_html_e( 'Manage your subscriptions, get important product notifications, and updates, all from the convenience of your WooCommerce dashboard', 'woocommerce' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Once connected, your WooCommerce.com purchases will be listed here.', 'woocommerce' ); ?></p>
|
||||
<p><a class="button button-primary button-helper-connect" href="<?php echo esc_url( $connect_url ); ?>"><?php esc_html_e( 'Connect', 'woocommerce' ); ?></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php defined( 'ABSPATH' ) or exit(); ?>
|
||||
|
||||
<a class="button button-update" href="<?php echo esc_url( $refresh_url ); ?>"><span class="dashicons dashicons-image-rotate"></span> <?php esc_html_e( 'Update', 'woocommerce' ); ?></a>
|
||||
<div class="user-info">
|
||||
<header>
|
||||
<p><?php esc_html_e( 'Connected to WooCommerce.com', 'woocommerce' ); ?> <span class="chevron dashicons dashicons-arrow-down-alt2"></span></p>
|
||||
</header>
|
||||
<section>
|
||||
<p><?php echo get_avatar( $auth_user_data['email'], 48 ); ?> <?php echo esc_html( $auth_user_data['email'] ); ?></p>
|
||||
<div class="actions">
|
||||
<a class="" href="https://woocommerce.com/my-account/my-subscriptions/" target="_blank"><span class="dashicons dashicons-admin-generic"></span> <?php esc_html_e( 'My Subscriptions', 'woocommerce' ); ?></a>
|
||||
<a class="" href="<?php echo esc_url( $disconnect_url ); ?>"><span class="dashicons dashicons-no"></span> <?php esc_html_e( 'Disconnect', 'woocommerce' ); ?></a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper admin navigation.
|
||||
*
|
||||
* @package WooCommerce\Helper
|
||||
*
|
||||
* @deprecated 5.7.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Utilities\FeaturesUtil;
|
||||
|
||||
$addons_url = admin_url( 'admin.php?page=wc-addons' );
|
||||
if ( FeaturesUtil::feature_is_enabled( 'marketplace' ) ) {
|
||||
$addons_url = admin_url( 'admin.php?page=wc-admin&path=/extensions&tab=extensions' );
|
||||
}
|
||||
|
||||
defined( 'ABSPATH' ) || exit(); ?>
|
||||
|
||||
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
|
||||
<a href="<?php echo esc_url( $addons_url ); ?>" class="nav-tab"><?php esc_html_e( 'Browse Extensions', 'woocommerce' ); ?></a>
|
||||
|
||||
<?php
|
||||
$count_html = WC_Helper_Updater::get_updates_count_html();
|
||||
/* translators: %s: WooCommerce.com Subscriptions tab count HTML. */
|
||||
$menu_title = sprintf( __( 'My Subscriptions %s', 'woocommerce' ), $count_html );
|
||||
?>
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons§ion=helper' ) ); ?>" class="nav-tab nav-tab-active"><?php echo wp_kses_post( $menu_title ); ?></a>
|
||||
</nav>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php defined( 'ABSPATH' ) or exit(); ?>
|
||||
|
||||
<?php foreach ( $notices as $notice ) : ?>
|
||||
<div class="notice <?php echo sanitize_html_class( $notice['type'] ); ?>">
|
||||
<?php echo wpautop( $notice['message'] ); ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@@ -0,0 +1,757 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Product_CSV_Importer_Controller file.
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Utilities\I18nUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WP_Importer' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Product importer controller - handles file upload and forms in admin.
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
* @version 3.1.0
|
||||
*/
|
||||
class WC_Product_CSV_Importer_Controller {
|
||||
|
||||
/**
|
||||
* The path to the current file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $file = '';
|
||||
|
||||
/**
|
||||
* The current import step.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $step = '';
|
||||
|
||||
/**
|
||||
* Progress steps.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $steps = array();
|
||||
|
||||
/**
|
||||
* Errors.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $errors = array();
|
||||
|
||||
/**
|
||||
* The current delimiter for the file being read.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $delimiter = ',';
|
||||
|
||||
/**
|
||||
* Whether to use previous mapping selections.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $map_preferences = false;
|
||||
|
||||
/**
|
||||
* Whether to skip existing products.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $update_existing = false;
|
||||
|
||||
/**
|
||||
* The character encoding to use to interpret the input file, or empty string for autodetect.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $character_encoding = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Get importer instance.
|
||||
*
|
||||
* @param string $file File to import.
|
||||
* @param array $args Importer arguments.
|
||||
* @return WC_Product_CSV_Importer
|
||||
*/
|
||||
public static function get_importer( $file, $args = array() ) {
|
||||
$importer_class = apply_filters( 'woocommerce_product_csv_importer_class', 'WC_Product_CSV_Importer' );
|
||||
$args = apply_filters( 'woocommerce_product_csv_importer_args', $args, $importer_class );
|
||||
return new $importer_class( $file, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file is a valid CSV file.
|
||||
*
|
||||
* @param string $file File path.
|
||||
* @param bool $check_path Whether to also check the file is located in a valid location (Default: true).
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_file_valid_csv( $file, $check_path = true ) {
|
||||
return wc_is_file_valid_csv( $file, $check_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the valid filetypes for a CSV file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_valid_csv_filetypes() {
|
||||
return apply_filters(
|
||||
'woocommerce_csv_product_import_valid_filetypes',
|
||||
array(
|
||||
'csv' => 'text/csv',
|
||||
'txt' => 'text/plain',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$default_steps = array(
|
||||
'upload' => array(
|
||||
'name' => __( 'Upload CSV file', 'woocommerce' ),
|
||||
'view' => array( $this, 'upload_form' ),
|
||||
'handler' => array( $this, 'upload_form_handler' ),
|
||||
),
|
||||
'mapping' => array(
|
||||
'name' => __( 'Column mapping', 'woocommerce' ),
|
||||
'view' => array( $this, 'mapping_form' ),
|
||||
'handler' => '',
|
||||
),
|
||||
'import' => array(
|
||||
'name' => __( 'Import', 'woocommerce' ),
|
||||
'view' => array( $this, 'import' ),
|
||||
'handler' => '',
|
||||
),
|
||||
'done' => array(
|
||||
'name' => __( 'Done!', 'woocommerce' ),
|
||||
'view' => array( $this, 'done' ),
|
||||
'handler' => '',
|
||||
),
|
||||
);
|
||||
|
||||
$this->steps = apply_filters( 'woocommerce_product_csv_importer_steps', $default_steps );
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) );
|
||||
$this->file = isset( $_REQUEST['file'] ) ? wc_clean( wp_unslash( $_REQUEST['file'] ) ) : '';
|
||||
$this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false;
|
||||
$this->delimiter = ! empty( $_REQUEST['delimiter'] ) ? wc_clean( wp_unslash( $_REQUEST['delimiter'] ) ) : ',';
|
||||
$this->map_preferences = isset( $_REQUEST['map_preferences'] ) ? (bool) $_REQUEST['map_preferences'] : false;
|
||||
$this->character_encoding = isset( $_REQUEST['character_encoding'] ) ? wc_clean( wp_unslash( $_REQUEST['character_encoding'] ) ) : 'UTF-8';
|
||||
// phpcs:enable
|
||||
|
||||
// Import mappings for CSV data.
|
||||
include_once dirname( __FILE__ ) . '/mappings/mappings.php';
|
||||
|
||||
if ( $this->map_preferences ) {
|
||||
add_filter( 'woocommerce_csv_product_import_mapped_columns', array( $this, 'auto_map_user_preferences' ), 9999 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the next step's screen.
|
||||
*
|
||||
* @param string $step slug (default: current step).
|
||||
* @return string URL for next step if a next step exists.
|
||||
* Admin URL if it's the last step.
|
||||
* Empty string on failure.
|
||||
*/
|
||||
public function get_next_step_link( $step = '' ) {
|
||||
if ( ! $step ) {
|
||||
$step = $this->step;
|
||||
}
|
||||
|
||||
$keys = array_keys( $this->steps );
|
||||
|
||||
if ( end( $keys ) === $step ) {
|
||||
return admin_url();
|
||||
}
|
||||
|
||||
$step_index = array_search( $step, $keys, true );
|
||||
|
||||
if ( false === $step_index ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'step' => $keys[ $step_index + 1 ],
|
||||
'file' => str_replace( DIRECTORY_SEPARATOR, '/', $this->file ),
|
||||
'delimiter' => $this->delimiter,
|
||||
'update_existing' => $this->update_existing,
|
||||
'map_preferences' => $this->map_preferences,
|
||||
'character_encoding' => $this->character_encoding,
|
||||
'_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects.
|
||||
);
|
||||
|
||||
return add_query_arg( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output header view.
|
||||
*/
|
||||
protected function output_header() {
|
||||
include dirname( __FILE__ ) . '/views/html-csv-import-header.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output steps view.
|
||||
*/
|
||||
protected function output_steps() {
|
||||
include dirname( __FILE__ ) . '/views/html-csv-import-steps.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output footer view.
|
||||
*/
|
||||
protected function output_footer() {
|
||||
include dirname( __FILE__ ) . '/views/html-csv-import-footer.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add error message.
|
||||
*
|
||||
* @param string $message Error message.
|
||||
* @param array $actions List of actions with 'url' and 'label'.
|
||||
*/
|
||||
protected function add_error( $message, $actions = array() ) {
|
||||
$this->errors[] = array(
|
||||
'message' => $message,
|
||||
'actions' => $actions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add error message.
|
||||
*/
|
||||
protected function output_errors() {
|
||||
if ( ! $this->errors ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->errors as $error ) {
|
||||
echo '<div class="error inline">';
|
||||
echo '<p>' . esc_html( $error['message'] ) . '</p>';
|
||||
|
||||
if ( ! empty( $error['actions'] ) ) {
|
||||
echo '<p>';
|
||||
foreach ( $error['actions'] as $action ) {
|
||||
echo '<a class="button button-primary" href="' . esc_url( $action['url'] ) . '">' . esc_html( $action['label'] ) . '</a> ';
|
||||
}
|
||||
echo '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch current step and show correct view.
|
||||
*/
|
||||
public function dispatch() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( ! empty( $_POST['save_step'] ) && ! empty( $this->steps[ $this->step ]['handler'] ) ) {
|
||||
call_user_func( $this->steps[ $this->step ]['handler'], $this );
|
||||
}
|
||||
$this->output_header();
|
||||
$this->output_steps();
|
||||
$this->output_errors();
|
||||
call_user_func( $this->steps[ $this->step ]['view'], $this );
|
||||
$this->output_footer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output information about the uploading process.
|
||||
*/
|
||||
protected function upload_form() {
|
||||
$bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() );
|
||||
$size = size_format( $bytes );
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
include dirname( __FILE__ ) . '/views/html-product-csv-import-form.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the upload form and store options.
|
||||
*/
|
||||
public function upload_form_handler() {
|
||||
check_admin_referer( 'woocommerce-csv-importer' );
|
||||
|
||||
$file = $this->handle_upload();
|
||||
|
||||
if ( is_wp_error( $file ) ) {
|
||||
$this->add_error( $file->get_error_message() );
|
||||
return;
|
||||
} else {
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
wp_redirect( esc_url_raw( $this->get_next_step_link() ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the CSV upload and initial parsing of the file to prepare for
|
||||
* displaying author import options.
|
||||
*
|
||||
* @return string|WP_Error
|
||||
*/
|
||||
public function handle_upload() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce already verified in WC_Product_CSV_Importer_Controller::upload_form_handler()
|
||||
$file_url = isset( $_POST['file_url'] ) ? wc_clean( wp_unslash( $_POST['file_url'] ) ) : '';
|
||||
|
||||
if ( empty( $file_url ) ) {
|
||||
if ( ! isset( $_FILES['import'] ) ) {
|
||||
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_empty', __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
if ( ! self::is_file_valid_csv( wc_clean( wp_unslash( $_FILES['import']['name'] ) ), false ) ) {
|
||||
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$overrides = array(
|
||||
'test_form' => false,
|
||||
'mimes' => self::get_valid_csv_filetypes(),
|
||||
);
|
||||
$import = $_FILES['import']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
$upload = wp_handle_upload( $import, $overrides );
|
||||
|
||||
if ( isset( $upload['error'] ) ) {
|
||||
return new WP_Error( 'woocommerce_product_csv_importer_upload_error', $upload['error'] );
|
||||
}
|
||||
|
||||
// Construct the object array.
|
||||
$object = array(
|
||||
'post_title' => basename( $upload['file'] ),
|
||||
'post_content' => $upload['url'],
|
||||
'post_mime_type' => $upload['type'],
|
||||
'guid' => $upload['url'],
|
||||
'context' => 'import',
|
||||
'post_status' => 'private',
|
||||
);
|
||||
|
||||
// Save the data.
|
||||
$id = wp_insert_attachment( $object, $upload['file'] );
|
||||
|
||||
/*
|
||||
* Schedule a cleanup for one day from now in case of failed
|
||||
* import or missing wp_import_cleanup() call.
|
||||
*/
|
||||
wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( $id ) );
|
||||
|
||||
return $upload['file'];
|
||||
} elseif (
|
||||
( 0 === stripos( realpath( ABSPATH . $file_url ), ABSPATH ) ) &&
|
||||
file_exists( ABSPATH . $file_url )
|
||||
) {
|
||||
if ( ! self::is_file_valid_csv( ABSPATH . $file_url ) ) {
|
||||
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
return ABSPATH . $file_url;
|
||||
}
|
||||
// phpcs:enable
|
||||
|
||||
return new WP_Error( 'woocommerce_product_csv_importer_upload_invalid_file', __( 'Please upload or provide the link to a valid CSV file.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping step.
|
||||
*/
|
||||
protected function mapping_form() {
|
||||
check_admin_referer( 'woocommerce-csv-importer' );
|
||||
$args = array(
|
||||
'lines' => 1,
|
||||
'delimiter' => $this->delimiter,
|
||||
'character_encoding' => $this->character_encoding,
|
||||
);
|
||||
|
||||
$importer = self::get_importer( $this->file, $args );
|
||||
$headers = $importer->get_raw_keys();
|
||||
$mapped_items = $this->auto_map_columns( $headers );
|
||||
$sample = current( $importer->get_raw_data() );
|
||||
|
||||
if ( empty( $sample ) ) {
|
||||
$this->add_error(
|
||||
__( 'The file is empty or using a different encoding than UTF-8, please try again with a new file.', 'woocommerce' ),
|
||||
array(
|
||||
array(
|
||||
'url' => admin_url( 'edit.php?post_type=product&page=product_importer' ),
|
||||
'label' => __( 'Upload a new file', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Force output the errors in the same page.
|
||||
$this->output_errors();
|
||||
return;
|
||||
}
|
||||
|
||||
include_once dirname( __FILE__ ) . '/views/html-csv-import-mapping.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the file if it exists and is valid.
|
||||
*/
|
||||
public function import() {
|
||||
// Displaying this page triggers Ajax action to run the import with a valid nonce,
|
||||
// therefore this page needs to be nonce protected as well.
|
||||
check_admin_referer( 'woocommerce-csv-importer' );
|
||||
|
||||
if ( ! self::is_file_valid_csv( $this->file ) ) {
|
||||
$this->add_error( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
|
||||
$this->output_errors();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! is_file( $this->file ) ) {
|
||||
$this->add_error( __( 'The file does not exist, please try again.', 'woocommerce' ) );
|
||||
$this->output_errors();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST['map_from'] ) && ! empty( $_POST['map_to'] ) ) {
|
||||
$mapping_from = wc_clean( wp_unslash( $_POST['map_from'] ) );
|
||||
$mapping_to = wc_clean( wp_unslash( $_POST['map_to'] ) );
|
||||
|
||||
// Save mapping preferences for future imports.
|
||||
update_user_option( get_current_user_id(), 'woocommerce_product_import_mapping', $mapping_to );
|
||||
} else {
|
||||
wp_redirect( esc_url_raw( $this->get_next_step_link( 'upload' ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
wp_localize_script(
|
||||
'wc-product-import',
|
||||
'wc_product_import_params',
|
||||
array(
|
||||
'import_nonce' => wp_create_nonce( 'wc-product-import' ),
|
||||
'mapping' => array(
|
||||
'from' => $mapping_from,
|
||||
'to' => $mapping_to,
|
||||
),
|
||||
'file' => $this->file,
|
||||
'update_existing' => $this->update_existing,
|
||||
'delimiter' => $this->delimiter,
|
||||
'character_encoding' => $this->character_encoding,
|
||||
)
|
||||
);
|
||||
wp_enqueue_script( 'wc-product-import' );
|
||||
|
||||
include_once dirname( __FILE__ ) . '/views/html-csv-import-progress.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Done step.
|
||||
*/
|
||||
protected function done() {
|
||||
check_admin_referer( 'woocommerce-csv-importer' );
|
||||
$imported = isset( $_GET['products-imported'] ) ? absint( $_GET['products-imported'] ) : 0;
|
||||
$imported_variations = isset( $_GET['products-imported-variations'] ) ? absint( $_GET['products-imported-variations'] ) : 0;
|
||||
$updated = isset( $_GET['products-updated'] ) ? absint( $_GET['products-updated'] ) : 0;
|
||||
$failed = isset( $_GET['products-failed'] ) ? absint( $_GET['products-failed'] ) : 0;
|
||||
$skipped = isset( $_GET['products-skipped'] ) ? absint( $_GET['products-skipped'] ) : 0;
|
||||
$file_name = isset( $_GET['file-name'] ) ? sanitize_text_field( wp_unslash( $_GET['file-name'] ) ) : '';
|
||||
$errors = array_filter( (array) get_user_option( 'product_import_error_log' ) );
|
||||
|
||||
include_once dirname( __FILE__ ) . '/views/html-csv-import-done.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Columns to normalize.
|
||||
*
|
||||
* @param array $columns List of columns names and keys.
|
||||
* @return array
|
||||
*/
|
||||
protected function normalize_columns_names( $columns ) {
|
||||
$normalized = array();
|
||||
|
||||
foreach ( $columns as $key => $value ) {
|
||||
$normalized[ strtolower( $key ) ] = $value;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto map column names.
|
||||
*
|
||||
* @param array $raw_headers Raw header columns.
|
||||
* @param bool $num_indexes If should use numbers or raw header columns as indexes.
|
||||
* @return array
|
||||
*/
|
||||
protected function auto_map_columns( $raw_headers, $num_indexes = true ) {
|
||||
$weight_unit_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', 'kg' ) );
|
||||
$dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', 'cm' ) );
|
||||
|
||||
/*
|
||||
* @hooked wc_importer_generic_mappings - 10
|
||||
* @hooked wc_importer_wordpress_mappings - 10
|
||||
* @hooked wc_importer_default_english_mappings - 100
|
||||
*/
|
||||
$default_columns = $this->normalize_columns_names(
|
||||
apply_filters(
|
||||
'woocommerce_csv_product_import_mapping_default_columns',
|
||||
array(
|
||||
__( 'ID', 'woocommerce' ) => 'id',
|
||||
__( 'Type', 'woocommerce' ) => 'type',
|
||||
__( 'SKU', 'woocommerce' ) => 'sku',
|
||||
__( 'Name', 'woocommerce' ) => 'name',
|
||||
__( 'Published', 'woocommerce' ) => 'published',
|
||||
__( 'Is featured?', 'woocommerce' ) => 'featured',
|
||||
__( 'Visibility in catalog', 'woocommerce' ) => 'catalog_visibility',
|
||||
__( 'Short description', 'woocommerce' ) => 'short_description',
|
||||
__( 'Description', 'woocommerce' ) => 'description',
|
||||
__( 'Date sale price starts', 'woocommerce' ) => 'date_on_sale_from',
|
||||
__( 'Date sale price ends', 'woocommerce' ) => 'date_on_sale_to',
|
||||
__( 'Tax status', 'woocommerce' ) => 'tax_status',
|
||||
__( 'Tax class', 'woocommerce' ) => 'tax_class',
|
||||
__( 'In stock?', 'woocommerce' ) => 'stock_status',
|
||||
__( 'Stock', 'woocommerce' ) => 'stock_quantity',
|
||||
__( 'Backorders allowed?', 'woocommerce' ) => 'backorders',
|
||||
__( 'Low stock amount', 'woocommerce' ) => 'low_stock_amount',
|
||||
__( 'Sold individually?', 'woocommerce' ) => 'sold_individually',
|
||||
/* translators: %s: Weight unit */
|
||||
sprintf( __( 'Weight (%s)', 'woocommerce' ), $weight_unit_label ) => 'weight',
|
||||
/* translators: %s: Length unit */
|
||||
sprintf( __( 'Length (%s)', 'woocommerce' ), $dimension_unit_label ) => 'length',
|
||||
/* translators: %s: Width unit */
|
||||
sprintf( __( 'Width (%s)', 'woocommerce' ), $dimension_unit_label ) => 'width',
|
||||
/* translators: %s: Height unit */
|
||||
sprintf( __( 'Height (%s)', 'woocommerce' ), $dimension_unit_label ) => 'height',
|
||||
__( 'Allow customer reviews?', 'woocommerce' ) => 'reviews_allowed',
|
||||
__( 'Purchase note', 'woocommerce' ) => 'purchase_note',
|
||||
__( 'Sale price', 'woocommerce' ) => 'sale_price',
|
||||
__( 'Regular price', 'woocommerce' ) => 'regular_price',
|
||||
__( 'Categories', 'woocommerce' ) => 'category_ids',
|
||||
__( 'Tags', 'woocommerce' ) => 'tag_ids',
|
||||
__( 'Shipping class', 'woocommerce' ) => 'shipping_class_id',
|
||||
__( 'Images', 'woocommerce' ) => 'images',
|
||||
__( 'Download limit', 'woocommerce' ) => 'download_limit',
|
||||
__( 'Download expiry days', 'woocommerce' ) => 'download_expiry',
|
||||
__( 'Parent', 'woocommerce' ) => 'parent_id',
|
||||
__( 'Upsells', 'woocommerce' ) => 'upsell_ids',
|
||||
__( 'Cross-sells', 'woocommerce' ) => 'cross_sell_ids',
|
||||
__( 'Grouped products', 'woocommerce' ) => 'grouped_products',
|
||||
__( 'External URL', 'woocommerce' ) => 'product_url',
|
||||
__( 'Button text', 'woocommerce' ) => 'button_text',
|
||||
__( 'Position', 'woocommerce' ) => 'menu_order',
|
||||
),
|
||||
$raw_headers
|
||||
)
|
||||
);
|
||||
|
||||
$special_columns = $this->get_special_columns(
|
||||
$this->normalize_columns_names(
|
||||
apply_filters(
|
||||
'woocommerce_csv_product_import_mapping_special_columns',
|
||||
array(
|
||||
/* translators: %d: Attribute number */
|
||||
__( 'Attribute %d name', 'woocommerce' ) => 'attributes:name',
|
||||
/* translators: %d: Attribute number */
|
||||
__( 'Attribute %d value(s)', 'woocommerce' ) => 'attributes:value',
|
||||
/* translators: %d: Attribute number */
|
||||
__( 'Attribute %d visible', 'woocommerce' ) => 'attributes:visible',
|
||||
/* translators: %d: Attribute number */
|
||||
__( 'Attribute %d global', 'woocommerce' ) => 'attributes:taxonomy',
|
||||
/* translators: %d: Attribute number */
|
||||
__( 'Attribute %d default', 'woocommerce' ) => 'attributes:default',
|
||||
/* translators: %d: Download number */
|
||||
__( 'Download %d ID', 'woocommerce' ) => 'downloads:id',
|
||||
/* translators: %d: Download number */
|
||||
__( 'Download %d name', 'woocommerce' ) => 'downloads:name',
|
||||
/* translators: %d: Download number */
|
||||
__( 'Download %d URL', 'woocommerce' ) => 'downloads:url',
|
||||
/* translators: %d: Meta number */
|
||||
__( 'Meta: %s', 'woocommerce' ) => 'meta:',
|
||||
),
|
||||
$raw_headers
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$headers = array();
|
||||
foreach ( $raw_headers as $key => $field ) {
|
||||
$normalized_field = strtolower( $field );
|
||||
$index = $num_indexes ? $key : $field;
|
||||
$headers[ $index ] = $normalized_field;
|
||||
|
||||
if ( isset( $default_columns[ $normalized_field ] ) ) {
|
||||
$headers[ $index ] = $default_columns[ $normalized_field ];
|
||||
} else {
|
||||
foreach ( $special_columns as $regex => $special_key ) {
|
||||
// Don't use the normalized field in the regex since meta might be case-sensitive.
|
||||
if ( preg_match( $regex, $field, $matches ) ) {
|
||||
$headers[ $index ] = $special_key . $matches[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_csv_product_import_mapped_columns', $headers, $raw_headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Map columns using the user's latest import mappings.
|
||||
*
|
||||
* @param array $headers Header columns.
|
||||
* @return array
|
||||
*/
|
||||
public function auto_map_user_preferences( $headers ) {
|
||||
$mapping_preferences = get_user_option( 'woocommerce_product_import_mapping' );
|
||||
|
||||
if ( ! empty( $mapping_preferences ) && is_array( $mapping_preferences ) ) {
|
||||
return $mapping_preferences;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize special column name regex.
|
||||
*
|
||||
* @param string $value Raw special column name.
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitize_special_column_name_regex( $value ) {
|
||||
return '/' . str_replace( array( '%d', '%s' ), '(.*)', trim( quotemeta( $value ) ) ) . '/i';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get special columns.
|
||||
*
|
||||
* @param array $columns Raw special columns.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_special_columns( $columns ) {
|
||||
$formatted = array();
|
||||
|
||||
foreach ( $columns as $key => $value ) {
|
||||
$regex = $this->sanitize_special_column_name_regex( $key );
|
||||
|
||||
$formatted[ $regex ] = $value;
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapping options.
|
||||
*
|
||||
* @param string $item Item name.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_mapping_options( $item = '' ) {
|
||||
// Get index for special column names.
|
||||
$index = $item;
|
||||
|
||||
if ( preg_match( '/\d+/', $item, $matches ) ) {
|
||||
$index = $matches[0];
|
||||
}
|
||||
|
||||
// Properly format for meta field.
|
||||
$meta = str_replace( 'meta:', '', $item );
|
||||
|
||||
// Available options.
|
||||
$weight_unit_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit', 'kg' ) );
|
||||
$dimension_unit_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit', 'cm' ) );
|
||||
$options = array(
|
||||
'id' => __( 'ID', 'woocommerce' ),
|
||||
'type' => __( 'Type', 'woocommerce' ),
|
||||
'sku' => __( 'SKU', 'woocommerce' ),
|
||||
'name' => __( 'Name', 'woocommerce' ),
|
||||
'published' => __( 'Published', 'woocommerce' ),
|
||||
'featured' => __( 'Is featured?', 'woocommerce' ),
|
||||
'catalog_visibility' => __( 'Visibility in catalog', 'woocommerce' ),
|
||||
'short_description' => __( 'Short description', 'woocommerce' ),
|
||||
'description' => __( 'Description', 'woocommerce' ),
|
||||
'price' => array(
|
||||
'name' => __( 'Price', 'woocommerce' ),
|
||||
'options' => array(
|
||||
'regular_price' => __( 'Regular price', 'woocommerce' ),
|
||||
'sale_price' => __( 'Sale price', 'woocommerce' ),
|
||||
'date_on_sale_from' => __( 'Date sale price starts', 'woocommerce' ),
|
||||
'date_on_sale_to' => __( 'Date sale price ends', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
'tax_status' => __( 'Tax status', 'woocommerce' ),
|
||||
'tax_class' => __( 'Tax class', 'woocommerce' ),
|
||||
'stock_status' => __( 'In stock?', 'woocommerce' ),
|
||||
'stock_quantity' => _x( 'Stock', 'Quantity in stock', 'woocommerce' ),
|
||||
'backorders' => __( 'Backorders allowed?', 'woocommerce' ),
|
||||
'low_stock_amount' => __( 'Low stock amount', 'woocommerce' ),
|
||||
'sold_individually' => __( 'Sold individually?', 'woocommerce' ),
|
||||
/* translators: %s: weight unit */
|
||||
'weight' => sprintf( __( 'Weight (%s)', 'woocommerce' ), $weight_unit_label ),
|
||||
'dimensions' => array(
|
||||
'name' => __( 'Dimensions', 'woocommerce' ),
|
||||
'options' => array(
|
||||
/* translators: %s: dimension unit */
|
||||
'length' => sprintf( __( 'Length (%s)', 'woocommerce' ), $dimension_unit_label ),
|
||||
/* translators: %s: dimension unit */
|
||||
'width' => sprintf( __( 'Width (%s)', 'woocommerce' ), $dimension_unit_label ),
|
||||
/* translators: %s: dimension unit */
|
||||
'height' => sprintf( __( 'Height (%s)', 'woocommerce' ), $dimension_unit_label ),
|
||||
),
|
||||
),
|
||||
'category_ids' => __( 'Categories', 'woocommerce' ),
|
||||
'tag_ids' => __( 'Tags (comma separated)', 'woocommerce' ),
|
||||
'tag_ids_spaces' => __( 'Tags (space separated)', 'woocommerce' ),
|
||||
'shipping_class_id' => __( 'Shipping class', 'woocommerce' ),
|
||||
'images' => __( 'Images', 'woocommerce' ),
|
||||
'parent_id' => __( 'Parent', 'woocommerce' ),
|
||||
'upsell_ids' => __( 'Upsells', 'woocommerce' ),
|
||||
'cross_sell_ids' => __( 'Cross-sells', 'woocommerce' ),
|
||||
'grouped_products' => __( 'Grouped products', 'woocommerce' ),
|
||||
'external' => array(
|
||||
'name' => __( 'External product', 'woocommerce' ),
|
||||
'options' => array(
|
||||
'product_url' => __( 'External URL', 'woocommerce' ),
|
||||
'button_text' => __( 'Button text', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
'downloads' => array(
|
||||
'name' => __( 'Downloads', 'woocommerce' ),
|
||||
'options' => array(
|
||||
'downloads:id' . $index => __( 'Download ID', 'woocommerce' ),
|
||||
'downloads:name' . $index => __( 'Download name', 'woocommerce' ),
|
||||
'downloads:url' . $index => __( 'Download URL', 'woocommerce' ),
|
||||
'download_limit' => __( 'Download limit', 'woocommerce' ),
|
||||
'download_expiry' => __( 'Download expiry days', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
'attributes' => array(
|
||||
'name' => __( 'Attributes', 'woocommerce' ),
|
||||
'options' => array(
|
||||
'attributes:name' . $index => __( 'Attribute name', 'woocommerce' ),
|
||||
'attributes:value' . $index => __( 'Attribute value(s)', 'woocommerce' ),
|
||||
'attributes:taxonomy' . $index => __( 'Is a global attribute?', 'woocommerce' ),
|
||||
'attributes:visible' . $index => __( 'Attribute visibility', 'woocommerce' ),
|
||||
'attributes:default' . $index => __( 'Default attribute', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
'reviews_allowed' => __( 'Allow customer reviews?', 'woocommerce' ),
|
||||
'purchase_note' => __( 'Purchase note', 'woocommerce' ),
|
||||
'meta:' . $meta => __( 'Import as meta data', 'woocommerce' ),
|
||||
'menu_order' => __( 'Position', 'woocommerce' ),
|
||||
);
|
||||
|
||||
return apply_filters( 'woocommerce_csv_product_import_mapping_options', $options, $item );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<?php
|
||||
/**
|
||||
* Tax importer class file
|
||||
*
|
||||
* @version 2.3.0
|
||||
* @package WooCommerce\Admin
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WP_Importer' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax Rates importer - import tax rates and local tax rates into WooCommerce.
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
* @version 2.3.0
|
||||
*/
|
||||
class WC_Tax_Rate_Importer extends WP_Importer {
|
||||
|
||||
/**
|
||||
* The current file id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The current file url.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $file_url;
|
||||
|
||||
/**
|
||||
* The current import page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $import_page;
|
||||
|
||||
/**
|
||||
* The current delimiter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $delimiter;
|
||||
|
||||
/**
|
||||
* Error message for import.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $import_error_message;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->import_page = 'woocommerce_tax_rate_csv';
|
||||
$this->delimiter = empty( $_POST['delimiter'] ) ? ',' : (string) wc_clean( wp_unslash( $_POST['delimiter'] ) ); // WPCS: CSRF ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered callback function for the WordPress Importer.
|
||||
*
|
||||
* Manages the three separate stages of the CSV import process.
|
||||
*/
|
||||
public function dispatch() {
|
||||
|
||||
$this->header();
|
||||
|
||||
$step = empty( $_GET['step'] ) ? 0 : (int) $_GET['step'];
|
||||
|
||||
switch ( $step ) {
|
||||
|
||||
case 0:
|
||||
$this->greet();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
check_admin_referer( 'import-upload' );
|
||||
|
||||
if ( $this->handle_upload() ) {
|
||||
|
||||
if ( $this->id ) {
|
||||
$file = get_attached_file( $this->id );
|
||||
} else {
|
||||
$file = ABSPATH . $this->file_url;
|
||||
}
|
||||
|
||||
add_filter( 'http_request_timeout', array( $this, 'bump_request_timeout' ) );
|
||||
|
||||
$this->import( $file );
|
||||
} else {
|
||||
$this->import_error( $this->import_error_message );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$this->footer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Import is starting.
|
||||
*/
|
||||
private function import_start() {
|
||||
if ( function_exists( 'gc_enable' ) ) {
|
||||
gc_enable(); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.gc_enableFound
|
||||
}
|
||||
wc_set_time_limit( 0 );
|
||||
@ob_flush();
|
||||
@flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 encode the data if `$enc` value isn't UTF-8.
|
||||
*
|
||||
* @param mixed $data Data.
|
||||
* @param string $enc Encoding.
|
||||
* @return string
|
||||
*/
|
||||
public function format_data_from_csv( $data, $enc ) {
|
||||
return ( 'UTF-8' === $enc ) ? $data : utf8_encode( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the file if it exists and is valid.
|
||||
*
|
||||
* @param mixed $file File.
|
||||
*/
|
||||
public function import( $file ) {
|
||||
if ( ! is_file( $file ) ) {
|
||||
$this->import_error( __( 'The file does not exist, please try again.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$this->import_start();
|
||||
|
||||
$loop = 0;
|
||||
$handle = fopen( $file, 'r' );
|
||||
|
||||
if ( false !== $handle ) {
|
||||
|
||||
$header = fgetcsv( $handle, 0, $this->delimiter );
|
||||
$count = is_countable( $header ) ? count( $header ) : 0;
|
||||
if ( 10 === $count ) {
|
||||
|
||||
$row = fgetcsv( $handle, 0, $this->delimiter );
|
||||
|
||||
while ( false !== $row ) {
|
||||
|
||||
list( $country, $state, $postcode, $city, $rate, $name, $priority, $compound, $shipping, $class ) = $row;
|
||||
|
||||
$tax_rate = array(
|
||||
'tax_rate_country' => $country,
|
||||
'tax_rate_state' => $state,
|
||||
'tax_rate' => $rate,
|
||||
'tax_rate_name' => $name,
|
||||
'tax_rate_priority' => $priority,
|
||||
'tax_rate_compound' => $compound ? 1 : 0,
|
||||
'tax_rate_shipping' => $shipping ? 1 : 0,
|
||||
'tax_rate_order' => $loop ++,
|
||||
'tax_rate_class' => $class,
|
||||
);
|
||||
|
||||
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
||||
WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, wc_clean( $postcode ) );
|
||||
WC_Tax::_update_tax_rate_cities( $tax_rate_id, wc_clean( $city ) );
|
||||
|
||||
$row = fgetcsv( $handle, 0, $this->delimiter );
|
||||
}
|
||||
} else {
|
||||
$this->import_error( __( 'The CSV is invalid.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
fclose( $handle );
|
||||
}
|
||||
|
||||
// Show Result.
|
||||
echo '<div class="updated settings-error"><p>';
|
||||
printf(
|
||||
/* translators: %s: tax rates count */
|
||||
esc_html__( 'Import complete - imported %s tax rates.', 'woocommerce' ),
|
||||
'<strong>' . absint( $loop ) . '</strong>'
|
||||
);
|
||||
echo '</p></div>';
|
||||
|
||||
$this->import_end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs post-import cleanup of files and the cache.
|
||||
*/
|
||||
public function import_end() {
|
||||
echo '<p>' . esc_html__( 'All done!', 'woocommerce' ) . ' <a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=tax' ) ) . '">' . esc_html__( 'View tax rates', 'woocommerce' ) . '</a></p>';
|
||||
|
||||
do_action( 'import_end' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the import error message.
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*/
|
||||
protected function set_import_error_message( $message ) {
|
||||
$this->import_error_message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the CSV upload and initial parsing of the file to prepare for.
|
||||
* displaying author import options.
|
||||
*
|
||||
* @return bool False if error uploading or invalid file, true otherwise
|
||||
*/
|
||||
public function handle_upload() {
|
||||
$file_url = isset( $_POST['file_url'] ) ? wc_clean( wp_unslash( $_POST['file_url'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce already verified in WC_Tax_Rate_Importer::dispatch()
|
||||
|
||||
if ( empty( $file_url ) ) {
|
||||
$file = wp_import_handle_upload();
|
||||
|
||||
if ( isset( $file['error'] ) ) {
|
||||
$this->set_import_error_message( $file['error'] );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! wc_is_file_valid_csv( $file['file'], false ) ) {
|
||||
// Remove file if not valid.
|
||||
wp_delete_attachment( $file['id'], true );
|
||||
|
||||
$this->set_import_error_message( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->id = absint( $file['id'] );
|
||||
} elseif (
|
||||
( 0 === stripos( realpath( ABSPATH . $file_url ), ABSPATH ) ) &&
|
||||
file_exists( ABSPATH . $file_url )
|
||||
) {
|
||||
if ( ! wc_is_file_valid_csv( ABSPATH . $file_url ) ) {
|
||||
$this->set_import_error_message( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->file_url = esc_attr( $file_url );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output header html.
|
||||
*/
|
||||
public function header() {
|
||||
echo '<div class="wrap">';
|
||||
echo '<h1>' . esc_html__( 'Import tax rates', 'woocommerce' ) . '</h1>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output footer html.
|
||||
*/
|
||||
public function footer() {
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output information about the uploading process.
|
||||
*/
|
||||
public function greet() {
|
||||
|
||||
echo '<div class="narrow">';
|
||||
echo '<p>' . esc_html__( 'Hi there! Upload a CSV file containing tax rates to import the contents into your shop. Choose a .csv file to upload, then click "Upload file and import".', 'woocommerce' ) . '</p>';
|
||||
|
||||
/* translators: 1: Link to tax rates sample file 2: Closing link. */
|
||||
echo '<p>' . sprintf( esc_html__( 'Your CSV needs to include columns in a specific order. %1$sClick here to download a sample%2$s.', 'woocommerce' ), '<a href="' . esc_url( WC()->plugin_url() ) . '/sample-data/sample_tax_rates.csv">', '</a>' ) . '</p>';
|
||||
|
||||
$action = 'admin.php?import=woocommerce_tax_rate_csv&step=1';
|
||||
|
||||
$bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() );
|
||||
$size = size_format( $bytes );
|
||||
$upload_dir = wp_upload_dir();
|
||||
if ( ! empty( $upload_dir['error'] ) ) :
|
||||
?>
|
||||
<div class="error">
|
||||
<p><?php esc_html_e( 'Before you can upload your import file, you will need to fix the following error:', 'woocommerce' ); ?></p>
|
||||
<p><strong><?php echo esc_html( $upload_dir['error'] ); ?></strong></p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<form enctype="multipart/form-data" id="import-upload-form" method="post" action="<?php echo esc_attr( wp_nonce_url( $action, 'import-upload' ) ); ?>">
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<label for="upload"><?php esc_html_e( 'Choose a file from your computer:', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="file" id="upload" name="import" size="25" />
|
||||
<input type="hidden" name="action" value="save" />
|
||||
<input type="hidden" name="max_file_size" value="<?php echo absint( $bytes ); ?>" />
|
||||
<small>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: maximum upload size */
|
||||
esc_html__( 'Maximum size: %s', 'woocommerce' ),
|
||||
esc_attr( $size )
|
||||
);
|
||||
?>
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
<label for="file_url"><?php esc_html_e( 'OR enter path to file:', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo ' ' . esc_html( ABSPATH ) . ' '; ?><input type="text" id="file_url" name="file_url" size="25" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label><?php esc_html_e( 'Delimiter', 'woocommerce' ); ?></label><br/></th>
|
||||
<td><input type="text" name="delimiter" placeholder="," size="2" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="submit">
|
||||
<button type="submit" class="button" value="<?php esc_attr_e( 'Upload file and import', 'woocommerce' ); ?>"><?php esc_html_e( 'Upload file and import', 'woocommerce' ); ?></button>
|
||||
</p>
|
||||
</form>
|
||||
<?php
|
||||
endif;
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show import error and quit.
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*/
|
||||
private function import_error( $message = '' ) {
|
||||
echo '<p><strong>' . esc_html__( 'Sorry, there has been an error.', 'woocommerce' ) . '</strong><br />';
|
||||
if ( $message ) {
|
||||
echo esc_html( $message );
|
||||
}
|
||||
echo '</p>';
|
||||
$this->footer();
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Added to http_request_timeout filter to force timeout at 60 seconds during import.
|
||||
*
|
||||
* @param int $val Value.
|
||||
* @return int 60
|
||||
*/
|
||||
public function bump_request_timeout( $val ) {
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Default mappings
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Importer current locale.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return string
|
||||
*/
|
||||
function wc_importer_current_locale() {
|
||||
$locale = get_locale();
|
||||
if ( function_exists( 'get_user_locale' ) ) {
|
||||
$locale = get_user_locale();
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add English mapping placeholders when not using English as current language.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $mappings Importer columns mappings.
|
||||
* @return array
|
||||
*/
|
||||
function wc_importer_default_english_mappings( $mappings ) {
|
||||
if ( 'en_US' === wc_importer_current_locale() && is_array( $mappings ) && count( $mappings ) > 0 ) {
|
||||
return $mappings;
|
||||
}
|
||||
|
||||
$weight_unit = get_option( 'woocommerce_weight_unit' );
|
||||
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
|
||||
$new_mappings = array(
|
||||
'ID' => 'id',
|
||||
'Type' => 'type',
|
||||
'SKU' => 'sku',
|
||||
'Name' => 'name',
|
||||
'Published' => 'published',
|
||||
'Is featured?' => 'featured',
|
||||
'Visibility in catalog' => 'catalog_visibility',
|
||||
'Short description' => 'short_description',
|
||||
'Description' => 'description',
|
||||
'Date sale price starts' => 'date_on_sale_from',
|
||||
'Date sale price ends' => 'date_on_sale_to',
|
||||
'Tax status' => 'tax_status',
|
||||
'Tax class' => 'tax_class',
|
||||
'In stock?' => 'stock_status',
|
||||
'Stock' => 'stock_quantity',
|
||||
'Backorders allowed?' => 'backorders',
|
||||
'Low stock amount' => 'low_stock_amount',
|
||||
'Sold individually?' => 'sold_individually',
|
||||
sprintf( 'Weight (%s)', $weight_unit ) => 'weight',
|
||||
sprintf( 'Length (%s)', $dimension_unit ) => 'length',
|
||||
sprintf( 'Width (%s)', $dimension_unit ) => 'width',
|
||||
sprintf( 'Height (%s)', $dimension_unit ) => 'height',
|
||||
'Allow customer reviews?' => 'reviews_allowed',
|
||||
'Purchase note' => 'purchase_note',
|
||||
'Sale price' => 'sale_price',
|
||||
'Regular price' => 'regular_price',
|
||||
'Categories' => 'category_ids',
|
||||
'Tags' => 'tag_ids',
|
||||
'Shipping class' => 'shipping_class_id',
|
||||
'Images' => 'images',
|
||||
'Download limit' => 'download_limit',
|
||||
'Download expiry days' => 'download_expiry',
|
||||
'Parent' => 'parent_id',
|
||||
'Upsells' => 'upsell_ids',
|
||||
'Cross-sells' => 'cross_sell_ids',
|
||||
'Grouped products' => 'grouped_products',
|
||||
'External URL' => 'product_url',
|
||||
'Button text' => 'button_text',
|
||||
'Position' => 'menu_order',
|
||||
);
|
||||
|
||||
return array_merge( $mappings, $new_mappings );
|
||||
}
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_default_english_mappings', 100 );
|
||||
|
||||
/**
|
||||
* Add English special mapping placeholders when not using English as current language.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $mappings Importer columns mappings.
|
||||
* @return array
|
||||
*/
|
||||
function wc_importer_default_special_english_mappings( $mappings ) {
|
||||
if ( 'en_US' === wc_importer_current_locale() && is_array( $mappings ) && count( $mappings ) > 0 ) {
|
||||
return $mappings;
|
||||
}
|
||||
|
||||
$new_mappings = array(
|
||||
'Attribute %d name' => 'attributes:name',
|
||||
'Attribute %d value(s)' => 'attributes:value',
|
||||
'Attribute %d visible' => 'attributes:visible',
|
||||
'Attribute %d global' => 'attributes:taxonomy',
|
||||
'Attribute %d default' => 'attributes:default',
|
||||
'Download %d ID' => 'downloads:id',
|
||||
'Download %d name' => 'downloads:name',
|
||||
'Download %d URL' => 'downloads:url',
|
||||
'Meta: %s' => 'meta:',
|
||||
);
|
||||
|
||||
return array_merge( $mappings, $new_mappings );
|
||||
}
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_special_columns', 'wc_importer_default_special_english_mappings', 100 );
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Generic mappings
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add generic mappings.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $mappings Importer columns mappings.
|
||||
* @return array
|
||||
*/
|
||||
function wc_importer_generic_mappings( $mappings ) {
|
||||
$generic_mappings = array(
|
||||
__( 'Title', 'woocommerce' ) => 'name',
|
||||
__( 'Product Title', 'woocommerce' ) => 'name',
|
||||
__( 'Price', 'woocommerce' ) => 'regular_price',
|
||||
__( 'Parent SKU', 'woocommerce' ) => 'parent_id',
|
||||
__( 'Quantity', 'woocommerce' ) => 'stock_quantity',
|
||||
__( 'Menu order', 'woocommerce' ) => 'menu_order',
|
||||
);
|
||||
|
||||
return array_merge( $mappings, $generic_mappings );
|
||||
}
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_generic_mappings' );
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Load up extra automatic mappings for the CSV importer.
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require dirname( __FILE__ ) . '/default.php';
|
||||
require dirname( __FILE__ ) . '/generic.php';
|
||||
require dirname( __FILE__ ) . '/shopify.php';
|
||||
require dirname( __FILE__ ) . '/wordpress.php';
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* Shopify mappings
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Shopify mappings.
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @param array $mappings Importer columns mappings.
|
||||
* @param array $raw_headers Raw headers from CSV being imported.
|
||||
* @return array
|
||||
*/
|
||||
function wc_importer_shopify_mappings( $mappings, $raw_headers ) {
|
||||
// Only map if this is looks like a Shopify export.
|
||||
if ( 0 !== count( array_diff( array( 'Title', 'Body (HTML)', 'Type', 'Variant SKU' ), $raw_headers ) ) ) {
|
||||
return $mappings;
|
||||
}
|
||||
$shopify_mappings = array(
|
||||
'Variant SKU' => 'sku',
|
||||
'Title' => 'name',
|
||||
'Body (HTML)' => 'description',
|
||||
'Quantity' => 'stock_quantity',
|
||||
'Variant Inventory Qty' => 'stock_quantity',
|
||||
'Image Src' => 'images',
|
||||
'Variant Image' => 'images',
|
||||
'Variant SKU' => 'sku',
|
||||
'Variant Price' => 'sale_price',
|
||||
'Variant Compare At Price' => 'regular_price',
|
||||
'Type' => 'category_ids',
|
||||
'Tags' => 'tag_ids_spaces',
|
||||
'Variant Grams' => 'weight',
|
||||
'Variant Requires Shipping' => 'meta:shopify_requires_shipping',
|
||||
'Variant Taxable' => 'tax_status',
|
||||
);
|
||||
return array_merge( $mappings, $shopify_mappings );
|
||||
}
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_shopify_mappings', 10, 2 );
|
||||
|
||||
/**
|
||||
* Add special wildcard Shopify mappings.
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @param array $mappings Importer columns mappings.
|
||||
* @param array $raw_headers Raw headers from CSV being imported.
|
||||
* @return array
|
||||
*/
|
||||
function wc_importer_shopify_special_mappings( $mappings, $raw_headers ) {
|
||||
// Only map if this is looks like a Shopify export.
|
||||
if ( 0 !== count( array_diff( array( 'Title', 'Body (HTML)', 'Type', 'Variant SKU' ), $raw_headers ) ) ) {
|
||||
return $mappings;
|
||||
}
|
||||
$shopify_mappings = array(
|
||||
'Option%d Name' => 'attributes:name',
|
||||
'Option%d Value' => 'attributes:value',
|
||||
);
|
||||
return array_merge( $mappings, $shopify_mappings );
|
||||
}
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_special_columns', 'wc_importer_shopify_special_mappings', 10, 2 );
|
||||
|
||||
/**
|
||||
* Expand special Shopify columns to WC format.
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @param array $data Array of data.
|
||||
* @return array Expanded data.
|
||||
*/
|
||||
function wc_importer_shopify_expand_data( $data ) {
|
||||
if ( isset( $data['meta:shopify_requires_shipping'] ) ) {
|
||||
$requires_shipping = wc_string_to_bool( $data['meta:shopify_requires_shipping'] );
|
||||
|
||||
if ( ! $requires_shipping ) {
|
||||
if ( isset( $data['type'] ) ) {
|
||||
$data['type'][] = 'virtual';
|
||||
} else {
|
||||
$data['type'] = array( 'virtual' );
|
||||
}
|
||||
}
|
||||
|
||||
unset( $data['meta:shopify_requires_shipping'] );
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
add_filter( 'woocommerce_product_importer_pre_expand_data', 'wc_importer_shopify_expand_data' );
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress mappings
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mappings for WordPress tables.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param array $mappings Importer columns mappings.
|
||||
* @return array
|
||||
*/
|
||||
function wc_importer_wordpress_mappings( $mappings ) {
|
||||
|
||||
$wp_mappings = array(
|
||||
'post_id' => 'id',
|
||||
'post_title' => 'name',
|
||||
'post_content' => 'description',
|
||||
'post_excerpt' => 'short_description',
|
||||
'post_parent' => 'parent_id',
|
||||
);
|
||||
|
||||
return array_merge( $mappings, $wp_mappings );
|
||||
}
|
||||
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_wordpress_mappings' );
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Importer - Done!
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<div class="wc-progress-form-content woocommerce-importer">
|
||||
<section class="woocommerce-importer-done">
|
||||
<?php
|
||||
$results = array();
|
||||
|
||||
if ( 0 < $imported ) {
|
||||
$results[] = sprintf(
|
||||
/* translators: %d: products count */
|
||||
_n( '%s product imported', '%s products imported', $imported, 'woocommerce' ),
|
||||
'<strong>' . number_format_i18n( $imported ) . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 0 < $updated ) {
|
||||
$results[] = sprintf(
|
||||
/* translators: %d: products count */
|
||||
_n( '%s product updated', '%s products updated', $updated, 'woocommerce' ),
|
||||
'<strong>' . number_format_i18n( $updated ) . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 0 < $imported_variations ) {
|
||||
$results[] = sprintf(
|
||||
/* translators: %d: products count */
|
||||
_n( '%s variations imported', '%s variations imported', $imported_variations, 'woocommerce' ),
|
||||
'<strong>' . number_format_i18n( $imported_variations ) . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 0 < $skipped ) {
|
||||
$results[] = sprintf(
|
||||
/* translators: %d: products count */
|
||||
_n( '%s product was skipped', '%s products were skipped', $skipped, 'woocommerce' ),
|
||||
'<strong>' . number_format_i18n( $skipped ) . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 0 < $failed ) {
|
||||
$results [] = sprintf(
|
||||
/* translators: %d: products count */
|
||||
_n( 'Failed to import %s product', 'Failed to import %s products', $failed, 'woocommerce' ),
|
||||
'<strong>' . number_format_i18n( $failed ) . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 0 < $failed || 0 < $skipped ) {
|
||||
$results[] = '<a href="#" class="woocommerce-importer-done-view-errors">' . __( 'View import log', 'woocommerce' ) . '</a>';
|
||||
}
|
||||
|
||||
if ( ! empty( $file_name ) ) {
|
||||
$results[] = sprintf(
|
||||
/* translators: %s: File name */
|
||||
__( 'File uploaded: %s', 'woocommerce' ),
|
||||
'<strong>' . $file_name . '</strong>'
|
||||
);
|
||||
}
|
||||
|
||||
/* translators: %d: import results */
|
||||
echo wp_kses_post( __( 'Import complete!', 'woocommerce' ) . ' ' . implode( '. ', $results ) );
|
||||
?>
|
||||
</section>
|
||||
<section class="wc-importer-error-log" style="display:none">
|
||||
<table class="widefat wc-importer-error-log-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Product', 'woocommerce' ); ?></th>
|
||||
<th><?php esc_html_e( 'Reason for failure', 'woocommerce' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
if ( is_array( $errors ) && count( $errors ) ) {
|
||||
foreach ( $errors as $error ) {
|
||||
if ( ! is_wp_error( $error ) ) {
|
||||
continue;
|
||||
}
|
||||
$error_data = $error->get_error_data();
|
||||
?>
|
||||
<tr>
|
||||
<th><code><?php echo esc_html( $error_data['row'] ); ?></code></th>
|
||||
<td><?php echo wp_kses_post( $error->get_error_message() ); ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<script type="text/javascript">
|
||||
jQuery(function() {
|
||||
jQuery( '.woocommerce-importer-done-view-errors' ).on( 'click', function() {
|
||||
jQuery( '.wc-importer-error-log' ).slideToggle();
|
||||
return false;
|
||||
} );
|
||||
} );
|
||||
</script>
|
||||
<div class="wc-actions">
|
||||
<a class="button button-primary" href="<?php echo esc_url( admin_url( 'edit.php?post_type=product' ) ); ?>"><?php esc_html_e( 'View products', 'woocommerce' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Header
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Header
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<div class="wrap woocommerce">
|
||||
<h1><?php esc_html_e( 'Import Products', 'woocommerce' ); ?></h1>
|
||||
|
||||
<div class="woocommerce-progress-form-wrapper">
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Importer - CSV mapping
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<form class="wc-progress-form-content woocommerce-importer" method="post" action="<?php echo esc_url( $this->get_next_step_link() ); ?>">
|
||||
<header>
|
||||
<h2><?php esc_html_e( 'Map CSV fields to products', 'woocommerce' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Select fields from your CSV file to map against products fields, or to ignore during import.', 'woocommerce' ); ?></p>
|
||||
</header>
|
||||
<section class="wc-importer-mapping-table-wrapper">
|
||||
<table class="widefat wc-importer-mapping-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Column name', 'woocommerce' ); ?></th>
|
||||
<th><?php esc_html_e( 'Map to field', 'woocommerce' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $headers as $index => $name ) : ?>
|
||||
<?php $mapped_value = $mapped_items[ $index ]; ?>
|
||||
<tr>
|
||||
<td class="wc-importer-mapping-table-name">
|
||||
<?php echo esc_html( $name ); ?>
|
||||
<?php if ( ! empty( $sample[ $index ] ) ) : ?>
|
||||
<span class="description"><?php esc_html_e( 'Sample:', 'woocommerce' ); ?> <code><?php echo esc_html( $sample[ $index ] ); ?></code></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="wc-importer-mapping-table-field">
|
||||
<input type="hidden" name="map_from[<?php echo esc_attr( $index ); ?>]" value="<?php echo esc_attr( $name ); ?>" />
|
||||
<select name="map_to[<?php echo esc_attr( $index ); ?>]">
|
||||
<option value=""><?php esc_html_e( 'Do not import', 'woocommerce' ); ?></option>
|
||||
<option value="">--------------</option>
|
||||
<?php foreach ( $this->get_mapping_options( $mapped_value ) as $key => $value ) : ?>
|
||||
<?php if ( is_array( $value ) ) : ?>
|
||||
<optgroup label="<?php echo esc_attr( $value['name'] ); ?>">
|
||||
<?php foreach ( $value['options'] as $sub_key => $sub_value ) : ?>
|
||||
<option value="<?php echo esc_attr( $sub_key ); ?>" <?php selected( $mapped_value, $sub_key ); ?>><?php echo esc_html( $sub_value ); ?></option>
|
||||
<?php endforeach ?>
|
||||
</optgroup>
|
||||
<?php else : ?>
|
||||
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $mapped_value, $key ); ?>><?php echo esc_html( $value ); ?></option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<div class="wc-actions">
|
||||
<button type="submit" class="button button-primary button-next" value="<?php esc_attr_e( 'Run the importer', 'woocommerce' ); ?>" name="save_step"><?php esc_html_e( 'Run the importer', 'woocommerce' ); ?></button>
|
||||
<input type="hidden" name="file" value="<?php echo esc_attr( $this->file ); ?>" />
|
||||
<input type="hidden" name="delimiter" value="<?php echo esc_attr( $this->delimiter ); ?>" />
|
||||
<input type="hidden" name="update_existing" value="<?php echo (int) $this->update_existing; ?>" />
|
||||
<?php if ( $args['character_encoding'] ) { ?>
|
||||
<input type="hidden" name="character_encoding" value="<?php echo esc_html( $args['character_encoding'] ); ?>" />
|
||||
<?php } ?>
|
||||
<?php wp_nonce_field( 'woocommerce-csv-importer' ); ?>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Importer - CSV import progress
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<div class="wc-progress-form-content woocommerce-importer woocommerce-importer__importing">
|
||||
<header>
|
||||
<span class="spinner is-active"></span>
|
||||
<h2><?php esc_html_e( 'Importing', 'woocommerce' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Your products are now being imported...', 'woocommerce' ); ?></p>
|
||||
</header>
|
||||
<section>
|
||||
<progress class="woocommerce-importer-progress" max="100" value="0"></progress>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Steps
|
||||
*
|
||||
* @package WooCommerce\Admin\Importers
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<ol class="wc-progress-steps">
|
||||
<?php foreach ( $this->steps as $step_key => $step ) : ?>
|
||||
<?php
|
||||
$step_class = '';
|
||||
if ( $step_key === $this->step ) {
|
||||
$step_class = 'active';
|
||||
} elseif ( array_search( $this->step, array_keys( $this->steps ), true ) > array_search( $step_key, array_keys( $this->steps ), true ) ) {
|
||||
$step_class = 'done';
|
||||
}
|
||||
?>
|
||||
<li class="<?php echo esc_attr( $step_class ); ?>">
|
||||
<?php echo esc_html( $step['name'] ); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin View: Product import form
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<form class="wc-progress-form-content woocommerce-importer" enctype="multipart/form-data" method="post">
|
||||
<header>
|
||||
<h2><?php esc_html_e( 'Import products from a CSV file', 'woocommerce' ); ?></h2>
|
||||
<p><?php esc_html_e( 'This tool allows you to import (or merge) product data to your store from a CSV or TXT file.', 'woocommerce' ); ?></p>
|
||||
</header>
|
||||
<section>
|
||||
<table class="form-table woocommerce-importer-options">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="upload">
|
||||
<?php esc_html_e( 'Choose a CSV file from your computer:', 'woocommerce' ); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php
|
||||
if ( ! empty( $upload_dir['error'] ) ) {
|
||||
?>
|
||||
<div class="inline error">
|
||||
<p><?php esc_html_e( 'Before you can upload your import file, you will need to fix the following error:', 'woocommerce' ); ?></p>
|
||||
<p><strong><?php echo esc_html( $upload_dir['error'] ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<input type="file" id="upload" name="import" size="25" />
|
||||
<input type="hidden" name="action" value="save" />
|
||||
<input type="hidden" name="max_file_size" value="<?php echo esc_attr( $bytes ); ?>" />
|
||||
<br>
|
||||
<small>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: maximum upload size */
|
||||
esc_html__( 'Maximum size: %s', 'woocommerce' ),
|
||||
esc_html( $size )
|
||||
);
|
||||
?>
|
||||
</small>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="woocommerce-importer-update-existing"><?php esc_html_e( 'Update existing products', 'woocommerce' ); ?></label><br/></th>
|
||||
<td>
|
||||
<input type="hidden" name="update_existing" value="0" />
|
||||
<input type="checkbox" id="woocommerce-importer-update-existing" name="update_existing" value="1" />
|
||||
<label for="woocommerce-importer-update-existing"><?php esc_html_e( 'Existing products that match by ID or SKU will be updated. Products that do not exist will be skipped.', 'woocommerce' ); ?></label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="woocommerce-importer-advanced hidden">
|
||||
<th>
|
||||
<label for="woocommerce-importer-file-url"><?php esc_html_e( 'Alternatively, enter the path to a CSV file on your server:', 'woocommerce' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<label for="woocommerce-importer-file-url" class="woocommerce-importer-file-url-field-wrapper">
|
||||
<code><?php echo esc_html( ABSPATH ) . ' '; ?></code><input type="text" id="woocommerce-importer-file-url" name="file_url" />
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="woocommerce-importer-advanced hidden">
|
||||
<th><label><?php esc_html_e( 'CSV Delimiter', 'woocommerce' ); ?></label><br/></th>
|
||||
<td><input type="text" name="delimiter" placeholder="," size="2" /></td>
|
||||
</tr>
|
||||
<tr class="woocommerce-importer-advanced hidden">
|
||||
<th><label><?php esc_html_e( 'Use previous column mapping preferences?', 'woocommerce' ); ?></label><br/></th>
|
||||
<td><input type="checkbox" id="woocommerce-importer-map-preferences" name="map_preferences" value="1" /></td>
|
||||
</tr>
|
||||
<tr class="woocommerce-importer-advanced hidden">
|
||||
<th><label><?php esc_html_e( 'Character encoding of the file', 'woocommerce' ); ?></label><br/></th>
|
||||
<td><select id="woocommerce-importer-character-encoding" name="character_encoding">
|
||||
<option value="" selected><?php esc_html_e( 'Autodetect', 'woocommerce' ); ?></option>
|
||||
<?php
|
||||
$encodings = mb_list_encodings();
|
||||
sort( $encodings, SORT_NATURAL );
|
||||
foreach ( $encodings as $encoding ) {
|
||||
echo '<option>' . esc_html( $encoding ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<script type="text/javascript">
|
||||
jQuery(function() {
|
||||
jQuery( '.woocommerce-importer-toggle-advanced-options' ).on( 'click', function() {
|
||||
var elements = jQuery( '.woocommerce-importer-advanced' );
|
||||
if ( elements.is( '.hidden' ) ) {
|
||||
elements.removeClass( 'hidden' );
|
||||
jQuery( this ).text( jQuery( this ).data( 'hidetext' ) );
|
||||
} else {
|
||||
elements.addClass( 'hidden' );
|
||||
jQuery( this ).text( jQuery( this ).data( 'showtext' ) );
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
});
|
||||
</script>
|
||||
<div class="wc-actions">
|
||||
<a href="#" class="woocommerce-importer-toggle-advanced-options" data-hidetext="<?php esc_attr_e( 'Hide advanced options', 'woocommerce' ); ?>" data-showtext="<?php esc_attr_e( 'Show advanced options', 'woocommerce' ); ?>"><?php esc_html_e( 'Show advanced options', 'woocommerce' ); ?></a>
|
||||
<button type="submit" class="button button-primary button-next" value="<?php esc_attr_e( 'Continue', 'woocommerce' ); ?>" name="save_step"><?php esc_html_e( 'Continue', 'woocommerce' ); ?></button>
|
||||
<?php wp_nonce_field( 'woocommerce-csv-importer' ); ?>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
/**
|
||||
* List tables.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_List_Table', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_List_Table Class.
|
||||
*/
|
||||
abstract class WC_Admin_List_Table {
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $list_table_type = '';
|
||||
|
||||
/**
|
||||
* Object being shown on the row.
|
||||
*
|
||||
* @var object|null
|
||||
*/
|
||||
protected $object = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( $this->list_table_type ) {
|
||||
add_action( 'manage_posts_extra_tablenav', array( $this, 'maybe_render_blank_state' ) );
|
||||
add_filter( 'view_mode_post_types', array( $this, 'disable_view_mode' ) );
|
||||
add_action( 'restrict_manage_posts', array( $this, 'restrict_manage_posts' ) );
|
||||
add_filter( 'request', array( $this, 'request_query' ) );
|
||||
add_filter( 'post_row_actions', array( $this, 'row_actions' ), 100, 2 );
|
||||
add_filter( 'default_hidden_columns', array( $this, 'default_hidden_columns' ), 10, 2 );
|
||||
add_filter( 'list_table_primary_column', array( $this, 'list_table_primary_column' ), 10, 2 );
|
||||
add_filter( 'manage_edit-' . $this->list_table_type . '_sortable_columns', array( $this, 'define_sortable_columns' ) );
|
||||
add_filter( 'manage_' . $this->list_table_type . '_posts_columns', array( $this, 'define_columns' ) );
|
||||
add_filter( 'bulk_actions-edit-' . $this->list_table_type, array( $this, 'define_bulk_actions' ) );
|
||||
add_action( 'manage_' . $this->list_table_type . '_posts_custom_column', array( $this, 'render_columns' ), 10, 2 );
|
||||
add_filter( 'handle_bulk_actions-edit-' . $this->list_table_type, array( $this, 'handle_bulk_actions' ), 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show blank slate.
|
||||
*
|
||||
* @param string $which String which tablenav is being shown.
|
||||
*/
|
||||
public function maybe_render_blank_state( $which ) {
|
||||
global $post_type;
|
||||
|
||||
if ( $post_type === $this->list_table_type && 'bottom' === $which ) {
|
||||
$counts = (array) wp_count_posts( $post_type );
|
||||
unset( $counts['auto-draft'] );
|
||||
$count = array_sum( $counts );
|
||||
|
||||
if ( 0 < $count ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->render_blank_state();
|
||||
|
||||
echo '<style type="text/css">#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions, .wrap .subsubsub { display: none; } #posts-filter .tablenav.bottom { height: auto; } </style>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render blank state. Extend to add content.
|
||||
*/
|
||||
protected function render_blank_state() {}
|
||||
|
||||
/**
|
||||
* Removes this type from list of post types that support "View Mode" switching.
|
||||
* View mode is seen on posts where you can switch between list or excerpt. Our post types don't support
|
||||
* it, so we want to hide the useless UI from the screen options tab.
|
||||
*
|
||||
* @param array $post_types Array of post types supporting view mode.
|
||||
* @return array Array of post types supporting view mode, without this type.
|
||||
*/
|
||||
public function disable_view_mode( $post_types ) {
|
||||
unset( $post_types[ $this->list_table_type ] );
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we should render search filters or not.
|
||||
*/
|
||||
public function restrict_manage_posts() {
|
||||
global $typenow;
|
||||
|
||||
if ( $this->list_table_type === $typenow ) {
|
||||
$this->render_filters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any filters.
|
||||
*
|
||||
* @param array $query_vars Query vars.
|
||||
* @return array
|
||||
*/
|
||||
public function request_query( $query_vars ) {
|
||||
global $typenow;
|
||||
|
||||
if ( $this->list_table_type === $typenow ) {
|
||||
return $this->query_filters( $query_vars );
|
||||
}
|
||||
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render any custom filters and search inputs for the list table.
|
||||
*/
|
||||
protected function render_filters() {}
|
||||
|
||||
/**
|
||||
* Handle any custom filters.
|
||||
*
|
||||
* @param array $query_vars Query vars.
|
||||
* @return array
|
||||
*/
|
||||
protected function query_filters( $query_vars ) {
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set row actions.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return array
|
||||
*/
|
||||
public function row_actions( $actions, $post ) {
|
||||
if ( $this->list_table_type === $post->post_type ) {
|
||||
return $this->get_row_actions( $actions, $post );
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row actions to show in the list table.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_row_actions( $actions, $post ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust which columns are displayed by default.
|
||||
*
|
||||
* @param array $hidden Current hidden columns.
|
||||
* @param object $screen Current screen.
|
||||
* @return array
|
||||
*/
|
||||
public function default_hidden_columns( $hidden, $screen ) {
|
||||
if ( isset( $screen->id ) && 'edit-' . $this->list_table_type === $screen->id ) {
|
||||
$hidden = array_merge( $hidden, $this->define_hidden_columns() );
|
||||
}
|
||||
return $hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set list table primary column.
|
||||
*
|
||||
* @param string $default Default value.
|
||||
* @param string $screen_id Current screen ID.
|
||||
* @return string
|
||||
*/
|
||||
public function list_table_primary_column( $default, $screen_id ) {
|
||||
if ( 'edit-' . $this->list_table_type === $screen_id && $this->get_primary_column() ) {
|
||||
return $this->get_primary_column();
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define primary column.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_primary_column() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define hidden columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_hidden_columns() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns are sortable.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_sortable_columns( $columns ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns to show on this screen.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_columns( $columns ) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define bulk actions.
|
||||
*
|
||||
* @param array $actions Existing actions.
|
||||
* @return array
|
||||
*/
|
||||
public function define_bulk_actions( $actions ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fetch any data for the row each column has access to it.
|
||||
*
|
||||
* @param int $post_id Post ID being shown.
|
||||
*/
|
||||
protected function prepare_row_data( $post_id ) {}
|
||||
|
||||
/**
|
||||
* Render individual columns.
|
||||
*
|
||||
* @param string $column Column ID to render.
|
||||
* @param int $post_id Post ID being shown.
|
||||
*/
|
||||
public function render_columns( $column, $post_id ) {
|
||||
$this->prepare_row_data( $post_id );
|
||||
|
||||
if ( ! $this->object ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_callable( array( $this, 'render_' . $column . '_column' ) ) ) {
|
||||
$this->{"render_{$column}_column"}();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bulk actions.
|
||||
*
|
||||
* @param string $redirect_to URL to redirect to.
|
||||
* @param string $action Action name.
|
||||
* @param array $ids List of ids.
|
||||
* @return string
|
||||
*/
|
||||
public function handle_bulk_actions( $redirect_to, $action, $ids ) {
|
||||
return esc_url_raw( $redirect_to );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* List tables: coupons.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_List_Table_Coupons', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_List_Table', false ) ) {
|
||||
include_once __DIR__ . '/abstract-class-wc-admin-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_List_Table_Coupons Class.
|
||||
*/
|
||||
class WC_Admin_List_Table_Coupons extends WC_Admin_List_Table {
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $list_table_type = 'shop_coupon';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
add_filter( 'disable_months_dropdown', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render blank state.
|
||||
*/
|
||||
protected function render_blank_state() {
|
||||
echo '<div class="woocommerce-BlankState">';
|
||||
echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'Coupons are a great way to offer discounts and rewards to your customers. They will appear here once created.', 'woocommerce' ) . '</h2>';
|
||||
echo '<a class="woocommerce-BlankState-cta button-primary button" href="' . esc_url( admin_url( 'post-new.php?post_type=shop_coupon' ) ) . '">' . esc_html__( 'Create your first coupon', 'woocommerce' ) . '</a>';
|
||||
echo '<a class="woocommerce-BlankState-cta button" target="_blank" href="https://woocommerce.com/document/coupon-management/?utm_source=blankslate&utm_medium=product&utm_content=couponsdoc&utm_campaign=woocommerceplugin">' . esc_html__( 'Learn more about coupons', 'woocommerce' ) . '</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define primary column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_primary_column() {
|
||||
return 'coupon_code';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row actions to show in the list table.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_row_actions( $actions, $post ) {
|
||||
unset( $actions['inline hide-if-no-js'] );
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns to show on this screen.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_columns( $columns ) {
|
||||
$show_columns = array();
|
||||
$show_columns['cb'] = $columns['cb'];
|
||||
$show_columns['coupon_code'] = __( 'Code', 'woocommerce' );
|
||||
$show_columns['type'] = __( 'Coupon type', 'woocommerce' );
|
||||
$show_columns['amount'] = __( 'Coupon amount', 'woocommerce' );
|
||||
$show_columns['description'] = __( 'Description', 'woocommerce' );
|
||||
$show_columns['products'] = __( 'Product IDs', 'woocommerce' );
|
||||
$show_columns['usage'] = __( 'Usage / Limit', 'woocommerce' );
|
||||
$show_columns['expiry_date'] = __( 'Expiry date', 'woocommerce' );
|
||||
|
||||
return $show_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fetch any data for the row each column has access to it. the_coupon global is there for bw compat.
|
||||
*
|
||||
* @param int $post_id Post ID being shown.
|
||||
*/
|
||||
protected function prepare_row_data( $post_id ) {
|
||||
global $the_coupon;
|
||||
|
||||
if ( empty( $this->object ) || $this->object->get_id() !== $post_id ) {
|
||||
$this->object = new WC_Coupon( $post_id );
|
||||
$the_coupon = $this->object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: coupon_code.
|
||||
*/
|
||||
protected function render_coupon_code_column() {
|
||||
global $post;
|
||||
|
||||
$edit_link = get_edit_post_link( $this->object->get_id() );
|
||||
$title = $this->object->get_code();
|
||||
|
||||
echo '<strong><a class="row-title" href="' . esc_url( $edit_link ) . '">' . esc_html( $title ) . '</a>';
|
||||
_post_states( $post );
|
||||
echo '</strong>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: type.
|
||||
*/
|
||||
protected function render_type_column() {
|
||||
echo esc_html( wc_get_coupon_type( $this->object->get_discount_type() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: amount.
|
||||
*/
|
||||
protected function render_amount_column() {
|
||||
echo esc_html( wc_format_localized_price( $this->object->get_amount() ) );
|
||||
}
|
||||
/**
|
||||
* Render column: products.
|
||||
*/
|
||||
protected function render_products_column() {
|
||||
$product_ids = $this->object->get_product_ids();
|
||||
if ( is_array( $product_ids ) && count( $product_ids ) > 0 ) {
|
||||
echo esc_html( implode( ', ', $product_ids ) );
|
||||
} else {
|
||||
echo '–';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: usage_limit.
|
||||
*/
|
||||
protected function render_usage_limit_column() {
|
||||
$usage_limit = $this->object->get_usage_limit();
|
||||
|
||||
if ( $usage_limit ) {
|
||||
echo esc_html( $usage_limit );
|
||||
} else {
|
||||
echo '–';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: usage.
|
||||
*/
|
||||
protected function render_usage_column() {
|
||||
$usage_count = $this->object->get_usage_count();
|
||||
$usage_limit = $this->object->get_usage_limit();
|
||||
|
||||
printf(
|
||||
/* translators: 1: count 2: limit */
|
||||
__( '%1$s / %2$s', 'woocommerce' ),
|
||||
esc_html( $usage_count ),
|
||||
$usage_limit ? esc_html( $usage_limit ) : '∞'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: expiry_date.
|
||||
*/
|
||||
protected function render_expiry_date_column() {
|
||||
$expiry_date = $this->object->get_date_expires();
|
||||
|
||||
if ( $expiry_date ) {
|
||||
echo esc_html( $expiry_date->date_i18n( 'F j, Y' ) );
|
||||
} else {
|
||||
echo '–';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: description.
|
||||
*/
|
||||
protected function render_description_column() {
|
||||
echo wp_kses_post( $this->object->get_description() ? $this->object->get_description() : '–' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render any custom filters and search inputs for the list table.
|
||||
*/
|
||||
protected function render_filters() {
|
||||
?>
|
||||
<select name="coupon_type" id="dropdown_shop_coupon_type">
|
||||
<option value=""><?php esc_html_e( 'Show all types', 'woocommerce' ); ?></option>
|
||||
<?php
|
||||
$types = wc_get_coupon_types();
|
||||
|
||||
foreach ( $types as $name => $type ) {
|
||||
echo '<option value="' . esc_attr( $name ) . '"';
|
||||
|
||||
if ( isset( $_GET['coupon_type'] ) ) { // WPCS: input var ok.
|
||||
selected( $name, wc_clean( wp_unslash( $_GET['coupon_type'] ) ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
echo '>' . esc_html( $type ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any custom filters.
|
||||
*
|
||||
* @param array $query_vars Query vars.
|
||||
* @return array
|
||||
*/
|
||||
protected function query_filters( $query_vars ) {
|
||||
if ( ! empty( $_GET['coupon_type'] ) ) { // WPCS: input var ok, sanitization ok.
|
||||
$query_vars['meta_key'] = 'discount_type'; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
$query_vars['meta_value'] = wc_clean( wp_unslash( $_GET['coupon_type'] ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value, WordPress.VIP.SuperGlobalInputUsage.AccessDetected
|
||||
}
|
||||
return $query_vars;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
<?php
|
||||
/**
|
||||
* List tables: orders.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_List_Table_Orders', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_List_Table', false ) ) {
|
||||
include_once __DIR__ . '/abstract-class-wc-admin-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_List_Table_Orders Class.
|
||||
*/
|
||||
class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $list_table_type = 'shop_order';
|
||||
|
||||
/**
|
||||
* The data store-agnostic list table implementation (introduced to support custom order tables),
|
||||
* which we use here to render columns.
|
||||
*
|
||||
* @var ListTable $orders_list_table
|
||||
*/
|
||||
private $orders_list_table;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->orders_list_table = wc_get_container()->get( ListTable::class );
|
||||
add_action( 'admin_notices', array( $this, 'bulk_admin_notices' ) );
|
||||
add_action( 'admin_footer', array( $this, 'order_preview_template' ) );
|
||||
add_filter( 'get_search_query', array( $this, 'search_label' ) );
|
||||
add_filter( 'query_vars', array( $this, 'add_custom_query_var' ) );
|
||||
add_action( 'parse_query', array( $this, 'search_custom_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render blank state.
|
||||
*/
|
||||
protected function render_blank_state() {
|
||||
$this->orders_list_table->render_blank_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define primary column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_primary_column() {
|
||||
return 'order_number';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row actions to show in the list table.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_row_actions( $actions, $post ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define hidden columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function define_hidden_columns() {
|
||||
return array(
|
||||
'shipping_address',
|
||||
'billing_address',
|
||||
'wc_actions',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns are sortable.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_sortable_columns( $columns ) {
|
||||
$custom = array(
|
||||
'order_number' => 'ID',
|
||||
'order_total' => 'order_total',
|
||||
'order_date' => 'date',
|
||||
);
|
||||
unset( $columns['comments'] );
|
||||
|
||||
return wp_parse_args( $custom, $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns to show on this screen.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_columns( $columns ) {
|
||||
$show_columns = array();
|
||||
$show_columns['cb'] = $columns['cb'];
|
||||
$show_columns['order_number'] = __( 'Order', 'woocommerce' );
|
||||
$show_columns['order_date'] = __( 'Date', 'woocommerce' );
|
||||
$show_columns['order_status'] = __( 'Status', 'woocommerce' );
|
||||
$show_columns['billing_address'] = __( 'Billing', 'woocommerce' );
|
||||
$show_columns['shipping_address'] = __( 'Ship to', 'woocommerce' );
|
||||
$show_columns['order_total'] = __( 'Total', 'woocommerce' );
|
||||
$show_columns['wc_actions'] = __( 'Actions', 'woocommerce' );
|
||||
|
||||
wp_enqueue_script( 'wc-orders' );
|
||||
|
||||
return $show_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define bulk actions.
|
||||
*
|
||||
* @param array $actions Existing actions.
|
||||
* @return array
|
||||
*/
|
||||
public function define_bulk_actions( $actions ) {
|
||||
if ( isset( $actions['edit'] ) ) {
|
||||
unset( $actions['edit'] );
|
||||
}
|
||||
|
||||
$actions['mark_processing'] = __( 'Change status to processing', 'woocommerce' );
|
||||
$actions['mark_on-hold'] = __( 'Change status to on-hold', 'woocommerce' );
|
||||
$actions['mark_completed'] = __( 'Change status to completed', 'woocommerce' );
|
||||
$actions['mark_cancelled'] = __( 'Change status to cancelled', 'woocommerce' );
|
||||
|
||||
if ( wc_string_to_bool( get_option( 'woocommerce_allow_bulk_remove_personal_data', 'no' ) ) ) {
|
||||
$actions['remove_personal_data'] = __( 'Remove personal data', 'woocommerce' );
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fetch any data for the row each column has access to it. the_order global is there for bw compat.
|
||||
*
|
||||
* @param int $post_id Post ID being shown.
|
||||
*/
|
||||
protected function prepare_row_data( $post_id ) {
|
||||
global $the_order;
|
||||
|
||||
if ( empty( $this->object ) || $this->object->get_id() !== $post_id ) {
|
||||
$this->object = wc_get_order( $post_id );
|
||||
$the_order = $this->object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: order_number.
|
||||
*/
|
||||
protected function render_order_number_column() {
|
||||
$this->orders_list_table->render_order_number_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: order_status.
|
||||
*/
|
||||
protected function render_order_status_column() {
|
||||
$this->orders_list_table->render_order_status_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: order_date.
|
||||
*/
|
||||
protected function render_order_date_column() {
|
||||
$this->orders_list_table->render_order_date_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: order_total.
|
||||
*/
|
||||
protected function render_order_total_column() {
|
||||
$this->orders_list_table->render_order_total_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: wc_actions.
|
||||
*/
|
||||
protected function render_wc_actions_column() {
|
||||
$this->orders_list_table->render_wc_actions_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: billing_address.
|
||||
*/
|
||||
protected function render_billing_address_column() {
|
||||
$this->orders_list_table->render_billing_address_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: shipping_address.
|
||||
*/
|
||||
protected function render_shipping_address_column() {
|
||||
$this->orders_list_table->render_shipping_address_column( $this->object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Template for order preview.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function order_preview_template() {
|
||||
echo $this->orders_list_table->get_order_preview_template();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items to display in the preview as HTML.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_order_preview_item_html( $order ) {
|
||||
$hidden_order_itemmeta = apply_filters(
|
||||
'woocommerce_hidden_order_itemmeta',
|
||||
array(
|
||||
'_qty',
|
||||
'_tax_class',
|
||||
'_product_id',
|
||||
'_variation_id',
|
||||
'_line_subtotal',
|
||||
'_line_subtotal_tax',
|
||||
'_line_total',
|
||||
'_line_tax',
|
||||
'method_id',
|
||||
'cost',
|
||||
'_reduced_stock',
|
||||
'_restock_refunded_items',
|
||||
)
|
||||
);
|
||||
|
||||
$line_items = apply_filters( 'woocommerce_admin_order_preview_line_items', $order->get_items(), $order );
|
||||
$columns = apply_filters(
|
||||
'woocommerce_admin_order_preview_line_item_columns',
|
||||
array(
|
||||
'product' => __( 'Product', 'woocommerce' ),
|
||||
'quantity' => __( 'Quantity', 'woocommerce' ),
|
||||
'tax' => __( 'Tax', 'woocommerce' ),
|
||||
'total' => __( 'Total', 'woocommerce' ),
|
||||
),
|
||||
$order
|
||||
);
|
||||
|
||||
if ( ! wc_tax_enabled() ) {
|
||||
unset( $columns['tax'] );
|
||||
}
|
||||
|
||||
$html = '
|
||||
<div class="wc-order-preview-table-wrapper">
|
||||
<table cellspacing="0" class="wc-order-preview-table">
|
||||
<thead>
|
||||
<tr>';
|
||||
|
||||
foreach ( $columns as $column => $label ) {
|
||||
$html .= '<th class="wc-order-preview-table__column--' . esc_attr( $column ) . '">' . esc_html( $label ) . '</th>';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
foreach ( $line_items as $item_id => $item ) {
|
||||
|
||||
$product_object = is_callable( array( $item, 'get_product' ) ) ? $item->get_product() : null;
|
||||
$row_class = apply_filters( 'woocommerce_admin_html_order_preview_item_class', '', $item, $order );
|
||||
|
||||
$html .= '<tr class="wc-order-preview-table__item wc-order-preview-table__item--' . esc_attr( $item_id ) . ( $row_class ? ' ' . esc_attr( $row_class ) : '' ) . '">';
|
||||
|
||||
foreach ( $columns as $column => $label ) {
|
||||
$html .= '<td class="wc-order-preview-table__column--' . esc_attr( $column ) . '">';
|
||||
switch ( $column ) {
|
||||
case 'product':
|
||||
$html .= wp_kses_post( $item->get_name() );
|
||||
|
||||
if ( $product_object ) {
|
||||
$html .= '<div class="wc-order-item-sku">' . esc_html( $product_object->get_sku() ) . '</div>';
|
||||
}
|
||||
|
||||
$meta_data = $item->get_all_formatted_meta_data( '' );
|
||||
|
||||
if ( $meta_data ) {
|
||||
$html .= '<table cellspacing="0" class="wc-order-item-meta">';
|
||||
|
||||
foreach ( $meta_data as $meta_id => $meta ) {
|
||||
if ( in_array( $meta->key, $hidden_order_itemmeta, true ) ) {
|
||||
continue;
|
||||
}
|
||||
$html .= '<tr><th>' . wp_kses_post( $meta->display_key ) . ':</th><td>' . wp_kses_post( force_balance_tags( $meta->display_value ) ) . '</td></tr>';
|
||||
}
|
||||
$html .= '</table>';
|
||||
}
|
||||
break;
|
||||
case 'quantity':
|
||||
$html .= esc_html( $item->get_quantity() );
|
||||
break;
|
||||
case 'tax':
|
||||
$html .= wc_price( $item->get_total_tax(), array( 'currency' => $order->get_currency() ) );
|
||||
break;
|
||||
case 'total':
|
||||
$html .= wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) );
|
||||
break;
|
||||
default:
|
||||
$html .= apply_filters( 'woocommerce_admin_order_preview_line_item_column_' . sanitize_key( $column ), '', $item, $item_id, $order );
|
||||
break;
|
||||
}
|
||||
$html .= '</td>';
|
||||
}
|
||||
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
</tbody>
|
||||
</table>
|
||||
</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actions to display in the preview as HTML.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_order_preview_actions_html( $order ) {
|
||||
$actions = array();
|
||||
$status_actions = array();
|
||||
|
||||
if ( $order->has_status( array( 'pending' ) ) ) {
|
||||
$status_actions['on-hold'] = array(
|
||||
'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' ),
|
||||
'name' => __( 'On-hold', 'woocommerce' ),
|
||||
'title' => __( 'Change order status to on-hold', 'woocommerce' ),
|
||||
'action' => 'on-hold',
|
||||
);
|
||||
}
|
||||
|
||||
if ( $order->has_status( array( 'pending', 'on-hold' ) ) ) {
|
||||
$status_actions['processing'] = array(
|
||||
'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' ),
|
||||
'name' => __( 'Processing', 'woocommerce' ),
|
||||
'title' => __( 'Change order status to processing', 'woocommerce' ),
|
||||
'action' => 'processing',
|
||||
);
|
||||
}
|
||||
|
||||
if ( $order->has_status( array( 'pending', 'on-hold', 'processing' ) ) ) {
|
||||
$status_actions['complete'] = array(
|
||||
'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' ),
|
||||
'name' => __( 'Completed', 'woocommerce' ),
|
||||
'title' => __( 'Change order status to completed', 'woocommerce' ),
|
||||
'action' => 'complete',
|
||||
);
|
||||
}
|
||||
|
||||
if ( $status_actions ) {
|
||||
$actions['status'] = array(
|
||||
'group' => __( 'Change status: ', 'woocommerce' ),
|
||||
'actions' => $status_actions,
|
||||
);
|
||||
}
|
||||
|
||||
return wc_render_action_buttons( apply_filters( 'woocommerce_admin_order_preview_actions', $actions, $order ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order details to send to the ajax endpoint for previews.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
* @return array
|
||||
*/
|
||||
public static function order_preview_get_order_details( $order ) {
|
||||
if ( ! $order ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$payment_via = $order->get_payment_method_title();
|
||||
$payment_method = $order->get_payment_method();
|
||||
$payment_gateways = WC()->payment_gateways() ? WC()->payment_gateways->payment_gateways() : array();
|
||||
$transaction_id = $order->get_transaction_id();
|
||||
|
||||
if ( $transaction_id ) {
|
||||
|
||||
$url = isset( $payment_gateways[ $payment_method ] ) ? $payment_gateways[ $payment_method ]->get_transaction_url( $order ) : false;
|
||||
|
||||
if ( $url ) {
|
||||
$payment_via .= ' (<a href="' . esc_url( $url ) . '" target="_blank">' . esc_html( $transaction_id ) . '</a>)';
|
||||
} else {
|
||||
$payment_via .= ' (' . esc_html( $transaction_id ) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$billing_address = $order->get_formatted_billing_address();
|
||||
$shipping_address = $order->get_formatted_shipping_address();
|
||||
|
||||
return apply_filters(
|
||||
'woocommerce_admin_order_preview_get_order_details',
|
||||
array(
|
||||
'data' => $order->get_data(),
|
||||
'order_number' => $order->get_order_number(),
|
||||
'item_html' => self::get_order_preview_item_html( $order ),
|
||||
'actions_html' => self::get_order_preview_actions_html( $order ),
|
||||
'ship_to_billing' => wc_ship_to_billing_address_only(),
|
||||
'needs_shipping' => $order->needs_shipping_address(),
|
||||
'formatted_billing_address' => $billing_address ? $billing_address : __( 'N/A', 'woocommerce' ),
|
||||
'formatted_shipping_address' => $shipping_address ? $shipping_address : __( 'N/A', 'woocommerce' ),
|
||||
'shipping_address_map_url' => $order->get_shipping_address_map_url(),
|
||||
'payment_via' => $payment_via,
|
||||
'shipping_via' => $order->get_shipping_method(),
|
||||
'status' => $order->get_status(),
|
||||
'status_name' => wc_get_order_status_name( $order->get_status() ),
|
||||
),
|
||||
$order
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bulk actions.
|
||||
*
|
||||
* @param string $redirect_to URL to redirect to.
|
||||
* @param string $action Action name.
|
||||
* @param array $ids List of ids.
|
||||
* @return string
|
||||
*/
|
||||
public function handle_bulk_actions( $redirect_to, $action, $ids ) {
|
||||
$ids = apply_filters( 'woocommerce_bulk_action_ids', array_reverse( array_map( 'absint', $ids ) ), $action, 'order' );
|
||||
$changed = 0;
|
||||
|
||||
if ( 'remove_personal_data' === $action ) {
|
||||
$report_action = 'removed_personal_data';
|
||||
|
||||
foreach ( $ids as $id ) {
|
||||
$order = wc_get_order( $id );
|
||||
|
||||
if ( $order ) {
|
||||
do_action( 'woocommerce_remove_order_personal_data', $order );
|
||||
$changed++;
|
||||
}
|
||||
}
|
||||
} elseif ( false !== strpos( $action, 'mark_' ) ) {
|
||||
$order_statuses = wc_get_order_statuses();
|
||||
$new_status = substr( $action, 5 ); // Get the status name from action.
|
||||
$report_action = 'marked_' . $new_status;
|
||||
|
||||
// Sanity check: bail out if this is actually not a status, or is not a registered status.
|
||||
if ( isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
|
||||
// Initialize payment gateways in case order has hooked status transition actions.
|
||||
WC()->payment_gateways();
|
||||
|
||||
foreach ( $ids as $id ) {
|
||||
$order = wc_get_order( $id );
|
||||
$order->update_status( $new_status, __( 'Order status changed by bulk edit:', 'woocommerce' ), true );
|
||||
do_action( 'woocommerce_order_edit_status', $id, $new_status );
|
||||
$changed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $changed ) {
|
||||
$redirect_to = add_query_arg(
|
||||
array(
|
||||
'post_type' => $this->list_table_type,
|
||||
'bulk_action' => $report_action,
|
||||
'changed' => $changed,
|
||||
'ids' => join( ',', $ids ),
|
||||
),
|
||||
$redirect_to
|
||||
);
|
||||
}
|
||||
|
||||
return esc_url_raw( $redirect_to );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation message that order status changed for number of orders.
|
||||
*/
|
||||
public function bulk_admin_notices() {
|
||||
global $post_type, $pagenow;
|
||||
|
||||
// Bail out if not on shop order list page.
|
||||
if ( 'edit.php' !== $pagenow || 'shop_order' !== $post_type || ! isset( $_REQUEST['bulk_action'] ) ) { // WPCS: input var ok, CSRF ok.
|
||||
return;
|
||||
}
|
||||
|
||||
$order_statuses = wc_get_order_statuses();
|
||||
$number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0; // WPCS: input var ok, CSRF ok.
|
||||
$bulk_action = wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) ); // WPCS: input var ok, CSRF ok.
|
||||
|
||||
// Check if any status changes happened.
|
||||
foreach ( $order_statuses as $slug => $name ) {
|
||||
if ( 'marked_' . str_replace( 'wc-', '', $slug ) === $bulk_action ) { // WPCS: input var ok, CSRF ok.
|
||||
/* translators: %d: orders count */
|
||||
$message = sprintf( _n( '%s order status changed.', '%s order statuses changed.', $number, 'woocommerce' ), number_format_i18n( $number ) );
|
||||
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'removed_personal_data' === $bulk_action ) { // WPCS: input var ok, CSRF ok.
|
||||
/* translators: %d: orders count */
|
||||
$message = sprintf( _n( 'Removed personal data from %s order.', 'Removed personal data from %s orders.', $number, 'woocommerce' ), number_format_i18n( $number ) );
|
||||
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See if we should render search filters or not.
|
||||
*/
|
||||
public function restrict_manage_posts() {
|
||||
global $typenow;
|
||||
|
||||
if ( in_array( $typenow, wc_get_order_types( 'order-meta-boxes' ), true ) ) {
|
||||
$this->render_filters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render any custom filters and search inputs for the list table.
|
||||
*/
|
||||
protected function render_filters() {
|
||||
$this->orders_list_table->customers_filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any filters.
|
||||
*
|
||||
* @param array $query_vars Query vars.
|
||||
* @return array
|
||||
*/
|
||||
public function request_query( $query_vars ) {
|
||||
global $typenow;
|
||||
|
||||
if ( in_array( $typenow, wc_get_order_types( 'order-meta-boxes' ), true ) ) {
|
||||
return $this->query_filters( $query_vars );
|
||||
}
|
||||
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any custom filters.
|
||||
*
|
||||
* @param array $query_vars Query vars.
|
||||
* @return array
|
||||
*/
|
||||
protected function query_filters( $query_vars ) {
|
||||
global $wp_post_statuses;
|
||||
|
||||
// Filter the orders by the posted customer.
|
||||
if ( ! empty( $_GET['_customer_user'] ) ) { // WPCS: input var ok.
|
||||
// @codingStandardsIgnoreStart.
|
||||
$query_vars['meta_query'] = array(
|
||||
array(
|
||||
'key' => '_customer_user',
|
||||
'value' => (int) $_GET['_customer_user'], // WPCS: input var ok, sanitization ok.
|
||||
'compare' => '=',
|
||||
),
|
||||
);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
// Sorting.
|
||||
if ( isset( $query_vars['orderby'] ) ) {
|
||||
if ( 'order_total' === $query_vars['orderby'] ) {
|
||||
// @codingStandardsIgnoreStart
|
||||
$query_vars = array_merge( $query_vars, array(
|
||||
'meta_key' => '_order_total',
|
||||
'orderby' => 'meta_value_num',
|
||||
) );
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
// Status.
|
||||
if ( empty( $query_vars['post_status'] ) ) {
|
||||
$post_statuses = wc_get_order_statuses();
|
||||
|
||||
foreach ( $post_statuses as $status => $value ) {
|
||||
if ( isset( $wp_post_statuses[ $status ] ) && false === $wp_post_statuses[ $status ]->show_in_admin_all_list ) {
|
||||
unset( $post_statuses[ $status ] );
|
||||
}
|
||||
}
|
||||
|
||||
$query_vars['post_status'] = array_keys( $post_statuses );
|
||||
}
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the label when searching orders.
|
||||
*
|
||||
* @param mixed $query Current search query.
|
||||
* @return string
|
||||
*/
|
||||
public function search_label( $query ) {
|
||||
global $pagenow, $typenow;
|
||||
|
||||
if ( 'edit.php' !== $pagenow || 'shop_order' !== $typenow || ! get_query_var( 'shop_order_search' ) || ! isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return $query;
|
||||
}
|
||||
|
||||
return wc_clean( wp_unslash( $_GET['s'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Query vars for custom searches.
|
||||
*
|
||||
* @param mixed $public_query_vars Array of query vars.
|
||||
* @return array
|
||||
*/
|
||||
public function add_custom_query_var( $public_query_vars ) {
|
||||
$public_query_vars[] = 'shop_order_search';
|
||||
return $public_query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search custom fields as well as content.
|
||||
*
|
||||
* @param WP_Query $wp Query object.
|
||||
*/
|
||||
public function search_custom_fields( $wp ) {
|
||||
global $pagenow;
|
||||
|
||||
if ( 'edit.php' !== $pagenow || 'shop_order' !== $wp->query_vars['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
|
||||
$post_ids = isset( $_GET['s'] ) && ! empty( $wp->query_vars['s'] ) ? wc_order_search( wc_clean( wp_unslash( $_GET['s'] ) ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( ! empty( $post_ids ) ) {
|
||||
// Remove "s" - we don't want to search order name.
|
||||
unset( $wp->query_vars['s'] );
|
||||
|
||||
// so we know we're doing this.
|
||||
$wp->query_vars['shop_order_search'] = true;
|
||||
|
||||
// Search by found posts.
|
||||
$wp->query_vars['post__in'] = array_merge( $post_ids, array( 0 ) );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['order_date_type'] ) && isset( $_GET['m'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$date_type = wc_clean( wp_unslash( $_GET['order_date_type'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$date_query = wc_clean( wp_unslash( $_GET['m'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// date_paid and date_completed are stored in postmeta, so we need to do a meta query.
|
||||
if ( 'date_paid' === $date_type || 'date_completed' === $date_type ) {
|
||||
$date_start = \DateTime::createFromFormat( 'Ymd H:i:s', "$date_query 00:00:00" );
|
||||
$date_end = \DateTime::createFromFormat( 'Ymd H:i:s', "$date_query 23:59:59" );
|
||||
|
||||
unset( $wp->query_vars['m'] );
|
||||
|
||||
if ( $date_start && $date_end ) {
|
||||
$wp->query_vars['meta_key'] = "_$date_type"; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
$wp->query_vars['meta_value'] = array( strval( $date_start->getTimestamp() ), strval( $date_end->getTimestamp() ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
$wp->query_vars['meta_compare'] = 'BETWEEN';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
<?php
|
||||
/**
|
||||
* List tables: products.
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @version 3.3.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Admin_List_Table_Products', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Admin_List_Table', false ) ) {
|
||||
include_once __DIR__ . '/abstract-class-wc-admin-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Admin_List_Table_Products Class.
|
||||
*/
|
||||
class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $list_table_type = 'product';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
add_filter( 'disable_months_dropdown', '__return_true' );
|
||||
add_filter( 'query_vars', array( $this, 'add_custom_query_var' ) );
|
||||
add_filter( 'views_edit-product', array( $this, 'product_views' ) );
|
||||
add_filter( 'get_search_query', array( $this, 'search_label' ) );
|
||||
add_filter( 'posts_clauses', array( $this, 'posts_clauses' ), 10, 2 );
|
||||
add_action( 'manage_product_posts_custom_column', array( $this, 'add_sample_product_badge' ), 9, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render blank state.
|
||||
*/
|
||||
protected function render_blank_state() {
|
||||
echo '<div class="woocommerce-BlankState">';
|
||||
|
||||
echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'Ready to start selling something awesome?', 'woocommerce' ) . '</h2>';
|
||||
|
||||
echo '<div class="woocommerce-BlankState-buttons">';
|
||||
|
||||
echo '<a class="woocommerce-BlankState-cta button-primary button" href="' . esc_url( admin_url( 'post-new.php?post_type=product&tutorial=true' ) ) . '">' . esc_html__( 'Create Product', 'woocommerce' ) . '</a>';
|
||||
echo '<a class="woocommerce-BlankState-cta button" href="' . esc_url( admin_url( 'edit.php?post_type=product&page=product_importer' ) ) . '">' . esc_html__( 'Start Import', 'woocommerce' ) . '</a>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
do_action( 'wc_marketplace_suggestions_products_empty_state' );
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define primary column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_primary_column() {
|
||||
return 'name';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row actions to show in the list table.
|
||||
*
|
||||
* @param array $actions Array of actions.
|
||||
* @param WP_Post $post Current post object.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_row_actions( $actions, $post ) {
|
||||
/* translators: %d: product ID. */
|
||||
return array_merge( array( 'id' => sprintf( __( 'ID: %d', 'woocommerce' ), $post->ID ) ), $actions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns are sortable.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_sortable_columns( $columns ) {
|
||||
$custom = array(
|
||||
'price' => 'price',
|
||||
'sku' => 'sku',
|
||||
'name' => 'title',
|
||||
);
|
||||
return wp_parse_args( $custom, $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define which columns to show on this screen.
|
||||
*
|
||||
* @param array $columns Existing columns.
|
||||
* @return array
|
||||
*/
|
||||
public function define_columns( $columns ) {
|
||||
if ( empty( $columns ) && ! is_array( $columns ) ) {
|
||||
$columns = array();
|
||||
}
|
||||
|
||||
unset( $columns['title'], $columns['comments'], $columns['date'] );
|
||||
|
||||
$show_columns = array();
|
||||
$show_columns['cb'] = '<input type="checkbox" />';
|
||||
$show_columns['thumb'] = '<span class="wc-image tips" data-tip="' . esc_attr__( 'Image', 'woocommerce' ) . '">' . __( 'Image', 'woocommerce' ) . '</span>';
|
||||
$show_columns['name'] = __( 'Name', 'woocommerce' );
|
||||
|
||||
if ( wc_product_sku_enabled() ) {
|
||||
$show_columns['sku'] = __( 'SKU', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
|
||||
$show_columns['is_in_stock'] = __( 'Stock', 'woocommerce' );
|
||||
}
|
||||
|
||||
$show_columns['price'] = __( 'Price', 'woocommerce' );
|
||||
$show_columns['product_cat'] = __( 'Categories', 'woocommerce' );
|
||||
$show_columns['product_tag'] = __( 'Tags', 'woocommerce' );
|
||||
$show_columns['featured'] = '<span class="wc-featured parent-tips" data-tip="' . esc_attr__( 'Featured', 'woocommerce' ) . '">' . __( 'Featured', 'woocommerce' ) . '</span>';
|
||||
$show_columns['date'] = __( 'Date', 'woocommerce' );
|
||||
|
||||
return array_merge( $show_columns, $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fetch any data for the row each column has access to it. the_product global is there for bw compat.
|
||||
*
|
||||
* @param int $post_id Post ID being shown.
|
||||
*/
|
||||
protected function prepare_row_data( $post_id ) {
|
||||
global $the_product;
|
||||
|
||||
if ( empty( $this->object ) || $this->object->get_id() !== $post_id ) {
|
||||
$the_product = wc_get_product( $post_id );
|
||||
$this->object = $the_product;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: thumb.
|
||||
*/
|
||||
protected function render_thumb_column() {
|
||||
echo '<a href="' . esc_url( get_edit_post_link( $this->object->get_id() ) ) . '">' . $this->object->get_image( 'thumbnail' ) . '</a>'; // WPCS: XSS ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: name.
|
||||
*/
|
||||
protected function render_name_column() {
|
||||
global $post;
|
||||
|
||||
$edit_link = get_edit_post_link( $this->object->get_id() );
|
||||
$title = _draft_or_post_title();
|
||||
|
||||
echo '<strong><a class="row-title" href="' . esc_url( $edit_link ) . '">' . esc_html( $title ) . '</a>';
|
||||
|
||||
_post_states( $post );
|
||||
|
||||
echo '</strong>';
|
||||
|
||||
if ( $this->object->get_parent_id() > 0 ) {
|
||||
echo ' ← <a href="' . esc_url( get_edit_post_link( $this->object->get_parent_id() ) ) . '">' . get_the_title( $this->object->get_parent_id() ) . '</a>'; // @codingStandardsIgnoreLine.
|
||||
}
|
||||
|
||||
get_inline_data( $post );
|
||||
|
||||
/* Custom inline data for woocommerce. */
|
||||
echo '
|
||||
<div class="hidden" id="woocommerce_inline_' . absint( $this->object->get_id() ) . '">
|
||||
<div class="menu_order">' . esc_html( $this->object->get_menu_order() ) . '</div>
|
||||
<div class="sku">' . esc_html( $this->object->get_sku() ) . '</div>
|
||||
<div class="regular_price">' . esc_html( $this->object->get_regular_price() ) . '</div>
|
||||
<div class="sale_price">' . esc_html( $this->object->get_sale_price() ) . '</div>
|
||||
<div class="weight">' . esc_html( $this->object->get_weight() ) . '</div>
|
||||
<div class="length">' . esc_html( $this->object->get_length() ) . '</div>
|
||||
<div class="width">' . esc_html( $this->object->get_width() ) . '</div>
|
||||
<div class="height">' . esc_html( $this->object->get_height() ) . '</div>
|
||||
<div class="shipping_class">' . esc_html( $this->object->get_shipping_class() ) . '</div>
|
||||
<div class="visibility">' . esc_html( $this->object->get_catalog_visibility() ) . '</div>
|
||||
<div class="stock_status">' . esc_html( $this->object->get_stock_status() ) . '</div>
|
||||
<div class="stock">' . esc_html( $this->object->get_stock_quantity() ) . '</div>
|
||||
<div class="manage_stock">' . esc_html( wc_bool_to_string( $this->object->get_manage_stock() ) ) . '</div>
|
||||
<div class="featured">' . esc_html( wc_bool_to_string( $this->object->get_featured() ) ) . '</div>
|
||||
<div class="product_type">' . esc_html( $this->object->get_type() ) . '</div>
|
||||
<div class="product_is_virtual">' . esc_html( wc_bool_to_string( $this->object->get_virtual() ) ) . '</div>
|
||||
<div class="tax_status">' . esc_html( $this->object->get_tax_status() ) . '</div>
|
||||
<div class="tax_class">' . esc_html( $this->object->get_tax_class() ) . '</div>
|
||||
<div class="backorders">' . esc_html( $this->object->get_backorders() ) . '</div>
|
||||
<div class="low_stock_amount">' . esc_html( $this->object->get_low_stock_amount() ) . '</div>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: sku.
|
||||
*/
|
||||
protected function render_sku_column() {
|
||||
echo $this->object->get_sku() ? esc_html( $this->object->get_sku() ) : '<span class="na">–</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: price.
|
||||
*/
|
||||
protected function render_price_column() {
|
||||
echo $this->object->get_price_html() ? wp_kses_post( $this->object->get_price_html() ) : '<span class="na">–</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: product_cat.
|
||||
*/
|
||||
protected function render_product_cat_column() {
|
||||
$terms = get_the_terms( $this->object->get_id(), 'product_cat' );
|
||||
if ( ! $terms ) {
|
||||
echo '<span class="na">–</span>';
|
||||
} else {
|
||||
$termlist = array();
|
||||
foreach ( $terms as $term ) {
|
||||
$termlist[] = '<a href="' . esc_url( admin_url( 'edit.php?product_cat=' . $term->slug . '&post_type=product' ) ) . ' ">' . esc_html( $term->name ) . '</a>';
|
||||
}
|
||||
|
||||
echo apply_filters( 'woocommerce_admin_product_term_list', implode( ', ', $termlist ), 'product_cat', $this->object->get_id(), $termlist, $terms ); // WPCS: XSS ok.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: product_tag.
|
||||
*/
|
||||
protected function render_product_tag_column() {
|
||||
$terms = get_the_terms( $this->object->get_id(), 'product_tag' );
|
||||
if ( ! $terms ) {
|
||||
echo '<span class="na">–</span>';
|
||||
} else {
|
||||
$termlist = array();
|
||||
foreach ( $terms as $term ) {
|
||||
$termlist[] = '<a href="' . esc_url( admin_url( 'edit.php?product_tag=' . $term->slug . '&post_type=product' ) ) . ' ">' . esc_html( $term->name ) . '</a>';
|
||||
}
|
||||
|
||||
echo apply_filters( 'woocommerce_admin_product_term_list', implode( ', ', $termlist ), 'product_tag', $this->object->get_id(), $termlist, $terms ); // WPCS: XSS ok.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: featured.
|
||||
*/
|
||||
protected function render_featured_column() {
|
||||
$url = wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_feature_product&product_id=' . $this->object->get_id() ), 'woocommerce-feature-product' );
|
||||
echo '<a href="' . esc_url( $url ) . '" aria-label="' . esc_attr__( 'Toggle featured', 'woocommerce' ) . '">';
|
||||
if ( $this->object->is_featured() ) {
|
||||
echo '<span class="wc-featured tips" data-tip="' . esc_attr__( 'Yes', 'woocommerce' ) . '">' . esc_html__( 'Yes', 'woocommerce' ) . '</span>';
|
||||
} else {
|
||||
echo '<span class="wc-featured not-featured tips" data-tip="' . esc_attr__( 'No', 'woocommerce' ) . '">' . esc_html__( 'No', 'woocommerce' ) . '</span>';
|
||||
}
|
||||
echo '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render column: is_in_stock.
|
||||
*/
|
||||
protected function render_is_in_stock_column() {
|
||||
if ( $this->object->is_on_backorder() ) {
|
||||
$stock_html = '<mark class="onbackorder">' . __( 'On backorder', 'woocommerce' ) . '</mark>';
|
||||
} elseif ( $this->object->is_in_stock() ) {
|
||||
$stock_html = '<mark class="instock">' . __( 'In stock', 'woocommerce' ) . '</mark>';
|
||||
} else {
|
||||
$stock_html = '<mark class="outofstock">' . __( 'Out of stock', 'woocommerce' ) . '</mark>';
|
||||
}
|
||||
|
||||
if ( $this->object->managing_stock() ) {
|
||||
$stock_html .= ' (' . wc_stock_amount( $this->object->get_stock_quantity() ) . ')';
|
||||
}
|
||||
|
||||
echo wp_kses_post( apply_filters( 'woocommerce_admin_stock_html', $stock_html, $this->object ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Query vars for custom searches.
|
||||
*
|
||||
* @param mixed $public_query_vars Array of query vars.
|
||||
* @return array
|
||||
*/
|
||||
public function add_custom_query_var( $public_query_vars ) {
|
||||
$public_query_vars[] = 'sku';
|
||||
return $public_query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render any custom filters and search inputs for the list table.
|
||||
*/
|
||||
protected function render_filters() {
|
||||
$filters = apply_filters(
|
||||
'woocommerce_products_admin_list_table_filters',
|
||||
array(
|
||||
'product_category' => array( $this, 'render_products_category_filter' ),
|
||||
'product_type' => array( $this, 'render_products_type_filter' ),
|
||||
'stock_status' => array( $this, 'render_products_stock_status_filter' ),
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
foreach ( $filters as $filter_callback ) {
|
||||
call_user_func( $filter_callback );
|
||||
}
|
||||
$output = ob_get_clean();
|
||||
|
||||
echo apply_filters( 'woocommerce_product_filters', $output ); // WPCS: XSS ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the product category filter for the list table.
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
protected function render_products_category_filter() {
|
||||
$categories_count = (int) wp_count_terms( 'product_cat' );
|
||||
|
||||
if ( $categories_count <= apply_filters( 'woocommerce_product_category_filter_threshold', 100 ) ) {
|
||||
wc_product_dropdown_categories(
|
||||
array(
|
||||
'option_select_text' => __( 'Filter by category', 'woocommerce' ),
|
||||
'hide_empty' => 0,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$current_category_slug = isset( $_GET['product_cat'] ) ? wc_clean( wp_unslash( $_GET['product_cat'] ) ) : false; // WPCS: input var ok, CSRF ok.
|
||||
$current_category = $current_category_slug ? get_term_by( 'slug', $current_category_slug, 'product_cat' ) : false;
|
||||
?>
|
||||
<select class="wc-category-search" name="product_cat" data-placeholder="<?php esc_attr_e( 'Filter by category', 'woocommerce' ); ?>" data-allow_clear="true">
|
||||
<?php if ( $current_category_slug && $current_category ) : ?>
|
||||
<option value="<?php echo esc_attr( $current_category_slug ); ?>" selected="selected"><?php echo esc_html( htmlspecialchars( wp_kses_post( $current_category->name ) ) ); ?></option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the product type filter for the list table.
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
protected function render_products_type_filter() {
|
||||
$current_product_type = isset( $_REQUEST['product_type'] ) ? wc_clean( wp_unslash( $_REQUEST['product_type'] ) ) : false; // WPCS: input var ok, sanitization ok.
|
||||
$output = '<select name="product_type" id="dropdown_product_type"><option value="">' . esc_html__( 'Filter by product type', 'woocommerce' ) . '</option>';
|
||||
|
||||
foreach ( wc_get_product_types() as $value => $label ) {
|
||||
$output .= '<option value="' . esc_attr( $value ) . '" ';
|
||||
$output .= selected( $value, $current_product_type, false );
|
||||
$output .= '>' . esc_html( $label ) . '</option>';
|
||||
|
||||
if ( 'simple' === $value ) {
|
||||
|
||||
$output .= '<option value="downloadable" ';
|
||||
$output .= selected( 'downloadable', $current_product_type, false );
|
||||
$output .= '> ' . ( is_rtl() ? '←' : '→' ) . ' ' . esc_html__( 'Downloadable', 'woocommerce' ) . '</option>';
|
||||
|
||||
$output .= '<option value="virtual" ';
|
||||
$output .= selected( 'virtual', $current_product_type, false );
|
||||
$output .= '> ' . ( is_rtl() ? '←' : '→' ) . ' ' . esc_html__( 'Virtual', 'woocommerce' ) . '</option>';
|
||||
}
|
||||
}
|
||||
|
||||
$output .= '</select>';
|
||||
echo $output; // WPCS: XSS ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the stock status filter for the list table.
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public function render_products_stock_status_filter() {
|
||||
$current_stock_status = isset( $_REQUEST['stock_status'] ) ? wc_clean( wp_unslash( $_REQUEST['stock_status'] ) ) : false; // WPCS: input var ok, sanitization ok.
|
||||
$stock_statuses = wc_get_product_stock_status_options();
|
||||
$output = '<select name="stock_status"><option value="">' . esc_html__( 'Filter by stock status', 'woocommerce' ) . '</option>';
|
||||
|
||||
foreach ( $stock_statuses as $status => $label ) {
|
||||
$output .= '<option ' . selected( $status, $current_stock_status, false ) . ' value="' . esc_attr( $status ) . '">' . esc_html( $label ) . '</option>';
|
||||
}
|
||||
|
||||
$output .= '</select>';
|
||||
echo $output; // WPCS: XSS ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Search by SKU or ID for products.
|
||||
*
|
||||
* @deprecated 4.4.0 Logic moved to query_filters.
|
||||
* @param string $where Where clause SQL.
|
||||
* @return string
|
||||
*/
|
||||
public function sku_search( $where ) {
|
||||
wc_deprecated_function( 'WC_Admin_List_Table_Products::sku_search', '4.4.0', 'Logic moved to query_filters.' );
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change views on the edit product screen.
|
||||
*
|
||||
* @param array $views Array of views.
|
||||
* @return array
|
||||
*/
|
||||
public function product_views( $views ) {
|
||||
global $wp_query;
|
||||
|
||||
// Products do not have authors.
|
||||
unset( $views['mine'] );
|
||||
|
||||
// Add sorting link.
|
||||
if ( current_user_can( 'edit_others_products' ) ) {
|
||||
$class = ( isset( $wp_query->query['orderby'] ) && 'menu_order title' === $wp_query->query['orderby'] ) ? 'current' : '';
|
||||
$query_string = remove_query_arg( array( 'orderby', 'order' ) );
|
||||
$query_string = add_query_arg( 'orderby', rawurlencode( 'menu_order title' ), $query_string );
|
||||
$query_string = add_query_arg( 'order', rawurlencode( 'ASC' ), $query_string );
|
||||
$views['byorder'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Sorting', 'woocommerce' ) . '</a>';
|
||||
}
|
||||
|
||||
return $views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the label when searching products
|
||||
*
|
||||
* @param string $query Search Query.
|
||||
* @return string
|
||||
*/
|
||||
public function search_label( $query ) {
|
||||
global $pagenow, $typenow;
|
||||
|
||||
if ( 'edit.php' !== $pagenow || 'product' !== $typenow || ! get_query_var( 'product_search' ) || ! isset( $_GET['s'] ) ) { // WPCS: input var ok.
|
||||
return $query;
|
||||
}
|
||||
|
||||
return wc_clean( wp_unslash( $_GET['s'] ) ); // WPCS: input var ok, sanitization ok.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any custom filters.
|
||||
*
|
||||
* @param array $query_vars Query vars.
|
||||
* @return array
|
||||
*/
|
||||
protected function query_filters( $query_vars ) {
|
||||
$this->remove_ordering_args();
|
||||
// Custom order by arguments.
|
||||
if ( isset( $query_vars['orderby'] ) ) {
|
||||
$orderby = strtolower( $query_vars['orderby'] );
|
||||
$order = isset( $query_vars['order'] ) ? strtoupper( $query_vars['order'] ) : 'DESC';
|
||||
|
||||
if ( 'price' === $orderby ) {
|
||||
$callback = 'DESC' === $order ? 'order_by_price_desc_post_clauses' : 'order_by_price_asc_post_clauses';
|
||||
add_filter( 'posts_clauses', array( $this, $callback ) );
|
||||
}
|
||||
|
||||
if ( 'sku' === $orderby ) {
|
||||
$callback = 'DESC' === $order ? 'order_by_sku_desc_post_clauses' : 'order_by_sku_asc_post_clauses';
|
||||
add_filter( 'posts_clauses', array( $this, $callback ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Type filtering.
|
||||
if ( isset( $query_vars['product_type'] ) ) {
|
||||
if ( 'downloadable' === $query_vars['product_type'] ) {
|
||||
$query_vars['product_type'] = '';
|
||||
add_filter( 'posts_clauses', array( $this, 'filter_downloadable_post_clauses' ) );
|
||||
} elseif ( 'virtual' === $query_vars['product_type'] ) {
|
||||
$query_vars['product_type'] = '';
|
||||
add_filter( 'posts_clauses', array( $this, 'filter_virtual_post_clauses' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Stock status filter.
|
||||
if ( ! empty( $_GET['stock_status'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
add_filter( 'posts_clauses', array( $this, 'filter_stock_status_post_clauses' ) );
|
||||
}
|
||||
|
||||
// Shipping class taxonomy.
|
||||
if ( ! empty( $_GET['product_shipping_class'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$query_vars['tax_query'][] = array(
|
||||
'taxonomy' => 'product_shipping_class',
|
||||
'field' => 'slug',
|
||||
'terms' => sanitize_title( wp_unslash( $_GET['product_shipping_class'] ) ),
|
||||
'operator' => 'IN',
|
||||
);
|
||||
}
|
||||
|
||||
// Search using CRUD.
|
||||
if ( ! empty( $query_vars['s'] ) ) {
|
||||
$data_store = WC_Data_Store::load( 'product' );
|
||||
$ids = $data_store->search_products( wc_clean( wp_unslash( $query_vars['s'] ) ), '', true, true );
|
||||
$query_vars['post__in'] = array_merge( $ids, array( 0 ) );
|
||||
$query_vars['product_search'] = true;
|
||||
unset( $query_vars['s'] );
|
||||
}
|
||||
|
||||
return $query_vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param array $args Array of SELECT statement pieces (from, where, etc).
|
||||
* @param WP_Query $query WP_Query instance.
|
||||
* @return array
|
||||
*/
|
||||
public function posts_clauses( $args, $query ) {
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove ordering queries.
|
||||
*
|
||||
* @param array $posts Posts array, keeping this for backwards compatibility defaulting to empty array.
|
||||
* @return array
|
||||
*/
|
||||
public function remove_ordering_args( $posts = array() ) {
|
||||
remove_filter( 'posts_clauses', array( $this, 'order_by_price_asc_post_clauses' ) );
|
||||
remove_filter( 'posts_clauses', array( $this, 'order_by_price_desc_post_clauses' ) );
|
||||
remove_filter( 'posts_clauses', array( $this, 'order_by_sku_asc_post_clauses' ) );
|
||||
remove_filter( 'posts_clauses', array( $this, 'order_by_sku_desc_post_clauses' ) );
|
||||
remove_filter( 'posts_clauses', array( $this, 'filter_downloadable_post_clauses' ) );
|
||||
remove_filter( 'posts_clauses', array( $this, 'filter_virtual_post_clauses' ) );
|
||||
remove_filter( 'posts_clauses', array( $this, 'filter_stock_status_post_clauses' ) );
|
||||
return $posts; // Keeping this here for backward compatibility.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle numeric price sorting.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_price_asc_post_clauses( $args ) {
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['orderby'] = ' wc_product_meta_lookup.min_price ASC, wc_product_meta_lookup.product_id ASC ';
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle numeric price sorting.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_price_desc_post_clauses( $args ) {
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['orderby'] = ' wc_product_meta_lookup.max_price DESC, wc_product_meta_lookup.product_id DESC ';
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sku sorting.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_sku_asc_post_clauses( $args ) {
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['orderby'] = ' wc_product_meta_lookup.sku ASC, wc_product_meta_lookup.product_id ASC ';
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle sku sorting.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function order_by_sku_desc_post_clauses( $args ) {
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['orderby'] = ' wc_product_meta_lookup.sku DESC, wc_product_meta_lookup.product_id DESC ';
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by type.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function filter_downloadable_post_clauses( $args ) {
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['where'] .= ' AND wc_product_meta_lookup.downloadable=1 ';
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by type.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function filter_virtual_post_clauses( $args ) {
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['where'] .= ' AND wc_product_meta_lookup.virtual=1 ';
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by stock status.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @return array
|
||||
*/
|
||||
public function filter_stock_status_post_clauses( $args ) {
|
||||
global $wpdb;
|
||||
if ( ! empty( $_GET['stock_status'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$args['join'] = $this->append_product_sorting_table_join( $args['join'] );
|
||||
$args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.stock_status=%s ', wc_clean( wp_unslash( $_GET['stock_status'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join wc_product_meta_lookup to posts if not already joined.
|
||||
*
|
||||
* @param string $sql SQL join.
|
||||
* @return string
|
||||
*/
|
||||
private function append_product_sorting_table_join( $sql ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
|
||||
$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies post query so that it includes parent products whose variations have particular shipping class assigned.
|
||||
*
|
||||
* @param array $pieces Array of SELECT statement pieces (from, where, etc).
|
||||
* @param WP_Query $wp_query WP_Query instance.
|
||||
* @return array Array of products, including parents of variations.
|
||||
*/
|
||||
public function add_variation_parents_for_shipping_class( $pieces, $wp_query ) {
|
||||
global $wpdb;
|
||||
if ( isset( $_GET['product_shipping_class'] ) && '0' !== $_GET['product_shipping_class'] ) { // WPCS: input var ok.
|
||||
$replaced_where = str_replace( ".post_type = 'product'", ".post_type = 'product_variation'", $pieces['where'] );
|
||||
$pieces['where'] .= " OR {$wpdb->posts}.ID in (
|
||||
SELECT {$wpdb->posts}.post_parent FROM
|
||||
{$wpdb->posts} LEFT JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)
|
||||
WHERE 1=1 $replaced_where
|
||||
)";
|
||||
return $pieces;
|
||||
}
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sample product badge to the product list table.
|
||||
*
|
||||
* @param string $column_name Column name.
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*/
|
||||
public function add_sample_product_badge( $column_name, $post_id ) {
|
||||
$is_sample_product = 'product' === get_post_type( $post_id ) && get_post_meta( $post_id, '_headstart_post', true );
|
||||
|
||||
if ( $is_sample_product && 'name' === $column_name ) {
|
||||
echo '<span class="sample-product-badge" style="margin-right: 6px;border-radius: 4px; background: #F6F7F7; padding: 4px; color: #3C434A;font-size: 12px;font-style: normal;font-weight: 400;line-height: 16px; height: 24px;">' . esc_html__( 'Sample', 'woocommerce' ) . '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* Marketplace suggestions
|
||||
*
|
||||
* Behaviour for displaying in-context suggestions for marketplace extensions.
|
||||
*
|
||||
* @package WooCommerce\Classes
|
||||
* @since 3.6.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Marketplace suggestions core behaviour.
|
||||
*/
|
||||
class WC_Marketplace_Suggestions {
|
||||
|
||||
/**
|
||||
* Initialise.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! self::allow_suggestions() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add suggestions to the product tabs.
|
||||
add_action( 'woocommerce_product_data_tabs', array( __CLASS__, 'product_data_tabs' ) );
|
||||
add_action( 'woocommerce_product_data_panels', array( __CLASS__, 'product_data_panels' ) );
|
||||
|
||||
// Register ajax api handlers.
|
||||
add_action( 'wp_ajax_woocommerce_add_dismissed_marketplace_suggestion', array( __CLASS__, 'post_add_dismissed_suggestion_handler' ) );
|
||||
|
||||
// Register hooks for rendering suggestions container markup.
|
||||
add_action( 'wc_marketplace_suggestions_products_empty_state', array( __CLASS__, 'render_products_list_empty_state' ) );
|
||||
add_action( 'wc_marketplace_suggestions_orders_empty_state', array( __CLASS__, 'render_orders_list_empty_state' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Product data tabs filter
|
||||
*
|
||||
* Adds a new Extensions tab to the product data meta box.
|
||||
*
|
||||
* @param array $tabs Existing tabs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function product_data_tabs( $tabs ) {
|
||||
$tabs['marketplace-suggestions'] = array(
|
||||
'label' => _x( 'Get more options', 'Marketplace suggestions', 'woocommerce' ),
|
||||
'target' => 'marketplace_suggestions',
|
||||
'class' => array(),
|
||||
'priority' => 1000,
|
||||
);
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render additional panels in the product data metabox.
|
||||
*/
|
||||
public static function product_data_panels() {
|
||||
include dirname( __FILE__ ) . '/templates/html-product-data-extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of suggestions the user has dismissed.
|
||||
*/
|
||||
public static function get_dismissed_suggestions() {
|
||||
$dismissed_suggestions = array();
|
||||
|
||||
$dismissed_suggestions_data = get_user_meta( get_current_user_id(), 'wc_marketplace_suggestions_dismissed_suggestions', true );
|
||||
if ( $dismissed_suggestions_data ) {
|
||||
$dismissed_suggestions = $dismissed_suggestions_data;
|
||||
if ( ! is_array( $dismissed_suggestions ) ) {
|
||||
$dismissed_suggestions = array();
|
||||
}
|
||||
}
|
||||
|
||||
return $dismissed_suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST handler for adding a dismissed suggestion.
|
||||
*/
|
||||
public static function post_add_dismissed_suggestion_handler() {
|
||||
if ( ! check_ajax_referer( 'add_dismissed_marketplace_suggestion' ) ) {
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$post_data = wp_unslash( $_POST );
|
||||
$suggestion_slug = sanitize_text_field( $post_data['slug'] );
|
||||
if ( ! $suggestion_slug ) {
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$dismissed_suggestions = self::get_dismissed_suggestions();
|
||||
|
||||
if ( in_array( $suggestion_slug, $dismissed_suggestions, true ) ) {
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$dismissed_suggestions[] = $suggestion_slug;
|
||||
update_user_meta(
|
||||
get_current_user_id(),
|
||||
'wc_marketplace_suggestions_dismissed_suggestions',
|
||||
$dismissed_suggestions
|
||||
);
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render suggestions containers in products list empty state.
|
||||
*/
|
||||
public static function render_products_list_empty_state() {
|
||||
self::render_suggestions_container( 'products-list-empty-header' );
|
||||
self::render_suggestions_container( 'products-list-empty-body' );
|
||||
self::render_suggestions_container( 'products-list-empty-footer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render suggestions containers in orders list empty state.
|
||||
*/
|
||||
public static function render_orders_list_empty_state() {
|
||||
self::render_suggestions_container( 'orders-list-empty-header' );
|
||||
self::render_suggestions_container( 'orders-list-empty-body' );
|
||||
self::render_suggestions_container( 'orders-list-empty-footer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a suggestions container element, with the specified context.
|
||||
*
|
||||
* @param string $context Suggestion context name (rendered as a css class).
|
||||
*/
|
||||
public static function render_suggestions_container( $context ) {
|
||||
include dirname( __FILE__ ) . '/views/container.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Should suggestions be displayed?
|
||||
*
|
||||
* @param string $screen_id The current admin screen.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function show_suggestions_for_screen( $screen_id ) {
|
||||
// We only show suggestions on certain admin screens.
|
||||
if ( ! in_array( $screen_id, array( 'edit-product', 'edit-shop_order', 'product', wc_get_page_screen_id( 'shop-order' ) ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::allow_suggestions();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Should suggestions be displayed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function allow_suggestions() {
|
||||
// We currently only support English suggestions.
|
||||
$locale = get_locale();
|
||||
$suggestion_locales = array(
|
||||
'en_AU',
|
||||
'en_CA',
|
||||
'en_GB',
|
||||
'en_NZ',
|
||||
'en_US',
|
||||
'en_ZA',
|
||||
);
|
||||
if ( ! in_array( $locale, $suggestion_locales, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Suggestions are only displayed if user can install plugins.
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Suggestions may be disabled via a setting under Accounts & Privacy.
|
||||
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// User can disabled all suggestions via filter.
|
||||
return apply_filters( 'woocommerce_allow_marketplace_suggestions', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull suggestion data from options. This is retrieved from a remote endpoint.
|
||||
*
|
||||
* @return array of json API data
|
||||
*/
|
||||
public static function get_suggestions_api_data() {
|
||||
$data = get_option( 'woocommerce_marketplace_suggestions', array() );
|
||||
|
||||
// If the options have never been updated, or were updated over a week ago, queue update.
|
||||
if ( empty( $data['updated'] ) || ( time() - WEEK_IN_SECONDS ) > $data['updated'] ) {
|
||||
$next = WC()->queue()->get_next( 'woocommerce_update_marketplace_suggestions' );
|
||||
if ( ! $next ) {
|
||||
WC()->queue()->cancel_all( 'woocommerce_update_marketplace_suggestions' );
|
||||
WC()->queue()->schedule_single( time(), 'woocommerce_update_marketplace_suggestions' );
|
||||
}
|
||||
}
|
||||
|
||||
return ! empty( $data['suggestions'] ) ? $data['suggestions'] : array();
|
||||
}
|
||||
}
|
||||
|
||||
WC_Marketplace_Suggestions::init();
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Marketplace suggestions updater
|
||||
*
|
||||
* Uses WC_Queue to ensure marketplace suggestions data is up to date and cached locally.
|
||||
*
|
||||
* @package WooCommerce\Classes
|
||||
* @since 3.6.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Marketplace Suggestions Updater
|
||||
*/
|
||||
class WC_Marketplace_Updater {
|
||||
|
||||
/**
|
||||
* Setup.
|
||||
*/
|
||||
public static function load() {
|
||||
add_action( 'init', array( __CLASS__, 'init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule events and hook appropriate actions.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'woocommerce_update_marketplace_suggestions', array( __CLASS__, 'update_marketplace_suggestions' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches new marketplace data, updates wc_marketplace_suggestions.
|
||||
*/
|
||||
public static function update_marketplace_suggestions() {
|
||||
$data = get_option(
|
||||
'woocommerce_marketplace_suggestions',
|
||||
array(
|
||||
'suggestions' => array(),
|
||||
'updated' => time(),
|
||||
)
|
||||
);
|
||||
|
||||
$data['updated'] = time();
|
||||
|
||||
$url = 'https://woocommerce.com/wp-json/wccom/marketplace-suggestions/1.0/suggestions.json';
|
||||
$request = wp_safe_remote_get(
|
||||
$url,
|
||||
array(
|
||||
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $request ) ) {
|
||||
self::retry();
|
||||
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $request );
|
||||
if ( empty( $body ) ) {
|
||||
self::retry();
|
||||
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
|
||||
}
|
||||
|
||||
$body = json_decode( $body, true );
|
||||
if ( empty( $body ) || ! is_array( $body ) ) {
|
||||
self::retry();
|
||||
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
|
||||
}
|
||||
|
||||
$data['suggestions'] = $body;
|
||||
return update_option( 'woocommerce_marketplace_suggestions', $data, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when an error has occurred when fetching suggestions.
|
||||
* Re-schedules the job earlier than the main weekly one.
|
||||
*/
|
||||
public static function retry() {
|
||||
WC()->queue()->cancel_all( 'woocommerce_update_marketplace_suggestions' );
|
||||
WC()->queue()->schedule_single( time() + DAY_IN_SECONDS, 'woocommerce_update_marketplace_suggestions' );
|
||||
}
|
||||
}
|
||||
|
||||
WC_Marketplace_Updater::load();
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* The marketplace suggestions tab HTML in the product tabs
|
||||
*
|
||||
* @package WooCommerce\Classes
|
||||
* @since 3.6.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
<div id="marketplace_suggestions" class="panel woocommerce_options_panel hidden">
|
||||
<?php
|
||||
WC_Marketplace_Suggestions::render_suggestions_container( 'product-edit-meta-tab-header' );
|
||||
WC_Marketplace_Suggestions::render_suggestions_container( 'product-edit-meta-tab-body' );
|
||||
WC_Marketplace_Suggestions::render_suggestions_container( 'product-edit-meta-tab-footer' );
|
||||
?>
|
||||
<div class="marketplace-suggestions-metabox-nosuggestions-placeholder hidden">
|
||||
<img src="https://woocommerce.com/wp-content/plugins/wccom-plugins/marketplace-suggestions/icons/get_more_options.svg" class="marketplace-suggestion-icon">
|
||||
<div class="marketplace-suggestion-placeholder-content">
|
||||
<h4><?php esc_html_e( 'Enhance your products', 'woocommerce' ); ?></h4>
|
||||
<p><?php esc_html_e( 'Extensions can add new functionality to your product pages that make your store stand out', 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
<a href="https://woocommerce.com/product-category/woocommerce-extensions/?utm_source=editproduct&utm_campaign=marketplacesuggestions&utm_medium=product" target="blank" class="button"><?php esc_html_e( 'Browse the Marketplace', 'woocommerce' ); ?></a><br />
|
||||
<a class="marketplace-suggestion-manage-link" href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=woocommerce_com' ) ); ?>"><?php esc_html_e( 'Manage suggestions', 'woocommerce' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Marketplace suggestions container
|
||||
*
|
||||
* @package WooCommerce\Templates
|
||||
* @version 3.6.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="marketplace-suggestions-container"
|
||||
data-marketplace-suggestions-context="<?php echo esc_attr( $context ); ?>"
|
||||
>
|
||||
</div>
|
||||
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
/**
|
||||
* Coupon Data
|
||||
*
|
||||
* Display the coupon data meta box.
|
||||
*
|
||||
* @author WooThemes
|
||||
* @category Admin
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Coupon_Data Class.
|
||||
*/
|
||||
class WC_Meta_Box_Coupon_Data {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
|
||||
|
||||
$coupon_id = absint( $post->ID );
|
||||
$coupon = new WC_Coupon( $coupon_id );
|
||||
|
||||
?>
|
||||
|
||||
<style type="text/css">
|
||||
#edit-slug-box, #minor-publishing-actions { display:none }
|
||||
</style>
|
||||
<div id="coupon_options" class="panel-wrap coupon_data">
|
||||
|
||||
<div class="wc-tabs-back"></div>
|
||||
|
||||
<ul class="coupon_data_tabs wc-tabs" style="display:none;">
|
||||
<?php
|
||||
$coupon_data_tabs = apply_filters(
|
||||
'woocommerce_coupon_data_tabs',
|
||||
array(
|
||||
'general' => array(
|
||||
'label' => __( 'General', 'woocommerce' ),
|
||||
'target' => 'general_coupon_data',
|
||||
'class' => 'general_coupon_data',
|
||||
),
|
||||
'usage_restriction' => array(
|
||||
'label' => __( 'Usage restriction', 'woocommerce' ),
|
||||
'target' => 'usage_restriction_coupon_data',
|
||||
'class' => '',
|
||||
),
|
||||
'usage_limit' => array(
|
||||
'label' => __( 'Usage limits', 'woocommerce' ),
|
||||
'target' => 'usage_limit_coupon_data',
|
||||
'class' => '',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $coupon_data_tabs as $key => $tab ) :
|
||||
?>
|
||||
<li class="<?php echo $key; ?>_options <?php echo $key; ?>_tab <?php echo implode( ' ', (array) $tab['class'] ); ?>">
|
||||
<a href="#<?php echo $tab['target']; ?>">
|
||||
<span><?php echo esc_html( $tab['label'] ); ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div id="general_coupon_data" class="panel woocommerce_options_panel">
|
||||
<?php
|
||||
|
||||
// Type.
|
||||
woocommerce_wp_select(
|
||||
array(
|
||||
'id' => 'discount_type',
|
||||
'label' => __( 'Discount type', 'woocommerce' ),
|
||||
'options' => wc_get_coupon_types(),
|
||||
'value' => $coupon->get_discount_type( 'edit' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Amount.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'coupon_amount',
|
||||
'label' => __( 'Coupon amount', 'woocommerce' ),
|
||||
'placeholder' => wc_format_localized_price( 0 ),
|
||||
'description' => __( 'Value of the coupon.', 'woocommerce' ),
|
||||
'data_type' => 'percent' === $coupon->get_discount_type( 'edit' ) ? 'decimal' : 'price',
|
||||
'desc_tip' => true,
|
||||
'value' => $coupon->get_amount( 'edit' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Free Shipping.
|
||||
if ( wc_shipping_enabled() ) {
|
||||
woocommerce_wp_checkbox(
|
||||
array(
|
||||
'id' => 'free_shipping',
|
||||
'label' => __( 'Allow free shipping', 'woocommerce' ),
|
||||
// translators: %s: URL to free shipping document.
|
||||
'description' => sprintf( __( 'Check this box if the coupon grants free shipping. A <a href="%s" target="_blank">free shipping method</a> must be enabled in your shipping zone and be set to require "a valid free shipping coupon" (see the "Free Shipping Requires" setting).', 'woocommerce' ), 'https://woocommerce.com/document/free-shipping/' ),
|
||||
'value' => wc_bool_to_string( $coupon->get_free_shipping( 'edit' ) ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Expiry date.
|
||||
$expiry_date = $coupon->get_date_expires( 'edit' ) ? $coupon->get_date_expires( 'edit' )->date( 'Y-m-d' ) : '';
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'expiry_date',
|
||||
'value' => esc_attr( $expiry_date ),
|
||||
'label' => __( 'Coupon expiry date', 'woocommerce' ),
|
||||
'placeholder' => 'YYYY-MM-DD',
|
||||
'description' => __( 'The coupon will expire at 00:00:00 of this date.', 'woocommerce' ),
|
||||
'desc_tip' => true,
|
||||
'class' => 'date-picker',
|
||||
'custom_attributes' => array(
|
||||
'pattern' => apply_filters( 'woocommerce_date_input_html_pattern', '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
do_action( 'woocommerce_coupon_options', $coupon->get_id(), $coupon );
|
||||
|
||||
?>
|
||||
</div>
|
||||
<div id="usage_restriction_coupon_data" class="panel woocommerce_options_panel">
|
||||
<?php
|
||||
|
||||
echo '<div class="options_group">';
|
||||
|
||||
// minimum spend.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'minimum_amount',
|
||||
'label' => __( 'Minimum spend', 'woocommerce' ),
|
||||
'placeholder' => __( 'No minimum', 'woocommerce' ),
|
||||
'description' => __( 'This field allows you to set the minimum spend (subtotal) allowed to use the coupon.', 'woocommerce' ),
|
||||
'data_type' => 'price',
|
||||
'desc_tip' => true,
|
||||
'value' => $coupon->get_minimum_amount( 'edit' ),
|
||||
)
|
||||
);
|
||||
|
||||
// maximum spend.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'maximum_amount',
|
||||
'label' => __( 'Maximum spend', 'woocommerce' ),
|
||||
'placeholder' => __( 'No maximum', 'woocommerce' ),
|
||||
'description' => __( 'This field allows you to set the maximum spend (subtotal) allowed when using the coupon.', 'woocommerce' ),
|
||||
'data_type' => 'price',
|
||||
'desc_tip' => true,
|
||||
'value' => $coupon->get_maximum_amount( 'edit' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Individual use.
|
||||
woocommerce_wp_checkbox(
|
||||
array(
|
||||
'id' => 'individual_use',
|
||||
'label' => __( 'Individual use only', 'woocommerce' ),
|
||||
'description' => __( 'Check this box if the coupon cannot be used in conjunction with other coupons.', 'woocommerce' ),
|
||||
'value' => wc_bool_to_string( $coupon->get_individual_use( 'edit' ) ),
|
||||
)
|
||||
);
|
||||
|
||||
// Exclude Sale Products.
|
||||
woocommerce_wp_checkbox(
|
||||
array(
|
||||
'id' => 'exclude_sale_items',
|
||||
'label' => __( 'Exclude sale items', 'woocommerce' ),
|
||||
'description' => __( 'Check this box if the coupon should not apply to items on sale. Per-item coupons will only work if the item is not on sale. Per-cart coupons will only work if there are items in the cart that are not on sale.', 'woocommerce' ),
|
||||
'value' => wc_bool_to_string( $coupon->get_exclude_sale_items( 'edit' ) ),
|
||||
)
|
||||
);
|
||||
|
||||
echo '</div><div class="options_group">';
|
||||
|
||||
// Product ids.
|
||||
?>
|
||||
<p class="form-field">
|
||||
<label><?php _e( 'Products', 'woocommerce' ); ?></label>
|
||||
<select class="wc-product-search" multiple="multiple" style="width: 50%;" name="product_ids[]" data-placeholder="<?php esc_attr_e( 'Search for a product…', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products_and_variations">
|
||||
<?php
|
||||
$product_ids = $coupon->get_product_ids( 'edit' );
|
||||
|
||||
foreach ( $product_ids as $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( is_object( $product ) ) {
|
||||
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . esc_html( wp_strip_all_tags( $product->get_formatted_name() ) ) . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php echo wc_help_tip( __( 'Products that the coupon will be applied to, or that need to be in the cart in order for the "Fixed cart discount" to be applied.', 'woocommerce' ) ); ?>
|
||||
</p>
|
||||
|
||||
<?php // Exclude Product ids. ?>
|
||||
<p class="form-field">
|
||||
<label><?php _e( 'Exclude products', 'woocommerce' ); ?></label>
|
||||
<select class="wc-product-search" multiple="multiple" style="width: 50%;" name="exclude_product_ids[]" data-placeholder="<?php esc_attr_e( 'Search for a product…', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products_and_variations">
|
||||
<?php
|
||||
$product_ids = $coupon->get_excluded_product_ids( 'edit' );
|
||||
|
||||
foreach ( $product_ids as $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( is_object( $product ) ) {
|
||||
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . esc_html( wp_strip_all_tags( $product->get_formatted_name() ) ) . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php echo wc_help_tip( __( 'Products that the coupon will not be applied to, or that cannot be in the cart in order for the "Fixed cart discount" to be applied.', 'woocommerce' ) ); ?>
|
||||
</p>
|
||||
<?php
|
||||
|
||||
echo '</div><div class="options_group">';
|
||||
|
||||
// Categories.
|
||||
?>
|
||||
<p class="form-field">
|
||||
<label for="product_categories"><?php _e( 'Product categories', 'woocommerce' ); ?></label>
|
||||
<select id="product_categories" name="product_categories[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php esc_attr_e( 'Any category', 'woocommerce' ); ?>">
|
||||
<?php
|
||||
$category_ids = $coupon->get_product_categories( 'edit' );
|
||||
$categories = get_terms( 'product_cat', 'orderby=name&hide_empty=0' );
|
||||
|
||||
if ( $categories ) {
|
||||
foreach ( $categories as $cat ) {
|
||||
echo '<option value="' . esc_attr( $cat->term_id ) . '"' . wc_selected( $cat->term_id, $category_ids ) . '>' . esc_html( $cat->name ) . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select> <?php echo wc_help_tip( __( 'Product categories that the coupon will be applied to, or that need to be in the cart in order for the "Fixed cart discount" to be applied.', 'woocommerce' ) ); ?>
|
||||
</p>
|
||||
|
||||
<?php // Exclude Categories. ?>
|
||||
<p class="form-field">
|
||||
<label for="exclude_product_categories"><?php _e( 'Exclude categories', 'woocommerce' ); ?></label>
|
||||
<select id="exclude_product_categories" name="exclude_product_categories[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php esc_attr_e( 'No categories', 'woocommerce' ); ?>">
|
||||
<?php
|
||||
$category_ids = $coupon->get_excluded_product_categories( 'edit' );
|
||||
$categories = get_terms( 'product_cat', 'orderby=name&hide_empty=0' );
|
||||
|
||||
if ( $categories ) {
|
||||
foreach ( $categories as $cat ) {
|
||||
echo '<option value="' . esc_attr( $cat->term_id ) . '"' . wc_selected( $cat->term_id, $category_ids ) . '>' . esc_html( $cat->name ) . '</option>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php echo wc_help_tip( __( 'Product categories that the coupon will not be applied to, or that cannot be in the cart in order for the "Fixed cart discount" to be applied.', 'woocommerce' ) ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="options_group">
|
||||
<?php
|
||||
// Customers.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'customer_email',
|
||||
'label' => __( 'Allowed emails', 'woocommerce' ),
|
||||
'placeholder' => __( 'No restrictions', 'woocommerce' ),
|
||||
'description' => __( 'List of allowed billing emails to check against when an order is placed. Separate email addresses with commas. You can also use an asterisk (*) to match parts of an email. For example "*@gmail.com" would match all gmail addresses.', 'woocommerce' ),
|
||||
'value' => implode( ', ', (array) $coupon->get_email_restrictions( 'edit' ) ),
|
||||
'desc_tip' => true,
|
||||
'type' => 'email',
|
||||
'class' => '',
|
||||
'custom_attributes' => array(
|
||||
'multiple' => 'multiple',
|
||||
),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php do_action( 'woocommerce_coupon_options_usage_restriction', $coupon->get_id(), $coupon ); ?>
|
||||
</div>
|
||||
<div id="usage_limit_coupon_data" class="panel woocommerce_options_panel">
|
||||
<div class="options_group">
|
||||
<?php
|
||||
// Usage limit per coupons.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'usage_limit',
|
||||
'label' => __( 'Usage limit per coupon', 'woocommerce' ),
|
||||
'placeholder' => esc_attr__( 'Unlimited usage', 'woocommerce' ),
|
||||
'description' => __( 'How many times this coupon can be used before it is void.', 'woocommerce' ),
|
||||
'type' => 'number',
|
||||
'desc_tip' => true,
|
||||
'class' => 'short',
|
||||
'custom_attributes' => array(
|
||||
'step' => 1,
|
||||
'min' => 0,
|
||||
),
|
||||
'value' => $coupon->get_usage_limit( 'edit' ) ? $coupon->get_usage_limit( 'edit' ) : '',
|
||||
)
|
||||
);
|
||||
|
||||
// Usage limit per product.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'limit_usage_to_x_items',
|
||||
'label' => __( 'Limit usage to X items', 'woocommerce' ),
|
||||
'placeholder' => esc_attr__( 'Apply to all qualifying items in cart', 'woocommerce' ),
|
||||
'description' => __( 'The maximum number of individual items this coupon can apply to when using product discounts. Leave blank to apply to all qualifying items in cart.', 'woocommerce' ),
|
||||
'desc_tip' => true,
|
||||
'class' => 'short',
|
||||
'type' => 'number',
|
||||
'custom_attributes' => array(
|
||||
'step' => 1,
|
||||
'min' => 0,
|
||||
),
|
||||
'value' => $coupon->get_limit_usage_to_x_items( 'edit' ) ? $coupon->get_limit_usage_to_x_items( 'edit' ) : '',
|
||||
)
|
||||
);
|
||||
|
||||
// Usage limit per users.
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => 'usage_limit_per_user',
|
||||
'label' => __( 'Usage limit per user', 'woocommerce' ),
|
||||
'placeholder' => esc_attr__( 'Unlimited usage', 'woocommerce' ),
|
||||
'description' => __( 'How many times this coupon can be used by an individual user. Uses billing email for guests, and user ID for logged in users.', 'woocommerce' ),
|
||||
'desc_tip' => true,
|
||||
'class' => 'short',
|
||||
'type' => 'number',
|
||||
'custom_attributes' => array(
|
||||
'step' => 1,
|
||||
'min' => 0,
|
||||
),
|
||||
'value' => $coupon->get_usage_limit_per_user( 'edit' ) ? $coupon->get_usage_limit_per_user( 'edit' ) : '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php do_action( 'woocommerce_coupon_options_usage_limit', $coupon->get_id(), $coupon ); ?>
|
||||
</div>
|
||||
<?php do_action( 'woocommerce_coupon_data_panels', $coupon->get_id(), $coupon ); ?>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public static function save( $post_id, $post ) {
|
||||
// Check for dupe coupons.
|
||||
$coupon_code = wc_format_coupon_code( $post->post_title );
|
||||
$id_from_code = wc_get_coupon_id_by_code( $coupon_code, $post_id );
|
||||
|
||||
if ( $id_from_code ) {
|
||||
WC_Admin_Meta_Boxes::add_error( __( 'Coupon code already exists - customers will use the latest coupon with this code.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$product_categories = isset( $_POST['product_categories'] ) ? (array) $_POST['product_categories'] : array();
|
||||
$exclude_product_categories = isset( $_POST['exclude_product_categories'] ) ? (array) $_POST['exclude_product_categories'] : array();
|
||||
|
||||
$coupon = new WC_Coupon( $post_id );
|
||||
$coupon->set_props(
|
||||
array(
|
||||
'code' => $post->post_title,
|
||||
'discount_type' => wc_clean( $_POST['discount_type'] ),
|
||||
'amount' => wc_format_decimal( $_POST['coupon_amount'] ),
|
||||
'date_expires' => wc_clean( $_POST['expiry_date'] ),
|
||||
'individual_use' => isset( $_POST['individual_use'] ),
|
||||
'product_ids' => isset( $_POST['product_ids'] ) ? array_filter( array_map( 'intval', (array) $_POST['product_ids'] ) ) : array(),
|
||||
'excluded_product_ids' => isset( $_POST['exclude_product_ids'] ) ? array_filter( array_map( 'intval', (array) $_POST['exclude_product_ids'] ) ) : array(),
|
||||
'usage_limit' => absint( $_POST['usage_limit'] ),
|
||||
'usage_limit_per_user' => absint( $_POST['usage_limit_per_user'] ),
|
||||
'limit_usage_to_x_items' => absint( $_POST['limit_usage_to_x_items'] ),
|
||||
'free_shipping' => isset( $_POST['free_shipping'] ),
|
||||
'product_categories' => array_filter( array_map( 'intval', $product_categories ) ),
|
||||
'excluded_product_categories' => array_filter( array_map( 'intval', $exclude_product_categories ) ),
|
||||
'exclude_sale_items' => isset( $_POST['exclude_sale_items'] ),
|
||||
'minimum_amount' => wc_format_decimal( $_POST['minimum_amount'] ),
|
||||
'maximum_amount' => wc_format_decimal( $_POST['maximum_amount'] ),
|
||||
'email_restrictions' => array_filter( array_map( 'trim', explode( ',', wc_clean( $_POST['customer_email'] ) ) ) ),
|
||||
)
|
||||
);
|
||||
$coupon->save();
|
||||
do_action( 'woocommerce_coupon_options_save', $post_id, $coupon );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* Order Actions
|
||||
*
|
||||
* Functions for displaying the order actions meta box.
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Order_Actions Class.
|
||||
*/
|
||||
class WC_Meta_Box_Order_Actions {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post|WC_Order $post Post or order object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $theorder;
|
||||
|
||||
OrderUtil::init_theorder_object( $post );
|
||||
$order = $theorder;
|
||||
|
||||
$order_id = $order->get_id();
|
||||
$order_actions = self::get_available_order_actions_for_order( $order );
|
||||
?>
|
||||
<ul class="order_actions submitbox">
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Fires at the start of order actions meta box rendering.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'woocommerce_order_actions_start', $order_id );
|
||||
?>
|
||||
|
||||
|
||||
<li class="wide" id="actions">
|
||||
<select name="wc_order_action">
|
||||
<option value=""><?php esc_html_e( 'Choose an action...', 'woocommerce' ); ?></option>
|
||||
<?php foreach ( $order_actions as $action => $title ) { ?>
|
||||
<option value="<?php echo esc_attr( $action ); ?>"><?php echo esc_html( $title ); ?></option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<button class="button wc-reload"><span><?php esc_html_e( 'Apply', 'woocommerce' ); ?></span></button>
|
||||
</li>
|
||||
|
||||
<li class="wide">
|
||||
<div id="delete-action">
|
||||
<?php
|
||||
if ( current_user_can( 'delete_post', $order_id ) ) {
|
||||
|
||||
if ( ! EMPTY_TRASH_DAYS ) {
|
||||
$delete_text = __( 'Delete permanently', 'woocommerce' );
|
||||
} else {
|
||||
$delete_text = __( 'Move to Trash', 'woocommerce' );
|
||||
}
|
||||
?>
|
||||
<a class="submitdelete deletion" href="<?php echo esc_url( self::get_trash_or_delete_order_link( $order_id ) ); ?>"><?php echo esc_html( $delete_text ); ?></a>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button save_order button-primary" name="save" value="<?php echo 'auto-draft' === $order->get_status() ? esc_attr__( 'Create', 'woocommerce' ) : esc_attr__( 'Update', 'woocommerce' ); ?>"><?php echo 'auto-draft' === $order->get_status() ? esc_html__( 'Create', 'woocommerce' ) : esc_html__( 'Update', 'woocommerce' ); ?></button>
|
||||
</li>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Fires at the end of order actions meta box rendering.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'woocommerce_order_actions_end', $order_id );
|
||||
?>
|
||||
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms a trash/delete order URL.
|
||||
*
|
||||
* @param int $order_id The order ID for which we want a trash/delete URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_trash_or_delete_order_link( int $order_id ): string {
|
||||
if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
|
||||
$order_type = wc_get_order( $order_id )->get_type();
|
||||
$order_list_url = wc_get_container()->get( PageController::class )->get_base_page_url( $order_type );
|
||||
$trash_order_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'trash',
|
||||
'id' => array( $order_id ),
|
||||
'_wp_http_referer' => $order_list_url,
|
||||
),
|
||||
$order_list_url
|
||||
);
|
||||
|
||||
return wp_nonce_url( $trash_order_url, 'bulk-orders' );
|
||||
}
|
||||
|
||||
return get_delete_post_link( $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param WP_Post $post Post Object.
|
||||
*/
|
||||
public static function save( $post_id, $post ) {
|
||||
// Order data saved, now get it so we can manipulate status.
|
||||
$order = wc_get_order( $post_id );
|
||||
|
||||
// Handle button actions.
|
||||
if ( ! empty( $_POST['wc_order_action'] ) ) { // @codingStandardsIgnoreLine
|
||||
|
||||
$action = wc_clean( wp_unslash( $_POST['wc_order_action'] ) ); // @codingStandardsIgnoreLine
|
||||
|
||||
if ( 'send_order_details' === $action ) {
|
||||
do_action( 'woocommerce_before_resend_order_emails', $order, 'customer_invoice' );
|
||||
|
||||
// Send the customer invoice email.
|
||||
WC()->payment_gateways();
|
||||
WC()->shipping();
|
||||
WC()->mailer()->customer_invoice( $order );
|
||||
|
||||
// Note the event.
|
||||
$order->add_order_note( __( 'Order details manually sent to customer.', 'woocommerce' ), false, true );
|
||||
|
||||
do_action( 'woocommerce_after_resend_order_email', $order, 'customer_invoice' );
|
||||
|
||||
// Change the post saved message.
|
||||
add_filter( 'redirect_post_location', array( __CLASS__, 'set_email_sent_message' ) );
|
||||
|
||||
} elseif ( 'send_order_details_admin' === $action ) {
|
||||
|
||||
do_action( 'woocommerce_before_resend_order_emails', $order, 'new_order' );
|
||||
|
||||
WC()->payment_gateways();
|
||||
WC()->shipping();
|
||||
add_filter( 'woocommerce_new_order_email_allows_resend', '__return_true' );
|
||||
WC()->mailer()->emails['WC_Email_New_Order']->trigger( $order->get_id(), $order, true );
|
||||
remove_filter( 'woocommerce_new_order_email_allows_resend', '__return_true' );
|
||||
|
||||
do_action( 'woocommerce_after_resend_order_email', $order, 'new_order' );
|
||||
|
||||
// Change the post saved message.
|
||||
add_filter( 'redirect_post_location', array( __CLASS__, 'set_email_sent_message' ) );
|
||||
|
||||
} elseif ( 'regenerate_download_permissions' === $action ) {
|
||||
|
||||
$data_store = WC_Data_Store::load( 'customer-download' );
|
||||
$data_store->delete_by_order_id( $post_id );
|
||||
wc_downloadable_product_permissions( $post_id, true );
|
||||
|
||||
} else {
|
||||
|
||||
if ( ! did_action( 'woocommerce_order_action_' . sanitize_title( $action ) ) ) {
|
||||
do_action( 'woocommerce_order_action_' . sanitize_title( $action ), $order );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the correct message ID.
|
||||
*
|
||||
* @param string $location Location.
|
||||
* @since 2.3.0
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
public static function set_email_sent_message( $location ) {
|
||||
return add_query_arg( 'message', 11, $location );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available order actions for a given order.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WC_Order|null $order The order object or null if no order is available.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_available_order_actions_for_order( $order ) {
|
||||
$actions = array(
|
||||
'send_order_details' => __( 'Send order details to customer', 'woocommerce' ),
|
||||
'send_order_details_admin' => __( 'Resend new order notification', 'woocommerce' ),
|
||||
'regenerate_download_permissions' => __( 'Regenerate download permissions', 'woocommerce' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter: woocommerce_order_actions
|
||||
* Allows filtering of the available order actions for an order.
|
||||
*
|
||||
* @since 2.1.0 Filter was added.
|
||||
* @since 5.8.0 The $order param was added.
|
||||
*
|
||||
* @param array $actions The available order actions for the order.
|
||||
* @param WC_Order|null $order The order object or null if no order is available.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_order_actions', $actions, $order );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,772 @@
|
||||
<?php
|
||||
/**
|
||||
* Order Data
|
||||
*
|
||||
* Functions for displaying the order data meta box.
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.2.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Order_Data Class.
|
||||
*/
|
||||
class WC_Meta_Box_Order_Data {
|
||||
|
||||
/**
|
||||
* Billing fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $billing_fields = array();
|
||||
|
||||
/**
|
||||
* Shipping fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $shipping_fields = array();
|
||||
|
||||
/**
|
||||
* Get billing fields for the meta box.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string $context Context of fields (view or edit).
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_billing_fields( $order = false, $context = 'edit' ) {
|
||||
/**
|
||||
* Provides an opportunity to modify the list of order billing fields displayed on the admin.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @param array Billing fields.
|
||||
* @param WC_Order|false $order Order object.
|
||||
* @param string $context Context of fields (view or edit).
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_admin_billing_fields',
|
||||
array(
|
||||
'first_name' => array(
|
||||
'label' => __( 'First name', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'last_name' => array(
|
||||
'label' => __( 'Last name', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'company' => array(
|
||||
'label' => __( 'Company', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'address_1' => array(
|
||||
'label' => __( 'Address line 1', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'address_2' => array(
|
||||
'label' => __( 'Address line 2', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'city' => array(
|
||||
'label' => __( 'City', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'postcode' => array(
|
||||
'label' => __( 'Postcode / ZIP', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'country' => array(
|
||||
'label' => __( 'Country / Region', 'woocommerce' ),
|
||||
'show' => false,
|
||||
'class' => 'js_field-country select short',
|
||||
'type' => 'select',
|
||||
'options' => array( '' => __( 'Select a country / region…', 'woocommerce' ) ) + WC()->countries->get_allowed_countries(),
|
||||
),
|
||||
'state' => array(
|
||||
'label' => __( 'State / County', 'woocommerce' ),
|
||||
'class' => 'js_field-state select short',
|
||||
'show' => false,
|
||||
),
|
||||
'email' => array(
|
||||
'label' => __( 'Email address', 'woocommerce' ),
|
||||
),
|
||||
'phone' => array(
|
||||
'label' => __( 'Phone', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
$order,
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping fields for the meta box.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param string $context Context of fields (view or edit).
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_shipping_fields( $order = false, $context = 'edit' ) {
|
||||
/**
|
||||
* Provides an opportunity to modify the list of order shipping fields displayed on the admin.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @param array Shipping fields.
|
||||
* @param WC_Order|false $order Order object.
|
||||
* @param string $context Context of fields (view or edit).
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_admin_shipping_fields',
|
||||
array(
|
||||
'first_name' => array(
|
||||
'label' => __( 'First name', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'last_name' => array(
|
||||
'label' => __( 'Last name', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'company' => array(
|
||||
'label' => __( 'Company', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'address_1' => array(
|
||||
'label' => __( 'Address line 1', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'address_2' => array(
|
||||
'label' => __( 'Address line 2', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'city' => array(
|
||||
'label' => __( 'City', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'postcode' => array(
|
||||
'label' => __( 'Postcode / ZIP', 'woocommerce' ),
|
||||
'show' => false,
|
||||
),
|
||||
'country' => array(
|
||||
'label' => __( 'Country / Region', 'woocommerce' ),
|
||||
'show' => false,
|
||||
'type' => 'select',
|
||||
'class' => 'js_field-country select short',
|
||||
'options' => array( '' => __( 'Select a country / region…', 'woocommerce' ) ) + WC()->countries->get_shipping_countries(),
|
||||
),
|
||||
'state' => array(
|
||||
'label' => __( 'State / County', 'woocommerce' ),
|
||||
'class' => 'js_field-state select short',
|
||||
'show' => false,
|
||||
),
|
||||
'phone' => array(
|
||||
'label' => __( 'Phone', 'woocommerce' ),
|
||||
),
|
||||
),
|
||||
$order,
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init billing and shipping fields we display + save. Maintained for backwards compat.
|
||||
*/
|
||||
public static function init_address_fields() {
|
||||
self::$billing_fields = self::get_billing_fields();
|
||||
self::$shipping_fields = self::get_shipping_fields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post|WC_Order $post Post or order object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $theorder;
|
||||
|
||||
OrderUtil::init_theorder_object( $post );
|
||||
|
||||
$order = $theorder;
|
||||
|
||||
if ( WC()->payment_gateways() ) {
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
} else {
|
||||
$payment_gateways = array();
|
||||
}
|
||||
|
||||
$payment_method = $order->get_payment_method();
|
||||
|
||||
$order_type_object = get_post_type_object( $order->get_type() );
|
||||
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
|
||||
?>
|
||||
<style type="text/css">
|
||||
#post-body-content, #titlediv { display:none }
|
||||
</style>
|
||||
<div class="panel-wrap woocommerce">
|
||||
<input name="post_title" type="hidden" value="<?php echo esc_attr( empty( $order->get_title() ) ? __( 'Order', 'woocommerce' ) : $order->get_title() ); ?>" />
|
||||
<input name="post_status" type="hidden" value="<?php echo esc_attr( $order->get_status() ); ?>" />
|
||||
<div id="order_data" class="panel woocommerce-order-data">
|
||||
<h2 class="woocommerce-order-data__heading">
|
||||
<?php
|
||||
|
||||
printf(
|
||||
/* translators: 1: order type 2: order number */
|
||||
esc_html__( '%1$s #%2$s details', 'woocommerce' ),
|
||||
esc_html( $order_type_object->labels->singular_name ),
|
||||
esc_html( $order->get_order_number() )
|
||||
);
|
||||
|
||||
?>
|
||||
</h2>
|
||||
<p class="woocommerce-order-data__meta order_number">
|
||||
<?php
|
||||
|
||||
$meta_list = array();
|
||||
|
||||
if ( $payment_method && 'other' !== $payment_method ) {
|
||||
$payment_method_string = sprintf(
|
||||
/* translators: %s: payment method */
|
||||
__( 'Payment via %s', 'woocommerce' ),
|
||||
esc_html( isset( $payment_gateways[ $payment_method ] ) ? $payment_gateways[ $payment_method ]->get_title() : $payment_method )
|
||||
);
|
||||
|
||||
$transaction_id = $order->get_transaction_id();
|
||||
if ( $transaction_id ) {
|
||||
|
||||
$to_add = null;
|
||||
if ( isset( $payment_gateways[ $payment_method ] ) ) {
|
||||
$url = $payment_gateways[ $payment_method ]->get_transaction_url( $order );
|
||||
if ( $url ) {
|
||||
$to_add .= ' (<a href="' . esc_url( $url ) . '" target="_blank">' . esc_html( $transaction_id ) . '</a>)';
|
||||
}
|
||||
}
|
||||
|
||||
$to_add = $to_add ?? ' (' . esc_html( $transaction_id ) . ')';
|
||||
$payment_method_string .= $to_add;
|
||||
}
|
||||
|
||||
$meta_list[] = $payment_method_string;
|
||||
}
|
||||
|
||||
if ( $order->get_date_paid() ) {
|
||||
$meta_list[] = sprintf(
|
||||
/* translators: 1: date 2: time */
|
||||
__( 'Paid on %1$s @ %2$s', 'woocommerce' ),
|
||||
wc_format_datetime( $order->get_date_paid() ),
|
||||
wc_format_datetime( $order->get_date_paid(), get_option( 'time_format' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$ip_address = $order->get_customer_ip_address();
|
||||
if ( $ip_address ) {
|
||||
$meta_list[] = sprintf(
|
||||
/* translators: %s: IP address */
|
||||
__( 'Customer IP: %s', 'woocommerce' ),
|
||||
'<span class="woocommerce-Order-customerIP">' . esc_html( $ip_address ) . '</span>'
|
||||
);
|
||||
}
|
||||
|
||||
echo wp_kses_post( implode( '. ', $meta_list ) );
|
||||
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
/**
|
||||
* Hook allowing extenders to render custom content
|
||||
* within the Order details box.
|
||||
*
|
||||
* This allows urgent notices or other important
|
||||
* order-related info to be displayed upfront in
|
||||
* the order page. Example: display a notice if
|
||||
* the order is disputed.
|
||||
*
|
||||
* @param $order WC_Order The order object being displayed.
|
||||
* @since 7.9.0
|
||||
*/
|
||||
do_action( 'woocommerce_admin_order_data_after_payment_info', $order );
|
||||
?>
|
||||
<div class="order_data_column_container">
|
||||
<div class="order_data_column">
|
||||
<h3><?php esc_html_e( 'General', 'woocommerce' ); ?></h3>
|
||||
|
||||
<p class="form-field form-field-wide">
|
||||
<?php
|
||||
$order_date_created_localised = ! is_null( $order->get_date_created() ) ? $order->get_date_created()->getOffsetTimestamp() : '';
|
||||
?>
|
||||
<label for="order_date"><?php esc_html_e( 'Date created:', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="date-picker" name="order_date" maxlength="10" value="<?php echo esc_attr( date_i18n( 'Y-m-d', $order_date_created_localised ) ); ?>" pattern="<?php echo esc_attr( apply_filters( 'woocommerce_date_input_html_pattern', '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])' ) ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment ?>" />@
|
||||
‎
|
||||
<input type="number" class="hour" placeholder="<?php esc_attr_e( 'h', 'woocommerce' ); ?>" name="order_date_hour" min="0" max="23" step="1" value="<?php echo esc_attr( date_i18n( 'H', $order_date_created_localised ) ); ?>" pattern="([01]?[0-9]{1}|2[0-3]{1})" />:
|
||||
<input type="number" class="minute" placeholder="<?php esc_attr_e( 'm', 'woocommerce' ); ?>" name="order_date_minute" min="0" max="59" step="1" value="<?php echo esc_attr( date_i18n( 'i', $order_date_created_localised ) ); ?>" pattern="[0-5]{1}[0-9]{1}" />
|
||||
<input type="hidden" name="order_date_second" value="<?php echo esc_attr( date_i18n( 's', $order_date_created_localised ) ); ?>" />
|
||||
</p>
|
||||
|
||||
<p class="form-field form-field-wide wc-order-status">
|
||||
<label for="order_status">
|
||||
<?php
|
||||
esc_html_e( 'Status:', 'woocommerce' );
|
||||
if ( $order->needs_payment() ) {
|
||||
printf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( $order->get_checkout_payment_url() ),
|
||||
esc_html__( 'Customer payment page →', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</label>
|
||||
<select id="order_status" name="order_status" class="wc-enhanced-select">
|
||||
<?php
|
||||
$statuses = wc_get_order_statuses();
|
||||
foreach ( $statuses as $status => $status_name ) {
|
||||
echo '<option value="' . esc_attr( $status ) . '" ' . selected( $status, 'wc-' . $order->get_status( 'edit' ), false ) . '>' . esc_html( $status_name ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="form-field form-field-wide wc-customer-user">
|
||||
<!--email_off--> <!-- Disable CloudFlare email obfuscation -->
|
||||
<label for="customer_user">
|
||||
<?php
|
||||
esc_html_e( 'Customer:', 'woocommerce' );
|
||||
if ( $order->get_user_id( 'edit' ) ) {
|
||||
$args = array(
|
||||
'post_status' => 'all',
|
||||
'post_type' => 'shop_order',
|
||||
'_customer_user' => $order->get_user_id( 'edit' ),
|
||||
);
|
||||
printf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( add_query_arg( $args, admin_url( 'edit.php' ) ) ),
|
||||
' ' . esc_html__( 'View other orders →', 'woocommerce' )
|
||||
);
|
||||
printf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( add_query_arg( 'user_id', $order->get_user_id( 'edit' ), admin_url( 'user-edit.php' ) ) ),
|
||||
' ' . esc_html__( 'Profile →', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</label>
|
||||
<?php
|
||||
$user_string = '';
|
||||
$user_id = '';
|
||||
if ( $order->get_user_id() ) {
|
||||
$user_id = absint( $order->get_user_id() );
|
||||
$customer = new WC_Customer( $user_id );
|
||||
/* translators: 1: user display name 2: user ID 3: user email */
|
||||
$user_string = sprintf(
|
||||
/* translators: 1: customer name, 2 customer id, 3: customer email */
|
||||
esc_html__( '%1$s (#%2$s – %3$s)', 'woocommerce' ),
|
||||
$customer->get_first_name() . ' ' . $customer->get_last_name(),
|
||||
$customer->get_id(),
|
||||
$customer->get_email()
|
||||
);
|
||||
}
|
||||
?>
|
||||
<select class="wc-customer-search" id="customer_user" name="customer_user" data-placeholder="<?php esc_attr_e( 'Guest', 'woocommerce' ); ?>" data-allow_clear="true">
|
||||
<?php
|
||||
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
/**
|
||||
* Filter to customize the display of the currently selected customer for an order in the order edit page.
|
||||
* This is the same filter used in the ajax call for customer search in the same metabox.
|
||||
*
|
||||
* @since 7.2.0 (this instance of the filter)
|
||||
*
|
||||
* @param array @user_info An array containing one item with the name and email of the user currently selected as the customer for the order.
|
||||
*/
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo esc_html( htmlspecialchars( wp_kses_post( current( apply_filters( 'woocommerce_json_search_found_customers', array( $user_string ) ) ) ) ) ); ?></option>
|
||||
<?php // phpcs:enable WooCommerce.Commenting.CommentHooks.MissingHookComment ?>
|
||||
</select>
|
||||
<!--/email_off-->
|
||||
</p>
|
||||
<?php do_action( 'woocommerce_admin_order_data_after_order_details', $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment ?>
|
||||
</div>
|
||||
<div class="order_data_column">
|
||||
<h3>
|
||||
<?php esc_html_e( 'Billing', 'woocommerce' ); ?>
|
||||
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
|
||||
<span>
|
||||
<a href="#" class="load_customer_billing" style="display:none;"><?php esc_html_e( 'Load billing address', 'woocommerce' ); ?></a>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="address">
|
||||
<?php
|
||||
// Display values.
|
||||
if ( $order->get_formatted_billing_address() ) {
|
||||
echo '<p>' . wp_kses( $order->get_formatted_billing_address(), array( 'br' => array() ) ) . '</p>';
|
||||
} else {
|
||||
echo '<p class="none_set"><strong>' . esc_html__( 'Address:', 'woocommerce' ) . '</strong> ' . esc_html__( 'No billing address set.', 'woocommerce' ) . '</p>';
|
||||
}
|
||||
|
||||
$billing_fields = self::get_billing_fields( $order, 'view' );
|
||||
|
||||
foreach ( $billing_fields as $key => $field ) {
|
||||
if ( isset( $field['show'] ) && false === $field['show'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_name = 'billing_' . $key;
|
||||
|
||||
if ( isset( $field['value'] ) ) {
|
||||
$field_value = $field['value'];
|
||||
} elseif ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
|
||||
$field_value = $order->{"get_$field_name"}( 'edit' );
|
||||
} else {
|
||||
$field_value = $order->get_meta( '_' . $field_name );
|
||||
}
|
||||
|
||||
if ( 'billing_phone' === $field_name ) {
|
||||
$field_value = wc_make_phone_clickable( $field_value );
|
||||
} elseif ( 'billing_email' === $field_name ) {
|
||||
$field_value = '<a href="' . esc_url( 'mailto:' . $field_value ) . '">' . $field_value . '</a>';
|
||||
} else {
|
||||
$field_value = make_clickable( esc_html( $field_value ) );
|
||||
}
|
||||
|
||||
if ( $field_value ) {
|
||||
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="edit_address">
|
||||
<?php
|
||||
// Display form.
|
||||
$billing_fields = self::get_billing_fields( $order, 'edit' );
|
||||
|
||||
foreach ( $billing_fields as $key => $field ) {
|
||||
if ( ! isset( $field['type'] ) ) {
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
if ( ! isset( $field['id'] ) ) {
|
||||
$field['id'] = '_billing_' . $key;
|
||||
}
|
||||
|
||||
$field_name = 'billing_' . $key;
|
||||
|
||||
if ( ! isset( $field['value'] ) ) {
|
||||
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
|
||||
$field['value'] = $order->{"get_$field_name"}( 'edit' );
|
||||
} else {
|
||||
$field['value'] = $order->get_meta( '_' . $field_name );
|
||||
}
|
||||
}
|
||||
|
||||
switch ( $field['type'] ) {
|
||||
case 'select':
|
||||
woocommerce_wp_select( $field, $order );
|
||||
break;
|
||||
case 'checkbox':
|
||||
woocommerce_wp_checkbox( $field, $order );
|
||||
break;
|
||||
default:
|
||||
woocommerce_wp_text_input( $field, $order );
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<p class="form-field form-field-wide">
|
||||
<label><?php esc_html_e( 'Payment method:', 'woocommerce' ); ?></label>
|
||||
<select name="_payment_method" id="_payment_method" class="first">
|
||||
<option value=""><?php esc_html_e( 'N/A', 'woocommerce' ); ?></option>
|
||||
<?php
|
||||
$found_method = false;
|
||||
|
||||
foreach ( $payment_gateways as $gateway ) {
|
||||
if ( 'yes' === $gateway->enabled ) {
|
||||
echo '<option value="' . esc_attr( $gateway->id ) . '" ' . selected( $payment_method, $gateway->id, false ) . '>' . esc_html( $gateway->get_title() ) . '</option>';
|
||||
if ( $payment_method === $gateway->id ) {
|
||||
$found_method = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $found_method && ! empty( $payment_method ) ) {
|
||||
echo '<option value="' . esc_attr( $payment_method ) . '" selected="selected">' . esc_html__( 'Other', 'woocommerce' ) . '</option>';
|
||||
} else {
|
||||
echo '<option value="other">' . esc_html__( 'Other', 'woocommerce' ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
|
||||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => '_transaction_id',
|
||||
'label' => __( 'Transaction ID', 'woocommerce' ),
|
||||
'value' => $order->get_transaction_id( 'edit' ),
|
||||
),
|
||||
$order
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
||||
<?php do_action( 'woocommerce_admin_order_data_after_billing_address', $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment ?>
|
||||
</div>
|
||||
<div class="order_data_column">
|
||||
<h3>
|
||||
<?php esc_html_e( 'Shipping', 'woocommerce' ); ?>
|
||||
<a href="#" class="edit_address"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
|
||||
<span>
|
||||
<a href="#" class="load_customer_shipping" style="display:none;"><?php esc_html_e( 'Load shipping address', 'woocommerce' ); ?></a>
|
||||
<a href="#" class="billing-same-as-shipping" style="display:none;"><?php esc_html_e( 'Copy billing address', 'woocommerce' ); ?></a>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="address">
|
||||
<?php
|
||||
// Display values.
|
||||
if ( $order->get_formatted_shipping_address() ) {
|
||||
echo '<p>' . wp_kses( $order->get_formatted_shipping_address(), array( 'br' => array() ) ) . '</p>';
|
||||
} else {
|
||||
echo '<p class="none_set"><strong>' . esc_html__( 'Address:', 'woocommerce' ) . '</strong> ' . esc_html__( 'No shipping address set.', 'woocommerce' ) . '</p>';
|
||||
}
|
||||
|
||||
$shipping_fields = self::get_shipping_fields( $order, 'view' );
|
||||
|
||||
if ( ! empty( $shipping_fields ) ) {
|
||||
foreach ( $shipping_fields as $key => $field ) {
|
||||
if ( isset( $field['show'] ) && false === $field['show'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_name = 'shipping_' . $key;
|
||||
|
||||
if ( isset( $field['value'] ) ) {
|
||||
$field_value = $field['value'];
|
||||
} elseif ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
|
||||
$field_value = $order->{"get_$field_name"}( 'edit' );
|
||||
} else {
|
||||
$field_value = $order->get_meta( '_' . $field_name );
|
||||
}
|
||||
|
||||
if ( 'shipping_phone' === $field_name ) {
|
||||
$field_value = wc_make_phone_clickable( $field_value );
|
||||
}
|
||||
|
||||
if ( ! $field_value ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' === get_option( 'woocommerce_enable_order_comments', 'yes' ) ) && $order->get_customer_note() ) { // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
echo '<p class="order_note"><strong>' . esc_html( __( 'Customer provided note:', 'woocommerce' ) ) . '</strong> ' . nl2br( esc_html( $order->get_customer_note() ) ) . '</p>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="edit_address">
|
||||
<?php
|
||||
// Display form.
|
||||
$shipping_fields = self::get_shipping_fields( $order, 'edit' );
|
||||
|
||||
if ( ! empty( $shipping_fields ) ) {
|
||||
foreach ( $shipping_fields as $key => $field ) {
|
||||
if ( ! isset( $field['type'] ) ) {
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
if ( ! isset( $field['id'] ) ) {
|
||||
$field['id'] = '_shipping_' . $key;
|
||||
}
|
||||
|
||||
$field_name = 'shipping_' . $key;
|
||||
|
||||
if ( ! isset( $field['value'] ) ) {
|
||||
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
|
||||
$field['value'] = $order->{"get_$field_name"}( 'edit' );
|
||||
} else {
|
||||
$field['value'] = $order->get_meta( '_' . $field_name );
|
||||
}
|
||||
}
|
||||
|
||||
switch ( $field['type'] ) {
|
||||
case 'select':
|
||||
woocommerce_wp_select( $field, $order );
|
||||
break;
|
||||
case 'checkbox':
|
||||
woocommerce_wp_checkbox( $field, $order );
|
||||
break;
|
||||
default:
|
||||
woocommerce_wp_text_input( $field, $order );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows 3rd parties to alter whether the customer note should be displayed on the admin.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*
|
||||
* @param bool TRUE if the note should be displayed. FALSE otherwise.
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_enable_order_notes_field', 'yes' === get_option( 'woocommerce_enable_order_comments', 'yes' ) ) ) :
|
||||
?>
|
||||
<p class="form-field form-field-wide">
|
||||
<label for="customer_note"><?php esc_html_e( 'Customer provided note', 'woocommerce' ); ?>:</label>
|
||||
<textarea rows="1" cols="40" name="customer_note" tabindex="6" id="excerpt" placeholder="<?php esc_attr_e( 'Customer notes about the order', 'woocommerce' ); ?>"><?php echo wp_kses_post( $order->get_customer_note() ); ?></textarea>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php do_action( 'woocommerce_admin_order_data_after_shipping_address', $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @throws Exception Required request data is missing.
|
||||
*/
|
||||
public static function save( $order_id ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
|
||||
if ( ! isset( $_POST['order_status'] ) ) {
|
||||
throw new Exception( __( 'Order status is missing.', 'woocommerce' ), 400 );
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST['_payment_method'] ) ) {
|
||||
throw new Exception( __( 'Payment method is missing.', 'woocommerce' ), 400 );
|
||||
}
|
||||
|
||||
// Ensure gateways are loaded in case they need to insert data into the emails.
|
||||
WC()->payment_gateways();
|
||||
WC()->shipping();
|
||||
|
||||
// Get order object.
|
||||
$order = wc_get_order( $order_id );
|
||||
$props = array();
|
||||
|
||||
// Create order key.
|
||||
if ( ! $order->get_order_key() ) {
|
||||
$props['order_key'] = wc_generate_order_key();
|
||||
}
|
||||
|
||||
// Update customer.
|
||||
$customer_id = isset( $_POST['customer_user'] ) ? absint( $_POST['customer_user'] ) : 0;
|
||||
if ( $customer_id !== $order->get_customer_id() ) {
|
||||
$props['customer_id'] = $customer_id;
|
||||
}
|
||||
|
||||
// Update billing fields.
|
||||
$billing_fields = self::get_billing_fields( $order, 'edit' );
|
||||
|
||||
if ( ! empty( $billing_fields ) ) {
|
||||
foreach ( $billing_fields as $key => $field ) {
|
||||
if ( ! isset( $field['id'] ) ) {
|
||||
$field['id'] = '_billing_' . $key;
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST[ $field['id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
|
||||
|
||||
// Update a field if it includes an update callback.
|
||||
if ( isset( $field['update_callback'] ) ) {
|
||||
call_user_func( $field['update_callback'], $field['id'], $value, $order );
|
||||
} elseif ( is_callable( array( $order, 'set_billing_' . $key ) ) ) {
|
||||
$props[ 'billing_' . $key ] = $value;
|
||||
} else {
|
||||
$order->update_meta_data( $field['id'], $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update shipping fields.
|
||||
$shipping_fields = self::get_shipping_fields( $order, 'edit' );
|
||||
|
||||
if ( ! empty( $shipping_fields ) ) {
|
||||
foreach ( $shipping_fields as $key => $field ) {
|
||||
if ( ! isset( $field['id'] ) ) {
|
||||
$field['id'] = '_shipping_' . $key;
|
||||
}
|
||||
|
||||
if ( ! isset( $_POST[ $field['id'] ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = isset( $_POST[ $field['id'] ] ) ? wc_clean( wp_unslash( $_POST[ $field['id'] ] ) ) : '';
|
||||
|
||||
// Update a field if it includes an update callback.
|
||||
if ( isset( $field['update_callback'] ) ) {
|
||||
call_user_func( $field['update_callback'], $field['id'], $value, $order );
|
||||
} elseif ( is_callable( array( $order, 'set_shipping_' . $key ) ) ) {
|
||||
$props[ 'shipping_' . $key ] = $value;
|
||||
} else {
|
||||
$order->update_meta_data( $field['id'], $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $_POST['_transaction_id'] ) ) {
|
||||
$props['transaction_id'] = wc_clean( wp_unslash( $_POST['_transaction_id'] ) );
|
||||
}
|
||||
|
||||
// Payment method handling.
|
||||
if ( $order->get_payment_method() !== wc_clean( wp_unslash( $_POST['_payment_method'] ) ) ) {
|
||||
$methods = WC()->payment_gateways->payment_gateways();
|
||||
$payment_method = wc_clean( wp_unslash( $_POST['_payment_method'] ) );
|
||||
$payment_method_title = $payment_method;
|
||||
|
||||
if ( isset( $methods ) && isset( $methods[ $payment_method ] ) ) {
|
||||
$payment_method_title = $methods[ $payment_method ]->get_title();
|
||||
}
|
||||
|
||||
if ( 'other' === $payment_method ) {
|
||||
$payment_method_title = esc_html__( 'Other', 'woocommerce' );
|
||||
}
|
||||
|
||||
$props['payment_method'] = $payment_method;
|
||||
$props['payment_method_title'] = $payment_method_title;
|
||||
}
|
||||
|
||||
// Update date.
|
||||
if ( empty( $_POST['order_date'] ) ) {
|
||||
$date = time();
|
||||
} else {
|
||||
if ( ! isset( $_POST['order_date_hour'] ) || ! isset( $_POST['order_date_minute'] ) || ! isset( $_POST['order_date_second'] ) ) {
|
||||
throw new Exception( __( 'Order date, hour, minute and/or second are missing.', 'woocommerce' ), 400 );
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
$date = gmdate( 'Y-m-d H:i:s', strtotime( $_POST['order_date'] . ' ' . (int) $_POST['order_date_hour'] . ':' . (int) $_POST['order_date_minute'] . ':' . (int) $_POST['order_date_second'] ) );
|
||||
}
|
||||
|
||||
$props['date_created'] = $date;
|
||||
|
||||
// Set created via prop if new post.
|
||||
if ( isset( $_POST['original_post_status'] ) && 'auto-draft' === $_POST['original_post_status'] ) {
|
||||
$props['created_via'] = 'admin';
|
||||
}
|
||||
|
||||
// Customer note.
|
||||
if ( isset( $_POST['customer_note'] ) ) {
|
||||
$props['customer_note'] = sanitize_textarea_field( wp_unslash( $_POST['customer_note'] ) );
|
||||
}
|
||||
|
||||
// Save order data.
|
||||
$order->set_props( $props );
|
||||
$order->set_status( wc_clean( wp_unslash( $_POST['order_status'] ) ), '', true );
|
||||
$order->save();
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Order Downloads
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Order_Downloads Class.
|
||||
*/
|
||||
class WC_Meta_Box_Order_Downloads {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WC_Order|WP_Post $post Post or order object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
if ( $post instanceof WC_Order ) {
|
||||
$order_id = $post->get_id();
|
||||
} else {
|
||||
$order_id = $post->ID;
|
||||
}
|
||||
?>
|
||||
<div class="order_download_permissions wc-metaboxes-wrapper">
|
||||
|
||||
<div class="wc-metaboxes">
|
||||
<?php
|
||||
$data_store = WC_Data_Store::load( 'customer-download' );
|
||||
$download_permissions = array();
|
||||
if ( 0 !== $order_id ) {
|
||||
$download_permissions = $data_store->get_downloads(
|
||||
array(
|
||||
'order_id' => $order_id,
|
||||
'orderby' => 'product_id',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$product = null;
|
||||
$loop = 0;
|
||||
$file_counter = 1;
|
||||
|
||||
if ( $download_permissions && count( $download_permissions ) > 0 ) {
|
||||
foreach ( $download_permissions as $download ) {
|
||||
if ( ! $product || $product->get_id() !== $download->get_product_id() ) {
|
||||
$product = wc_get_product( $download->get_product_id() );
|
||||
$file_counter = 1;
|
||||
}
|
||||
|
||||
// don't show permissions to files that have since been removed.
|
||||
if ( ! $product || ! $product->exists() || ! $product->has_file( $download->get_download_id() ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Show file title instead of count if set.
|
||||
$file = $product->get_file( $download->get_download_id() );
|
||||
// translators: file name.
|
||||
$file_count = isset( $file['name'] ) ? $file['name'] : sprintf( __( 'File %d', 'woocommerce' ), $file_counter );
|
||||
|
||||
include __DIR__ . '/views/html-order-download-permission.php';
|
||||
|
||||
$loop++;
|
||||
$file_counter++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<p class="buttons">
|
||||
<select id="grant_access_id" class="wc-product-search" name="grant_access_id[]" multiple="multiple" style="width: 400px;" data-placeholder="<?php esc_attr_e( 'Search for a downloadable product…', 'woocommerce' ); ?>" data-action="woocommerce_json_search_downloadable_products_and_variations"></select>
|
||||
<button type="button" class="button grant_access">
|
||||
<?php esc_html_e( 'Grant access', 'woocommerce' ); ?>
|
||||
</button>
|
||||
</p>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public static function save( $post_id, $post ) {
|
||||
if ( isset( $_POST['permission_id'] ) ) {
|
||||
$permission_ids = $_POST['permission_id'];
|
||||
$downloads_remaining = $_POST['downloads_remaining'];
|
||||
$access_expires = $_POST['access_expires'];
|
||||
$max = max( array_keys( $permission_ids ) );
|
||||
|
||||
for ( $i = 0; $i <= $max; $i ++ ) {
|
||||
if ( ! isset( $permission_ids[ $i ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$download = new WC_Customer_Download( $permission_ids[ $i ] );
|
||||
$download->set_downloads_remaining( wc_clean( $downloads_remaining[ $i ] ) );
|
||||
$download->set_access_expires( array_key_exists( $i, $access_expires ) && '' !== $access_expires[ $i ] ? strtotime( $access_expires[ $i ] ) : '' );
|
||||
$download->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Order Data
|
||||
*
|
||||
* Functions for displaying the order items meta box.
|
||||
*
|
||||
* @author WooThemes
|
||||
* @category Admin
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Order_Items Class.
|
||||
*/
|
||||
class WC_Meta_Box_Order_Items {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post|WC_Order $post Post or order object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $post, $thepostid, $theorder;
|
||||
|
||||
OrderUtil::init_theorder_object( $post );
|
||||
if ( ! is_int( $thepostid ) && ( $post instanceof WP_Post ) ) {
|
||||
$thepostid = $post->ID;
|
||||
}
|
||||
|
||||
$order = $theorder;
|
||||
$data = ( $post instanceof WP_Post ) ? get_post_meta( $post->ID ) : array();
|
||||
|
||||
include __DIR__ . '/views/html-order-items.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function save( $post_id ) {
|
||||
/**
|
||||
* This $_POST variable's data has been validated and escaped
|
||||
* inside `wc_save_order_items()` function.
|
||||
*/
|
||||
wc_save_order_items( $post_id, $_POST );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Order Notes
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Order_Notes Class.
|
||||
*/
|
||||
class WC_Meta_Box_Order_Notes {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post|WC_Order $post Post or order object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
if ( $post instanceof WC_Order ) {
|
||||
$order_id = $post->get_id();
|
||||
} else {
|
||||
$order_id = $post->ID;
|
||||
}
|
||||
|
||||
$args = array( 'order_id' => $order_id );
|
||||
|
||||
if ( 0 !== $order_id ) {
|
||||
$notes = wc_get_order_notes( $args );
|
||||
} else {
|
||||
$notes = array();
|
||||
}
|
||||
|
||||
include __DIR__ . '/views/html-order-notes.php';
|
||||
?>
|
||||
<div class="add_note">
|
||||
<p>
|
||||
<label for="add_order_note"><?php esc_html_e( 'Add note', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Add a note for your reference, or add a customer note (the user will be notified).', 'woocommerce' ) ); ?></label>
|
||||
<textarea type="text" name="order_note" id="add_order_note" class="input-text" cols="20" rows="5"></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="order_note_type" class="screen-reader-text"><?php esc_html_e( 'Note type', 'woocommerce' ); ?></label>
|
||||
<select name="order_note_type" id="order_note_type">
|
||||
<option value=""><?php esc_html_e( 'Private note', 'woocommerce' ); ?></option>
|
||||
<option value="customer"><?php esc_html_e( 'Note to customer', 'woocommerce' ); ?></option>
|
||||
</select>
|
||||
<button type="button" class="add_note button"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Product Categories meta box
|
||||
*
|
||||
* Display the product categories meta box.
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 7.5.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Product_Categories Class.
|
||||
*/
|
||||
class WC_Meta_Box_Product_Categories {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post $post Current post object.
|
||||
* @param array $box {
|
||||
* Categories meta box arguments.
|
||||
*
|
||||
* @type string $id Meta box 'id' attribute.
|
||||
* @type string $title Meta box title.
|
||||
* @type callable $callback Meta box display callback.
|
||||
* @type array $args {
|
||||
* Extra meta box arguments.
|
||||
*
|
||||
* @type string $taxonomy Taxonomy. Default 'category'.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public static function output( $post, $box ) {
|
||||
$categories_count = (int) wp_count_terms( 'product_cat' );
|
||||
|
||||
/**
|
||||
* Filters the category metabox search threshold, for when to render the typeahead field.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param number $threshold The default threshold.
|
||||
* @returns number The threshold that will be used.
|
||||
*/
|
||||
if ( $categories_count <= apply_filters( 'woocommerce_product_category_metabox_search_threshold', 5 ) && function_exists( 'post_categories_meta_box' ) ) {
|
||||
return post_categories_meta_box( $post, $box );
|
||||
}
|
||||
|
||||
$defaults = array( 'taxonomy' => 'category' );
|
||||
if ( ! isset( $box['args'] ) || ! is_array( $box['args'] ) ) {
|
||||
$args = array();
|
||||
} else {
|
||||
$args = $box['args'];
|
||||
}
|
||||
$parsed_args = wp_parse_args( $args, $defaults );
|
||||
$tax_name = $parsed_args['taxonomy'];
|
||||
$selected_categories = wp_get_object_terms( $post->ID, 'product_cat' );
|
||||
?>
|
||||
<div id="taxonomy-<?php echo esc_attr( $tax_name ); ?>-metabox"></div>
|
||||
<?php foreach ( (array) $selected_categories as $term ) { ?>
|
||||
<input
|
||||
type="hidden"
|
||||
value="<?php echo esc_attr( $term->term_id ); ?>"
|
||||
name="tax_input[<?php esc_attr( $tax_name ); ?>][]"
|
||||
data-name="<?php echo esc_attr( $term->name ); ?>"
|
||||
/>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
<?php
|
||||
/**
|
||||
* Product Data
|
||||
*
|
||||
* Displays the product data box, tabbed, with several panels covering price, stock etc.
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 3.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Product_Data Class.
|
||||
*/
|
||||
class WC_Meta_Box_Product_Data {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $thepostid, $product_object;
|
||||
|
||||
$thepostid = $post->ID;
|
||||
$product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
|
||||
|
||||
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
|
||||
|
||||
include __DIR__ . '/views/html-product-data-panel.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tab content/settings.
|
||||
*/
|
||||
private static function output_tabs() {
|
||||
global $post, $thepostid, $product_object;
|
||||
|
||||
include __DIR__ . '/views/html-product-data-general.php';
|
||||
include __DIR__ . '/views/html-product-data-inventory.php';
|
||||
include __DIR__ . '/views/html-product-data-shipping.php';
|
||||
include __DIR__ . '/views/html-product-data-linked-products.php';
|
||||
include __DIR__ . '/views/html-product-data-attributes.php';
|
||||
include __DIR__ . '/views/html-product-data-advanced.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of product type options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_product_type_options() {
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
return apply_filters(
|
||||
'product_type_options',
|
||||
wc_get_default_product_type_options(),
|
||||
);
|
||||
/* phpcs: enable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of tabs to show.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_product_data_tabs() {
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
$tabs = apply_filters(
|
||||
'woocommerce_product_data_tabs',
|
||||
array(
|
||||
'general' => array(
|
||||
'label' => __( 'General', 'woocommerce' ),
|
||||
'target' => 'general_product_data',
|
||||
'class' => array( 'hide_if_grouped' ),
|
||||
'priority' => 10,
|
||||
),
|
||||
'inventory' => array(
|
||||
'label' => __( 'Inventory', 'woocommerce' ),
|
||||
'target' => 'inventory_product_data',
|
||||
'class' => array( 'show_if_simple', 'show_if_variable', 'show_if_grouped', 'show_if_external' ),
|
||||
'priority' => 20,
|
||||
),
|
||||
'shipping' => array(
|
||||
'label' => __( 'Shipping', 'woocommerce' ),
|
||||
'target' => 'shipping_product_data',
|
||||
'class' => array( 'hide_if_virtual', 'hide_if_grouped', 'hide_if_external' ),
|
||||
'priority' => 30,
|
||||
),
|
||||
'linked_product' => array(
|
||||
'label' => __( 'Linked Products', 'woocommerce' ),
|
||||
'target' => 'linked_product_data',
|
||||
'class' => array(),
|
||||
'priority' => 40,
|
||||
),
|
||||
'attribute' => array(
|
||||
'label' => __( 'Attributes', 'woocommerce' ),
|
||||
'target' => 'product_attributes',
|
||||
'class' => array(),
|
||||
'priority' => 50,
|
||||
),
|
||||
'variations' => array(
|
||||
'label' => __( 'Variations', 'woocommerce' ),
|
||||
'target' => 'variable_product_options',
|
||||
'class' => array( 'show_if_variable' ),
|
||||
'priority' => 60,
|
||||
),
|
||||
'advanced' => array(
|
||||
'label' => __( 'Advanced', 'woocommerce' ),
|
||||
'target' => 'advanced_product_data',
|
||||
'class' => array(),
|
||||
'priority' => 70,
|
||||
),
|
||||
)
|
||||
);
|
||||
/* phpcs: enable */
|
||||
|
||||
// Sort tabs based on priority.
|
||||
uasort( $tabs, array( __CLASS__, 'product_data_tabs_sort' ) );
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to sort product data tabs on priority.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param int $a First item.
|
||||
* @param int $b Second item.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function product_data_tabs_sort( $a, $b ) {
|
||||
if ( ! isset( $a['priority'], $b['priority'] ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( $a['priority'] === $b['priority'] ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a['priority'] < $b['priority'] ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for finding variation attributes.
|
||||
*
|
||||
* @param WC_Product_Attribute $attribute Product attribute.
|
||||
* @return bool
|
||||
*/
|
||||
private static function filter_variation_attributes( $attribute ) {
|
||||
return true === $attribute->get_variation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter callback for finding non-variation attributes.
|
||||
*
|
||||
* @param WC_Product_Attribute $attribute Product attribute.
|
||||
* @return bool
|
||||
*/
|
||||
private static function filter_non_variation_attributes( $attribute ) {
|
||||
return false === $attribute->get_variation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show options for the variable product type.
|
||||
*/
|
||||
public static function output_variations() {
|
||||
global $post, $wpdb, $product_object;
|
||||
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
$variation_attributes = array_filter( $product_object->get_attributes(), array( __CLASS__, 'filter_variation_attributes' ) );
|
||||
$default_attributes = $product_object->get_default_attributes();
|
||||
$variations_count = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_count', $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'product_variation' AND post_status IN ('publish', 'private')", $post->ID ) ), $post->ID ) );
|
||||
$variations_per_page = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) );
|
||||
$variations_total_pages = ceil( $variations_count / $variations_per_page );
|
||||
$modal_title = get_bloginfo( 'name' ) . __( ' says', 'woocommerce' );
|
||||
/* phpcs: enable */
|
||||
|
||||
include __DIR__ . '/views/html-product-data-variations.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare downloads for save.
|
||||
*
|
||||
* @param array $file_names File names.
|
||||
* @param array $file_urls File urls.
|
||||
* @param array $file_hashes File hashes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function prepare_downloads( $file_names, $file_urls, $file_hashes ) {
|
||||
$downloads = array();
|
||||
|
||||
if ( ! empty( $file_urls ) ) {
|
||||
$file_url_size = count( $file_urls );
|
||||
|
||||
for ( $i = 0; $i < $file_url_size; $i ++ ) {
|
||||
if ( ! empty( $file_urls[ $i ] ) ) {
|
||||
$downloads[] = array(
|
||||
'name' => wc_clean( $file_names[ $i ] ),
|
||||
'file' => wp_unslash( trim( $file_urls[ $i ] ) ),
|
||||
'download_id' => wc_clean( $file_hashes[ $i ] ),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $downloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare children for save.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function prepare_children() {
|
||||
return isset( $_POST['grouped_products'] ) ? array_filter( array_map( 'intval', (array) $_POST['grouped_products'] ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare attributes for save.
|
||||
*
|
||||
* @param array $data Attribute data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function prepare_attributes( $data = false ) {
|
||||
$attributes = array();
|
||||
|
||||
if ( ! $data ) {
|
||||
$data = stripslashes_deep( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
|
||||
if ( isset( $data['attribute_names'], $data['attribute_values'] ) ) {
|
||||
$attribute_names = $data['attribute_names'];
|
||||
$attribute_values = $data['attribute_values'];
|
||||
$attribute_visibility = isset( $data['attribute_visibility'] ) ? $data['attribute_visibility'] : array();
|
||||
$attribute_variation = isset( $data['attribute_variation'] ) ? $data['attribute_variation'] : array();
|
||||
$attribute_position = $data['attribute_position'];
|
||||
$attribute_names_max_key = max( array_keys( $attribute_names ) );
|
||||
|
||||
for ( $i = 0; $i <= $attribute_names_max_key; $i++ ) {
|
||||
if ( empty( $attribute_names[ $i ] ) || ! isset( $attribute_values[ $i ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$attribute_id = 0;
|
||||
$attribute_name = wc_clean( esc_html( $attribute_names[ $i ] ) );
|
||||
|
||||
if ( 'pa_' === substr( $attribute_name, 0, 3 ) ) {
|
||||
$attribute_id = wc_attribute_taxonomy_id_by_name( $attribute_name );
|
||||
}
|
||||
|
||||
$options = isset( $attribute_values[ $i ] ) ? $attribute_values[ $i ] : '';
|
||||
|
||||
if ( is_array( $options ) ) {
|
||||
// Term ids sent as array.
|
||||
$options = wp_parse_id_list( $options );
|
||||
} else {
|
||||
// Terms or text sent in textarea.
|
||||
$options = 0 < $attribute_id ? wc_sanitize_textarea( esc_html( wc_sanitize_term_text_based( $options ) ) ) : wc_sanitize_textarea( esc_html( $options ) );
|
||||
$options = wc_get_text_attributes( $options );
|
||||
}
|
||||
|
||||
if ( empty( $options ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attribute = new WC_Product_Attribute();
|
||||
$attribute->set_id( $attribute_id );
|
||||
$attribute->set_name( $attribute_name );
|
||||
$attribute->set_options( $options );
|
||||
$attribute->set_position( $attribute_position[ $i ] );
|
||||
$attribute->set_visible( isset( $attribute_visibility[ $i ] ) );
|
||||
$attribute->set_variation( isset( $attribute_variation[ $i ] ) );
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
$attributes[] = apply_filters( 'woocommerce_admin_meta_boxes_prepare_attribute', $attribute, $data, $i );
|
||||
/* phpcs: enable */
|
||||
}
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare attributes for a specific variation or defaults.
|
||||
*
|
||||
* @param array $all_attributes List of attribute keys.
|
||||
* @param string $key_prefix Attribute key prefix.
|
||||
* @param int $index Attribute array index.
|
||||
* @return array
|
||||
*/
|
||||
private static function prepare_set_attributes( $all_attributes, $key_prefix = 'attribute_', $index = null ) {
|
||||
$attributes = array();
|
||||
|
||||
if ( $all_attributes ) {
|
||||
foreach ( $all_attributes as $attribute ) {
|
||||
if ( $attribute->get_variation() ) {
|
||||
$attribute_key = sanitize_title( $attribute->get_name() );
|
||||
|
||||
if ( ! is_null( $index ) ) {
|
||||
$value = isset( $_POST[ $key_prefix . $attribute_key ][ $index ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ][ $index ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
} else {
|
||||
$value = isset( $_POST[ $key_prefix . $attribute_key ] ) ? wp_unslash( $_POST[ $key_prefix . $attribute_key ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
}
|
||||
|
||||
if ( $attribute->is_taxonomy() ) {
|
||||
// Don't use wc_clean as it destroys sanitized characters.
|
||||
$value = sanitize_title( $value );
|
||||
} else {
|
||||
$value = html_entity_decode( wc_clean( $value ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // WPCS: sanitization ok.
|
||||
}
|
||||
|
||||
$attributes[ $attribute_key ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $post_id WP post id.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public static function save( $post_id, $post ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
// Process product type first so we have the correct class to run setters.
|
||||
$product_type = empty( $_POST['product-type'] ) ? WC_Product_Factory::get_product_type( $post_id ) : sanitize_title( wp_unslash( $_POST['product-type'] ) );
|
||||
$classname = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' );
|
||||
$product = new $classname( $post_id );
|
||||
$attributes = self::prepare_attributes();
|
||||
$stock = null;
|
||||
|
||||
// Handle stock changes.
|
||||
if ( isset( $_POST['_stock'] ) ) {
|
||||
if ( isset( $_POST['_original_stock'] ) && wc_stock_amount( $product->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( wp_unslash( $_POST['_original_stock'] ) ) ) {
|
||||
/* translators: 1: product ID 2: quantity in stock */
|
||||
WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', 'woocommerce' ), $product->get_id(), $product->get_stock_quantity( 'edit' ) ) );
|
||||
} else {
|
||||
$stock = wc_stock_amount( wp_unslash( $_POST['_stock'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dates.
|
||||
$date_on_sale_from = '';
|
||||
$date_on_sale_to = '';
|
||||
|
||||
// Force date from to beginning of day.
|
||||
if ( isset( $_POST['_sale_price_dates_from'] ) ) {
|
||||
$date_on_sale_from = wc_clean( wp_unslash( $_POST['_sale_price_dates_from'] ) );
|
||||
|
||||
if ( ! empty( $date_on_sale_from ) ) {
|
||||
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
}
|
||||
}
|
||||
|
||||
// Force date to to the end of the day.
|
||||
if ( isset( $_POST['_sale_price_dates_to'] ) ) {
|
||||
$date_on_sale_to = wc_clean( wp_unslash( $_POST['_sale_price_dates_to'] ) );
|
||||
|
||||
if ( ! empty( $date_on_sale_to ) ) {
|
||||
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
}
|
||||
}
|
||||
|
||||
$errors = $product->set_props(
|
||||
array(
|
||||
'sku' => isset( $_POST['_sku'] ) ? wc_clean( wp_unslash( $_POST['_sku'] ) ) : null,
|
||||
'purchase_note' => isset( $_POST['_purchase_note'] ) ? wp_kses_post( wp_unslash( $_POST['_purchase_note'] ) ) : '',
|
||||
'downloadable' => isset( $_POST['_downloadable'] ),
|
||||
'virtual' => isset( $_POST['_virtual'] ),
|
||||
'featured' => isset( $_POST['_featured'] ),
|
||||
'catalog_visibility' => isset( $_POST['_visibility'] ) ? wc_clean( wp_unslash( $_POST['_visibility'] ) ) : null,
|
||||
'tax_status' => isset( $_POST['_tax_status'] ) ? wc_clean( wp_unslash( $_POST['_tax_status'] ) ) : null,
|
||||
'tax_class' => isset( $_POST['_tax_class'] ) ? sanitize_title( wp_unslash( $_POST['_tax_class'] ) ) : null,
|
||||
'weight' => isset( $_POST['_weight'] ) ? wc_clean( wp_unslash( $_POST['_weight'] ) ) : null,
|
||||
'length' => isset( $_POST['_length'] ) ? wc_clean( wp_unslash( $_POST['_length'] ) ) : null,
|
||||
'width' => isset( $_POST['_width'] ) ? wc_clean( wp_unslash( $_POST['_width'] ) ) : null,
|
||||
'height' => isset( $_POST['_height'] ) ? wc_clean( wp_unslash( $_POST['_height'] ) ) : null,
|
||||
'shipping_class_id' => isset( $_POST['product_shipping_class'] ) ? absint( wp_unslash( $_POST['product_shipping_class'] ) ) : null,
|
||||
'sold_individually' => ! empty( $_POST['_sold_individually'] ),
|
||||
'upsell_ids' => isset( $_POST['upsell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['upsell_ids'] ) ) : array(),
|
||||
'cross_sell_ids' => isset( $_POST['crosssell_ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['crosssell_ids'] ) ) : array(),
|
||||
'regular_price' => isset( $_POST['_regular_price'] ) ? wc_clean( wp_unslash( $_POST['_regular_price'] ) ) : null,
|
||||
'sale_price' => isset( $_POST['_sale_price'] ) ? wc_clean( wp_unslash( $_POST['_sale_price'] ) ) : null,
|
||||
'date_on_sale_from' => $date_on_sale_from,
|
||||
'date_on_sale_to' => $date_on_sale_to,
|
||||
'manage_stock' => ! empty( $_POST['_manage_stock'] ),
|
||||
'backorders' => isset( $_POST['_backorders'] ) ? wc_clean( wp_unslash( $_POST['_backorders'] ) ) : null,
|
||||
'stock_status' => isset( $_POST['_stock_status'] ) ? wc_clean( wp_unslash( $_POST['_stock_status'] ) ) : null,
|
||||
'stock_quantity' => $stock,
|
||||
'low_stock_amount' => isset( $_POST['_low_stock_amount'] ) && '' !== $_POST['_low_stock_amount'] ? wc_stock_amount( wp_unslash( $_POST['_low_stock_amount'] ) ) : '',
|
||||
'download_limit' => isset( $_POST['_download_limit'] ) && '' !== $_POST['_download_limit'] ? absint( wp_unslash( $_POST['_download_limit'] ) ) : '',
|
||||
'download_expiry' => isset( $_POST['_download_expiry'] ) && '' !== $_POST['_download_expiry'] ? absint( wp_unslash( $_POST['_download_expiry'] ) ) : '',
|
||||
// Those are sanitized inside prepare_downloads.
|
||||
'downloads' => self::prepare_downloads(
|
||||
isset( $_POST['_wc_file_names'] ) ? wp_unslash( $_POST['_wc_file_names'] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
isset( $_POST['_wc_file_urls'] ) ? wp_unslash( $_POST['_wc_file_urls'] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
isset( $_POST['_wc_file_hashes'] ) ? wp_unslash( $_POST['_wc_file_hashes'] ) : array() // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
),
|
||||
'product_url' => isset( $_POST['_product_url'] ) ? esc_url_raw( wp_unslash( $_POST['_product_url'] ) ) : '',
|
||||
'button_text' => isset( $_POST['_button_text'] ) ? wc_clean( wp_unslash( $_POST['_button_text'] ) ) : '',
|
||||
'children' => 'grouped' === $product_type ? self::prepare_children() : null,
|
||||
'reviews_allowed' => ! empty( $_POST['comment_status'] ) && 'open' === $_POST['comment_status'],
|
||||
'attributes' => $attributes,
|
||||
'default_attributes' => self::prepare_set_attributes( $attributes, 'default_attribute_' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set props before save.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
do_action( 'woocommerce_admin_process_product_object', $product );
|
||||
|
||||
$product->save();
|
||||
|
||||
if ( $product->is_type( 'variable' ) ) {
|
||||
$original_post_title = isset( $_POST['original_post_title'] ) ? wc_clean( wp_unslash( $_POST['original_post_title'] ) ) : '';
|
||||
$post_title = isset( $_POST['post_title'] ) ? wc_clean( wp_unslash( $_POST['post_title'] ) ) : '';
|
||||
|
||||
$product->get_data_store()->sync_variation_names( $product, $original_post_title, $post_title );
|
||||
}
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id );
|
||||
/* phpcs:enable WordPress.Security.NonceVerification.Missing and WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
}
|
||||
|
||||
/**
|
||||
* Save variation meta box data.
|
||||
*
|
||||
* @param int $post_id WP post id.
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public static function save_variations( $post_id, $post ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
if ( isset( $_POST['variable_post_id'] ) ) {
|
||||
$parent = wc_get_product( $post_id );
|
||||
$parent->set_default_attributes( self::prepare_set_attributes( $parent->get_attributes(), 'default_attribute_' ) );
|
||||
$parent->save();
|
||||
|
||||
$max_loop = max( array_keys( wp_unslash( $_POST['variable_post_id'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$data_store = $parent->get_data_store();
|
||||
$data_store->sort_all_product_variations( $parent->get_id() );
|
||||
$new_variation_menu_order_id = ! empty( $_POST['new_variation_menu_order_id'] ) ? wc_clean( wp_unslash( $_POST['new_variation_menu_order_id'] ) ) : false;
|
||||
$new_variation_menu_order_value = ! empty( $_POST['new_variation_menu_order_value'] ) ? wc_clean( wp_unslash( $_POST['new_variation_menu_order_value'] ) ) : false;
|
||||
|
||||
// Only perform this operation if setting menu order via the prompt.
|
||||
if ( $new_variation_menu_order_id && $new_variation_menu_order_value ) {
|
||||
/*
|
||||
* We need to gather all the variations with menu order that is
|
||||
* equal or greater than the menu order that is newly set and
|
||||
* increment them by one so that we can correctly insert the updated
|
||||
* variation menu order.
|
||||
*/
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE {$wpdb->posts} SET menu_order = menu_order + 1 WHERE post_type = 'product_variation' AND post_parent = %d AND post_status = 'publish' AND menu_order >= %d AND ID != %d",
|
||||
$post_id,
|
||||
$new_variation_menu_order_value,
|
||||
$new_variation_menu_order_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for ( $i = 0; $i <= $max_loop; $i++ ) {
|
||||
|
||||
if ( ! isset( $_POST['variable_post_id'][ $i ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$variation_id = absint( $_POST['variable_post_id'][ $i ] );
|
||||
$variation = wc_get_product_object( 'variation', $variation_id );
|
||||
$stock = null;
|
||||
|
||||
// Handle stock changes.
|
||||
if ( isset( $_POST['variable_stock'], $_POST['variable_stock'][ $i ] ) ) {
|
||||
if ( isset( $_POST['variable_original_stock'], $_POST['variable_original_stock'][ $i ] ) && wc_stock_amount( $variation->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( wp_unslash( $_POST['variable_original_stock'][ $i ] ) ) ) {
|
||||
/* translators: 1: product ID 2: quantity in stock */
|
||||
WC_Admin_Meta_Boxes::add_error( sprintf( __( 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', 'woocommerce' ), $variation->get_id(), $variation->get_stock_quantity( 'edit' ) ) );
|
||||
} else {
|
||||
$stock = wc_stock_amount( wp_unslash( $_POST['variable_stock'][ $i ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dates.
|
||||
$date_on_sale_from = '';
|
||||
$date_on_sale_to = '';
|
||||
|
||||
// Force date from to beginning of day.
|
||||
if ( isset( $_POST['variable_sale_price_dates_from'][ $i ] ) ) {
|
||||
$date_on_sale_from = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_from'][ $i ] ) );
|
||||
|
||||
if ( ! empty( $date_on_sale_from ) ) {
|
||||
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
}
|
||||
}
|
||||
|
||||
// Force date to to the end of the day.
|
||||
if ( isset( $_POST['variable_sale_price_dates_to'][ $i ] ) ) {
|
||||
$date_on_sale_to = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_to'][ $i ] ) );
|
||||
|
||||
if ( ! empty( $date_on_sale_to ) ) {
|
||||
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
}
|
||||
}
|
||||
|
||||
$errors = $variation->set_props(
|
||||
array(
|
||||
'status' => isset( $_POST['variable_enabled'][ $i ] ) ? 'publish' : 'private',
|
||||
'menu_order' => isset( $_POST['variation_menu_order'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variation_menu_order'][ $i ] ) ) : null,
|
||||
'regular_price' => isset( $_POST['variable_regular_price'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_regular_price'][ $i ] ) ) : null,
|
||||
'sale_price' => isset( $_POST['variable_sale_price'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sale_price'][ $i ] ) ) : null,
|
||||
'virtual' => isset( $_POST['variable_is_virtual'][ $i ] ),
|
||||
'downloadable' => isset( $_POST['variable_is_downloadable'][ $i ] ),
|
||||
'date_on_sale_from' => $date_on_sale_from,
|
||||
'date_on_sale_to' => $date_on_sale_to,
|
||||
'description' => isset( $_POST['variable_description'][ $i ] ) ? wp_kses_post( wp_unslash( $_POST['variable_description'][ $i ] ) ) : null,
|
||||
'download_limit' => isset( $_POST['variable_download_limit'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_download_limit'][ $i ] ) ) : null,
|
||||
'download_expiry' => isset( $_POST['variable_download_expiry'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_download_expiry'][ $i ] ) ) : null,
|
||||
// Those are sanitized inside prepare_downloads.
|
||||
'downloads' => self::prepare_downloads(
|
||||
isset( $_POST['_wc_variation_file_names'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_names'][ $variation_id ] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
isset( $_POST['_wc_variation_file_urls'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_urls'][ $variation_id ] ) : array(), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
isset( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) ? wp_unslash( $_POST['_wc_variation_file_hashes'][ $variation_id ] ) : array() // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
),
|
||||
'manage_stock' => isset( $_POST['variable_manage_stock'][ $i ] ),
|
||||
'stock_quantity' => $stock,
|
||||
'low_stock_amount' => isset( $_POST['variable_low_stock_amount'][ $i ] ) && '' !== $_POST['variable_low_stock_amount'][ $i ] ? wc_stock_amount( wp_unslash( $_POST['variable_low_stock_amount'][ $i ] ) ) : '',
|
||||
'backorders' => isset( $_POST['variable_backorders'], $_POST['variable_backorders'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_backorders'][ $i ] ) ) : null,
|
||||
'stock_status' => isset( $_POST['variable_stock_status'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_stock_status'][ $i ] ) ) : null,
|
||||
'image_id' => isset( $_POST['upload_image_id'][ $i ] ) ? wc_clean( wp_unslash( $_POST['upload_image_id'][ $i ] ) ) : null,
|
||||
'attributes' => self::prepare_set_attributes( $parent->get_attributes(), 'attribute_', $i ),
|
||||
'sku' => isset( $_POST['variable_sku'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_sku'][ $i ] ) ) : '',
|
||||
'weight' => isset( $_POST['variable_weight'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_weight'][ $i ] ) ) : '',
|
||||
'length' => isset( $_POST['variable_length'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_length'][ $i ] ) ) : '',
|
||||
'width' => isset( $_POST['variable_width'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_width'][ $i ] ) ) : '',
|
||||
'height' => isset( $_POST['variable_height'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_height'][ $i ] ) ) : '',
|
||||
'shipping_class_id' => isset( $_POST['variable_shipping_class'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_shipping_class'][ $i ] ) ) : null,
|
||||
'tax_class' => isset( $_POST['variable_tax_class'][ $i ] ) ? sanitize_title( wp_unslash( $_POST['variable_tax_class'][ $i ] ) ) : null,
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
WC_Admin_Meta_Boxes::add_error( $errors->get_error_message() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set variation props before save.
|
||||
*
|
||||
* @param object $variation WC_Product_Variation object.
|
||||
* @param int $i
|
||||
* @since 3.8.0
|
||||
*/
|
||||
do_action( 'woocommerce_admin_process_variation_object', $variation, $i );
|
||||
|
||||
$variation->save();
|
||||
/* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */
|
||||
do_action( 'woocommerce_save_product_variation', $variation_id, $i );
|
||||
/* phpcs: enable */
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* Product Images
|
||||
*
|
||||
* Display the product images meta box.
|
||||
*
|
||||
* @author WooThemes
|
||||
* @category Admin
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Product_Images Class.
|
||||
*/
|
||||
class WC_Meta_Box_Product_Images {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
global $thepostid, $product_object;
|
||||
|
||||
$thepostid = $post->ID;
|
||||
$product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
|
||||
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
|
||||
?>
|
||||
<div id="product_images_container">
|
||||
<ul class="product_images">
|
||||
<?php
|
||||
$product_image_gallery = $product_object->get_gallery_image_ids( 'edit' );
|
||||
|
||||
$attachments = array_filter( $product_image_gallery );
|
||||
$update_meta = false;
|
||||
$updated_gallery_ids = array();
|
||||
|
||||
if ( ! empty( $attachments ) ) {
|
||||
foreach ( $attachments as $attachment_id ) {
|
||||
$attachment = wp_get_attachment_image( $attachment_id, 'thumbnail' );
|
||||
|
||||
// if attachment is empty skip.
|
||||
if ( empty( $attachment ) ) {
|
||||
$update_meta = true;
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<li class="image" data-attachment_id="<?php echo esc_attr( $attachment_id ); ?>">
|
||||
<?php echo $attachment; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<ul class="actions">
|
||||
<li><a href="#" class="delete tips" data-tip="<?php esc_attr_e( 'Delete image', 'woocommerce' ); ?>"><?php esc_html_e( 'Delete', 'woocommerce' ); ?></a></li>
|
||||
</ul>
|
||||
<?php
|
||||
// Allow for extra info to be exposed or extra action to be executed for this attachment.
|
||||
do_action( 'woocommerce_admin_after_product_gallery_item', $thepostid, $attachment_id );
|
||||
?>
|
||||
</li>
|
||||
<?php
|
||||
|
||||
// rebuild ids to be saved.
|
||||
$updated_gallery_ids[] = $attachment_id;
|
||||
}
|
||||
|
||||
// need to update product meta to set new gallery ids
|
||||
if ( $update_meta ) {
|
||||
update_post_meta( $post->ID, '_product_image_gallery', implode( ',', $updated_gallery_ids ) );
|
||||
}
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
|
||||
<input type="hidden" id="product_image_gallery" name="product_image_gallery" value="<?php echo esc_attr( implode( ',', $updated_gallery_ids ) ); ?>" />
|
||||
|
||||
</div>
|
||||
<p class="add_product_images hide-if-no-js">
|
||||
<a href="#" data-choose="<?php esc_attr_e( 'Add images to product gallery', 'woocommerce' ); ?>" data-update="<?php esc_attr_e( 'Add to gallery', 'woocommerce' ); ?>" data-delete="<?php esc_attr_e( 'Delete image', 'woocommerce' ); ?>" data-text="<?php esc_attr_e( 'Delete', 'woocommerce' ); ?>"><?php esc_html_e( 'Add product gallery images', 'woocommerce' ); ?></a>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public static function save( $post_id, $post ) {
|
||||
$product_type = empty( $_POST['product-type'] ) ? WC_Product_Factory::get_product_type( $post_id ) : sanitize_title( stripslashes( $_POST['product-type'] ) );
|
||||
$classname = WC_Product_Factory::get_product_classname( $post_id, $product_type ? $product_type : 'simple' );
|
||||
$product = new $classname( $post_id );
|
||||
$attachment_ids = isset( $_POST['product_image_gallery'] ) ? array_filter( explode( ',', wc_clean( $_POST['product_image_gallery'] ) ) ) : array();
|
||||
|
||||
$product->set_gallery_image_ids( $attachment_ids );
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* Product Reviews
|
||||
*
|
||||
* Functions for displaying product reviews data meta box.
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Product_Reviews
|
||||
*/
|
||||
class WC_Meta_Box_Product_Reviews {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param object $comment Comment being shown.
|
||||
*/
|
||||
public static function output( $comment ) {
|
||||
wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
|
||||
|
||||
$current = get_comment_meta( $comment->comment_ID, 'rating', true );
|
||||
?>
|
||||
<select name="rating" id="rating">
|
||||
<?php
|
||||
for ( $rating = 1; $rating <= 5; $rating ++ ) {
|
||||
printf( '<option value="%1$s"%2$s>%1$s</option>', $rating, selected( $current, $rating, false ) ); // WPCS: XSS ok.
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta box data
|
||||
*
|
||||
* @param mixed $data Data to save.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function save( $data ) {
|
||||
// Not allowed, return regular value without updating meta.
|
||||
if ( ! isset( $_POST['woocommerce_meta_nonce'], $_POST['rating'] ) || ! wp_verify_nonce( wp_unslash( $_POST['woocommerce_meta_nonce'] ), 'woocommerce_save_data' ) ) { // WPCS: input var ok, sanitization ok.
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( $_POST['rating'] > 5 || $_POST['rating'] < 0 ) { // WPCS: input var ok.
|
||||
return $data;
|
||||
}
|
||||
|
||||
$comment_id = $data['comment_ID'];
|
||||
|
||||
update_comment_meta( $comment_id, 'rating', intval( wp_unslash( $_POST['rating'] ) ) ); // WPCS: input var ok.
|
||||
|
||||
// Return regular value after updating.
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Product Short Description
|
||||
*
|
||||
* Replaces the standard excerpt box.
|
||||
*
|
||||
* @package WooCommerce\Admin\Meta Boxes
|
||||
* @version 2.1.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Meta_Box_Product_Short_Description Class.
|
||||
*/
|
||||
class WC_Meta_Box_Product_Short_Description {
|
||||
|
||||
/**
|
||||
* Output the metabox.
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
*/
|
||||
public static function output( $post ) {
|
||||
|
||||
$settings = array(
|
||||
'textarea_name' => 'excerpt',
|
||||
'quicktags' => array( 'buttons' => 'em,strong,link' ),
|
||||
'tinymce' => array(
|
||||
'theme_advanced_buttons1' => 'bold,italic,strikethrough,separator,bullist,numlist,separator,blockquote,separator,justifyleft,justifycenter,justifyright,separator,link,unlink,separator,undo,redo,separator',
|
||||
'theme_advanced_buttons2' => '',
|
||||
),
|
||||
'editor_css' => '<style>#wp-excerpt-editor-container .wp-editor-area{height:175px; width:100%;}</style>',
|
||||
);
|
||||
|
||||
wp_editor( htmlspecialchars_decode( $post->post_excerpt, ENT_QUOTES ), 'excerpt', apply_filters( 'woocommerce_product_short_description_editor_settings', $settings ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<div class="wc-metabox closed">
|
||||
<h3 class="fixed">
|
||||
<button type="button" data-permission_id="<?php echo esc_attr( $download->get_id() ); ?>" rel="<?php echo esc_attr( $download->get_product_id() ) . ',' . esc_attr( $download->get_download_id() ); ?>" class="revoke_access button"><?php esc_html_e( 'Revoke access', 'woocommerce' ); ?></button>
|
||||
<div class="handlediv" aria-label="<?php esc_attr_e( 'Click to toggle', 'woocommerce' ); ?>"></div>
|
||||
<strong>
|
||||
<?php
|
||||
printf(
|
||||
'#%s — %s — %s: %s — ',
|
||||
esc_html( $product->get_id() ),
|
||||
esc_html( apply_filters( 'woocommerce_admin_download_permissions_title', $product->get_name(), $download->get_product_id(), $download->get_order_id(), $download->get_order_key(), $download->get_download_id() ) ),
|
||||
esc_html( $file_count ),
|
||||
esc_html( wc_get_filename_from_url( $product->get_file_download_path( $download->get_download_id() ) ) )
|
||||
);
|
||||
printf( _n( 'Downloaded %s time', 'Downloaded %s times', $download->get_download_count(), 'woocommerce' ), esc_html( $download->get_download_count() ) )
|
||||
?>
|
||||
</strong>
|
||||
</h3>
|
||||
<table cellpadding="0" cellspacing="0" class="wc-metabox-content">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label><?php esc_html_e( 'Downloads remaining', 'woocommerce' ); ?></label>
|
||||
<input type="hidden" name="permission_id[<?php echo esc_attr( $loop ); ?>]" value="<?php echo esc_attr( $download->get_id() ); ?>" />
|
||||
<input type="number" step="1" min="0" class="short" name="downloads_remaining[<?php echo esc_attr( $loop ); ?>]" value="<?php echo esc_attr( $download->get_downloads_remaining() ); ?>" placeholder="<?php esc_attr_e( 'Unlimited', 'woocommerce' ); ?>" />
|
||||
</td>
|
||||
<td>
|
||||
<label><?php esc_html_e( 'Access expires', 'woocommerce' ); ?></label>
|
||||
<input type="text" class="short date-picker" name="access_expires[<?php echo esc_attr( $loop ); ?>]" value="<?php echo ! is_null( $download->get_access_expires() ) ? esc_attr( date_i18n( 'Y-m-d', $download->get_access_expires()->getTimestamp() ) ) : ''; ?>" maxlength="10" placeholder="<?php esc_attr_e( 'Never', 'woocommerce' ); ?>" pattern="<?php echo esc_attr( apply_filters( 'woocommerce_date_input_html_pattern', '[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])' ) ); ?>" />
|
||||
</td>
|
||||
<td>
|
||||
<label><?php esc_html_e( 'Customer download link', 'woocommerce' ); ?></label>
|
||||
<?php
|
||||
$download_link = add_query_arg(
|
||||
array(
|
||||
'download_file' => $download->get_product_id(),
|
||||
'order' => $download->get_order_key(),
|
||||
'email' => urlencode( $download->get_user_email() ),
|
||||
'key' => $download->get_download_id(),
|
||||
),
|
||||
trailingslashit( home_url() )
|
||||
);
|
||||
?>
|
||||
<a id="copy-download-link" class="button" href="<?php echo esc_url( $download_link ); ?>" data-tip="<?php esc_attr_e( 'Copied!', 'woocommerce' ); ?>" data-tip-failed="<?php esc_attr_e( 'Copying to clipboard failed. You should be able to right-click the button and copy.', 'woocommerce' ); ?>"><?php esc_html_e( 'Copy link', 'woocommerce' ); ?></a>
|
||||
</td>
|
||||
<td>
|
||||
<label><?php esc_html_e( 'Customer download log', 'woocommerce' ); ?></label>
|
||||
<?php
|
||||
$report_url = add_query_arg(
|
||||
'permission_id',
|
||||
rawurlencode( $download->get_id() ),
|
||||
admin_url( 'admin.php?page=wc-reports&tab=orders&report=downloads' )
|
||||
);
|
||||
echo '<a class="button" href="' . esc_url( $report_url ) . '">';
|
||||
esc_html_e( 'View report', 'woocommerce' );
|
||||
echo '</a>';
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Shows an order item fee
|
||||
*
|
||||
* @var object $item The item being displayed
|
||||
* @var int $item_id The id of the item being displayed
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<tr class="fee <?php echo ( ! empty( $class ) ) ? esc_attr( $class ) : ''; ?>" data-order_item_id="<?php echo esc_attr( $item_id ); ?>">
|
||||
<td class="thumb"><div></div></td>
|
||||
|
||||
<td class="name">
|
||||
<div class="view">
|
||||
<?php echo esc_html( $item->get_name() ? $item->get_name() : __( 'Fee', 'woocommerce' ) ); ?>
|
||||
</div>
|
||||
<div class="edit" style="display: none;">
|
||||
<input type="text" placeholder="<?php esc_attr_e( 'Fee name', 'woocommerce' ); ?>" name="order_item_name[<?php echo absint( $item_id ); ?>]" value="<?php echo ( $item->get_name() ) ? esc_attr( $item->get_name() ) : ''; ?>" />
|
||||
<input type="hidden" class="order_item_id" name="order_item_id[]" value="<?php echo esc_attr( $item_id ); ?>" />
|
||||
<input type="hidden" name="order_item_tax_class[<?php echo absint( $item_id ); ?>]" value="<?php echo esc_attr( $item->get_tax_class() ); ?>" />
|
||||
</div>
|
||||
<?php do_action( 'woocommerce_after_order_fee_item_name', $item_id, $item, null ); ?>
|
||||
</td>
|
||||
|
||||
<?php do_action( 'woocommerce_admin_order_item_values', null, $item, absint( $item_id ) ); ?>
|
||||
|
||||
<td class="item_cost" width="1%"> </td>
|
||||
<td class="quantity" width="1%"> </td>
|
||||
|
||||
<td class="line_cost" width="1%">
|
||||
<div class="view">
|
||||
<?php
|
||||
echo wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) );
|
||||
|
||||
if ( $refunded = -1 * $order->get_total_refunded_for_item( $item_id, 'fee' ) ) {
|
||||
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="edit" style="display: none;">
|
||||
<input type="text" name="line_total[<?php echo absint( $item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $item->get_total() ) ); ?>" class="line_total wc_input_price" />
|
||||
</div>
|
||||
<div class="refund" style="display: none;">
|
||||
<input type="text" name="refund_line_total[<?php echo absint( $item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" class="refund_line_total wc_input_price" />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<?php
|
||||
if ( ( $tax_data = $item->get_taxes() ) && wc_tax_enabled() ) {
|
||||
foreach ( $order_taxes as $tax_item ) {
|
||||
$tax_item_id = $tax_item->get_rate_id();
|
||||
$tax_item_total = isset( $tax_data['total'][ $tax_item_id ] ) ? $tax_data['total'][ $tax_item_id ] : '';
|
||||
?>
|
||||
<td class="line_tax" width="1%">
|
||||
<div class="view">
|
||||
<?php
|
||||
echo ( '' !== $tax_item_total ) ? wc_price( wc_round_tax_total( $tax_item_total ), array( 'currency' => $order->get_currency() ) ) : '–';
|
||||
|
||||
if ( $refunded = -1 * $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'fee' ) ) {
|
||||
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="edit" style="display: none;">
|
||||
<input type="text" name="line_tax[<?php echo absint( $item_id ); ?>][<?php echo esc_attr( $tax_item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" value="<?php echo ( isset( $tax_item_total ) ) ? esc_attr( wc_format_localized_price( $tax_item_total ) ) : ''; ?>" class="line_tax wc_input_price" />
|
||||
</div>
|
||||
<div class="refund" style="display: none;">
|
||||
<input type="text" name="refund_line_tax[<?php echo absint( $item_id ); ?>][<?php echo esc_attr( $tax_item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" class="refund_line_tax wc_input_price" data-tax_id="<?php echo esc_attr( $tax_item_id ); ?>" />
|
||||
</div>
|
||||
</td>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td class="wc-order-edit-line-item">
|
||||
<?php if ( $order->is_editable() ) : ?>
|
||||
<div class="wc-order-edit-line-item-actions">
|
||||
<a class="edit-order-item" href="#"></a><a class="delete-order-item" href="#"></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Shows an order item meta
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @var object $item The item being displayed
|
||||
* @var int $item_id The id of the item being displayed
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$hidden_order_itemmeta = apply_filters(
|
||||
'woocommerce_hidden_order_itemmeta',
|
||||
array(
|
||||
'_qty',
|
||||
'_tax_class',
|
||||
'_product_id',
|
||||
'_variation_id',
|
||||
'_line_subtotal',
|
||||
'_line_subtotal_tax',
|
||||
'_line_total',
|
||||
'_line_tax',
|
||||
'method_id',
|
||||
'cost',
|
||||
'_reduced_stock',
|
||||
'_restock_refunded_items',
|
||||
)
|
||||
);
|
||||
?><div class="view">
|
||||
<?php
|
||||
$meta_data = $item->get_all_formatted_meta_data( '' );
|
||||
if ( $meta_data ) :
|
||||
?>
|
||||
<table cellspacing="0" class="display_meta">
|
||||
<?php
|
||||
foreach ( $meta_data as $meta_id => $meta ) :
|
||||
if ( in_array( $meta->key, $hidden_order_itemmeta, true ) ) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<th><?php echo wp_kses_post( $meta->display_key ); ?>:</th>
|
||||
<td><?php echo wp_kses_post( force_balance_tags( $meta->display_value ) ); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="edit" style="display: none;">
|
||||
<table class="meta" cellspacing="0">
|
||||
<tbody class="meta_items">
|
||||
<?php if ( $meta_data ) : ?>
|
||||
<?php
|
||||
foreach ( $meta_data as $meta_id => $meta ) :
|
||||
if ( in_array( $meta->key, $hidden_order_itemmeta, true ) ) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<tr data-meta_id="<?php echo esc_attr( $meta_id ); ?>">
|
||||
<td>
|
||||
<input type="text" maxlength="255" placeholder="<?php esc_attr_e( 'Name (required)', 'woocommerce' ); ?>" name="meta_key[<?php echo esc_attr( $item_id ); ?>][<?php echo esc_attr( $meta_id ); ?>]" value="<?php echo esc_attr( $meta->key ); ?>" />
|
||||
<textarea placeholder="<?php esc_attr_e( 'Value (required)', 'woocommerce' ); ?>" name="meta_value[<?php echo esc_attr( $item_id ); ?>][<?php echo esc_attr( $meta_id ); ?>]"><?php echo esc_textarea( rawurldecode( $meta->value ) ); ?></textarea>
|
||||
</td>
|
||||
<td width="1%"><button class="remove_order_item_meta button">×</button></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4"><button class="add_order_item_meta button"><?php esc_html_e( 'Add meta', 'woocommerce' ); ?></button></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* Shows an order item
|
||||
*
|
||||
* @package WooCommerce\Admin
|
||||
* @var WC_Order_Item $item The item being displayed
|
||||
* @var int $item_id The id of the item being displayed
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$product = $item->get_product();
|
||||
$product_link = $product ? admin_url( 'post.php?post=' . $item->get_product_id() . '&action=edit' ) : '';
|
||||
$thumbnail = $product ? apply_filters( 'woocommerce_admin_order_item_thumbnail', $product->get_image( 'thumbnail', array( 'title' => '' ), false ), $item_id, $item ) : '';
|
||||
$row_class = apply_filters( 'woocommerce_admin_html_order_item_class', ! empty( $class ) ? $class : '', $item, $order );
|
||||
?>
|
||||
<tr class="item <?php echo esc_attr( $row_class ); ?>" data-order_item_id="<?php echo esc_attr( $item_id ); ?>">
|
||||
<td class="thumb">
|
||||
<?php echo '<div class="wc-order-item-thumbnail">' . wp_kses_post( $thumbnail ) . '</div>'; ?>
|
||||
</td>
|
||||
<td class="name" data-sort-value="<?php echo esc_attr( $item->get_name() ); ?>">
|
||||
<?php
|
||||
echo $product_link ? '<a href="' . esc_url( $product_link ) . '" class="wc-order-item-name">' . wp_kses_post( $item->get_name() ) . '</a>' : '<div class="wc-order-item-name">' . wp_kses_post( $item->get_name() ) . '</div>';
|
||||
|
||||
if ( $product && $product->get_sku() ) {
|
||||
echo '<div class="wc-order-item-sku"><strong>' . esc_html__( 'SKU:', 'woocommerce' ) . '</strong> ' . esc_html( $product->get_sku() ) . '</div>';
|
||||
}
|
||||
|
||||
if ( $item->get_variation_id() ) {
|
||||
echo '<div class="wc-order-item-variation"><strong>' . esc_html__( 'Variation ID:', 'woocommerce' ) . '</strong> ';
|
||||
if ( 'product_variation' === get_post_type( $item->get_variation_id() ) ) {
|
||||
echo esc_html( $item->get_variation_id() );
|
||||
} else {
|
||||
/* translators: %s: variation id */
|
||||
printf( esc_html__( '%s (No longer exists)', 'woocommerce' ), esc_html( $item->get_variation_id() ) );
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
<input type="hidden" class="order_item_id" name="order_item_id[]" value="<?php echo esc_attr( $item_id ); ?>" />
|
||||
<input type="hidden" name="order_item_tax_class[<?php echo absint( $item_id ); ?>]" value="<?php echo esc_attr( $item->get_tax_class() ); ?>" />
|
||||
|
||||
<?php do_action( 'woocommerce_before_order_itemmeta', $item_id, $item, $product ); ?>
|
||||
<?php require __DIR__ . '/html-order-item-meta.php'; ?>
|
||||
<?php do_action( 'woocommerce_after_order_itemmeta', $item_id, $item, $product ); ?>
|
||||
</td>
|
||||
|
||||
<?php do_action( 'woocommerce_admin_order_item_values', $product, $item, absint( $item_id ) ); ?>
|
||||
|
||||
<td class="item_cost" width="1%" data-sort-value="<?php echo esc_attr( $order->get_item_subtotal( $item, false, true ) ); ?>">
|
||||
<div class="view">
|
||||
<?php
|
||||
echo wc_price( $order->get_item_subtotal( $item, false, true ), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="quantity" width="1%">
|
||||
<div class="view">
|
||||
<?php
|
||||
echo '<small class="times">×</small> ' . esc_html( $item->get_quantity() );
|
||||
|
||||
$refunded_qty = -1 * $order->get_qty_refunded_for_item( $item_id );
|
||||
|
||||
if ( $refunded_qty ) {
|
||||
echo '<small class="refunded">' . esc_html( $refunded_qty * -1 ) . '</small>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
$step = apply_filters( 'woocommerce_quantity_input_step', '1', $product );
|
||||
|
||||
/**
|
||||
* Filter to change the product quantity stepping in the order editor of the admin area.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @param string $step The current step amount to be used in the quantity editor.
|
||||
* @param WC_Product $product The product that is being edited.
|
||||
* @param string $context The context in which the quantity editor is shown, 'edit' or 'refund'.
|
||||
*/
|
||||
$step_edit = apply_filters( 'woocommerce_quantity_input_step_admin', $step, $product, 'edit' );
|
||||
$step_refund = apply_filters( 'woocommerce_quantity_input_step_admin', $step, $product, 'refund' );
|
||||
|
||||
/**
|
||||
* Filter to change the product quantity minimum in the order editor of the admin area.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @param string $step The current minimum amount to be used in the quantity editor.
|
||||
* @param WC_Product $product The product that is being edited.
|
||||
* @param string $context The context in which the quantity editor is shown, 'edit' or 'refund'.
|
||||
*/
|
||||
$min_edit = apply_filters( 'woocommerce_quantity_input_min_admin', '0', $product, 'edit' );
|
||||
$min_refund = apply_filters( 'woocommerce_quantity_input_min_admin', '0', $product, 'refund' );
|
||||
?>
|
||||
<div class="edit" style="display: none;">
|
||||
<input type="number" step="<?php echo esc_attr( $step_edit ); ?>" min="<?php echo esc_attr( $min_edit ); ?>" autocomplete="off" name="order_item_qty[<?php echo absint( $item_id ); ?>]" placeholder="0" value="<?php echo esc_attr( $item->get_quantity() ); ?>" data-qty="<?php echo esc_attr( $item->get_quantity() ); ?>" size="4" class="quantity" />
|
||||
</div>
|
||||
<div class="refund" style="display: none;">
|
||||
<input type="number" step="<?php echo esc_attr( $step_refund ); ?>" min="<?php echo esc_attr( $min_refund ); ?>" max="<?php echo absint( $item->get_quantity() ); ?>" autocomplete="off" name="refund_order_item_qty[<?php echo absint( $item_id ); ?>]" placeholder="0" size="4" class="refund_order_item_qty" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="line_cost" width="1%" data-sort-value="<?php echo esc_attr( $item->get_total() ); ?>">
|
||||
<div class="view">
|
||||
<?php
|
||||
echo wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
if ( $item->get_subtotal() !== $item->get_total() ) {
|
||||
/* translators: %s: discount amount */
|
||||
echo '<span class="wc-order-item-discount">' . sprintf( esc_html__( '%s discount', 'woocommerce' ), wc_price( wc_format_decimal( $item->get_subtotal() - $item->get_total(), '' ), array( 'currency' => $order->get_currency() ) ) ) . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$refunded = -1 * $order->get_total_refunded_for_item( $item_id );
|
||||
|
||||
if ( $refunded ) {
|
||||
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="edit" style="display: none;">
|
||||
<div class="split-input">
|
||||
<div class="input">
|
||||
<label><?php esc_attr_e( 'Before discount', 'woocommerce' ); ?></label>
|
||||
<input type="text" name="line_subtotal[<?php echo absint( $item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $item->get_subtotal() ) ); ?>" class="line_subtotal wc_input_price" data-subtotal="<?php echo esc_attr( wc_format_localized_price( $item->get_subtotal() ) ); ?>" />
|
||||
</div>
|
||||
<div class="input">
|
||||
<label><?php esc_attr_e( 'Total', 'woocommerce' ); ?></label>
|
||||
<input type="text" name="line_total[<?php echo absint( $item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $item->get_total() ) ); ?>" class="line_total wc_input_price" data-tip="<?php esc_attr_e( 'After pre-tax discounts.', 'woocommerce' ); ?>" data-total="<?php echo esc_attr( wc_format_localized_price( $item->get_total() ) ); ?>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="refund" style="display: none;">
|
||||
<input type="text" name="refund_line_total[<?php echo absint( $item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" class="refund_line_total wc_input_price" />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<?php
|
||||
$tax_data = wc_tax_enabled() ? $item->get_taxes() : false;
|
||||
|
||||
if ( $tax_data ) {
|
||||
foreach ( $order_taxes as $tax_item ) {
|
||||
$tax_item_id = $tax_item->get_rate_id();
|
||||
$tax_item_total = isset( $tax_data['total'][ $tax_item_id ] ) ? $tax_data['total'][ $tax_item_id ] : '';
|
||||
$tax_item_subtotal = isset( $tax_data['subtotal'][ $tax_item_id ] ) ? $tax_data['subtotal'][ $tax_item_id ] : '';
|
||||
|
||||
?>
|
||||
<td class="line_tax" width="1%">
|
||||
<div class="view">
|
||||
<?php
|
||||
if ( '' !== $tax_item_total ) {
|
||||
echo wc_price( wc_round_tax_total( $tax_item_total ), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
echo '–';
|
||||
}
|
||||
|
||||
$refunded = -1 * $order->get_tax_refunded_for_item( $item_id, $tax_item_id );
|
||||
|
||||
if ( $refunded ) {
|
||||
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="edit" style="display: none;">
|
||||
<div class="split-input">
|
||||
<div class="input">
|
||||
<label><?php esc_attr_e( 'Before discount', 'woocommerce' ); ?></label>
|
||||
<input type="text" name="line_subtotal_tax[<?php echo absint( $item_id ); ?>][<?php echo esc_attr( $tax_item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $tax_item_subtotal ) ); ?>" class="line_subtotal_tax wc_input_price" data-subtotal_tax="<?php echo esc_attr( wc_format_localized_price( $tax_item_subtotal ) ); ?>" data-tax_id="<?php echo esc_attr( $tax_item_id ); ?>" />
|
||||
</div>
|
||||
<div class="input">
|
||||
<label><?php esc_attr_e( 'Total', 'woocommerce' ); ?></label>
|
||||
<input type="text" name="line_tax[<?php echo absint( $item_id ); ?>][<?php echo esc_attr( $tax_item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $tax_item_total ) ); ?>" class="line_tax wc_input_price" data-total_tax="<?php echo esc_attr( wc_format_localized_price( $tax_item_total ) ); ?>" data-tax_id="<?php echo esc_attr( $tax_item_id ); ?>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="refund" style="display: none;">
|
||||
<input type="text" name="refund_line_tax[<?php echo absint( $item_id ); ?>][<?php echo esc_attr( $tax_item_id ); ?>]" placeholder="<?php echo esc_attr( wc_format_localized_price( 0 ) ); ?>" class="refund_line_tax wc_input_price" data-tax_id="<?php echo esc_attr( $tax_item_id ); ?>" />
|
||||
</div>
|
||||
</td>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td class="wc-order-edit-line-item" width="1%">
|
||||
<div class="wc-order-edit-line-item-actions">
|
||||
<?php if ( $order->is_editable() ) : ?>
|
||||
<a class="edit-order-item tips" href="#" data-tip="<?php esc_attr_e( 'Edit item', 'woocommerce' ); ?>"></a><a class="delete-order-item tips" href="#" data-tip="<?php esc_attr_e( 'Delete item', 'woocommerce' ); ?>"></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user