initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,858 @@
<?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;
/**
* 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 ) {
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_' . $key ) ) || is_callable( array( $this, 'get_' . $key ) );
if ( ! $has_setter_or_getter ) {
return false;
}
/* 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_' . $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_' . $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_' . $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.
*
* @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 = ! empty( $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 ( $raw_meta_data ) {
foreach ( $raw_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 ),
)
);
}
if ( ! $cache_loaded && ! empty( $this->cache_group ) ) {
wp_cache_set( $cache_key, $raw_meta_data, $this->cache_group );
}
}
}
/**
* 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 );
unset( $this->meta_data[ $array_key ] );
}
} elseif ( empty( $meta->id ) ) {
$meta->id = $this->data_store->add_meta( $this, $meta );
$meta->apply_changes();
} else {
if ( $meta->get_changes() ) {
$this->data_store->update_meta( $this, $meta );
$meta->apply_changes();
}
}
}
if ( ! empty( $this->cache_group ) ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
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() );
}
}
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 ) ) {
$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 );
}
}

View File

@ -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 );
}

View File

@ -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';
}
}

View File

@ -0,0 +1,57 @@
<?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 date( '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}";
return apply_filters(
'woocommerce_format_log_entry',
$entry,
array(
'timestamp' => $timestamp,
'level' => $level,
'message' => $message,
'context' => $context,
)
);
}
}

View File

@ -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

View File

@ -0,0 +1,562 @@
<?php
/**
* Abstract payment gateway
*
* Hanldes generic payment gateway functionality which is extended by idividual payment gateways.
*
* @class WC_Payment_Gateway
* @version 2.1.0
* @package WooCommerce\Abstracts
*/
use Automattic\Jetpack\Constants;
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() {
return apply_filters( 'woocommerce_gateway_title', $this->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 boolean 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' ),
);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -0,0 +1,959 @@
<?php
/**
* Abstract Settings API Class
*
* Admin Settings API used by Integrations, Shipping Methods, and Payment Gateways.
*
* @package WooCommerce\Abstracts
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_Settings_API class.
*/
abstract class WC_Settings_API {
/**
* The plugin ID. Used for option names.
*
* @var string
*/
public $plugin_id = 'woocommerce_';
/**
* ID of the class extending the settings API. Used in option names.
*
* @var string
*/
public $id = '';
/**
* Validation errors.
*
* @var array of strings
*/
public $errors = array();
/**
* Setting values.
*
* @var array
*/
public $settings = array();
/**
* Form option fields.
*
* @var array
*/
public $form_fields = array();
/**
* The posted settings data. When empty, $_POST data will be used.
*
* @var array
*/
protected $data = array();
/**
* Get the form fields after they are initialized.
*
* @return array of options
*/
public function get_form_fields() {
return apply_filters( 'woocommerce_settings_api_form_fields_' . $this->id, array_map( array( $this, 'set_defaults' ), $this->form_fields ) );
}
/**
* Set default required properties for each field.
*
* @param array $field Setting field array.
* @return array
*/
protected function set_defaults( $field ) {
if ( ! isset( $field['default'] ) ) {
$field['default'] = '';
}
return $field;
}
/**
* Output the admin options table.
*/
public function admin_options() {
echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>'; // WPCS: XSS ok.
}
/**
* Initialise settings form fields.
*
* Add an array of fields to be displayed on the gateway's settings screen.
*
* @since 1.0.0
*/
public function init_form_fields() {}
/**
* Return the name of the option in the WP DB.
*
* @since 2.6.0
* @return string
*/
public function get_option_key() {
return $this->plugin_id . $this->id . '_settings';
}
/**
* Get a fields type. Defaults to "text" if not set.
*
* @param array $field Field key.
* @return string
*/
public function get_field_type( $field ) {
return empty( $field['type'] ) ? 'text' : $field['type'];
}
/**
* Get a fields default value. Defaults to "" if not set.
*
* @param array $field Field key.
* @return string
*/
public function get_field_default( $field ) {
return empty( $field['default'] ) ? '' : $field['default'];
}
/**
* Get a field's posted and validated value.
*
* @param string $key Field key.
* @param array $field Field array.
* @param array $post_data Posted data.
* @return string
*/
public function get_field_value( $key, $field, $post_data = array() ) {
$type = $this->get_field_type( $field );
$field_key = $this->get_field_key( $key );
$post_data = empty( $post_data ) ? $_POST : $post_data; // WPCS: CSRF ok, input var ok.
$value = isset( $post_data[ $field_key ] ) ? $post_data[ $field_key ] : null;
if ( isset( $field['sanitize_callback'] ) && is_callable( $field['sanitize_callback'] ) ) {
return call_user_func( $field['sanitize_callback'], $value );
}
// Look for a validate_FIELDID_field method for special handling.
if ( is_callable( array( $this, 'validate_' . $key . '_field' ) ) ) {
return $this->{'validate_' . $key . '_field'}( $key, $value );
}
// Look for a validate_FIELDTYPE_field method.
if ( is_callable( array( $this, 'validate_' . $type . '_field' ) ) ) {
return $this->{'validate_' . $type . '_field'}( $key, $value );
}
// Fallback to text.
return $this->validate_text_field( $key, $value );
}
/**
* Sets the POSTed data. This method can be used to set specific data, instead of taking it from the $_POST array.
*
* @param array $data Posted data.
*/
public function set_post_data( $data = array() ) {
$this->data = $data;
}
/**
* Returns the POSTed data, to be used to save the settings.
*
* @return array
*/
public function get_post_data() {
if ( ! empty( $this->data ) && is_array( $this->data ) ) {
return $this->data;
}
return $_POST; // WPCS: CSRF ok, input var ok.
}
/**
* Update a single option.
*
* @since 3.4.0
* @param string $key Option key.
* @param mixed $value Value to set.
* @return bool was anything saved?
*/
public function update_option( $key, $value = '' ) {
if ( empty( $this->settings ) ) {
$this->init_settings();
}
$this->settings[ $key ] = $value;
return update_option( $this->get_option_key(), apply_filters( 'woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ), 'yes' );
}
/**
* Processes and saves options.
* If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
*
* @return bool was anything saved?
*/
public function process_admin_options() {
$this->init_settings();
$post_data = $this->get_post_data();
foreach ( $this->get_form_fields() as $key => $field ) {
if ( 'title' !== $this->get_field_type( $field ) ) {
try {
$this->settings[ $key ] = $this->get_field_value( $key, $field, $post_data );
} catch ( Exception $e ) {
$this->add_error( $e->getMessage() );
}
}
}
return update_option( $this->get_option_key(), apply_filters( 'woocommerce_settings_api_sanitized_fields_' . $this->id, $this->settings ), 'yes' );
}
/**
* Add an error message for display in admin on save.
*
* @param string $error Error message.
*/
public function add_error( $error ) {
$this->errors[] = $error;
}
/**
* Get admin error messages.
*/
public function get_errors() {
return $this->errors;
}
/**
* Display admin error messages.
*/
public function display_errors() {
if ( $this->get_errors() ) {
echo '<div id="woocommerce_errors" class="error notice is-dismissible">';
foreach ( $this->get_errors() as $error ) {
echo '<p>' . wp_kses_post( $error ) . '</p>';
}
echo '</div>';
}
}
/**
* Initialise Settings.
*
* Store all settings in a single database entry
* and make sure the $settings array is either the default
* or the settings stored in the database.
*
* @since 1.0.0
* @uses get_option(), add_option()
*/
public function init_settings() {
$this->settings = get_option( $this->get_option_key(), null );
// If there are no settings defined, use defaults.
if ( ! is_array( $this->settings ) ) {
$form_fields = $this->get_form_fields();
$this->settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) );
}
}
/**
* Get option from DB.
*
* Gets an option from the settings API, using defaults if necessary to prevent undefined notices.
*
* @param string $key Option key.
* @param mixed $empty_value Value when empty.
* @return string The value specified for the option or a default value for the option.
*/
public function get_option( $key, $empty_value = null ) {
if ( empty( $this->settings ) ) {
$this->init_settings();
}
// Get option default if unset.
if ( ! isset( $this->settings[ $key ] ) ) {
$form_fields = $this->get_form_fields();
$this->settings[ $key ] = isset( $form_fields[ $key ] ) ? $this->get_field_default( $form_fields[ $key ] ) : '';
}
if ( ! is_null( $empty_value ) && '' === $this->settings[ $key ] ) {
$this->settings[ $key ] = $empty_value;
}
return $this->settings[ $key ];
}
/**
* Prefix key for settings.
*
* @param string $key Field key.
* @return string
*/
public function get_field_key( $key ) {
return $this->plugin_id . $this->id . '_' . $key;
}
/**
* Generate Settings HTML.
*
* Generate the HTML for the fields on the "settings" screen.
*
* @param array $form_fields (default: array()) Array of form fields.
* @param bool $echo Echo or return.
* @return string the html for the settings
* @since 1.0.0
* @uses method_exists()
*/
public function generate_settings_html( $form_fields = array(), $echo = true ) {
if ( empty( $form_fields ) ) {
$form_fields = $this->get_form_fields();
}
$html = '';
foreach ( $form_fields as $k => $v ) {
$type = $this->get_field_type( $v );
if ( method_exists( $this, 'generate_' . $type . '_html' ) ) {
$html .= $this->{'generate_' . $type . '_html'}( $k, $v );
} else {
$html .= $this->generate_text_html( $k, $v );
}
}
if ( $echo ) {
echo $html; // WPCS: XSS ok.
} else {
return $html;
}
}
/**
* Get HTML for tooltips.
*
* @param array $data Data for the tooltip.
* @return string
*/
public function get_tooltip_html( $data ) {
if ( true === $data['desc_tip'] ) {
$tip = $data['description'];
} elseif ( ! empty( $data['desc_tip'] ) ) {
$tip = $data['desc_tip'];
} else {
$tip = '';
}
return $tip ? wc_help_tip( $tip, true ) : '';
}
/**
* Get HTML for descriptions.
*
* @param array $data Data for the description.
* @return string
*/
public function get_description_html( $data ) {
if ( true === $data['desc_tip'] ) {
$description = '';
} elseif ( ! empty( $data['desc_tip'] ) ) {
$description = $data['description'];
} elseif ( ! empty( $data['description'] ) ) {
$description = $data['description'];
} else {
$description = '';
}
return $description ? '<p class="description">' . wp_kses_post( $description ) . '</p>' . "\n" : '';
}
/**
* Get custom attributes.
*
* @param array $data Field data.
* @return string
*/
public function get_custom_attribute_html( $data ) {
$custom_attributes = array();
if ( ! empty( $data['custom_attributes'] ) && is_array( $data['custom_attributes'] ) ) {
foreach ( $data['custom_attributes'] as $attribute => $attribute_value ) {
$custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"';
}
}
return implode( ' ', $custom_attributes );
}
/**
* Generate Text Input HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_text_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<input class="input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="<?php echo esc_attr( $data['type'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( $this->get_option( $key ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?> />
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Price Input HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_price_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<input class="wc_input_price input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( wc_format_localized_price( $this->get_option( $key ) ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?> />
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Decimal Input HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_decimal_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<input class="wc_input_decimal input-text regular-input <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( wc_format_localized_decimal( $this->get_option( $key ) ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?> />
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Password Input HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_password_html( $key, $data ) {
$data['type'] = 'password';
return $this->generate_text_html( $key, $data );
}
/**
* Generate Color Picker Input HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_color_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<span class="colorpickpreview" style="background:<?php echo esc_attr( $this->get_option( $key ) ); ?>;">&nbsp;</span>
<input class="colorpick <?php echo esc_attr( $data['class'] ); ?>" type="text" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="<?php echo esc_attr( $this->get_option( $key ) ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?> />
<div id="colorPickerDiv_<?php echo esc_attr( $field_key ); ?>" class="colorpickdiv" style="z-index: 100; background: #eee; border: 1px solid #ccc; position: absolute; display: none;"></div>
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Textarea HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_textarea_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<textarea rows="3" cols="20" class="input-text wide-input <?php echo esc_attr( $data['class'] ); ?>" type="<?php echo esc_attr( $data['type'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" placeholder="<?php echo esc_attr( $data['placeholder'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?>><?php echo esc_textarea( $this->get_option( $key ) ); ?></textarea>
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Checkbox HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_checkbox_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'label' => '',
'disabled' => false,
'class' => '',
'css' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
$data = wp_parse_args( $data, $defaults );
if ( ! $data['label'] ) {
$data['label'] = $data['title'];
}
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<label for="<?php echo esc_attr( $field_key ); ?>">
<input <?php disabled( $data['disabled'], true ); ?> class="<?php echo esc_attr( $data['class'] ); ?>" type="checkbox" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" value="1" <?php checked( $this->get_option( $key ), 'yes' ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?> /> <?php echo wp_kses_post( $data['label'] ); ?></label><br/>
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Select HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_select_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
'options' => array(),
);
$data = wp_parse_args( $data, $defaults );
$value = $this->get_option( $key );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<select class="select <?php echo esc_attr( $data['class'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?>>
<?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?>
<?php if ( is_array( $option_value ) ) : ?>
<optgroup label="<?php echo esc_attr( $option_key ); ?>">
<?php foreach ( $option_value as $option_key_inner => $option_value_inner ) : ?>
<option value="<?php echo esc_attr( $option_key_inner ); ?>" <?php selected( (string) $option_key_inner, esc_attr( $value ) ); ?>><?php echo esc_html( $option_value_inner ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php else : ?>
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( (string) $option_key, esc_attr( $value ) ); ?>><?php echo esc_html( $option_value ); ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Multiselect HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_multiselect_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
'select_buttons' => false,
'options' => array(),
);
$data = wp_parse_args( $data, $defaults );
$value = (array) $this->get_option( $key, array() );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<fieldset>
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<select multiple="multiple" class="multiselect <?php echo esc_attr( $data['class'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>[]" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?>>
<?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?>
<?php if ( is_array( $option_value ) ) : ?>
<optgroup label="<?php echo esc_attr( $option_key ); ?>">
<?php foreach ( $option_value as $option_key_inner => $option_value_inner ) : ?>
<option value="<?php echo esc_attr( $option_key_inner ); ?>" <?php selected( in_array( (string) $option_key_inner, $value, true ), true ); ?>><?php echo esc_html( $option_value_inner ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php else : ?>
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( in_array( (string) $option_key, $value, true ), true ); ?>><?php echo esc_html( $option_value ); ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
<?php if ( $data['select_buttons'] ) : ?>
<br/><a class="select_all button" href="#"><?php esc_html_e( 'Select all', 'woocommerce' ); ?></a> <a class="select_none button" href="#"><?php esc_html_e( 'Select none', 'woocommerce' ); ?></a>
<?php endif; ?>
</fieldset>
</td>
</tr>
<?php
return ob_get_clean();
}
/**
* Generate Title HTML.
*
* @param string $key Field key.
* @param array $data Field data.
* @since 1.0.0
* @return string
*/
public function generate_title_html( $key, $data ) {
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'class' => '',
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
</table>
<h3 class="wc-settings-sub-title <?php echo esc_attr( $data['class'] ); ?>" id="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?></h3>
<?php if ( ! empty( $data['description'] ) ) : ?>
<p><?php echo wp_kses_post( $data['description'] ); ?></p>
<?php endif; ?>
<table class="form-table">
<?php
return ob_get_clean();
}
/**
* Validate Text Field.
*
* Make sure the data is escaped correctly, etc.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_text_field( $key, $value ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses_post( trim( stripslashes( $value ) ) );
}
/**
* Validate Price Field.
*
* Make sure the data is escaped correctly, etc.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_price_field( $key, $value ) {
$value = is_null( $value ) ? '' : $value;
return ( '' === $value ) ? '' : wc_format_decimal( trim( stripslashes( $value ) ) );
}
/**
* Validate Decimal Field.
*
* Make sure the data is escaped correctly, etc.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_decimal_field( $key, $value ) {
$value = is_null( $value ) ? '' : $value;
return ( '' === $value ) ? '' : wc_format_decimal( trim( stripslashes( $value ) ) );
}
/**
* Validate Password Field. No input sanitization is used to avoid corrupting passwords.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_password_field( $key, $value ) {
$value = is_null( $value ) ? '' : $value;
return trim( stripslashes( $value ) );
}
/**
* Validate Textarea Field.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_textarea_field( $key, $value ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses(
trim( stripslashes( $value ) ),
array_merge(
array(
'iframe' => array(
'src' => true,
'style' => true,
'id' => true,
'class' => true,
),
),
wp_kses_allowed_html( 'post' )
)
);
}
/**
* Validate Checkbox Field.
*
* If not set, return "no", otherwise return "yes".
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_checkbox_field( $key, $value ) {
return ! is_null( $value ) ? 'yes' : 'no';
}
/**
* Validate Select Field.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string
*/
public function validate_select_field( $key, $value ) {
$value = is_null( $value ) ? '' : $value;
return wc_clean( stripslashes( $value ) );
}
/**
* Validate Multiselect Field.
*
* @param string $key Field key.
* @param string $value Posted Value.
* @return string|array
*/
public function validate_multiselect_field( $key, $value ) {
return is_array( $value ) ? array_map( 'wc_clean', array_map( 'stripslashes', $value ) ) : '';
}
/**
* Validate the data on the "Settings" form.
*
* @deprecated 2.6.0 No longer used.
* @param array $form_fields Array of fields.
*/
public function validate_settings_fields( $form_fields = array() ) {
wc_deprecated_function( 'validate_settings_fields', '2.6' );
}
/**
* Format settings if needed.
*
* @deprecated 2.6.0 Unused.
* @param array $value Value to format.
* @return array
*/
public function format_settings( $value ) {
wc_deprecated_function( 'format_settings', '2.6' );
return $value;
}
}

View File

@ -0,0 +1,569 @@
<?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();
/**
* 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() . ' &times; ' . $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' );
}
}

View File

@ -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 &quot; 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 );
}
}

View File

@ -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

View File

@ -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&section=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&section=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>&hellip;' . 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 ),
)
);
}
}

View File

@ -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&section=keys&create-key=1' ) ) . '" class="add-new-h2">' . 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&section=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&section=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&section=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();

View File

@ -0,0 +1,497 @@
<?php
/**
* Load assets
*
* @package WooCommerce\Admin
* @version 3.7.0
*/
use Automattic\Jetpack\Constants;
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' ) );
}
/**
* 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() ) {
wp_register_style( 'woocommerce-general', WC()->plugin_url() . '/assets/css/woocommerce.css', array(), $version );
wp_style_add_data( 'woocommerce-general', 'rtl', 'replace' );
}
// 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' ) ) {
do_action( 'woocommerce_admin_css' );
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;
$screen = get_current_screen();
$screen_id = $screen ? $screen->id : '';
$wc_screen_id = sanitize_title( __( 'WooCommerce', '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( 'zeroclipboard', WC()->plugin_url() . '/assets/js/zeroclipboard/jquery.zeroclipboard' . $suffix . '.js', array( 'jquery' ), $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' ), $version );
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&hellip;', 'enhanced select', 'woocommerce' ),
'i18n_searching' => _x( 'Searching&hellip;', '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_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 = isset( $locale['decimal_point'] ) ? $locale['decimal_point'] : '.';
$params = array(
/* translators: %s: decimal */
'i18n_decimal_error' => sprintf( __( 'Please enter with one decimal point (%s) without thousand separators.', 'woocommerce' ), $decimal ),
/* translators: %s: price decimal separator */
'i18n_mon_decimal_error' => sprintf( __( 'Please enter 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' ),
'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' => wp_create_nonce( 'woocommerce-toggle-payment-gateway-enabled' ),
),
'urls' => array(
'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 );
}
// Meta boxes.
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' ), $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( __( 'Are you sure you want to link 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( __( 'variation added', 'woocommerce' ) ),
'i18n_variations_added' => esc_js( __( 'variations added', 'woocommerce' ) ),
'i18n_no_variations_added' => esc_js( __( 'No 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( __( '%qty% 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 );
}
if ( in_array( str_replace( 'edit-', '', $screen_id ), wc_get_order_types( 'order-meta-boxes' ) ) ) {
$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&hellip;', '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' ),
)
);
}
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', '' ),
)
);
}
if ( in_array( str_replace( 'edit-', '', $screen_id ), array_merge( array( 'shop_coupon', 'product' ), wc_get_order_types( 'order-meta-boxes' ) ) ) ) {
$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' );
if ( $post_id && in_array( get_post_type( $post_id ), wc_get_order_types( 'order-meta-boxes' ) ) ) {
$order = wc_get_order( $post_id );
if ( $order ) {
$currency = $order->get_currency();
if ( ! $order->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' ),
'remove_attribute' => __( 'Remove this attribute?', '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' ),
'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' => isset( $post->ID ) ? $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' ),
);
wp_localize_script( 'wc-admin-meta-boxes', 'woocommerce_admin_meta_boxes', $params );
}
// Term ordering - only when sorting by term_order.
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 );
}
// 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.
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' );
}
// 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&hellip;', '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&section=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' );
}
}
}
endif;
return new WC_Admin_Assets();

View File

@ -0,0 +1,477 @@
<?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&amp;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&amp;page=product_attributes&amp;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 ) :
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 ) ); ?>&amp;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&amp;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&amp;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 ) ) {
$terms = get_terms( $taxonomy, 'hide_empty=0' );
$terms_string = implode( ', ', wp_list_pluck( $terms, 'name' ) );
if ( $terms_string ) {
echo esc_html( $terms_string );
} else {
echo '<span class="na">&ndash;</span>';
}
} else {
echo '<span class="na">&ndash;</span>';
}
?>
<br /><a href="edit-tags.php?taxonomy=<?php echo esc_attr( wc_attribute_taxonomy_name( $tax->attribute_name ) ); ?>&amp;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
endif;
?>
</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&amp;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
}
}

View File

@ -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();

View File

@ -0,0 +1,213 @@
<?php
/**
* Admin Dashboard - Setup
*
* @package WooCommerce\Admin
* @version 2.1.0
*/
use Automattic\Jetpack\Constants;
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 {
/**
* List of tasks.
*
* @var array
*/
private $tasks = array(
'store_details' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&path=%2Fsetup-wizard',
),
'products' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&task=products',
),
'woocommerce-payments' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&path=%2Fpayments%2Fconnect',
),
'payments' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&task=payments',
),
'tax' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&task=tax',
),
'shipping' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&task=shipping',
),
'appearance' => array(
'completed' => false,
'button_link' => 'admin.php?page=wc-admin&task=appearance',
),
);
/**
* # of completed tasks.
*
* @var int
*/
private $completed_tasks_count = 0;
/**
* WC_Admin_Dashboard_Setup constructor.
*/
public function __construct() {
if ( $this->should_display_widget() ) {
$this->populate_general_tasks();
$this->populate_payment_tasks();
$this->completed_tasks_count = $this->get_completed_tasks_count();
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 = $task['button_link'];
$completed_tasks_count = $this->completed_tasks_count;
$tasks_count = count( $this->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';
}
/**
* Populate tasks from the database.
*/
private function populate_general_tasks() {
$tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() );
foreach ( $tasks as $task ) {
if ( isset( $this->tasks[ $task ] ) ) {
$this->tasks[ $task ]['completed'] = true;
$this->tasks[ $task ]['button_link'] = wc_admin_url( $this->tasks[ $task ]['button_link'] );
}
}
}
/**
* Getter for $tasks
*
* @return array
*/
public function get_tasks() {
return $this->tasks;
}
/**
* Return # of completed tasks
*/
public function get_completed_tasks_count() {
$completed_tasks = array_filter(
$this->tasks,
function( $task ) {
return $task['completed'];
}
);
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['completed'] ) {
return $task;
}
}
return null;
}
/**
* Check to see if we should display the widget
*
* @return bool
*/
private function should_display_widget() {
return WC()->is_wc_admin_active() &&
'yes' !== get_option( 'woocommerce_task_list_complete' ) &&
'yes' !== get_option( 'woocommerce_task_list_hidden' );
}
/**
* Populate payment tasks's visibility and completion
*/
private function populate_payment_tasks() {
$is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
$country = explode( ':', get_option( 'woocommerce_default_country', 'US:CA' ) )[0];
// woocommerce-payments requires its plugin activated and country must be US.
if ( ! $is_woo_payment_installed || 'US' !== $country ) {
unset( $this->tasks['woocommerce-payments'] );
}
// payments can't be used when woocommerce-payments exists and country is US.
if ( $is_woo_payment_installed && 'US' === $country ) {
unset( $this->tasks['payments'] );
}
if ( isset( $this->tasks['payments'] ) ) {
$gateways = WC()->payment_gateways->get_available_payment_gateways();
$enabled_gateways = array_filter(
$gateways,
function ( $gateway ) {
return 'yes' === $gateway->enabled;
}
);
$this->tasks['payments']['completed'] = ! empty( $enabled_gateways );
}
if ( isset( $this->tasks['woocommerce-payments'] ) ) {
$wc_pay_is_connected = false;
if ( class_exists( '\WC_Payments' ) ) {
$wc_payments_gateway = \WC_Payments::get_gateway();
$wc_pay_is_connected = method_exists( $wc_payments_gateway, 'is_connected' )
? $wc_payments_gateway->is_connected()
: false;
}
$this->tasks['woocommerce-payments']['completed'] = $wc_pay_is_connected;
}
}
}
endif;
return new WC_Admin_Dashboard_Setup();

View File

@ -0,0 +1,562 @@
<?php
/**
* Admin Dashboard
*
* @package WooCommerce\Admin
* @version 2.1.0
*/
use Automattic\Jetpack\Constants;
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;
$query = array();
$query['fields'] = "SELECT SUM( order_item_meta.meta_value ) as qty, order_item_meta_2.meta_value as product_id
FROM {$wpdb->posts} as posts";
$query['join'] = "INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.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 posts.post_type IN ( '" . implode( "','", wc_get_order_types( 'order-count' ) ) . "' ) ";
$query['where'] .= "AND posts.post_status IN ( 'wc-" . implode( "','wc-", apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) ) ) . "' ) ";
$query['where'] .= "AND order_item_meta.meta_key = '_qty' ";
$query['where'] .= "AND order_item_meta_2.meta_key = '_product_id' ";
$query['where'] .= "AND posts.post_date >= '" . gmdate( 'Y-m-01', current_time( 'timestamp' ) ) . "' ";
$query['where'] .= "AND posts.post_date <= '" . gmdate( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) . "' ";
$query['groupby'] = 'GROUP BY product_id';
$query['orderby'] = 'ORDER BY qty DESC';
$query['limits'] = 'LIMIT 1';
return $wpdb->get_row( implode( ' ', apply_filters( 'woocommerce_dashboard_status_widget_top_seller_query', $query ) ) ); //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';
$is_wc_admin_disabled = apply_filters( 'woocommerce_admin_disabled', false );
$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();
$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 = (array) wp_count_posts( $type );
$on_hold_count += isset( $counts['wc-on-hold'] ) ? $counts['wc-on-hold'] : 0;
$processing_count += isset( $counts['wc-processing'] ) ? $counts['wc-processing'] : 0;
}
?>
<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();

View File

@ -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 &#8220;%s&#8221; 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&amp;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();

View File

@ -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();

View File

@ -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 &amp; Support', 'woocommerce' ),
'content' =>
'<h2>' . __( 'Help &amp; 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://docs.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?template=4-Bug-report.md" 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/storefront/?utm_source=helptab&utm_medium=product&utm_content=wcthemes&utm_campaign=woocommerceplugin" target="_blank">' . __( 'Official theme', '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();

View File

@ -0,0 +1,306 @@
<?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;
if ( empty( $_POST['import_id'] ) || ! class_exists( 'WXR_Parser' ) ) { // PHPCS: input var ok, CSRF ok.
return;
}
$id = absint( $_POST['import_id'] ); // PHPCS: input var ok.
$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.
'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' => count( $results['imported'] ),
'failed' => count( $results['failed'] ),
'updated' => count( $results['updated'] ),
'skipped' => count( $results['skipped'] ),
)
);
} else {
wp_send_json_success(
array(
'position' => $importer->get_file_position(),
'percentage' => $percent_complete,
'imported' => count( $results['imported'] ),
'failed' => count( $results['failed'] ),
'updated' => count( $results['updated'] ),
'skipped' => count( $results['skipped'] ),
)
);
}
}
}
new WC_Admin_Importers();

View File

@ -0,0 +1,395 @@
<?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 {
/**
* 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() {
$levels = array(
array(
'value' => WC_Log_Levels::EMERGENCY,
'label' => __( 'Emergency', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::ALERT,
'label' => __( 'Alert', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::CRITICAL,
'label' => __( 'Critical', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::ERROR,
'label' => __( 'Error', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::WARNING,
'label' => __( 'Warning', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::NOTICE,
'label' => __( 'Notice', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::INFO,
'label' => __( 'Info', 'woocommerce' ),
),
array(
'value' => WC_Log_Levels::DEBUG,
'label' => __( 'Debug', 'woocommerce' ),
),
);
$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
}
/**
* 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' ),
);
}
/**
* 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 = array(
'emergency' => __( 'Emergency', 'woocommerce' ),
'alert' => __( 'Alert', 'woocommerce' ),
'critical' => __( 'Critical', 'woocommerce' ),
'error' => __( 'Error', 'woocommerce' ),
'warning' => __( 'Warning', 'woocommerce' ),
'notice' => __( 'Notice', 'woocommerce' ),
'info' => __( 'Info', 'woocommerce' ),
'debug' => __( 'Debug', 'woocommerce' ),
);
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 esc_html( $log['message'] );
}
/**
* Source column.
*
* @param array $log
* @return string
*/
public function column_source( $log ) {
return esc_html( $log['source'] );
}
/**
* 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( 'woocommerce_status_log_items_per_page', 10 );
$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
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( 'woocommerce_status_log_items_per_page', 10 );
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( 'woocommerce_status_log_items_per_page', 10 );
$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(),
);
}
}

View File

@ -0,0 +1,428 @@
<?php
/**
* Setup menus in WP admin.
*
* @package WooCommerce\Admin
* @version 2.5.0
*/
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'WC_Admin_Menus', false ) ) {
return new WC_Admin_Menus();
}
/**
* WC_Admin_Menus Class.
*/
class WC_Admin_Menus {
/**
* 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, 'admin_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 );
if ( apply_filters( 'woocommerce_show_addons_page', true ) ) {
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_filter( 'submenu_file', array( $this, 'update_menu_highlight' ), 10, 2 );
add_filter( 'admin_title', array( $this, 'update_my_subscriptions_title' ) );
// 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;
$woocommerce_icon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGZpbGw9IiNhMmFhYjIiIGQ9Ik02MTIuMTkyIDQyNi4zMzZjMC02Ljg5Ni0zLjEzNi01MS42LTI4LTUxLjYtMzcuMzYgMC00Ni43MDQgNzIuMjU2LTQ2LjcwNCA4Mi42MjQgMCAzLjQwOCAzLjE1MiA1OC40OTYgMjguMDMyIDU4LjQ5NiAzNC4xOTItLjAzMiA0Ni42NzItNzIuMjg4IDQ2LjY3Mi04OS41MnptMjAyLjE5MiAwYzAtNi44OTYtMy4xNTItNTEuNi0yOC4wMzItNTEuNi0zNy4yOCAwLTQ2LjYwOCA3Mi4yNTYtNDYuNjA4IDgyLjYyNCAwIDMuNDA4IDMuMDcyIDU4LjQ5NiAyNy45NTIgNTguNDk2IDM0LjE5Mi0uMDMyIDQ2LjY4OC03Mi4yODggNDYuNjg4LTg5LjUyek0xNDEuMjk2Ljc2OGMtNjguMjI0IDAtMTIzLjUwNCA1NS40ODgtMTIzLjUwNCAxMjMuOTJ2NjUwLjcyYzAgNjguNDMyIDU1LjI5NiAxMjMuOTIgMTIzLjUwNCAxMjMuOTJoMzM5LjgwOGwxMjMuNTA0IDEyMy45MzZWODk5LjMyOGgyNzguMDQ4YzY4LjIyNCAwIDEyMy41Mi01NS40NzIgMTIzLjUyLTEyMy45MnYtNjUwLjcyYzAtNjguNDMyLTU1LjI5Ni0xMjMuOTItMTIzLjUyLTEyMy45MmgtNzQxLjM2em01MjYuODY0IDQyMi4xNmMwIDU1LjA4OC0zMS4wODggMTU0Ljg4LTEwMi42NCAxNTQuODgtNi4yMDggMC0xOC40OTYtMy42MTYtMjUuNDI0LTYuMDE2LTMyLjUxMi0xMS4xNjgtNTAuMTkyLTQ5LjY5Ni01Mi4zNTItNjYuMjU2IDAgMC0zLjA3Mi0xNy43OTItMy4wNzItNDAuNzUyIDAtMjIuOTkyIDMuMDcyLTQ1LjMyOCAzLjA3Mi00NS4zMjggMTUuNTUyLTc1LjcyOCA0My41NTItMTA2LjczNiA5Ni40NDgtMTA2LjczNiA1OS4wNzItLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4ek00ODYuNDk2IDMwMi40YzAgMy4zOTItNDMuNTUyIDE0MS4xNjgtNDMuNTUyIDIxMy40MjR2NzUuNzEyYy0yLjU5MiAxMi4wOC00LjE2IDI0LjE0NC0yMS44MjQgMjQuMTQ0LTQ2LjYwOCAwLTg4Ljg4LTE1MS40NzItOTIuMDE2LTE2MS44NC02LjIwOCA2Ljg5Ni02Mi4yNCAxNjEuODQtOTYuNDQ4IDE2MS44NC0yNC44NjQgMC00My41NTItMTEzLjY0OC00Ni42MDgtMTIzLjkzNkMxNzYuNzA0IDQzNi42NzIgMTYwIDMzNC4yMjQgMTYwIDMyNy4zMjhjMC0yMC42NzIgMS4xNTItMzguNzM2IDI2LjA0OC0zOC43MzYgNi4yMDggMCAyMS42IDYuMDY0IDIzLjcxMiAxNy4xNjggMTEuNjQ4IDYyLjAzMiAxNi42ODggMTIwLjUxMiAyOS4xNjggMTg1Ljk2OCAxLjg1NiAyLjkyOCAxLjUwNCA3LjAwOCA0LjU2IDEwLjQzMiAzLjE1Mi0xMC4yODggNjYuOTI4LTE2OC43ODQgOTQuOTYtMTY4Ljc4NCAyMi41NDQgMCAzMC40IDQ0LjU5MiAzMy41MzYgNjEuODI0IDYuMjA4IDIwLjY1NiAxMy4wODggNTUuMjE2IDIyLjQxNiA4Mi43NTIgMC0xMy43NzYgMTIuNDgtMjAzLjEyIDY1LjM5Mi0yMDMuMTIgMTguNTkyLjAzMiAyNi43MDQgNi45MjggMjYuNzA0IDI3LjU2OHpNODcwLjMyIDQyMi45MjhjMCA1NS4wODgtMzEuMDg4IDE1NC44OC0xMDIuNjQgMTU0Ljg4LTYuMTkyIDAtMTguNDQ4LTMuNjE2LTI1LjQyNC02LjAxNi0zMi40MzItMTEuMTY4LTUwLjE3Ni00OS42OTYtNTIuMjg4LTY2LjI1NiAwIDAtMy44ODgtMTcuOTItMy44ODgtNDAuODk2czMuODg4LTQ1LjE4NCAzLjg4OC00NS4xODRjMTUuNTUyLTc1LjcyOCA0My40ODgtMTA2LjczNiA5Ni4zODQtMTA2LjczNiA1OS4xMDQtLjAzMiA4My45NjggNTguNTI4IDgzLjk2OCAxMTAuMjA4eiIvPjwvc3ZnPg==';
if ( current_user_can( 'edit_others_shop_orders' ) ) {
$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' );
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 ( current_user_can( 'edit_others_shop_orders' ) ) {
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' ) );
}
/**
* 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() {
add_submenu_page( 'woocommerce', __( 'WooCommerce status', 'woocommerce' ), __( 'Status', 'woocommerce' ), 'manage_woocommerce', 'wc-status', array( $this, 'status_page' ) );
}
/**
* Addons menu item.
*/
public function addons_menu() {
$count_html = WC_Helper_Updater::get_updates_count_html();
/* translators: %s: extensions count */
$menu_title = sprintf( __( 'My Subscriptions %s', 'woocommerce' ), $count_html );
add_submenu_page( 'woocommerce', __( 'WooCommerce Marketplace', 'woocommerce' ), __( 'Marketplace', 'woocommerce' ), 'manage_woocommerce', 'wc-addons', array( $this, 'addons_page' ) );
add_submenu_page( 'woocommerce', __( 'My WooCommerce.com Subscriptions', 'woocommerce' ), $menu_title, 'manage_woocommerce', 'wc-addons&section=helper', array( $this, 'addons_page' ) );
}
/**
* 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 || current_user_can( 'edit_others_shop_orders' );
}
/**
* 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 ) {
if ( in_array( $option, array( 'woocommerce_keys_per_page', 'woocommerce_webhooks_per_page' ), 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();
}
/**
* 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">
<span class="list-controls">
<a href="<?php echo esc_url( admin_url( 'nav-menus.php?page-tab=all&selectall=1#posttype-woocommerce-endpoints' ) ); ?>" class="select-all"><?php esc_html_e( 'Select all', 'woocommerce' ); ?></a>
</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' ),
)
);
}
/**
* Highlight the My Subscriptions menu item when on that page
*
* @param string $submenu_file The submenu file.
* @param string $parent_file currently opened page.
*
* @return string
*/
public function update_menu_highlight( $submenu_file, $parent_file ) {
if ( 'woocommerce' === $parent_file && isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
$submenu_file = 'wc-addons&section=helper';
}
return $submenu_file;
}
/**
* Update the My Subscriptions document title when on that page.
* We want to maintain existing page URL but add it as a separate page,
* which requires updating it manually.
*
* @param string $admin_title existing page title.
* @return string
*/
public function update_my_subscriptions_title( $admin_title ) {
if (
isset( $_GET['page'] ) && 'wc-addons' === $_GET['page'] &&
isset( $_GET['section'] ) && 'helper' === $_GET['section']
) {
$admin_title = 'My WooCommerce.com Subscriptions';
}
return $admin_title;
}
}
return new WC_Admin_Menus();

View File

@ -0,0 +1,255 @@
<?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;
defined( 'ABSPATH' ) || exit;
/**
* WC_Admin_Meta_Boxes.
*/
class WC_Admin_Meta_Boxes {
/**
* 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( 'save_post', array( $this, 'save_meta_boxes' ), 1, 2 );
/**
* Save Order Meta Boxes.
*
* In order:
* Save the order items.
* Save the order totals.
* Save the order downloads.
* Save order data - also updates status and sends out admin emails if needed. Last to show latest data.
* Save actions - sends out other emails. Last to show latest data.
*/
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Items::save', 10 );
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Downloads::save', 30, 2 );
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Data::save', 40 );
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Actions::save', 50, 2 );
// 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, 'save_errors' ) );
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.
*/
public function save_errors() {
update_option( 'woocommerce_meta_box_errors', self::$meta_box_errors );
}
/**
* Show any stored error messages.
*/
public function output_errors() {
$errors = array_filter( (array) get_option( 'woocommerce_meta_box_errors' ) );
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( 'woocommerce_meta_box_errors' );
}
}
/**
* 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 );
/* Translators: %s order type name. */
add_meta_box( 'woocommerce-order-data', sprintf( __( '%s data', 'woocommerce' ), $order_type_object->labels->singular_name ), 'WC_Meta_Box_Order_Data::output', $type, 'normal', 'high' );
add_meta_box( 'woocommerce-order-items', __( 'Items', 'woocommerce' ), 'WC_Meta_Box_Order_Items::output', $type, 'normal', 'high' );
/* Translators: %s order type name. */
add_meta_box( 'woocommerce-order-notes', sprintf( __( '%s notes', 'woocommerce' ), $order_type_object->labels->singular_name ), 'WC_Meta_Box_Order_Notes::output', $type, 'side', 'default' );
add_meta_box( 'woocommerce-order-downloads', __( 'Downloadable product permissions', 'woocommerce' ) . wc_help_tip( __( 'Note: Permissions for order items will automatically be granted when the order status changes to processing/completed.', 'woocommerce' ) ), 'WC_Meta_Box_Order_Downloads::output', $type, 'normal', 'default' );
/* Translators: %s order type name. */
add_meta_box( 'woocommerce-order-actions', sprintf( __( '%s actions', 'woocommerce' ), $order_type_object->labels->singular_name ), 'WC_Meta_Box_Order_Actions::output', $type, 'side', 'high' );
}
// 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' );
}
}
/**
* 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 ) || 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 ) ) {
do_action( 'woocommerce_process_shop_order_meta', $post_id, $post );
} elseif ( in_array( $post->post_type, array( 'product', 'shop_coupon' ), true ) ) {
do_action( 'woocommerce_process_' . $post->post_type . '_meta', $post_id, $post );
}
}
/**
* Remove block-based templates from the list of available templates for products.
*
* @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 || ! function_exists( 'gutenberg_get_block_template' ) ) {
return $templates;
}
$theme = wp_get_theme()->get_stylesheet();
$filtered_templates = array();
foreach ( $templates as $template_key => $template_name ) {
$gutenberg_template = gutenberg_get_block_template( $theme . '//' . $template_key );
if ( ! $gutenberg_template ) {
$filtered_templates[ $template_key ] = $template_name;
}
}
return $filtered_templates;
}
}
new WC_Admin_Meta_Boxes();

View File

@ -0,0 +1,618 @@
<?php
/**
* Display notices in admin
*
* @package WooCommerce\Admin
* @version 3.4.0
*/
use Automattic\Jetpack\Constants;
defined( 'ABSPATH' ) || exit;
/**
* WC_Admin_Notices Class.
*/
class WC_Admin_Notices {
/**
* Stores notices.
*
* @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',
WC_PHP_MIN_REQUIREMENTS_NOTICE => 'wp_php_min_requirements_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',
);
/**
* Constructor.
*/
public static function init() {
self::$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( 'wp_loaded', array( __CLASS__, 'hide_notices' ) );
// @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 notices to DB
*/
public static function store_notices() {
update_option( 'woocommerce_admin_notices', self::get_notices() );
}
/**
* Get notices
*
* @return array
*/
public static function get_notices() {
return self::$notices;
}
/**
* Remove all notices.
*/
public static function remove_all_notices() {
self::$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();
}
/**
* 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::$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::$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();
}
}
/**
* 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'] ) ) { // WPCS: input var ok, CSRF ok.
if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wc_notice_nonce'] ) ), 'woocommerce_hide_notices_nonce' ) ) { // WPCS: input var ok, CSRF ok.
wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
}
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You don&#8217;t have permission to do this.', 'woocommerce' ) );
}
$hide_notice = sanitize_text_field( wp_unslash( $_GET['wc-hide-notice'] ) ); // WPCS: input var ok, CSRF ok.
self::remove_notice( $hide_notice );
update_user_meta( get_current_user_id(), 'dismissed_' . $hide_notice . '_notice', true );
do_action( 'woocommerce_hide_' . $hide_notice . '_notice' );
}
}
/**
* Add notices + styles if needed.
*/
public static function add_notices() {
$notices = self::get_notices();
if ( empty( $notices ) ) {
return;
}
$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 dirname( __FILE__ ) . '/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' );
if ( $next_scheduled_date || ! empty( $_GET['do_update_woocommerce'] ) ) { // WPCS: input var ok, CSRF ok.
include dirname( __FILE__ ) . '/views/html-notice-updating.php';
} else {
include dirname( __FILE__ ) . '/views/html-notice-update.php';
}
} else {
include dirname( __FILE__ ) . '/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', __( '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 dirname( __FILE__ ) . '/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 dirname( __FILE__ ) . '/views/html-notice-legacy-shipping.php';
} else {
self::remove_notice( 'template_files' );
}
}
/**
* No shipping methods.
*/
public static function no_shipping_methods_notice() {
if ( wc_shipping_enabled() && ( empty( $_GET['page'] ) || empty( $_GET['tab'] ) || 'wc-settings' !== $_GET['page'] || 'shipping' !== $_GET['tab'] ) ) { // WPCS: input var ok, CSRF ok.
$product_count = wp_count_posts( 'product' );
$method_count = wc_get_shipping_method_count();
if ( $product_count->publish > 0 && 0 === $method_count ) {
include dirname( __FILE__ ) . '/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 dirname( __FILE__ ) . '/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 dirname( __FILE__ ) . '/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 relevent.
if ( ! wc_update_product_lookup_tables_is_running() ) {
self::remove_notice( 'regenerating_lookup_table' );
return;
}
include dirname( __FILE__ ) . '/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.
*
* @since 3.6.5
* @return void
*/
public static function wp_php_min_requirements_notice() {
if ( apply_filters( 'woocommerce_hide_php_wp_nag', get_user_meta( get_current_user_id(), 'dismissed_' . WC_PHP_MIN_REQUIREMENTS_NOTICE . '_notice', true ) ) ) {
self::remove_notice( WC_PHP_MIN_REQUIREMENTS_NOTICE );
return;
}
$old_php = version_compare( phpversion(), WC_NOTICE_MIN_PHP_VERSION, '<' );
$old_wp = version_compare( get_bloginfo( 'version' ), WC_NOTICE_MIN_WP_VERSION, '<' );
// Both PHP and WordPress up to date version => no notice.
if ( ! $old_php && ! $old_wp ) {
return;
}
if ( $old_php && $old_wp ) {
$msg = sprintf(
/* translators: 1: Minimum PHP version 2: Minimum WordPress version */
__( 'Update required: WooCommerce will soon require PHP version %1$s and WordPress version %2$s or newer.', 'woocommerce' ),
WC_NOTICE_MIN_PHP_VERSION,
WC_NOTICE_MIN_WP_VERSION
);
} elseif ( $old_php ) {
$msg = sprintf(
/* translators: %s: Minimum PHP version */
__( 'Update required: WooCommerce will soon require PHP version %s or newer.', 'woocommerce' ),
WC_NOTICE_MIN_PHP_VERSION
);
} elseif ( $old_wp ) {
$msg = sprintf(
/* translators: %s: Minimum WordPress version */
__( 'Update required: WooCommerce will soon require WordPress version %s or newer.', 'woocommerce' ),
WC_NOTICE_MIN_WP_VERSION
);
}
include dirname( __FILE__ ) . '/views/html-notice-wp-php-minimum-requirements.php';
}
/**
* 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' );
}
}
/**
* 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 dirname( __FILE__ ) . '/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 dirname( __FILE__ ) . '/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 dirname( __FILE__ ) . '/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 dirname( __FILE__ ) . '/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();

View File

@ -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();

View File

@ -0,0 +1,287 @@
<?php
/**
* Adds and controls pointers for contextual help/tutorials
*
* @package WooCommerce\Admin\Pointers
* @version 2.4.0
*/
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();
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;
}
// These pointers will chain - they will not be shown at once.
$pointers = array(
'pointers' => array(
'title' => array(
'target' => '#title',
'next' => 'content',
'next_trigger' => array(
'target' => '#title',
'event' => 'input',
),
'options' => array(
'content' => '<h3>' . esc_html__( 'Product name', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'Give your new product a name here. This is a required field and will be what your customers will see in your store.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'top',
'align' => 'left',
),
),
),
'content' => array(
'target' => '#wp-content-editor-container',
'next' => 'product-type',
'next_trigger' => array(),
'options' => array(
'content' => '<h3>' . esc_html__( 'Product description', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'This is your products main body of content. Here you should describe your product in detail.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'bottom',
'align' => 'middle',
),
),
),
'product-type' => array(
'target' => '#product-type',
'next' => 'virtual',
'next_trigger' => array(
'target' => '#product-type',
'event' => 'change blur click',
),
'options' => array(
'content' => '<h3>' . esc_html__( 'Choose product type', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'Choose a type for this product. Simple is suitable for most physical goods and services (we recommend setting up a simple product for now).', 'woocommerce' ) . '</p>' .
'<p>' . esc_html__( 'Variable is for more complex products such as t-shirts with multiple sizes.', 'woocommerce' ) . '</p>' .
'<p>' . esc_html__( 'Grouped products are for grouping several simple products into one.', 'woocommerce' ) . '</p>' .
'<p>' . esc_html__( 'Finally, external products are for linking off-site.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'bottom',
'align' => 'middle',
),
),
),
'virtual' => array(
'target' => '#_virtual',
'next' => 'downloadable',
'next_trigger' => array(
'target' => '#_virtual',
'event' => 'change',
),
'options' => array(
'content' => '<h3>' . esc_html__( 'Virtual products', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'Check the "Virtual" box if this is a non-physical item, for example a service, which does not need shipping.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'bottom',
'align' => 'middle',
),
),
),
'downloadable' => array(
'target' => '#_downloadable',
'next' => 'regular_price',
'next_trigger' => array(
'target' => '#_downloadable',
'event' => 'change',
),
'options' => array(
'content' => '<h3>' . esc_html__( 'Downloadable products', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'If purchasing this product gives a customer access to a downloadable file, e.g. software, check this box.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'bottom',
'align' => 'middle',
),
),
),
'regular_price' => array(
'target' => '#_regular_price',
'next' => 'postexcerpt',
'next_trigger' => array(
'target' => '#_regular_price',
'event' => 'input',
),
'options' => array(
'content' => '<h3>' . esc_html__( 'Prices', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'Next you need to give your product a price.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'bottom',
'align' => 'middle',
),
),
),
'postexcerpt' => array(
'target' => '#postexcerpt',
'next' => 'postimagediv',
'next_trigger' => array(
'target' => '#postexcerpt',
'event' => 'input',
),
'options' => array(
'content' => '<h3>' . esc_html__( 'Product short description', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'Add a quick summary for your product here. This will appear on the product page under the product name.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'bottom',
'align' => 'middle',
),
),
),
'postimagediv' => array(
'target' => '#postimagediv',
'next' => 'product_tag',
'options' => array(
'content' => '<h3>' . esc_html__( 'Product images', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( "Upload or assign an image to your product here. This image will be shown in your store's catalog.", 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'right',
'align' => 'middle',
),
),
),
'product_tag' => array(
'target' => '#tagsdiv-product_tag',
'next' => 'product_catdiv',
'options' => array(
'content' => '<h3>' . esc_html__( 'Product tags', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'You can optionally "tag" your products here. Tags are a method of labeling your products to make them easier for customers to find.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'right',
'align' => 'middle',
),
),
),
'product_catdiv' => array(
'target' => '#product_catdiv',
'next' => 'submitdiv',
'options' => array(
'content' => '<h3>' . esc_html__( 'Product categories', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'Optionally assign categories to your products to make them easier to browse through and find in your store.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'right',
'align' => 'middle',
),
),
),
'submitdiv' => array(
'target' => '#submitdiv',
'next' => '',
'options' => array(
'content' => '<h3>' . esc_html__( 'Publish your product!', 'woocommerce' ) . '</h3>' .
'<p>' . esc_html__( 'When you are finished editing your product, hit the "Publish" button to publish your product to your store.', 'woocommerce' ) . '</p>',
'position' => array(
'edge' => 'right',
'align' => 'middle',
),
),
),
),
);
$this->enqueue_pointers( $pointers );
}
/**
* Enqueue pointers and add script to page.
*
* @param array $pointers Pointers data.
*/
public function enqueue_pointers( $pointers ) {
$pointers = rawurlencode( wp_json_encode( $pointers ) );
wp_enqueue_style( 'wp-pointer' );
wp_enqueue_script( 'wp-pointer' );
wc_enqueue_js(
"jQuery( function( $ ) {
var wc_pointers = JSON.parse( decodeURIComponent( '{$pointers}' ) );
setTimeout( init_wc_pointers, 800 );
function init_wc_pointers() {
$.each( wc_pointers.pointers, function( i ) {
show_wc_pointer( i );
return false;
});
}
function show_wc_pointer( id ) {
var pointer = wc_pointers.pointers[ id ];
var options = $.extend( pointer.options, {
pointerClass: 'wp-pointer wc-pointer',
close: function() {
if ( pointer.next ) {
show_wc_pointer( pointer.next );
}
},
buttons: function( event, t ) {
var close = '" . esc_js( __( 'Dismiss', 'woocommerce' ) ) . "',
next = '" . esc_js( __( 'Next', 'woocommerce' ) ) . "',
button = $( '<a class=\"close\" href=\"#\">' + close + '</a>' ),
button2 = $( '<a class=\"button button-primary\" href=\"#\">' + next + '</a>' ),
wrapper = $( '<div class=\"wc-pointer-buttons\" />' );
button.on( 'click.pointer', function(e) {
e.preventDefault();
t.element.pointer('destroy');
});
button2.on( 'click.pointer', function(e) {
e.preventDefault();
t.element.pointer('close');
});
wrapper.append( button );
wrapper.append( button2 );
return wrapper;
},
} );
var this_pointer = $( pointer.target ).pointer( options );
this_pointer.pointer( 'open' );
if ( pointer.next_trigger ) {
$( pointer.next_trigger.target ).on( pointer.next_trigger.event, function() {
setTimeout( function() { this_pointer.pointer( 'close' ); }, 400 );
});
}
}
});"
);
}
}
new WC_Admin_Pointers();

View File

@ -0,0 +1,998 @@
<?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( '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' ) );
// Uploads.
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' ) );
// 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: %s: Product view URL. */
1 => sprintf( __( 'Product updated. <a href="%s">View Product</a>', 'woocommerce' ), esc_url( get_permalink( $post->ID ) ) ),
2 => __( 'Custom field updated.', 'woocommerce' ),
3 => __( 'Custom field deleted.', 'woocommerce' ),
4 => __( 'Product updated.', 'woocommerce' ),
5 => __( 'Revision restored.', 'woocommerce' ),
/* translators: %s: product url */
6 => sprintf( __( 'Product published. <a href="%s">View Product</a>', 'woocommerce' ), esc_url( get_permalink( $post->ID ) ) ),
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['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( $post->post_date ) ) . '</strong>'
),
10 => __( 'Order draft updated.', 'woocommerce' ),
11 => __( 'Order updated and sent.', 'woocommerce' ),
);
$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;
}
/**
* 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 );
}
}
$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 = wc_clean( 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 && 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
}
/**
* 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() {
do_action( 'media_upload_file' );
}
/**
* 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://docs.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 = $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 ) {
$percent = $price / 100;
$new_price = max( 0, $regular_price - ( NumberUtil::round( $regular_price * $percent, wc_get_price_decimals() ) ) );
} else {
$new_price = max( 0, $regular_price - $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();

View File

@ -0,0 +1,252 @@
<?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&hellip;', '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&hellip;', '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 ) {
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 ] ) );
}
}
}
}
/**
* 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();

View File

@ -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();
}
}

View File

@ -0,0 +1,941 @@
<?php
/**
* WooCommerce Admin Settings Class
*
* @package WooCommerce\Admin
* @version 3.4.0
*/
use Automattic\Jetpack\Constants;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
/**
* WC_Admin_Settings Class.
*/
class WC_Admin_Settings {
/**
* Setting pages.
*
* @var array
*/
private static $settings = array();
/**
* Error messages.
*
* @var array
*/
private static $errors = array();
/**
* Update messages.
*
* @var array
*/
private static $messages = array();
/**
* Include the settings page classes.
*/
public static function get_settings_pages() {
if ( empty( self::$settings ) ) {
$settings = array();
include_once dirname( __FILE__ ) . '/settings/class-wc-settings-page.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-general.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-products.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-tax.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-shipping.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-payment-gateways.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-accounts.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-emails.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-integrations.php';
$settings[] = include __DIR__ . '/settings/class-wc-settings-advanced.php';
self::$settings = apply_filters( 'woocommerce_get_settings_pages', $settings );
}
return self::$settings;
}
/**
* Save the settings.
*/
public static function save() {
global $current_tab;
check_admin_referer( 'woocommerce-settings' );
// Trigger actions.
do_action( 'woocommerce_settings_save_' . $current_tab );
do_action( 'woocommerce_update_options_' . $current_tab );
do_action( 'woocommerce_update_options' );
self::add_message( __( 'Your settings have been saved.', 'woocommerce' ) );
self::check_download_folder_protection();
// Clear any unwanted data and flush rules.
update_option( 'woocommerce_queue_flush_rewrite_rules', 'yes' );
WC()->query->init_query_vars();
WC()->query->add_endpoints();
do_action( 'woocommerce_settings_saved' );
}
/**
* Add a message.
*
* @param string $text Message.
*/
public static function add_message( $text ) {
self::$messages[] = $text;
}
/**
* Add an error.
*
* @param string $text Message.
*/
public static function add_error( $text ) {
self::$errors[] = $text;
}
/**
* Output messages + errors.
*/
public static function show_messages() {
if ( count( self::$errors ) > 0 ) {
foreach ( self::$errors as $error ) {
echo '<div id="message" class="error inline"><p><strong>' . esc_html( $error ) . '</strong></p></div>';
}
} elseif ( count( self::$messages ) > 0 ) {
foreach ( self::$messages as $message ) {
echo '<div id="message" class="updated inline"><p><strong>' . esc_html( $message ) . '</strong></p></div>';
}
}
}
/**
* Settings page.
*
* Handles the display of the main woocommerce settings page in admin.
*/
public static function output() {
global $current_section, $current_tab;
$suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min';
do_action( 'woocommerce_settings_start' );
wp_enqueue_script( 'woocommerce_settings', WC()->plugin_url() . '/assets/js/admin/settings' . $suffix . '.js', array( 'jquery', 'wp-util', 'jquery-ui-datepicker', 'jquery-ui-sortable', 'iris', 'selectWoo' ), WC()->version, true );
wp_localize_script(
'woocommerce_settings',
'woocommerce_settings_params',
array(
'i18n_nav_warning' => __( 'The changes you made will be lost if you navigate away from this page.', 'woocommerce' ),
'i18n_moved_up' => __( 'Item moved up', 'woocommerce' ),
'i18n_moved_down' => __( 'Item moved down', 'woocommerce' ),
'i18n_no_specific_countries_selected' => __( 'Selecting no country / region to sell to prevents from completing the checkout. Continue anyway?', 'woocommerce' ),
)
);
// Get tabs for the settings page.
$tabs = apply_filters( 'woocommerce_settings_tabs_array', array() );
include dirname( __FILE__ ) . '/views/html-admin-settings.php';
}
/**
* Get a setting from the settings API.
*
* @param string $option_name Option name.
* @param mixed $default Default value.
* @return mixed
*/
public static function get_option( $option_name, $default = '' ) {
if ( ! $option_name ) {
return $default;
}
// Array value.
if ( strstr( $option_name, '[' ) ) {
parse_str( $option_name, $option_array );
// Option name is first key.
$option_name = current( array_keys( $option_array ) );
// Get value.
$option_values = get_option( $option_name, '' );
$key = key( $option_array[ $option_name ] );
if ( isset( $option_values[ $key ] ) ) {
$option_value = $option_values[ $key ];
} else {
$option_value = null;
}
} else {
// Single value.
$option_value = get_option( $option_name, null );
}
if ( is_array( $option_value ) ) {
$option_value = wp_unslash( $option_value );
} elseif ( ! is_null( $option_value ) ) {
$option_value = stripslashes( $option_value );
}
return ( null === $option_value ) ? $default : $option_value;
}
/**
* Output admin fields.
*
* Loops through the woocommerce options array and outputs each field.
*
* @param array[] $options Opens array to output.
*/
public static function output_fields( $options ) {
foreach ( $options as $value ) {
if ( ! isset( $value['type'] ) ) {
continue;
}
if ( ! isset( $value['id'] ) ) {
$value['id'] = '';
}
if ( ! isset( $value['title'] ) ) {
$value['title'] = isset( $value['name'] ) ? $value['name'] : '';
}
if ( ! isset( $value['class'] ) ) {
$value['class'] = '';
}
if ( ! isset( $value['css'] ) ) {
$value['css'] = '';
}
if ( ! isset( $value['default'] ) ) {
$value['default'] = '';
}
if ( ! isset( $value['desc'] ) ) {
$value['desc'] = '';
}
if ( ! isset( $value['desc_tip'] ) ) {
$value['desc_tip'] = false;
}
if ( ! isset( $value['placeholder'] ) ) {
$value['placeholder'] = '';
}
if ( ! isset( $value['suffix'] ) ) {
$value['suffix'] = '';
}
if ( ! isset( $value['value'] ) ) {
$value['value'] = self::get_option( $value['id'], $value['default'] );
}
// Custom attribute handling.
$custom_attributes = array();
if ( ! empty( $value['custom_attributes'] ) && is_array( $value['custom_attributes'] ) ) {
foreach ( $value['custom_attributes'] as $attribute => $attribute_value ) {
$custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"';
}
}
// Description handling.
$field_description = self::get_field_description( $value );
$description = $field_description['description'];
$tooltip_html = $field_description['tooltip_html'];
// Switch based on type.
switch ( $value['type'] ) {
// Section Titles.
case 'title':
if ( ! empty( $value['title'] ) ) {
echo '<h2>' . esc_html( $value['title'] ) . '</h2>';
}
if ( ! empty( $value['desc'] ) ) {
echo '<div id="' . esc_attr( sanitize_title( $value['id'] ) ) . '-description">';
echo wp_kses_post( wpautop( wptexturize( $value['desc'] ) ) );
echo '</div>';
}
echo '<table class="form-table">' . "\n\n";
if ( ! empty( $value['id'] ) ) {
do_action( 'woocommerce_settings_' . sanitize_title( $value['id'] ) );
}
break;
// Section Ends.
case 'sectionend':
if ( ! empty( $value['id'] ) ) {
do_action( 'woocommerce_settings_' . sanitize_title( $value['id'] ) . '_end' );
}
echo '</table>';
if ( ! empty( $value['id'] ) ) {
do_action( 'woocommerce_settings_' . sanitize_title( $value['id'] ) . '_after' );
}
break;
// Standard text inputs and subtypes like 'number'.
case 'text':
case 'password':
case 'datetime':
case 'datetime-local':
case 'date':
case 'month':
case 'time':
case 'week':
case 'number':
case 'email':
case 'url':
case 'tel':
$option_value = $value['value'];
?><tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ); ?>">
<input
name="<?php echo esc_attr( $value['id'] ); ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
type="<?php echo esc_attr( $value['type'] ); ?>"
style="<?php echo esc_attr( $value['css'] ); ?>"
value="<?php echo esc_attr( $option_value ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
placeholder="<?php echo esc_attr( $value['placeholder'] ); ?>"
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
/><?php echo esc_html( $value['suffix'] ); ?> <?php echo $description; // WPCS: XSS ok. ?>
</td>
</tr>
<?php
break;
// Color picker.
case 'color':
$option_value = $value['value'];
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ); ?>">&lrm;
<span class="colorpickpreview" style="background: <?php echo esc_attr( $option_value ); ?>">&nbsp;</span>
<input
name="<?php echo esc_attr( $value['id'] ); ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
type="text"
dir="ltr"
style="<?php echo esc_attr( $value['css'] ); ?>"
value="<?php echo esc_attr( $option_value ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>colorpick"
placeholder="<?php echo esc_attr( $value['placeholder'] ); ?>"
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
/>&lrm; <?php echo $description; // WPCS: XSS ok. ?>
<div id="colorPickerDiv_<?php echo esc_attr( $value['id'] ); ?>" class="colorpickdiv" style="z-index: 100;background:#eee;border:1px solid #ccc;position:absolute;display:none;"></div>
</td>
</tr>
<?php
break;
// Textarea.
case 'textarea':
$option_value = $value['value'];
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ); ?>">
<?php echo $description; // WPCS: XSS ok. ?>
<textarea
name="<?php echo esc_attr( $value['id'] ); ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
style="<?php echo esc_attr( $value['css'] ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
placeholder="<?php echo esc_attr( $value['placeholder'] ); ?>"
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
><?php echo esc_textarea( $option_value ); // WPCS: XSS ok. ?></textarea>
</td>
</tr>
<?php
break;
// Select boxes.
case 'select':
case 'multiselect':
$option_value = $value['value'];
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ); ?>">
<select
name="<?php echo esc_attr( $value['id'] ); ?><?php echo ( 'multiselect' === $value['type'] ) ? '[]' : ''; ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
style="<?php echo esc_attr( $value['css'] ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
<?php echo 'multiselect' === $value['type'] ? 'multiple="multiple"' : ''; ?>
>
<?php
foreach ( $value['options'] as $key => $val ) {
?>
<option value="<?php echo esc_attr( $key ); ?>"
<?php
if ( is_array( $option_value ) ) {
selected( in_array( (string) $key, $option_value, true ), true );
} else {
selected( $option_value, (string) $key );
}
?>
><?php echo esc_html( $val ); ?></option>
<?php
}
?>
</select> <?php echo $description; // WPCS: XSS ok. ?>
</td>
</tr>
<?php
break;
// Radio inputs.
case 'radio':
$option_value = $value['value'];
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ); ?>">
<fieldset>
<?php echo $description; // WPCS: XSS ok. ?>
<ul>
<?php
foreach ( $value['options'] as $key => $val ) {
?>
<li>
<label><input
name="<?php echo esc_attr( $value['id'] ); ?>"
value="<?php echo esc_attr( $key ); ?>"
type="radio"
style="<?php echo esc_attr( $value['css'] ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
<?php checked( $key, $option_value ); ?>
/> <?php echo esc_html( $val ); ?></label>
</li>
<?php
}
?>
</ul>
</fieldset>
</td>
</tr>
<?php
break;
// Checkbox input.
case 'checkbox':
$option_value = $value['value'];
$visibility_class = array();
if ( ! isset( $value['hide_if_checked'] ) ) {
$value['hide_if_checked'] = false;
}
if ( ! isset( $value['show_if_checked'] ) ) {
$value['show_if_checked'] = false;
}
if ( 'yes' === $value['hide_if_checked'] || 'yes' === $value['show_if_checked'] ) {
$visibility_class[] = 'hidden_option';
}
if ( 'option' === $value['hide_if_checked'] ) {
$visibility_class[] = 'hide_options_if_checked';
}
if ( 'option' === $value['show_if_checked'] ) {
$visibility_class[] = 'show_options_if_checked';
}
if ( ! isset( $value['checkboxgroup'] ) || 'start' === $value['checkboxgroup'] ) {
?>
<tr valign="top" class="<?php echo esc_attr( implode( ' ', $visibility_class ) ); ?>">
<th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ); ?></th>
<td class="forminp forminp-checkbox">
<fieldset>
<?php
} else {
?>
<fieldset class="<?php echo esc_attr( implode( ' ', $visibility_class ) ); ?>">
<?php
}
if ( ! empty( $value['title'] ) ) {
?>
<legend class="screen-reader-text"><span><?php echo esc_html( $value['title'] ); ?></span></legend>
<?php
}
?>
<label for="<?php echo esc_attr( $value['id'] ); ?>">
<input
name="<?php echo esc_attr( $value['id'] ); ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
type="checkbox"
class="<?php echo esc_attr( isset( $value['class'] ) ? $value['class'] : '' ); ?>"
value="1"
<?php checked( $option_value, 'yes' ); ?>
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
/> <?php echo $description; // WPCS: XSS ok. ?>
</label> <?php echo $tooltip_html; // WPCS: XSS ok. ?>
<?php
if ( ! isset( $value['checkboxgroup'] ) || 'end' === $value['checkboxgroup'] ) {
?>
</fieldset>
</td>
</tr>
<?php
} else {
?>
</fieldset>
<?php
}
break;
// Image width settings. @todo deprecate and remove in 4.0. No longer needed by core.
case 'image_width':
$image_size = str_replace( '_image_size', '', $value['id'] );
$size = wc_get_image_size( $image_size );
$width = isset( $size['width'] ) ? $size['width'] : $value['default']['width'];
$height = isset( $size['height'] ) ? $size['height'] : $value['default']['height'];
$crop = isset( $size['crop'] ) ? $size['crop'] : $value['default']['crop'];
$disabled_attr = '';
$disabled_message = '';
if ( has_filter( 'woocommerce_get_image_size_' . $image_size ) ) {
$disabled_attr = 'disabled="disabled"';
$disabled_message = '<p><small>' . esc_html__( 'The settings of this image size have been disabled because its values are being overwritten by a filter.', 'woocommerce' ) . '</small></p>';
}
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html . $disabled_message; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp image_width_settings">
<input name="<?php echo esc_attr( $value['id'] ); ?>[width]" <?php echo $disabled_attr; // WPCS: XSS ok. ?> id="<?php echo esc_attr( $value['id'] ); ?>-width" type="text" size="3" value="<?php echo esc_attr( $width ); ?>" /> &times; <input name="<?php echo esc_attr( $value['id'] ); ?>[height]" <?php echo $disabled_attr; // WPCS: XSS ok. ?> id="<?php echo esc_attr( $value['id'] ); ?>-height" type="text" size="3" value="<?php echo esc_attr( $height ); ?>" />px
<label><input name="<?php echo esc_attr( $value['id'] ); ?>[crop]" <?php echo $disabled_attr; // WPCS: XSS ok. ?> id="<?php echo esc_attr( $value['id'] ); ?>-crop" type="checkbox" value="1" <?php checked( 1, $crop ); ?> /> <?php esc_html_e( 'Hard crop?', 'woocommerce' ); ?></label>
</td>
</tr>
<?php
break;
// Single page selects.
case 'single_select_page':
$args = array(
'name' => $value['id'],
'id' => $value['id'],
'sort_column' => 'menu_order',
'sort_order' => 'ASC',
'show_option_none' => ' ',
'class' => $value['class'],
'echo' => false,
'selected' => absint( $value['value'] ),
'post_status' => 'publish,private,draft',
);
if ( isset( $value['args'] ) ) {
$args = wp_parse_args( $value['args'], $args );
}
?>
<tr valign="top" class="single_select_page">
<th scope="row" class="titledesc">
<label><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<?php echo str_replace( ' id=', " data-placeholder='" . esc_attr__( 'Select a page&hellip;', 'woocommerce' ) . "' style='" . $value['css'] . "' class='" . $value['class'] . "' id=", wp_dropdown_pages( $args ) ); // WPCS: XSS ok. ?> <?php echo $description; // WPCS: XSS ok. ?>
</td>
</tr>
<?php
break;
case 'single_select_page_with_search':
$option_value = $value['value'];
$page = get_post( $option_value );
if ( ! is_null( $page ) ) {
$page = get_post( $option_value );
$option_display_name = sprintf(
/* translators: 1: page name 2: page ID */
__( '%1$s (ID: %2$s)', 'woocommerce' ),
$page->post_title,
$option_value
);
}
?>
<tr valign="top" class="single_select_page">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></label>
</th>
<td class="forminp forminp-<?php echo esc_attr( sanitize_title( $value['type'] ) ); ?>">
<select
name="<?php echo esc_attr( $value['id'] ); ?>"
id="<?php echo esc_attr( $value['id'] ); ?>"
style="<?php echo esc_attr( $value['css'] ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
<?php echo implode( ' ', $custom_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
data-placeholder="<?php esc_attr_e( 'Search for a page&hellip;', 'woocommerce' ); ?>"
data-allow_clear="true"
data-exclude="<?php echo wc_esc_json( wp_json_encode( $value['args']['exclude'] ) ); ?>"
>
<option value=""></option>
<?php if ( ! is_null( $page ) ) { ?>
<option value="<?php echo esc_attr( $option_value ); ?>" selected="selected">
<?php echo wp_strip_all_tags( $option_display_name ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</option>
<?php } ?>
</select> <?php echo $description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
<?php
break;
// Single country selects.
case 'single_select_country':
$country_setting = (string) $value['value'];
if ( strstr( $country_setting, ':' ) ) {
$country_setting = explode( ':', $country_setting );
$country = current( $country_setting );
$state = end( $country_setting );
} else {
$country = $country_setting;
$state = '*';
}
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp"><select name="<?php echo esc_attr( $value['id'] ); ?>" style="<?php echo esc_attr( $value['css'] ); ?>" data-placeholder="<?php esc_attr_e( 'Choose a country / region&hellip;', 'woocommerce' ); ?>" aria-label="<?php esc_attr_e( 'Country / Region', 'woocommerce' ); ?>" class="wc-enhanced-select">
<?php WC()->countries->country_dropdown_options( $country, $state ); ?>
</select> <?php echo $description; // WPCS: XSS ok. ?>
</td>
</tr>
<?php
break;
// Country multiselects.
case 'multi_select_countries':
$selections = (array) $value['value'];
if ( ! empty( $value['options'] ) ) {
$countries = $value['options'];
} else {
$countries = WC()->countries->countries;
}
asort( $countries );
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<select multiple="multiple" name="<?php echo esc_attr( $value['id'] ); ?>[]" style="width:350px" data-placeholder="<?php esc_attr_e( 'Choose countries / regions&hellip;', 'woocommerce' ); ?>" aria-label="<?php esc_attr_e( 'Country / Region', 'woocommerce' ); ?>" class="wc-enhanced-select">
<?php
if ( ! empty( $countries ) ) {
foreach ( $countries as $key => $val ) {
echo '<option value="' . esc_attr( $key ) . '"' . wc_selected( $key, $selections ) . '>' . esc_html( $val ) . '</option>'; // WPCS: XSS ok.
}
}
?>
</select> <?php echo ( $description ) ? $description : ''; // WPCS: XSS ok. ?> <br /><a class="select_all button" href="#"><?php esc_html_e( 'Select all', 'woocommerce' ); ?></a> <a class="select_none button" href="#"><?php esc_html_e( 'Select none', 'woocommerce' ); ?></a>
</td>
</tr>
<?php
break;
// Days/months/years selector.
case 'relative_date_selector':
$periods = array(
'days' => __( 'Day(s)', 'woocommerce' ),
'weeks' => __( 'Week(s)', 'woocommerce' ),
'months' => __( 'Month(s)', 'woocommerce' ),
'years' => __( 'Year(s)', 'woocommerce' ),
);
$option_value = wc_parse_relative_date_option( $value['value'] );
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php echo $tooltip_html; // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<input
name="<?php echo esc_attr( $value['id'] ); ?>[number]"
id="<?php echo esc_attr( $value['id'] ); ?>"
type="number"
style="width: 80px;"
value="<?php echo esc_attr( $option_value['number'] ); ?>"
class="<?php echo esc_attr( $value['class'] ); ?>"
placeholder="<?php echo esc_attr( $value['placeholder'] ); ?>"
step="1"
min="1"
<?php echo implode( ' ', $custom_attributes ); // WPCS: XSS ok. ?>
/>&nbsp;
<select name="<?php echo esc_attr( $value['id'] ); ?>[unit]" style="width: auto;">
<?php
foreach ( $periods as $value => $label ) {
echo '<option value="' . esc_attr( $value ) . '"' . selected( $option_value['unit'], $value, false ) . '>' . esc_html( $label ) . '</option>';
}
?>
</select> <?php echo ( $description ) ? $description : ''; // WPCS: XSS ok. ?>
</td>
</tr>
<?php
break;
// Default: run an action.
default:
do_action( 'woocommerce_admin_field_' . $value['type'], $value );
break;
}
}
}
/**
* Helper function to get the formatted description and tip HTML for a
* given form field. Plugins can call this when implementing their own custom
* settings types.
*
* @param array $value The form field value array.
* @return array The description and tip as a 2 element array.
*/
public static function get_field_description( $value ) {
$description = '';
$tooltip_html = '';
if ( true === $value['desc_tip'] ) {
$tooltip_html = $value['desc'];
} elseif ( ! empty( $value['desc_tip'] ) ) {
$description = $value['desc'];
$tooltip_html = $value['desc_tip'];
} elseif ( ! empty( $value['desc'] ) ) {
$description = $value['desc'];
}
if ( $description && in_array( $value['type'], array( 'textarea', 'radio' ), true ) ) {
$description = '<p style="margin-top:0">' . wp_kses_post( $description ) . '</p>';
} elseif ( $description && in_array( $value['type'], array( 'checkbox' ), true ) ) {
$description = wp_kses_post( $description );
} elseif ( $description ) {
$description = '<p class="description">' . wp_kses_post( $description ) . '</p>';
}
if ( $tooltip_html && in_array( $value['type'], array( 'checkbox' ), true ) ) {
$tooltip_html = '<p class="description">' . $tooltip_html . '</p>';
} elseif ( $tooltip_html ) {
$tooltip_html = wc_help_tip( $tooltip_html );
}
return array(
'description' => $description,
'tooltip_html' => $tooltip_html,
);
}
/**
* Save admin fields.
*
* Loops through the woocommerce options array and outputs each field.
*
* @param array $options Options array to output.
* @param array $data Optional. Data to use for saving. Defaults to $_POST.
* @return bool
*/
public static function save_fields( $options, $data = null ) {
if ( is_null( $data ) ) {
$data = $_POST; // WPCS: input var okay, CSRF ok.
}
if ( empty( $data ) ) {
return false;
}
// Options to update will be stored here and saved later.
$update_options = array();
$autoload_options = array();
// Loop options and get values to save.
foreach ( $options as $option ) {
if ( ! isset( $option['id'] ) || ! isset( $option['type'] ) || ( isset( $option['is_option'] ) && false === $option['is_option'] ) ) {
continue;
}
// Get posted value.
if ( strstr( $option['id'], '[' ) ) {
parse_str( $option['id'], $option_name_array );
$option_name = current( array_keys( $option_name_array ) );
$setting_name = key( $option_name_array[ $option_name ] );
$raw_value = isset( $data[ $option_name ][ $setting_name ] ) ? wp_unslash( $data[ $option_name ][ $setting_name ] ) : null;
} else {
$option_name = $option['id'];
$setting_name = '';
$raw_value = isset( $data[ $option['id'] ] ) ? wp_unslash( $data[ $option['id'] ] ) : null;
}
// Format the value based on option type.
switch ( $option['type'] ) {
case 'checkbox':
$value = '1' === $raw_value || 'yes' === $raw_value ? 'yes' : 'no';
break;
case 'textarea':
$value = wp_kses_post( trim( $raw_value ) );
break;
case 'multiselect':
case 'multi_select_countries':
$value = array_filter( array_map( 'wc_clean', (array) $raw_value ) );
break;
case 'image_width':
$value = array();
if ( isset( $raw_value['width'] ) ) {
$value['width'] = wc_clean( $raw_value['width'] );
$value['height'] = wc_clean( $raw_value['height'] );
$value['crop'] = isset( $raw_value['crop'] ) ? 1 : 0;
} else {
$value['width'] = $option['default']['width'];
$value['height'] = $option['default']['height'];
$value['crop'] = $option['default']['crop'];
}
break;
case 'select':
$allowed_values = empty( $option['options'] ) ? array() : array_map( 'strval', array_keys( $option['options'] ) );
if ( empty( $option['default'] ) && empty( $allowed_values ) ) {
$value = null;
break;
}
$default = ( empty( $option['default'] ) ? $allowed_values[0] : $option['default'] );
$value = in_array( $raw_value, $allowed_values, true ) ? $raw_value : $default;
break;
case 'relative_date_selector':
$value = wc_parse_relative_date_option( $raw_value );
break;
default:
$value = wc_clean( $raw_value );
break;
}
/**
* Fire an action when a certain 'type' of field is being saved.
*
* @deprecated 2.4.0 - doesn't allow manipulation of values!
*/
if ( has_action( 'woocommerce_update_option_' . sanitize_title( $option['type'] ) ) ) {
wc_deprecated_function( 'The woocommerce_update_option_X action', '2.4.0', 'woocommerce_admin_settings_sanitize_option filter' );
do_action( 'woocommerce_update_option_' . sanitize_title( $option['type'] ), $option );
continue;
}
/**
* Sanitize the value of an option.
*
* @since 2.4.0
*/
$value = apply_filters( 'woocommerce_admin_settings_sanitize_option', $value, $option, $raw_value );
/**
* Sanitize the value of an option by option name.
*
* @since 2.4.0
*/
$value = apply_filters( "woocommerce_admin_settings_sanitize_option_$option_name", $value, $option, $raw_value );
if ( is_null( $value ) ) {
continue;
}
// Check if option is an array and handle that differently to single values.
if ( $option_name && $setting_name ) {
if ( ! isset( $update_options[ $option_name ] ) ) {
$update_options[ $option_name ] = get_option( $option_name, array() );
}
if ( ! is_array( $update_options[ $option_name ] ) ) {
$update_options[ $option_name ] = array();
}
$update_options[ $option_name ][ $setting_name ] = $value;
} else {
$update_options[ $option_name ] = $value;
}
$autoload_options[ $option_name ] = isset( $option['autoload'] ) ? (bool) $option['autoload'] : true;
/**
* Fire an action before saved.
*
* @deprecated 2.4.0 - doesn't allow manipulation of values!
*/
do_action( 'woocommerce_update_option', $option );
}
// Save all options in our array.
foreach ( $update_options as $name => $value ) {
update_option( $name, $value, $autoload_options[ $name ] ? 'yes' : 'no' );
}
return true;
}
/**
* Checks which method we're using to serve downloads.
*
* If using force or x-sendfile, this ensures the .htaccess is in place.
*/
public static function check_download_folder_protection() {
$upload_dir = wp_get_upload_dir();
$downloads_path = $upload_dir['basedir'] . '/woocommerce_uploads';
$download_method = get_option( 'woocommerce_file_download_method' );
$file_path = $downloads_path . '/.htaccess';
$file_content = 'redirect' === $download_method ? 'Options -Indexes' : 'deny from all';
$create = false;
if ( wp_mkdir_p( $downloads_path ) && ! file_exists( $file_path ) ) {
$create = true;
} else {
$current_content = @file_get_contents( $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( $current_content !== $file_content ) {
unlink( $file_path );
$create = true;
}
}
if ( $create ) {
$file_handle = @fopen( $file_path, 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
if ( $file_handle ) {
fwrite( $file_handle, $file_content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
}
}
}
}
endif;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,439 @@
<?php
/**
* Debug/Status page
*
* @package WooCommerce\Admin\System Status
* @version 2.2.0
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Utilities\ArrayUtil;
defined( 'ABSPATH' ) || exit;
/**
* WC_Admin_Status Class.
*/
class WC_Admin_Status {
/**
* 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 = ArrayUtil::get_value_or_default( $tool, 'requires_refresh', false );
$tool = array(
'id' => $action,
'name' => $tool['name'],
'action' => $tool['button'],
'description' => $tool['desc'],
'disabled' => ArrayUtil::get_value_or_default( $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() {
$log_handler = Constants::get_constant( 'WC_LOG_HANDLER' );
if ( 'WC_Log_Handler_DB' === $log_handler ) {
self::status_logs_db();
} else {
self::status_logs_file();
}
}
/**
* 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() {
if ( ! empty( $_REQUEST['flush-logs'] ) ) { // WPCS: input var ok, CSRF ok.
self::flush_db_logs();
}
if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['log'] ) ) { // WPCS: input var ok, CSRF ok.
self::log_table_bulk_actions();
}
$log_table_list = new WC_Admin_Log_Table_List();
$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 ) ) {
$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();
}
/**
* Clear DB log table.
*
* @since 3.0.0
*/
private static function flush_db_logs() {
if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'woocommerce-status-logs' ) ) { // WPCS: input var ok, sanitization ok.
wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
}
WC_Log_Handler_DB::flush();
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
exit();
}
/**
* Bulk DB log table actions.
*
* @since 3.0.0
*/
private static function log_table_bulk_actions() {
if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'woocommerce-status-logs' ) ) { // WPCS: input var ok, sanitization ok.
wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
}
$log_ids = array_map( 'absint', (array) isset( $_REQUEST['log'] ) ? wp_unslash( $_REQUEST['log'] ) : array() ); // WPCS: input var ok, sanitization ok.
if ( ( isset( $_REQUEST['action'] ) && 'delete' === $_REQUEST['action'] ) || ( isset( $_REQUEST['action2'] ) && 'delete' === $_REQUEST['action2'] ) ) { // WPCS: input var ok, sanitization ok.
WC_Log_Handler_DB::delete( $log_ids );
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
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 seperated 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' ) ) {
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 = ' &ndash; <strong style="color: black;">' . esc_html__( 'Network enabled', 'woocommerce' ) . '</strong>';
}
}
$untested_string = '';
if ( array_key_exists( $plugin['plugin'], $untested_plugins ) ) {
$untested_string = ' &ndash; <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">&nbsp;</td>
<td>
<?php
/* translators: %s: plugin author */
printf( esc_html__( 'by %s', 'woocommerce' ), esc_html( $plugin['author_name'] ) );
echo ' &ndash; ' . esc_html( $version_string ) . $untested_string . $network_string; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
</td>
</tr>
<?php
}
}
}
}

View File

@ -0,0 +1,497 @@
<?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;
}
$meta_name = taxonomy_is_product_attribute( $taxonomy ) ? 'order_' . esc_attr( $taxonomy ) : 'order';
update_term_meta( $term_id, $meta_name, 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&amp;taxonomy=product_cat&amp;post_type=product&amp;tag_ID=' . absint( $term->term_id ), 'make_default_' . absint( $term->term_id ) ),
/* translators: %s: taxonomy term name */
esc_attr( sprintf( __( 'Make &#8220;%s&#8221; 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();

View File

@ -0,0 +1,316 @@
<?php
/**
* WooCommerce Webhooks Table List
*
* @package WooCommerce\Admin
* @version 3.3.0
*/
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
/**
* Webooks 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&amp;tab=advanced&amp;section=webhooks&amp;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&section=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() );
$class = empty( $_REQUEST['status'] ) ? ' class="current"' : ''; // WPCS: input var okay. CSRF ok.
/* translators: %s: count */
$status_links['all'] = "<a href='admin.php?page=wc-settings&amp;tab=advanced&amp;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;
}
if ( isset( $_REQUEST['status'] ) && sanitize_key( wp_unslash( $_REQUEST['status'] ) ) === $status_name ) { // WPCS: input var okay, CSRF ok.
$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&amp;tab=advanced&amp;section=webhooks&amp;status=$status_name'$class>" . sprintf( translate_nooped_plural( $label, $num_webhooks[ $status_name ] ), number_format_i18n( $num_webhooks[ $status_name ] ) ) . '</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 ( ! 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;
// 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,
)
);
}
}

View File

@ -0,0 +1,349 @@
<?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 ) {
if ( ! isset( $_GET['edit-webhook'] ) ) { // WPCS: input var okay, CSRF ok.
return false;
}
return $allow;
}
/**
* Check if is webhook settings page.
*
* @return bool
*/
private function is_webhook_settings_page() {
return isset( $_GET['page'], $_GET['tab'], $_GET['section'] ) && 'wc-settings' === $_GET['page'] && 'advanced' === $_GET['tab'] && 'webhooks' === $_GET['section']; // WPCS: input var okay, CSRF ok.
}
/**
* 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();
$webhook_id = isset( $_POST['webhook_id'] ) ? absint( $_POST['webhook_id'] ) : 0; // WPCS: input var okay, CSRF ok.
$webhook = new WC_Webhook( $webhook_id );
// Name.
if ( ! empty( $_POST['webhook_name'] ) ) { // WPCS: input var okay, CSRF ok.
$name = sanitize_text_field( wp_unslash( $_POST['webhook_name'] ) ); // WPCS: input var okay, CSRF ok.
} else {
$name = sprintf(
/* translators: %s: date */
__( 'Webhook created on %s', 'woocommerce' ),
// @codingStandardsIgnoreStart
strftime( _x( '%b %d, %Y @ %I:%M %p', 'Webhook created on date parsed by strftime', 'woocommerce' ) )
// @codingStandardsIgnoreEnd
);
}
$webhook->set_name( $name );
if ( ! $webhook->get_user_id() ) {
$webhook->set_user_id( get_current_user_id() );
}
// Status.
$webhook->set_status( ! empty( $_POST['webhook_status'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_status'] ) ) : 'disabled' ); // WPCS: input var okay, CSRF ok.
// Delivery URL.
$delivery_url = ! empty( $_POST['webhook_delivery_url'] ) ? esc_url_raw( wp_unslash( $_POST['webhook_delivery_url'] ) ) : ''; // WPCS: input var okay, CSRF ok.
if ( wc_is_valid_url( $delivery_url ) ) {
$webhook->set_delivery_url( $delivery_url );
}
// Secret.
$secret = ! empty( $_POST['webhook_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_secret'] ) ) : wp_generate_password( 50, true, true ); // WPCS: input var okay, CSRF ok.
$webhook->set_secret( $secret );
// Topic.
if ( ! empty( $_POST['webhook_topic'] ) ) { // WPCS: input var okay, CSRF ok.
$resource = '';
$event = '';
switch ( $_POST['webhook_topic'] ) { // WPCS: input var okay, CSRF ok.
case 'action':
$resource = 'action';
$event = ! empty( $_POST['webhook_action_event'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_action_event'] ) ) : ''; // WPCS: input var okay, CSRF ok.
break;
default:
list( $resource, $event ) = explode( '.', sanitize_text_field( wp_unslash( $_POST['webhook_topic'] ) ) ); // WPCS: input var okay, CSRF ok.
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();
$webhook->set_api_version( ! empty( $_POST['webhook_api_version'] ) ? sanitize_text_field( wp_unslash( $_POST['webhook_api_version'] ) ) : end( $rest_api_versions ) ); // WPCS: input var okay, CSRF ok.
$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&section=webhooks&edit-webhook=' . $webhook->get_id() . '&error=' . rawurlencode( implode( '|', $errors ) ) ) );
exit();
} elseif ( isset( $_POST['webhook_status'] ) && 'active' === $_POST['webhook_status'] && $webhook->get_pending_delivery() ) { // WPCS: input var okay, CSRF ok.
// 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&section=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&section=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 );
$status = isset( $_GET['status'] ) ? '&status=' . sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; // WPCS: input var okay, CSRF ok.
// Redirect to webhooks page.
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=advanced&section=webhooks' . $status . '&deleted=' . $qty ) );
exit();
}
/**
* Delete webhook.
*/
private function delete() {
check_admin_referer( 'delete-webhook' );
if ( isset( $_GET['delete'] ) ) { // WPCS: input var okay, CSRF ok.
$webhook_id = absint( $_GET['delete'] ); // WPCS: input var okay, CSRF ok.
if ( $webhook_id ) {
$this->bulk_delete( array( $webhook_id ) );
}
}
}
/**
* Webhooks admin actions.
*/
public function actions() {
if ( $this->is_webhook_settings_page() ) {
// Save.
if ( isset( $_POST['save'] ) && isset( $_POST['webhook_id'] ) ) { // WPCS: input var okay, CSRF ok.
$this->save();
}
// Delete webhook.
if ( isset( $_GET['delete'] ) ) { // WPCS: input var okay, CSRF ok.
$this->delete();
}
}
}
/**
* Page output.
*/
public static function page_output() {
// Hide the save button.
$GLOBALS['hide_save_button'] = true;
if ( isset( $_GET['edit-webhook'] ) ) { // WPCS: input var okay, CSRF ok.
$webhook_id = absint( $_GET['edit-webhook'] ); // WPCS: input var okay, CSRF ok.
$webhook = new WC_Webhook( $webhook_id );
include __DIR__ . '/settings/views/html-webhooks-edit.php';
return;
}
self::table_list_output();
}
/**
* Notices.
*/
public static function notices() {
if ( isset( $_GET['deleted'] ) ) { // WPCS: input var okay, CSRF ok.
$deleted = absint( $_GET['deleted'] ); // WPCS: input var okay, CSRF ok.
/* translators: %d: count */
WC_Admin_Settings::add_message( sprintf( _n( '%d webhook permanently deleted.', '%d webhooks permanently deleted.', $deleted, 'woocommerce' ), $deleted ) );
}
if ( isset( $_GET['updated'] ) ) { // WPCS: input var okay, CSRF ok.
WC_Admin_Settings::add_message( __( 'Webhook updated successfully.', 'woocommerce' ) );
}
if ( isset( $_GET['created'] ) ) { // WPCS: input var okay, CSRF ok.
WC_Admin_Settings::add_message( __( 'Webhook created successfully.', 'woocommerce' ) );
}
if ( isset( $_GET['error'] ) ) { // WPCS: input var okay, CSRF ok.
foreach ( explode( '|', sanitize_text_field( wp_unslash( $_GET['error'] ) ) ) as $message ) { // WPCS: input var okay, CSRF ok.
WC_Admin_Settings::add_error( trim( $message ) );
}
}
}
/**
* Add screen option.
*/
public function screen_option() {
global $webhooks_table_list;
if ( ! isset( $_GET['edit-webhook'] ) && $this->is_webhook_settings_page() ) { // WPCS: input var okay, CSRF ok.
$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&section=webhooks&edit-webhook=0' ) ) . '" class="add-new-h2">' . 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&section=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();

View File

@ -0,0 +1,317 @@
<?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 );
add_action( 'init', array( 'WC_Site_Tracking', 'init' ) );
// 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';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-event.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-client.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-footer-pixel.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-site-tracking.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;
if ( apply_filters( 'woocommerce_disable_admin_bar', true ) && ! is_ajax() && isset( $_SERVER['SCRIPT_FILENAME'] ) && basename( sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) ) !== 'admin-post.php' ) {
$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' ) . '">&#9733;&#9733;&#9733;&#9733;&#9733;</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 Scehduled 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();

View File

@ -0,0 +1,170 @@
<?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() ) {
$url = self::url( $endpoint );
if ( ! empty( $args['authenticated'] ) ) {
if ( ! self::_authenticate( $url, $args ) ) {
return new WP_Error( 'authentication', 'Authentication failed.' );
}
}
/**
* 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 );
}
/**
* 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;
}
$request_uri = parse_url( $url, PHP_URL_PATH );
$query_string = parse_url( $url, PHP_URL_QUERY );
if ( is_string( $query_string ) ) {
$request_uri .= '?' . $query_string;
}
$data = array(
'host' => parse_url( $url, PHP_URL_HOST ),
'request_uri' => $request_uri,
'method' => ! empty( $args['method'] ) ? $args['method'] : 'GET',
);
if ( ! empty( $args['body'] ) ) {
$data['body'] = $args['body'];
}
$signature = hash_hmac( 'sha256', json_encode( $data ), $auth['access_token_secret'] );
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.
*
* @return string The absolute endpoint URL.
*/
public static function url( $endpoint ) {
$endpoint = ltrim( $endpoint, '/' );
$endpoint = sprintf( '%s/%s', self::$api_base, $endpoint );
$endpoint = esc_url_raw( $endpoint );
return $endpoint;
}
}
WC_Helper_API::load();

View File

@ -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&section=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();

View File

@ -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 ( array_key_exists( $key, $options ) ) {
return $options[ $key ];
}
return $default;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* WooCommerce Admin Helper Plugin Info
*
* @package WooCommerce\Admin\Helper
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_Plugin_Info Class
*
* Provides the "View Information" core modals with data for WooCommerce.com
* hosted extensions.
*/
class WC_Helper_Plugin_Info {
/**
* Loads the class, runs on init.
*/
public static function load() {
add_filter( 'plugins_api', array( __CLASS__, 'plugins_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;
}
if ( empty( $args->slug ) ) {
return $response;
}
// Only for slugs that start with woo-
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_Helper_Plugin_Info::load();

View File

@ -0,0 +1,492 @@
<?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 );
}
/**
* 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' => $data['package'],
'upgrade_notice' => $data['upgrade_notice'],
);
if ( isset( $data['requires_php'] ) ) {
$item['requires_php'] = $data['requires_php'];
}
// We don't want to deliver a valid upgrade package when their subscription has expired.
// To avoid the generic "no_package" error that empty strings give, we will store an
// indication of expiration for the `upgrader_pre_download` filter to error on.
if ( ! self::_has_active_subscription( $plugin['_product_id'] ) ) {
$item['package'] = 'woocommerce-com-expired-' . $plugin['_product_id'];
}
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 ] );
}
}
$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' => '',
);
if ( self::_has_active_subscription( $theme['_product_id'] ) ) {
$item['package'] = $data['package'];
}
if ( version_compare( $theme['Version'], $data['version'], '<' ) ) {
$transient->response[ $slug ] = $item;
} else {
unset( $transient->response[ $slug ] );
$transient->checked[ $slug ] = $data['version'];
}
}
return $transient;
}
/**
* 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 informations.
*
* 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 respone 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 ) {
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' => 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'];
}
/**
* Check for an active subscription.
*
* Checks a given product id against all subscriptions on
* the current site. Returns true if at least one active
* subscription is found.
*
* @param int $product_id The product id to look for.
*
* @return bool True if active subscription found.
*/
private static function _has_active_subscription( $product_id ) {
if ( ! isset( $auth ) ) {
$auth = WC_Helper_Options::get( 'auth' );
}
if ( ! isset( $subscriptions ) ) {
$subscriptions = WC_Helper::get_subscriptions();
}
if ( empty( $auth['site_id'] ) || empty( $subscriptions ) ) {
return false;
}
// Check for an active subscription.
foreach ( $subscriptions as $subscription ) {
if ( $subscription['product_id'] != $product_id ) {
continue;
}
if ( in_array( absint( $auth['site_id'] ), $subscription['connections'] ) ) {
return true;
}
}
return false;
}
/**
* 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;
}
/**
* 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();
if ( ! $count ) {
return '';
}
$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&section=helper' ) )
)
);
}
}
WC_Helper_Updater::load();

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -0,0 +1,256 @@
<?php
/**
* Helper main view
*
* @package WooCommerce\Helper
*/
?>
<?php defined( 'ABSPATH' ) || exit(); ?>
<div class="wrap woocommerce wc-subscriptions-wrap wc-helper">
<h1 class="screen-reader-text"><?php esc_html_e( 'My Subscriptions', '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&section=helper&filter=' . $key );
$class_html = $current_filter === $key ? 'class="current"' : '';
?>
<li>
<a <?php echo esc_html( $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( $subscription['product_url'] ); ?>" 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( $data['_product_url'] ); ?>" 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>

View File

@ -0,0 +1,29 @@
<?php
/**
* Admin -> WooCommerce -> Extensions -> WooCommerce.com Subscriptions main page.
*
* @package WooCommerce\Views
*/
defined( 'ABSPATH' ) || exit();
?>
<div class="wrap woocommerce wc-addons-wrap wc-helper">
<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>

View File

@ -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 _e( 'Update', 'woocommerce' ); ?></a>
<div class="user-info">
<header>
<p><?php printf( __( '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 _e( 'My Subscriptions', 'woocommerce' ); ?></a>
<a class="" href="<?php echo esc_url( $disconnect_url ); ?>"><span class="dashicons dashicons-no"></span> <?php _e( 'Disconnect', 'woocommerce' ); ?></a>
</div>
</section>
</div>

View File

@ -0,0 +1,21 @@
<?php
/**
* Helper admin navigation.
*
* @package WooCommerce\Helper
*
* @deprecated 5.7.0
*/
defined( 'ABSPATH' ) || exit(); ?>
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons' ) ); ?>" 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( __( 'WooCommerce.com Subscriptions %s', 'woocommerce' ), $count_html );
?>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab nav-tab-active"><?php echo wp_kses_post( $menu_title ); ?></a>
</nav>

View File

@ -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; ?>

View File

@ -0,0 +1,751 @@
<?php
/**
* Class WC_Product_CSV_Importer_Controller file.
*
* @package WooCommerce\Admin\Importers
*/
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;
/**
* 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.
*
* @todo Replace this method with wc_is_file_valid_csv() function.
* @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 ) {
if ( $check_path && apply_filters( 'woocommerce_product_csv_importer_check_import_file_path', true ) && false !== stripos( $file, '://' ) ) {
return false;
}
$valid_filetypes = self::get_valid_csv_filetypes();
$filetype = wp_check_filetype( $file, $valid_filetypes );
if ( in_array( $filetype['type'], $valid_filetypes, true ) ) {
return true;
}
return false;
}
/**
* 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;
// 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,
'_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to &amp; 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 ( 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,
);
$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,
)
);
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;
$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 = get_option( 'woocommerce_weight_unit' );
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
/*
* @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 ) => 'weight',
/* translators: %s: Length unit */
sprintf( __( 'Length (%s)', 'woocommerce' ), $dimension_unit ) => 'length',
/* translators: %s: Width unit */
sprintf( __( 'Width (%s)', 'woocommerce' ), $dimension_unit ) => 'width',
/* translators: %s: Height unit */
sprintf( __( 'Height (%s)', 'woocommerce' ), $dimension_unit ) => '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 lastest 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 = get_option( 'woocommerce_weight_unit' );
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
$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 ),
'dimensions' => array(
'name' => __( 'Dimensions', 'woocommerce' ),
'options' => array(
/* translators: %s: dimension unit */
'length' => sprintf( __( 'Length (%s)', 'woocommerce' ), $dimension_unit ),
/* translators: %s: dimension unit */
'width' => sprintf( __( 'Width (%s)', 'woocommerce' ), $dimension_unit ),
/* translators: %s: dimension unit */
'height' => sprintf( __( 'Height (%s)', 'woocommerce' ), $dimension_unit ),
),
),
'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 );
}
}

View File

@ -0,0 +1,341 @@
<?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;
/**
* 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 );
}
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();
@ini_set( 'auto_detect_line_endings', '1' );
}
/**
* 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 );
if ( 10 === count( $header ) ) {
$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' );
}
/**
* 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->import_error( $file['error'] );
}
if ( ! wc_is_file_valid_csv( $file['file'], false ) ) {
// Remove file if not valid.
wp_delete_attachment( $file['id'], true );
$this->import_error( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
}
$this->id = absint( $file['id'] );
} elseif ( file_exists( ABSPATH . $file_url ) ) {
if ( ! wc_is_file_valid_csv( ABSPATH . $file_url ) ) {
$this->import_error( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) );
}
$this->file_url = esc_attr( $file_url );
} else {
$this->import_error();
}
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;
}
}

View File

@ -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() ) {
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() ) {
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 );

View File

@ -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' );

View File

@ -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';

View File

@ -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' );

View File

@ -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' );

View File

@ -0,0 +1,104 @@
<?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 < $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 ( 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 esc_html( $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>

View File

@ -0,0 +1,13 @@
<?php
/**
* Admin View: Header
*
* @package WooCommerce\Admin\Importers
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
</div>
</div>

View File

@ -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">

View File

@ -0,0 +1,65 @@
<?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 wp_nonce_field( 'woocommerce-csv-importer' ); ?>
</div>
</form>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,104 @@
<?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>
</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>

View File

@ -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 );
}
}

View File

@ -0,0 +1,232 @@
<?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://docs.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 columm: 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 columm: type.
*/
protected function render_type_column() {
echo esc_html( wc_get_coupon_type( $this->object->get_discount_type() ) );
}
/**
* Render columm: amount.
*/
protected function render_amount_column() {
echo esc_html( wc_format_localized_price( $this->object->get_amount() ) );
}
/**
* Render columm: products.
*/
protected function render_products_column() {
$product_ids = $this->object->get_product_ids();
if ( count( $product_ids ) > 0 ) {
echo esc_html( implode( ', ', $product_ids ) );
} else {
echo '&ndash;';
}
}
/**
* Render columm: 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 '&ndash;';
}
}
/**
* Render columm: 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 ) : '&infin;'
);
}
/**
* Render columm: 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 '&ndash;';
}
}
/**
* Render columm: description.
*/
protected function render_description_column() {
echo wp_kses_post( $this->object->get_description() ? $this->object->get_description() : '&ndash;' );
}
/**
* 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;
}
}

View File

@ -0,0 +1,892 @@
<?php
/**
* List tables: orders.
*
* @package WooCommerce\Admin
* @version 3.3.0
*/
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';
/**
* Constructor.
*/
public function __construct() {
parent::__construct();
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() {
echo '<div class="woocommerce-BlankState">';
echo '<h2 class="woocommerce-BlankState-message">' . esc_html__( 'When you receive a new order, it will appear here.', 'woocommerce' ) . '</h2>';
echo '<div class="woocommerce-BlankState-buttons">';
echo '<a class="woocommerce-BlankState-cta button-primary button" target="_blank" href="https://docs.woocommerce.com/document/managing-orders/?utm_source=blankslate&utm_medium=product&utm_content=ordersdoc&utm_campaign=woocommerceplugin">' . esc_html__( 'Learn more about orders', 'woocommerce' ) . '</a>';
echo '</div>';
do_action( 'wc_marketplace_suggestions_orders_empty_state' );
echo '</div>';
}
/**
* 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 columm: order_number.
*/
protected function render_order_number_column() {
$buyer = '';
if ( $this->object->get_billing_first_name() || $this->object->get_billing_last_name() ) {
/* translators: 1: first name 2: last name */
$buyer = trim( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->object->get_billing_first_name(), $this->object->get_billing_last_name() ) );
} elseif ( $this->object->get_billing_company() ) {
$buyer = trim( $this->object->get_billing_company() );
} elseif ( $this->object->get_customer_id() ) {
$user = get_user_by( 'id', $this->object->get_customer_id() );
$buyer = ucwords( $user->display_name );
}
/**
* Filter buyer name in list table orders.
*
* @since 3.7.0
* @param string $buyer Buyer name.
* @param WC_Order $order Order data.
*/
$buyer = apply_filters( 'woocommerce_admin_order_buyer_name', $buyer, $this->object );
if ( $this->object->get_status() === 'trash' ) {
echo '<strong>#' . esc_attr( $this->object->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong>';
} else {
echo '<a href="#" class="order-preview" data-order-id="' . absint( $this->object->get_id() ) . '" title="' . esc_attr( __( 'Preview', 'woocommerce' ) ) . '">' . esc_html( __( 'Preview', 'woocommerce' ) ) . '</a>';
echo '<a href="' . esc_url( admin_url( 'post.php?post=' . absint( $this->object->get_id() ) ) . '&action=edit' ) . '" class="order-view"><strong>#' . esc_attr( $this->object->get_order_number() ) . ' ' . esc_html( $buyer ) . '</strong></a>';
}
}
/**
* Render columm: order_status.
*/
protected function render_order_status_column() {
$tooltip = '';
$comment_count = get_comment_count( $this->object->get_id() );
$approved_comments_count = absint( $comment_count['approved'] );
if ( $approved_comments_count ) {
$latest_notes = wc_get_order_notes(
array(
'order_id' => $this->object->get_id(),
'limit' => 1,
'orderby' => 'date_created_gmt',
)
);
$latest_note = current( $latest_notes );
if ( isset( $latest_note->content ) && 1 === $approved_comments_count ) {
$tooltip = wc_sanitize_tooltip( $latest_note->content );
} elseif ( isset( $latest_note->content ) ) {
/* translators: %d: notes count */
$tooltip = wc_sanitize_tooltip( $latest_note->content . '<br/><small style="display:block">' . sprintf( _n( 'Plus %d other note', 'Plus %d other notes', ( $approved_comments_count - 1 ), 'woocommerce' ), $approved_comments_count - 1 ) . '</small>' );
} else {
/* translators: %d: notes count */
$tooltip = wc_sanitize_tooltip( sprintf( _n( '%d note', '%d notes', $approved_comments_count, 'woocommerce' ), $approved_comments_count ) );
}
}
if ( $tooltip ) {
printf( '<mark class="order-status %s tips" data-tip="%s"><span>%s</span></mark>', esc_attr( sanitize_html_class( 'status-' . $this->object->get_status() ) ), wp_kses_post( $tooltip ), esc_html( wc_get_order_status_name( $this->object->get_status() ) ) );
} else {
printf( '<mark class="order-status %s"><span>%s</span></mark>', esc_attr( sanitize_html_class( 'status-' . $this->object->get_status() ) ), esc_html( wc_get_order_status_name( $this->object->get_status() ) ) );
}
}
/**
* Render columm: order_date.
*/
protected function render_order_date_column() {
$order_timestamp = $this->object->get_date_created() ? $this->object->get_date_created()->getTimestamp() : '';
if ( ! $order_timestamp ) {
echo '&ndash;';
return;
}
// Check if the order was created within the last 24 hours, and not in the future.
if ( $order_timestamp > strtotime( '-1 day', time() ) && $order_timestamp <= time() ) {
$show_date = sprintf(
/* translators: %s: human-readable time difference */
_x( '%s ago', '%s = human-readable time difference', 'woocommerce' ),
human_time_diff( $this->object->get_date_created()->getTimestamp(), time() )
);
} else {
$show_date = $this->object->get_date_created()->date_i18n( apply_filters( 'woocommerce_admin_order_date_format', __( 'M j, Y', 'woocommerce' ) ) );
}
printf(
'<time datetime="%1$s" title="%2$s">%3$s</time>',
esc_attr( $this->object->get_date_created()->date( 'c' ) ),
esc_html( $this->object->get_date_created()->date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ),
esc_html( $show_date )
);
}
/**
* Render columm: order_total.
*/
protected function render_order_total_column() {
if ( $this->object->get_payment_method_title() ) {
/* translators: %s: method */
echo '<span class="tips" data-tip="' . esc_attr( sprintf( __( 'via %s', 'woocommerce' ), $this->object->get_payment_method_title() ) ) . '">' . wp_kses_post( $this->object->get_formatted_order_total() ) . '</span>';
} else {
echo wp_kses_post( $this->object->get_formatted_order_total() );
}
}
/**
* Render columm: wc_actions.
*/
protected function render_wc_actions_column() {
echo '<p>';
do_action( 'woocommerce_admin_order_actions_start', $this->object );
$actions = array();
if ( $this->object->has_status( array( 'pending', 'on-hold' ) ) ) {
$actions['processing'] = array(
'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=processing&order_id=' . $this->object->get_id() ), 'woocommerce-mark-order-status' ),
'name' => __( 'Processing', 'woocommerce' ),
'action' => 'processing',
);
}
if ( $this->object->has_status( array( 'pending', 'on-hold', 'processing' ) ) ) {
$actions['complete'] = array(
'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=completed&order_id=' . $this->object->get_id() ), 'woocommerce-mark-order-status' ),
'name' => __( 'Complete', 'woocommerce' ),
'action' => 'complete',
);
}
$actions = apply_filters( 'woocommerce_admin_order_actions', $actions, $this->object );
echo wc_render_action_buttons( $actions ); // WPCS: XSS ok.
do_action( 'woocommerce_admin_order_actions_end', $this->object );
echo '</p>';
}
/**
* Render columm: billing_address.
*/
protected function render_billing_address_column() {
$address = $this->object->get_formatted_billing_address();
if ( $address ) {
echo esc_html( preg_replace( '#<br\s*/?>#i', ', ', $address ) );
if ( $this->object->get_payment_method() ) {
/* translators: %s: payment method */
echo '<span class="description">' . sprintf( __( 'via %s', 'woocommerce' ), esc_html( $this->object->get_payment_method_title() ) ) . '</span>'; // WPCS: XSS ok.
}
} else {
echo '&ndash;';
}
}
/**
* Render columm: shipping_address.
*/
protected function render_shipping_address_column() {
$address = $this->object->get_formatted_shipping_address();
if ( $address ) {
echo '<a target="_blank" href="' . esc_url( $this->object->get_shipping_address_map_url() ) . '">' . esc_html( preg_replace( '#<br\s*/?>#i', ', ', $address ) ) . '</a>';
if ( $this->object->get_shipping_method() ) {
/* translators: %s: shipping method */
echo '<span class="description">' . sprintf( __( 'via %s', 'woocommerce' ), esc_html( $this->object->get_shipping_method() ) ) . '</span>'; // WPCS: XSS ok.
}
} else {
echo '&ndash;';
}
}
/**
* Template for order preview.
*
* @since 3.3.0
*/
public function order_preview_template() {
?>
<script type="text/template" id="tmpl-wc-modal-view-order">
<div class="wc-backbone-modal wc-order-preview">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<mark class="order-status status-{{ data.status }}"><span>{{ data.status_name }}</span></mark>
<?php /* translators: %s: order ID */ ?>
<h1><?php echo esc_html( sprintf( __( 'Order #%s', 'woocommerce' ), '{{ data.order_number }}' ) ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text"><?php esc_html_e( 'Close modal panel', 'woocommerce' ); ?></span>
</button>
</header>
<article>
<?php do_action( 'woocommerce_admin_order_preview_start' ); ?>
<div class="wc-order-preview-addresses">
<div class="wc-order-preview-address">
<h2><?php esc_html_e( 'Billing details', 'woocommerce' ); ?></h2>
{{{ data.formatted_billing_address }}}
<# if ( data.data.billing.email ) { #>
<strong><?php esc_html_e( 'Email', 'woocommerce' ); ?></strong>
<a href="mailto:{{ data.data.billing.email }}">{{ data.data.billing.email }}</a>
<# } #>
<# if ( data.data.billing.phone ) { #>
<strong><?php esc_html_e( 'Phone', 'woocommerce' ); ?></strong>
<a href="tel:{{ data.data.billing.phone }}">{{ data.data.billing.phone }}</a>
<# } #>
<# if ( data.payment_via ) { #>
<strong><?php esc_html_e( 'Payment via', 'woocommerce' ); ?></strong>
{{{ data.payment_via }}}
<# } #>
</div>
<# if ( data.needs_shipping ) { #>
<div class="wc-order-preview-address">
<h2><?php esc_html_e( 'Shipping details', 'woocommerce' ); ?></h2>
<# if ( data.ship_to_billing ) { #>
{{{ data.formatted_billing_address }}}
<# } else { #>
<a href="{{ data.shipping_address_map_url }}" target="_blank">{{{ data.formatted_shipping_address }}}</a>
<# } #>
<# if ( data.shipping_via ) { #>
<strong><?php esc_html_e( 'Shipping method', 'woocommerce' ); ?></strong>
{{ data.shipping_via }}
<# } #>
</div>
<# } #>
<# if ( data.data.customer_note ) { #>
<div class="wc-order-preview-note">
<strong><?php esc_html_e( 'Note', 'woocommerce' ); ?></strong>
{{ data.data.customer_note }}
</div>
<# } #>
</div>
{{{ data.item_html }}}
<?php do_action( 'woocommerce_admin_order_preview_end' ); ?>
</article>
<footer>
<div class="inner">
{{{ data.actions_html }}}
<a class="button button-primary button-large" aria-label="<?php esc_attr_e( 'Edit this order', 'woocommerce' ); ?>" href="<?php echo esc_url( admin_url( 'post.php?action=edit' ) ); ?>&post={{ data.data.id }}"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>
<?php
}
/**
* 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_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( '%d order status changed.', '%d 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 %d order.', 'Removed personal data from %d 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() {
$user_string = '';
$user_id = '';
if ( ! empty( $_GET['_customer_user'] ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended
$user_id = absint( $_GET['_customer_user'] ); // WPCS: input var ok, sanitization ok.
$user = get_user_by( 'id', $user_id );
$user_string = sprintf(
/* translators: 1: user display name 2: user ID 3: user email */
esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce' ),
$user->display_name,
absint( $user->ID ),
$user->user_email
);
}
?>
<select class="wc-customer-search" name="_customer_user" data-placeholder="<?php esc_attr_e( 'Filter by registered customer', 'woocommerce' ); ?>" data-allow_clear="true">
<option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo htmlspecialchars( wp_kses_post( $user_string ) ); // htmlspecialchars to prevent XSS when rendered by selectWoo. ?></option>
</select>
<?php
}
/**
* 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 || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] || ! isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
$post_ids = wc_order_search( wc_clean( wp_unslash( $_GET['s'] ) ) ); // WPCS: input var ok, sanitization ok.
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 ) );
}
}
}

View File

@ -0,0 +1,661 @@
<?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 );
}
/**
* 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 '&nbsp;&nbsp;&larr; <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">&ndash;</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">&ndash;</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">&ndash;</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">&ndash;</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() ? '&larr;' : '&rarr;' ) . ' ' . esc_html__( 'Downloadable', 'woocommerce' ) . '</option>';
$output .= '<option value="virtual" ';
$output .= selected( 'virtual', $current_product_type, false );
$output .= '> ' . ( is_rtl() ? '&larr;' : '&rarr;' ) . ' ' . 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;
}
}

View File

@ -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' ), 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();

View File

@ -0,0 +1,80 @@
<?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 );
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 occured 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();

View File

@ -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&amp;utm_campaign=marketplacesuggestions&amp;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&section=woocommerce_com' ) ); ?>"><?php esc_html_e( 'Manage suggestions', 'woocommerce' ); ?></a>
</div>
</div>

View File

@ -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>

View File

@ -0,0 +1,393 @@
<?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' ),
'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://docs.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&hellip;', '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&hellip;', '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 );
}
}

View File

@ -0,0 +1,178 @@
<?php
/**
* Order Actions
*
* Functions for displaying the order actions meta box.
*
* @package WooCommerce\Admin\Meta Boxes
* @version 2.1.0
*/
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 $post Post object.
*/
public static function output( $post ) {
global $theorder;
// This is used by some callbacks attached to hooks such as woocommerce_order_actions which rely on the global to determine if actions should be displayed for certain orders.
// Avoid using this global with the `woocommerce_order_actions` filter, instead use the $order filter arg.
if ( ! is_object( $theorder ) ) {
$theorder = wc_get_order( $post->ID );
}
$theorder = $theorder instanceof WC_Order ? $theorder : null;
$order_actions = self::get_available_order_actions_for_order( $theorder );
?>
<ul class="order_actions submitbox">
<?php do_action( 'woocommerce_order_actions_start', $post->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', $post->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( get_delete_post_link( $post->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' === $post->post_status ? esc_attr__( 'Create', 'woocommerce' ) : esc_attr__( 'Update', 'woocommerce' ); ?>"><?php echo 'auto-draft' === $post->post_status ? esc_html__( 'Create', 'woocommerce' ) : esc_html__( 'Update', 'woocommerce' ); ?></button>
</li>
<?php do_action( 'woocommerce_order_actions_end', $post->ID ); ?>
</ul>
<?php
}
/**
* 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' => __( 'Email invoice / 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 );
}
}

View File

@ -0,0 +1,637 @@
<?php
/**
* Order Data
*
* Functions for displaying the order data meta box.
*
* @author WooThemes
* @category Admin
* @package WooCommerce\Admin\Meta Boxes
* @version 2.2.0
*/
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();
/**
* Init billing and shipping fields we display + save.
*/
public static function init_address_fields() {
self::$billing_fields = 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&hellip;', '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' ),
),
)
);
self::$shipping_fields = 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&hellip;', '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' ),
),
)
);
}
/**
* Output the metabox.
*
* @param WP_Post $post
*/
public static function output( $post ) {
global $theorder;
if ( ! is_object( $theorder ) ) {
$theorder = wc_get_order( $post->ID );
}
$order = $theorder;
self::init_address_fields();
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( $post->post_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 empty( $post->post_title ) ? __( 'Order', 'woocommerce' ) : esc_attr( $post->post_title ); ?>" />
<input name="post_status" type="hidden" value="<?php echo esc_attr( $post->post_status ); ?>" />
<div id="order_data" class="panel woocommerce-order-data">
<h2 class="woocommerce-order-data__heading">
<?php
/* translators: 1: order type 2: order number */
printf(
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 ) {
/* translators: %s: payment method */
$payment_method_string = sprintf(
__( 'Payment via %s', 'woocommerce' ),
esc_html( isset( $payment_gateways[ $payment_method ] ) ? $payment_gateways[ $payment_method ]->get_title() : $payment_method )
);
if ( $transaction_id = $order->get_transaction_id() ) {
if ( isset( $payment_gateways[ $payment_method ] ) && ( $url = $payment_gateways[ $payment_method ]->get_transaction_url( $order ) ) ) {
$payment_method_string .= ' (<a href="' . esc_url( $url ) . '" target="_blank">' . esc_html( $transaction_id ) . '</a>)';
} else {
$payment_method_string .= ' (' . esc_html( $transaction_id ) . ')';
}
}
$meta_list[] = $payment_method_string;
}
if ( $order->get_date_paid() ) {
/* translators: 1: date 2: time */
$meta_list[] = sprintf(
__( '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' ) )
);
}
if ( $ip_address = $order->get_customer_ip_address() ) {
/* translators: %s: IP address */
$meta_list[] = sprintf(
__( 'Customer IP: %s', 'woocommerce' ),
'<span class="woocommerce-Order-customerIP">' . esc_html( $ip_address ) . '</span>'
);
}
echo wp_kses_post( implode( '. ', $meta_list ) );
?>
</p>
<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">
<label for="order_date"><?php _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', strtotime( $post->post_date ) ) ); ?>" 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])' ) ); ?>" />@
&lrm;
<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', strtotime( $post->post_date ) ) ); ?>" 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', strtotime( $post->post_date ) ) ); ?>" pattern="[0-5]{1}[0-9]{1}" />
<input type="hidden" name="order_date_second" value="<?php echo esc_attr( date_i18n( 's', strtotime( $post->post_date ) ) ); ?>" />
</p>
<p class="form-field form-field-wide wc-order-status">
<label for="order_status">
<?php
_e( 'Status:', 'woocommerce' );
if ( $order->needs_payment() ) {
printf(
'<a href="%s">%s</a>',
esc_url( $order->get_checkout_payment_url() ),
__( 'Customer payment page &rarr;', '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
_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' ) ) ),
' ' . __( 'View other orders &rarr;', 'woocommerce' )
);
printf(
'<a href="%s">%s</a>',
esc_url( add_query_arg( 'user_id', $order->get_user_id( 'edit' ), admin_url( 'user-edit.php' ) ) ),
' ' . __( 'Profile &rarr;', 'woocommerce' )
);
}
?>
</label>
<?php
$user_string = '';
$user_id = '';
if ( $order->get_user_id() ) {
$user_id = absint( $order->get_user_id() );
$user = get_user_by( 'id', $user_id );
/* translators: 1: user display name 2: user ID 3: user email */
$user_string = sprintf(
esc_html__( '%1$s (#%2$s &ndash; %3$s)', 'woocommerce' ),
$user->display_name,
absint( $user->ID ),
$user->user_email
);
}
?>
<select class="wc-customer-search" id="customer_user" name="customer_user" data-placeholder="<?php esc_attr_e( 'Guest', 'woocommerce' ); ?>" data-allow_clear="true">
<option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo htmlspecialchars( wp_kses_post( $user_string ) ); // htmlspecialchars to prevent XSS when rendered by selectWoo. ?></option>
</select>
<!--/email_off-->
</p>
<?php do_action( 'woocommerce_admin_order_data_after_order_details', $order ); ?>
</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>' . __( 'Address:', 'woocommerce' ) . '</strong> ' . __( 'No billing address set.', 'woocommerce' ) . '</p>';
}
foreach ( self::$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.
foreach ( self::$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 );
break;
default:
woocommerce_wp_text_input( $field );
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' ),
)
);
?>
</div>
<?php do_action( 'woocommerce_admin_order_data_after_billing_address', $order ); ?>
</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>' . __( 'Address:', 'woocommerce' ) . '</strong> ' . __( 'No shipping address set.', 'woocommerce' ) . '</p>';
}
if ( ! empty( self::$shipping_fields ) ) {
foreach ( self::$shipping_fields as $key => $field ) {
if ( isset( $field['show'] ) && false === $field['show'] ) {
continue;
}
$field_name = 'shipping_' . $key;
if ( 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 ) {
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' ) ) && $post->post_excerpt ) {
echo '<p class="order_note"><strong>' . __( 'Customer provided note:', 'woocommerce' ) . '</strong> ' . nl2br( esc_html( $post->post_excerpt ) ) . '</p>';
}
?>
</div>
<div class="edit_address">
<?php
// Display form.
if ( ! empty( self::$shipping_fields ) ) {
foreach ( self::$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 ( 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 );
break;
default:
woocommerce_wp_text_input( $field );
break;
}
}
}
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="excerpt"><?php _e( 'Customer provided note', 'woocommerce' ); ?>:</label>
<textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt" placeholder="<?php esc_attr_e( 'Customer notes about the order', 'woocommerce' ); ?>"><?php echo wp_kses_post( $post->post_excerpt ); ?></textarea>
</p>
<?php endif; ?>
</div>
<?php do_action( 'woocommerce_admin_order_data_after_shipping_address', $order ); ?>
</div>
</div>
<div class="clear"></div>
</div>
</div>
<?php
}
/**
* Save meta box data.
*
* @param int $order_id Order ID.
*/
public static function save( $order_id ) {
self::init_address_fields();
// 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.
if ( ! empty( self::$billing_fields ) ) {
foreach ( self::$billing_fields as $key => $field ) {
if ( ! isset( $field['id'] ) ) {
$field['id'] = '_billing_' . $key;
}
if ( ! isset( $_POST[ $field['id'] ] ) ) {
continue;
}
if ( is_callable( array( $order, 'set_billing_' . $key ) ) ) {
$props[ 'billing_' . $key ] = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
} else {
$order->update_meta_data( $field['id'], wc_clean( wp_unslash( $_POST[ $field['id'] ] ) ) );
}
}
}
// Update shipping fields.
if ( ! empty( self::$shipping_fields ) ) {
foreach ( self::$shipping_fields as $key => $field ) {
if ( ! isset( $field['id'] ) ) {
$field['id'] = '_shipping_' . $key;
}
if ( ! isset( $_POST[ $field['id'] ] ) ) {
continue;
}
if ( is_callable( array( $order, 'set_shipping_' . $key ) ) ) {
$props[ 'shipping_' . $key ] = wc_clean( wp_unslash( $_POST[ $field['id'] ] ) );
} else {
$order->update_meta_data( $field['id'], wc_clean( wp_unslash( $_POST[ $field['id'] ] ) ) );
}
}
}
if ( isset( $_POST['_transaction_id'] ) ) {
$props['transaction_id'] = wc_clean( wp_unslash( $_POST['_transaction_id'] ) );
}
// Payment method handling.
if ( $order->get_payment_method() !== 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 ( $payment_method == 'other') {
$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 {
$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'] ) && $_POST['original_post_status'] === 'auto-draft' ) {
$props['created_via'] = 'admin';
}
// Save order data.
$order->set_props( $props );
$order->set_status( wc_clean( wp_unslash( $_POST['order_status'] ) ), '', true );
$order->save();
}
}

View File

@ -0,0 +1,106 @@
<?php
/**
* Order Downloads
*
* @author WooThemes
* @category Admin
* @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 WP_Post $post
*/
public static function output( $post ) {
?>
<div class="order_download_permissions wc-metaboxes-wrapper">
<div class="wc-metaboxes">
<?php
$data_store = WC_Data_Store::load( 'customer-download' );
$download_permissions = $data_store->get_downloads(
array(
'order_id' => $post->ID,
'orderby' => 'product_id',
)
);
$product = null;
$loop = 0;
$file_counter = 1;
if ( $download_permissions && sizeof( $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() );
$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&hellip;', 'woocommerce' ); ?>" data-action="woocommerce_json_search_downloadable_products_and_variations"></select>
<button type="button" class="button grant_access">
<?php _e( 'Grant access', 'woocommerce' ); ?>
</button>
</p>
<div class="clear"></div>
</div>
</div>
<?php
}
/**
* Save meta box data.
*
* @param int $post_id
* @param WP_Post $post
*/
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();
}
}
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Order Data
*
* Functions for displaying the order items 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_Order_Items Class.
*/
class WC_Meta_Box_Order_Items {
/**
* Output the metabox.
*
* @param WP_Post $post
*/
public static function output( $post ) {
global $post, $thepostid, $theorder;
if ( ! is_int( $thepostid ) ) {
$thepostid = $post->ID;
}
if ( ! is_object( $theorder ) ) {
$theorder = wc_get_order( $thepostid );
}
$order = $theorder;
$data = get_post_meta( $post->ID );
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 );
}
}

View File

@ -0,0 +1,49 @@
<?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 $post Post object.
*/
public static function output( $post ) {
global $post;
$args = array(
'order_id' => $post->ID,
);
$notes = wc_get_order_notes( $args );
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
}
}

View File

@ -0,0 +1,568 @@
<?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() {
return apply_filters(
'product_type_options',
array(
'virtual' => array(
'id' => '_virtual',
'wrapper_class' => 'show_if_simple',
'label' => __( 'Virtual', 'woocommerce' ),
'description' => __( 'Virtual products are intangible and are not shipped.', 'woocommerce' ),
'default' => 'no',
),
'downloadable' => array(
'id' => '_downloadable',
'wrapper_class' => 'show_if_simple',
'label' => __( 'Downloadable', 'woocommerce' ),
'description' => __( 'Downloadable products give access to a file upon purchase.', 'woocommerce' ),
'default' => 'no',
),
)
);
}
/**
* Return array of tabs to show.
*
* @return array
*/
private static function get_product_data_tabs() {
$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,
),
)
);
// 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();
}
/**
* Show options for the variable product type.
*/
public static function output_variations() {
global $post, $wpdb, $product_object;
$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 );
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 ] ) );
$attributes[] = apply_filters( 'woocommerce_admin_meta_boxes_prepare_attribute', $attribute, $data, $i );
}
}
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 );
}
do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* 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 ] ) ? wc_clean( 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();
do_action( 'woocommerce_save_product_variation', $variation_id, $i );
}
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 ) );
}
}

View File

@ -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 &mdash; %s &mdash; %s: %s &mdash; ',
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>

View File

@ -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%">&nbsp;</td>
<td class="quantity" width="1%">&nbsp;</td>
<td class="line_cost" width="1%">
<div class="view">
<?php
echo wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) );
if ( $refunded = $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() ) ) : '&ndash;';
if ( $refunded = $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>

View File

@ -0,0 +1,66 @@
<?php
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 if ( $meta_data = $item->get_formatted_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 = $item->get_formatted_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">&times;</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&nbsp;meta', 'woocommerce' ); ?></button></td>
</tr>
</tfoot>
</table>
</div>

View File

@ -0,0 +1,193 @@
<?php
/**
* Shows an order item
*
* @package WooCommerce\Admin
* @var object $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">&times;</small> ' . esc_html( $item->get_quantity() );
$refunded_qty = $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 = $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 ] : '';
if ( '' !== $tax_item_subtotal ) {
$round_at_subtotal = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
$tax_item_total = wc_round_tax_total( $tax_item_total, $round_at_subtotal ? wc_get_rounding_precision() : null );
$tax_item_subtotal = wc_round_tax_total( $tax_item_subtotal, $round_at_subtotal ? wc_get_rounding_precision() : null );
}
?>
<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 '&ndash;';
}
$refunded = $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>

View File

@ -0,0 +1,478 @@
<?php
/**
* Order items HTML for meta box.
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
global $wpdb;
$payment_gateway = wc_get_payment_gateway_by_order( $order );
$line_items = $order->get_items( apply_filters( 'woocommerce_admin_order_item_types', 'line_item' ) );
$discounts = $order->get_items( 'discount' );
$line_items_fee = $order->get_items( 'fee' );
$line_items_shipping = $order->get_items( 'shipping' );
if ( wc_tax_enabled() ) {
$order_taxes = $order->get_taxes();
$tax_classes = WC_Tax::get_tax_classes();
$classes_options = wc_get_product_tax_class_options();
$show_tax_columns = count( $order_taxes ) === 1;
}
?>
<div class="woocommerce_order_items_wrapper wc-order-items-editable">
<table cellpadding="0" cellspacing="0" class="woocommerce_order_items">
<thead>
<tr>
<th class="item sortable" colspan="2" data-sort="string-ins"><?php esc_html_e( 'Item', 'woocommerce' ); ?></th>
<?php do_action( 'woocommerce_admin_order_item_headers', $order ); ?>
<th class="item_cost sortable" data-sort="float"><?php esc_html_e( 'Cost', 'woocommerce' ); ?></th>
<th class="quantity sortable" data-sort="int"><?php esc_html_e( 'Qty', 'woocommerce' ); ?></th>
<th class="line_cost sortable" data-sort="float"><?php esc_html_e( 'Total', 'woocommerce' ); ?></th>
<?php
if ( ! empty( $order_taxes ) ) :
foreach ( $order_taxes as $tax_id => $tax_item ) :
$tax_class = wc_get_tax_class_by_tax_id( $tax_item['rate_id'] );
$tax_class_name = isset( $classes_options[ $tax_class ] ) ? $classes_options[ $tax_class ] : __( 'Tax', 'woocommerce' );
$column_label = ! empty( $tax_item['label'] ) ? $tax_item['label'] : __( 'Tax', 'woocommerce' );
/* translators: %1$s: tax item name %2$s: tax class name */
$column_tip = sprintf( esc_html__( '%1$s (%2$s)', 'woocommerce' ), $tax_item['name'], $tax_class_name );
?>
<th class="line_tax tips" data-tip="<?php echo esc_attr( $column_tip ); ?>">
<?php echo esc_attr( $column_label ); ?>
<input type="hidden" class="order-tax-id" name="order_taxes[<?php echo esc_attr( $tax_id ); ?>]" value="<?php echo esc_attr( $tax_item['rate_id'] ); ?>">
<?php if ( $order->is_editable() ) : ?>
<a class="delete-order-tax" href="#" data-rate_id="<?php echo esc_attr( $tax_id ); ?>"></a>
<?php endif; ?>
</th>
<?php
endforeach;
endif;
?>
<th class="wc-order-edit-line-item" width="1%">&nbsp;</th>
</tr>
</thead>
<tbody id="order_line_items">
<?php
foreach ( $line_items as $item_id => $item ) {
do_action( 'woocommerce_before_order_item_' . $item->get_type() . '_html', $item_id, $item, $order );
include __DIR__ . '/html-order-item.php';
do_action( 'woocommerce_order_item_' . $item->get_type() . '_html', $item_id, $item, $order );
}
do_action( 'woocommerce_admin_order_items_after_line_items', $order->get_id() );
?>
</tbody>
<tbody id="order_fee_line_items">
<?php
foreach ( $line_items_fee as $item_id => $item ) {
include __DIR__ . '/html-order-fee.php';
}
do_action( 'woocommerce_admin_order_items_after_fees', $order->get_id() );
?>
</tbody>
<tbody id="order_shipping_line_items">
<?php
$shipping_methods = WC()->shipping() ? WC()->shipping()->load_shipping_methods() : array();
foreach ( $line_items_shipping as $item_id => $item ) {
include __DIR__ . '/html-order-shipping.php';
}
do_action( 'woocommerce_admin_order_items_after_shipping', $order->get_id() );
?>
</tbody>
<tbody id="order_refunds">
<?php
$refunds = $order->get_refunds();
if ( $refunds ) {
foreach ( $refunds as $refund ) {
include __DIR__ . '/html-order-refund.php';
}
do_action( 'woocommerce_admin_order_items_after_refunds', $order->get_id() );
}
?>
</tbody>
</table>
</div>
<div class="wc-order-data-row wc-order-totals-items wc-order-items-editable">
<?php
$coupons = $order->get_items( 'coupon' );
if ( $coupons ) :
?>
<div class="wc-used-coupons">
<ul class="wc_coupon_list">
<li><strong><?php esc_html_e( 'Coupon(s)', 'woocommerce' ); ?></strong></li>
<?php
foreach ( $coupons as $item_id => $item ) :
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' LIMIT 1;", $item->get_code() ) ); // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
$class = $order->is_editable() ? 'code editable' : 'code';
?>
<li class="<?php echo esc_attr( $class ); ?>">
<?php if ( $post_id ) : ?>
<?php
$post_url = apply_filters(
'woocommerce_admin_order_item_coupon_url',
add_query_arg(
array(
'post' => $post_id,
'action' => 'edit',
),
admin_url( 'post.php' )
),
$item,
$order
);
?>
<a href="<?php echo esc_url( $post_url ); ?>" class="tips" data-tip="<?php echo esc_attr( wc_price( $item->get_discount(), array( 'currency' => $order->get_currency() ) ) ); ?>">
<span><?php echo esc_html( $item->get_code() ); ?></span>
</a>
<?php else : ?>
<span class="tips" data-tip="<?php echo esc_attr( wc_price( $item->get_discount(), array( 'currency' => $order->get_currency() ) ) ); ?>">
<span><?php echo esc_html( $item->get_code() ); ?></span>
</span>
<?php endif; ?>
<?php if ( $order->is_editable() ) : ?>
<a class="remove-coupon" href="javascript:void(0)" aria-label="Remove" data-code="<?php echo esc_attr( $item->get_code() ); ?>"></a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<table class="wc-order-totals">
<tr>
<td class="label"><?php esc_html_e( 'Items Subtotal:', 'woocommerce' ); ?></td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_subtotal(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
<?php if ( 0 < $order->get_total_discount() ) : ?>
<tr>
<td class="label"><?php esc_html_e( 'Coupon(s):', 'woocommerce' ); ?></td>
<td width="1%"></td>
<td class="total">-
<?php echo wc_price( $order->get_total_discount(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
<?php endif; ?>
<?php if ( 0 < $order->get_total_fees() ) : ?>
<tr>
<td class="label"><?php esc_html_e( 'Fees:', 'woocommerce' ); ?></td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_total_fees(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
<?php endif; ?>
<?php do_action( 'woocommerce_admin_order_totals_after_discount', $order->get_id() ); ?>
<?php if ( $order->get_shipping_methods() ) : ?>
<tr>
<td class="label"><?php esc_html_e( 'Shipping:', 'woocommerce' ); ?></td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
<?php endif; ?>
<?php do_action( 'woocommerce_admin_order_totals_after_shipping', $order->get_id() ); ?>
<?php if ( wc_tax_enabled() ) : ?>
<?php foreach ( $order->get_tax_totals() as $code => $tax_total ) : ?>
<tr>
<td class="label"><?php echo esc_html( $tax_total->label ); ?>:</td>
<td width="1%"></td>
<td class="total">
<?php
// We use wc_round_tax_total here because tax may need to be round up or round down depending upon settings, whereas wc_price alone will always round it down.
echo wc_price( wc_round_tax_total( $tax_total->amount ), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
<?php do_action( 'woocommerce_admin_order_totals_after_tax', $order->get_id() ); ?>
<tr>
<td class="label"><?php esc_html_e( 'Order Total', 'woocommerce' ); ?>:</td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_total(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
</table>
<div class="clear"></div>
<?php if ( in_array( $order->get_status(), array( 'processing', 'completed', 'refunded' ), true ) && ! empty( $order->get_date_paid() ) ) : ?>
<table class="wc-order-totals" style="border-top: 1px solid #999; margin-top:12px; padding-top:12px">
<tr>
<td class="<?php echo $order->get_total_refunded() ? 'label' : 'label label-highlight'; ?>"><?php esc_html_e( 'Paid', 'woocommerce' ); ?>: <br /></td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_total(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
<tr>
<td>
<span class="description">
<?php
if ( $order->get_payment_method_title() ) {
/* translators: 1: payment date. 2: payment method */
echo esc_html( sprintf( __( '%1$s via %2$s', 'woocommerce' ), $order->get_date_paid()->date_i18n( get_option( 'date_format' ) ), $order->get_payment_method_title() ) );
} else {
echo esc_html( $order->get_date_paid()->date_i18n( get_option( 'date_format' ) ) );
}
?>
</span>
</td>
<td colspan="2"></td>
</tr>
</table>
<div class="clear"></div>
<?php endif; ?>
<?php if ( $order->get_total_refunded() ) : ?>
<table class="wc-order-totals" style="border-top: 1px solid #999; margin-top:12px; padding-top:12px">
<tr>
<td class="label refunded-total"><?php esc_html_e( 'Refunded', 'woocommerce' ); ?>:</td>
<td width="1%"></td>
<td class="total refunded-total">-<?php echo wc_price( $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
</tr>
<?php do_action( 'woocommerce_admin_order_totals_after_refunded', $order->get_id() ); ?>
<tr>
<td class="label label-highlight"><?php esc_html_e( 'Net Payment', 'woocommerce' ); ?>:</td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_total() - $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>
</tr>
</table>
<?php endif; ?>
<div class="clear"></div>
<table class="wc-order-totals">
<?php do_action( 'woocommerce_admin_order_totals_after_total', $order->get_id() ); ?>
</table>
<div class="clear"></div>
</div>
<div class="wc-order-data-row wc-order-bulk-actions wc-order-data-row-toggle">
<p class="add-items">
<?php if ( $order->is_editable() ) : ?>
<button type="button" class="button add-line-item"><?php esc_html_e( 'Add item(s)', 'woocommerce' ); ?></button>
<?php if ( wc_coupons_enabled() ) : ?>
<button type="button" class="button add-coupon"><?php esc_html_e( 'Apply coupon', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php else : ?>
<span class="description"><?php echo wc_help_tip( __( 'To edit this order change the status back to "Pending payment"', 'woocommerce' ) ); ?> <?php esc_html_e( 'This order is no longer editable.', 'woocommerce' ); ?></span>
<?php endif; ?>
<?php if ( 0 < $order->get_total() - $order->get_total_refunded() || 0 < absint( $order->get_item_count() - $order->get_item_count_refunded() ) ) : ?>
<button type="button" class="button refund-items"><?php esc_html_e( 'Refund', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php
// Allow adding custom buttons.
do_action( 'woocommerce_order_item_add_action_buttons', $order );
?>
<?php if ( $order->is_editable() ) : ?>
<button type="button" class="button button-primary calculate-action"><?php esc_html_e( 'Recalculate', 'woocommerce' ); ?></button>
<?php endif; ?>
</p>
</div>
<div class="wc-order-data-row wc-order-add-item wc-order-data-row-toggle" style="display:none;">
<button type="button" class="button add-order-item"><?php esc_html_e( 'Add product(s)', 'woocommerce' ); ?></button>
<button type="button" class="button add-order-fee"><?php esc_html_e( 'Add fee', 'woocommerce' ); ?></button>
<button type="button" class="button add-order-shipping"><?php esc_html_e( 'Add shipping', 'woocommerce' ); ?></button>
<?php if ( wc_tax_enabled() ) : ?>
<button type="button" class="button add-order-tax"><?php esc_html_e( 'Add tax', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php
// Allow adding custom buttons.
do_action( 'woocommerce_order_item_add_line_buttons', $order );
?>
<button type="button" class="button cancel-action"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></button>
<button type="button" class="button button-primary save-action"><?php esc_html_e( 'Save', 'woocommerce' ); ?></button>
</div>
<?php if ( 0 < $order->get_total() - $order->get_total_refunded() || 0 < absint( $order->get_item_count() - $order->get_item_count_refunded() ) ) : ?>
<div class="wc-order-data-row wc-order-refund-items wc-order-data-row-toggle" style="display: none;">
<table class="wc-order-totals">
<?php if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) : ?>
<tr>
<td class="label"><label for="restock_refunded_items"><?php esc_html_e( 'Restock refunded items', 'woocommerce' ); ?>:</label></td>
<td class="total"><input type="checkbox" id="restock_refunded_items" name="restock_refunded_items" <?php checked( apply_filters( 'woocommerce_restock_refunded_items', true ) ); ?> /></td>
</tr>
<?php endif; ?>
<tr>
<td class="label"><?php esc_html_e( 'Amount already refunded', 'woocommerce' ); ?>:</td>
<td class="total">-<?php echo wc_price( $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
</tr>
<tr>
<td class="label"><?php esc_html_e( 'Total available to refund', 'woocommerce' ); ?>:</td>
<td class="total"><?php echo wc_price( $order->get_total() - $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
</tr>
<tr>
<td class="label">
<label for="refund_amount">
<?php echo wc_help_tip( __( 'Refund the line items above. This will show the total amount to be refunded', 'woocommerce' ) ); ?>
<?php esc_html_e( 'Refund amount', 'woocommerce' ); ?>:
</label>
</td>
<td class="total">
<input type="text" id="refund_amount" name="refund_amount" class="wc_input_price"
<?php
if ( wc_tax_enabled() ) {
// If taxes are enabled, using this refund amount can cause issues due to taxes not being refunded also.
// The refunds should be added to the line items, not the order as a whole.
echo 'readonly';
}
?>
/>
<div class="clear"></div>
</td>
</tr>
<tr>
<td class="label">
<label for="refund_reason">
<?php echo wc_help_tip( __( 'Note: the refund reason will be visible by the customer.', 'woocommerce' ) ); ?>
<?php esc_html_e( 'Reason for refund (optional):', 'woocommerce' ); ?>
</label>
</td>
<td class="total">
<input type="text" id="refund_reason" name="refund_reason" />
<div class="clear"></div>
</td>
</tr>
</table>
<div class="clear"></div>
<div class="refund-actions">
<?php
$refund_amount = '<span class="wc-order-refund-amount">' . wc_price( 0, array( 'currency' => $order->get_currency() ) ) . '</span>';
$gateway_name = false !== $payment_gateway ? ( ! empty( $payment_gateway->method_title ) ? $payment_gateway->method_title : $payment_gateway->get_title() ) : __( 'Payment gateway', 'woocommerce' );
if ( false !== $payment_gateway && $payment_gateway->can_refund_order( $order ) ) {
/* translators: refund amount, gateway name */
echo '<button type="button" class="button button-primary do-api-refund">' . sprintf( esc_html__( 'Refund %1$s via %2$s', 'woocommerce' ), wp_kses_post( $refund_amount ), esc_html( $gateway_name ) ) . '</button>';
}
?>
<?php /* translators: refund amount */ ?>
<button type="button" class="button button-primary do-manual-refund tips" data-tip="<?php esc_attr_e( 'You will need to manually issue a refund through your payment gateway after using this.', 'woocommerce' ); ?>"><?php printf( esc_html__( 'Refund %s manually', 'woocommerce' ), wp_kses_post( $refund_amount ) ); ?></button>
<button type="button" class="button cancel-action"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></button>
<input type="hidden" id="refunded_amount" name="refunded_amount" value="<?php echo esc_attr( $order->get_total_refunded() ); ?>" />
<div class="clear"></div>
</div>
</div>
<?php endif; ?>
<script type="text/template" id="tmpl-wc-modal-add-products">
<div class="wc-backbone-modal">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1><?php esc_html_e( 'Add products', 'woocommerce' ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text">Close modal panel</span>
</button>
</header>
<article>
<form action="" method="post">
<table class="widefat">
<thead>
<tr>
<th><?php esc_html_e( 'Product', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Quantity', 'woocommerce' ); ?></th>
</tr>
</thead>
<?php
$row = '
<td><select class="wc-product-search" name="item_id" data-allow_clear="true" data-display_stock="true" data-exclude_type="variable" data-placeholder="' . esc_attr__( 'Search for a product&hellip;', 'woocommerce' ) . '"></select></td>
<td><input type="number" step="1" min="0" max="9999" autocomplete="off" name="item_qty" placeholder="1" size="4" class="quantity" /></td>';
?>
<tbody data-row="<?php echo esc_attr( $row ); ?>">
<tr>
<?php echo $row; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</tr>
</tbody>
</table>
</form>
</article>
<footer>
<div class="inner">
<button id="btn-ok" class="button button-primary button-large"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>
<script type="text/template" id="tmpl-wc-modal-add-tax">
<div class="wc-backbone-modal">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<h1><?php esc_html_e( 'Add tax', 'woocommerce' ); ?></h1>
<button class="modal-close modal-close-link dashicons dashicons-no-alt">
<span class="screen-reader-text">Close modal panel</span>
</button>
</header>
<article>
<form action="" method="post">
<table class="widefat">
<thead>
<tr>
<th>&nbsp;</th>
<th><?php esc_html_e( 'Rate name', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Tax class', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Rate code', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Rate %', 'woocommerce' ); ?></th>
</tr>
</thead>
<?php
$rates = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates ORDER BY tax_rate_name LIMIT 100" );
foreach ( $rates as $rate ) {
echo '
<tr>
<td><input type="radio" id="add_order_tax_' . absint( $rate->tax_rate_id ) . '" name="add_order_tax" value="' . absint( $rate->tax_rate_id ) . '" /></td>
<td><label for="add_order_tax_' . absint( $rate->tax_rate_id ) . '">' . esc_html( WC_Tax::get_rate_label( $rate ) ) . '</label></td>
<td>' . ( isset( $classes_options[ $rate->tax_rate_class ] ) ? esc_html( $classes_options[ $rate->tax_rate_class ] ) : '-' ) . '</td>
<td>' . esc_html( WC_Tax::get_rate_code( $rate ) ) . '</td>
<td>' . esc_html( WC_Tax::get_rate_percent( $rate ) ) . '</td>
</tr>
';
}
?>
</table>
<?php if ( absint( $wpdb->get_var( "SELECT COUNT(tax_rate_id) FROM {$wpdb->prefix}woocommerce_tax_rates;" ) ) > 100 ) : ?>
<p>
<label for="manual_tax_rate_id"><?php esc_html_e( 'Or, enter tax rate ID:', 'woocommerce' ); ?></label><br/>
<input type="number" name="manual_tax_rate_id" id="manual_tax_rate_id" step="1" placeholder="<?php esc_attr_e( 'Optional', 'woocommerce' ); ?>" />
</p>
<?php endif; ?>
</form>
</article>
<footer>
<div class="inner">
<button id="btn-ok" class="button button-primary button-large"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"></div>
</script>

View File

@ -0,0 +1,48 @@
<?php
/**
* Order notes HTML for meta box.
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<ul class="order_notes">
<?php
if ( $notes ) {
foreach ( $notes as $note ) {
$css_class = array( 'note' );
$css_class[] = $note->customer_note ? 'customer-note' : '';
$css_class[] = 'system' === $note->added_by ? 'system-note' : '';
$css_class = apply_filters( 'woocommerce_order_note_class', array_filter( $css_class ), $note );
?>
<li rel="<?php echo absint( $note->id ); ?>" class="<?php echo esc_attr( implode( ' ', $css_class ) ); ?>">
<div class="note_content">
<?php echo wpautop( wptexturize( wp_kses_post( $note->content ) ) ); // @codingStandardsIgnoreLine ?>
</div>
<p class="meta">
<abbr class="exact-date" title="<?php echo esc_attr( $note->date_created->date( 'Y-m-d H:i:s' ) ); ?>">
<?php
/* translators: %1$s: note date %2$s: note time */
echo esc_html( sprintf( __( '%1$s at %2$s', 'woocommerce' ), $note->date_created->date_i18n( wc_date_format() ), $note->date_created->date_i18n( wc_time_format() ) ) );
?>
</abbr>
<?php
if ( 'system' !== $note->added_by ) :
/* translators: %s: note author */
echo esc_html( sprintf( ' ' . __( 'by %s', 'woocommerce' ), $note->added_by ) );
endif;
?>
<a href="#" class="delete_note" role="button"><?php esc_html_e( 'Delete note', 'woocommerce' ); ?></a>
</p>
</li>
<?php
}
} else {
?>
<li class="no-items"><?php esc_html_e( 'There are no notes yet.', 'woocommerce' ); ?></li>
<?php
}
?>
</ul>

View File

@ -0,0 +1,79 @@
<?php
/**
* Show order refund
*
* @var object $refund The refund object.
* @package WooCommerce\Admin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
$who_refunded = new WP_User( $refund->get_refunded_by() );
?>
<tr class="refund <?php echo ( ! empty( $class ) ) ? esc_attr( $class ) : ''; ?>" data-order_refund_id="<?php echo esc_attr( $refund->get_id() ); ?>">
<td class="thumb"><div></div></td>
<td class="name">
<?php
if ( $who_refunded->exists() ) {
printf(
/* translators: 1: refund id 2: refund date 3: username */
esc_html__( 'Refund #%1$s - %2$s by %3$s', 'woocommerce' ),
esc_html( $refund->get_id() ),
esc_html( wc_format_datetime( $refund->get_date_created(), get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) ) ),
sprintf(
'<abbr class="refund_by" title="%1$s">%2$s</abbr>',
/* translators: 1: ID who refunded */
sprintf( esc_attr__( 'ID: %d', 'woocommerce' ), absint( $who_refunded->ID ) ),
esc_html( $who_refunded->display_name )
)
);
} else {
printf(
/* translators: 1: refund id 2: refund date */
esc_html__( 'Refund #%1$s - %2$s', 'woocommerce' ),
esc_html( $refund->get_id() ),
esc_html( wc_format_datetime( $refund->get_date_created(), get_option( 'date_format' ) . ', ' . get_option( 'time_format' ) ) )
);
}
?>
<?php if ( $refund->get_reason() ) : ?>
<p class="description"><?php echo esc_html( $refund->get_reason() ); ?></p>
<?php endif; ?>
<input type="hidden" class="order_refund_id" name="order_refund_id[]" value="<?php echo esc_attr( $refund->get_id() ); ?>" />
<?php do_action( 'woocommerce_after_order_refund_item_name', $refund ); ?>
</td>
<?php do_action( 'woocommerce_admin_order_item_values', null, $refund, $refund->get_id() ); ?>
<td class="item_cost" width="1%">&nbsp;</td>
<td class="quantity" width="1%">&nbsp;</td>
<td class="line_cost" width="1%">
<div class="view">
<?php
echo wp_kses_post(
wc_price( '-' . $refund->get_amount(), array( 'currency' => $refund->get_currency() ) )
);
?>
</div>
</td>
<?php
if ( wc_tax_enabled() ) :
$total_taxes = count( $order_taxes );
?>
<?php for ( $i = 0; $i < $total_taxes; $i++ ) : ?>
<td class="line_tax" width="1%"></td>
<?php endfor; ?>
<?php endif; ?>
<td class="wc-order-edit-line-item">
<div class="wc-order-edit-line-item-actions">
<a class="delete_refund" href="#"></a>
</div>
</td>
</tr>

View File

@ -0,0 +1,116 @@
<?php
/**
* Shows a shipping line
*
* @package WooCommerce\Admin
*
* @var object $item The item being displayed
* @var int $item_id The id of the item being displayed
*
* @package WooCommerce\Admin\Views
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<tr class="shipping <?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() : __( 'Shipping', 'woocommerce' ) ); ?>
</div>
<div class="edit" style="display: none;">
<input type="hidden" name="shipping_method_id[]" value="<?php echo esc_attr( $item_id ); ?>" />
<input type="text" class="shipping_method_name" placeholder="<?php esc_attr_e( 'Shipping name', 'woocommerce' ); ?>" name="shipping_method_title[<?php echo esc_attr( $item_id ); ?>]" value="<?php echo esc_attr( $item->get_name() ); ?>" />
<select class="shipping_method" name="shipping_method[<?php echo esc_attr( $item_id ); ?>]">
<optgroup label="<?php esc_attr_e( 'Shipping method', 'woocommerce' ); ?>">
<option value=""><?php esc_html_e( 'N/A', 'woocommerce' ); ?></option>
<?php
$found_method = false;
foreach ( $shipping_methods as $method ) {
$is_active = $item->get_method_id() === $method->id;
echo '<option value="' . esc_attr( $method->id ) . '" ' . selected( true, $is_active, false ) . '>' . esc_html( $method->get_method_title() ) . '</option>';
if ( $is_active ) {
$found_method = true;
}
}
if ( ! $found_method && $item->get_method_id() ) {
echo '<option value="' . esc_attr( $item->get_method_id() ) . '" selected="selected">' . esc_html__( 'Other', 'woocommerce' ) . '</option>';
} else {
echo '<option value="other">' . esc_html__( 'Other', 'woocommerce' ) . '</option>';
}
?>
</optgroup>
</select>
</div>
<?php do_action( 'woocommerce_before_order_itemmeta', $item_id, $item, null ); ?>
<?php require __DIR__ . '/html-order-item-meta.php'; ?>
<?php do_action( 'woocommerce_after_order_itemmeta', $item_id, $item, null ); ?>
</td>
<?php do_action( 'woocommerce_admin_order_item_values', null, $item, absint( $item_id ) ); ?>
<td class="item_cost" width="1%">&nbsp;</td>
<td class="quantity" width="1%">&nbsp;</td>
<td class="line_cost" width="1%">
<div class="view">
<?php
echo wp_kses_post( wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) ) );
$refunded = $order->get_total_refunded_for_item( $item_id, 'shipping' );
if ( $refunded ) {
echo wp_kses_post( '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );
}
?>
</div>
<div class="edit" style="display: none;">
<input type="text" name="shipping_cost[<?php echo esc_attr( $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
$tax_data = $item->get_taxes();
if ( $tax_data && 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 wp_kses_post( ( '' !== $tax_item_total ) ? wc_price( $tax_item_total, array( 'currency' => $order->get_currency() ) ) : '&ndash;' );
$refunded = $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'shipping' );
if ( $refunded ) {
echo wp_kses_post( '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );
}
?>
</div>
<div class="edit" style="display: none;">
<input type="text" name="shipping_taxes[<?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>

View File

@ -0,0 +1,89 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div data-taxonomy="<?php echo esc_attr( $attribute->get_taxonomy() ); ?>" class="woocommerce_attribute wc-metabox postbox closed <?php echo esc_attr( implode( ' ', $metabox_class ) ); ?>" rel="<?php echo esc_attr( $attribute->get_position() ); ?>">
<h3>
<a href="#" class="remove_row delete"><?php esc_html_e( 'Remove', 'woocommerce' ); ?></a>
<div class="handlediv" title="<?php esc_attr_e( 'Click to toggle', 'woocommerce' ); ?>"></div>
<div class="tips sort" data-tip="<?php esc_attr_e( 'Drag and drop to set admin attribute order', 'woocommerce' ); ?>"></div>
<strong class="attribute_name"><?php echo wc_attribute_label( $attribute->get_name() ); ?></strong>
</h3>
<div class="woocommerce_attribute_data wc-metabox-content hidden">
<table cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="attribute_name">
<label><?php esc_html_e( 'Name', 'woocommerce' ); ?>:</label>
<?php if ( $attribute->is_taxonomy() ) : ?>
<strong><?php echo wc_attribute_label( $attribute->get_name() ); ?></strong>
<input type="hidden" name="attribute_names[<?php echo esc_attr( $i ); ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" />
<?php else : ?>
<input type="text" class="attribute_name" name="attribute_names[<?php echo esc_attr( $i ); ?>]" value="<?php echo esc_attr( $attribute->get_name() ); ?>" />
<?php endif; ?>
<input type="hidden" name="attribute_position[<?php echo esc_attr( $i ); ?>]" class="attribute_position" value="<?php echo esc_attr( $attribute->get_position() ); ?>" />
</td>
<td rowspan="3">
<label><?php esc_html_e( 'Value(s)', 'woocommerce' ); ?>:</label>
<?php
if ( $attribute->is_taxonomy() && $attribute_taxonomy = $attribute->get_taxonomy_object() ) {
$attribute_types = wc_get_attribute_types();
if ( ! array_key_exists( $attribute_taxonomy->attribute_type, $attribute_types ) ) {
$attribute_taxonomy->attribute_type = 'select';
}
if ( 'select' === $attribute_taxonomy->attribute_type ) {
?>
<select multiple="multiple" data-placeholder="<?php esc_attr_e( 'Select terms', 'woocommerce' ); ?>" class="multiselect attribute_values wc-enhanced-select" name="attribute_values[<?php echo esc_attr( $i ); ?>][]">
<?php
$args = array(
'orderby' => ! empty( $attribute_taxonomy->attribute_orderby ) ? $attribute_taxonomy->attribute_orderby : 'name',
'hide_empty' => 0,
);
$all_terms = get_terms( $attribute->get_taxonomy(), apply_filters( 'woocommerce_product_attribute_terms', $args ) );
if ( $all_terms ) {
foreach ( $all_terms as $term ) {
$options = $attribute->get_options();
$options = ! empty( $options ) ? $options : array();
echo '<option value="' . esc_attr( $term->term_id ) . '"' . wc_selected( $term->term_id, $options ) . '>' . esc_html( apply_filters( 'woocommerce_product_attribute_term_name', $term->name, $term ) ) . '</option>';
}
}
?>
</select>
<button class="button plus select_all_attributes"><?php esc_html_e( 'Select all', 'woocommerce' ); ?></button>
<button class="button minus select_no_attributes"><?php esc_html_e( 'Select none', 'woocommerce' ); ?></button>
<button class="button fr plus add_new_attribute"><?php esc_html_e( 'Add new', 'woocommerce' ); ?></button>
<?php
}
do_action( 'woocommerce_product_option_terms', $attribute_taxonomy, $i, $attribute );
} else {
/* translators: %s: WC_DELIMITER */
?>
<textarea name="attribute_values[<?php echo esc_attr( $i ); ?>]" cols="5" rows="5" placeholder="<?php printf( esc_attr__( 'Enter some text, or some attributes by "%s" separating values.', 'woocommerce' ), WC_DELIMITER ); ?>"><?php echo esc_textarea( wc_implode_text_attributes( $attribute->get_options() ) ); ?></textarea>
<?php
}
?>
</td>
</tr>
<tr>
<td>
<label><input type="checkbox" class="checkbox" <?php checked( $attribute->get_visible(), true ); ?> name="attribute_visibility[<?php echo esc_attr( $i ); ?>]" value="1" /> <?php esc_html_e( 'Visible on the product page', 'woocommerce' ); ?></label>
</td>
</tr>
<tr>
<td>
<div class="enable_variation show_if_variable">
<label><input type="checkbox" class="checkbox" <?php checked( $attribute->get_variation(), true ); ?> name="attribute_variation[<?php echo esc_attr( $i ); ?>]" value="1" /> <?php esc_html_e( 'Used for variations', 'woocommerce' ); ?></label>
</div>
</td>
</tr>
<?php do_action( 'woocommerce_after_product_attribute_settings', $attribute, $i ); ?>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,57 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div id="advanced_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group hide_if_external hide_if_grouped">
<?php
woocommerce_wp_textarea_input(
array(
'id' => '_purchase_note',
'value' => $product_object->get_purchase_note( 'edit' ),
'label' => __( 'Purchase note', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Enter an optional note to send the customer after purchase.', 'woocommerce' ),
)
);
?>
</div>
<div class="options_group">
<?php
woocommerce_wp_text_input(
array(
'id' => 'menu_order',
'value' => $product_object->get_menu_order( 'edit' ),
'label' => __( 'Menu order', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Custom ordering position.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => '1',
),
)
);
?>
</div>
<?php if ( post_type_supports( 'product', 'comments' ) ) : ?>
<div class="options_group reviews">
<?php
woocommerce_wp_checkbox(
array(
'id' => 'comment_status',
'value' => $product_object->get_reviews_allowed( 'edit' ) ? 'open' : 'closed',
'label' => __( 'Enable reviews', 'woocommerce' ),
'cbvalue' => 'open',
)
);
do_action( 'woocommerce_product_options_reviews' );
?>
</div>
<?php endif; ?>
<?php do_action( 'woocommerce_product_options_advanced' ); ?>
</div>

View File

@ -0,0 +1,56 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div id="product_attributes" class="panel wc-metaboxes-wrapper hidden">
<div class="toolbar toolbar-top">
<span class="expand-close">
<a href="#" class="expand_all"><?php esc_html_e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php esc_html_e( 'Close', 'woocommerce' ); ?></a>
</span>
<select name="attribute_taxonomy" class="attribute_taxonomy">
<option value=""><?php esc_html_e( 'Custom product attribute', 'woocommerce' ); ?></option>
<?php
global $wc_product_attributes;
// Array of defined attribute taxonomies.
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( ! empty( $attribute_taxonomies ) ) {
foreach ( $attribute_taxonomies as $tax ) {
$attribute_taxonomy_name = wc_attribute_taxonomy_name( $tax->attribute_name );
$label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;
echo '<option value="' . esc_attr( $attribute_taxonomy_name ) . '">' . esc_html( $label ) . '</option>';
}
}
?>
</select>
<button type="button" class="button add_attribute"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
</div>
<div class="product_attributes wc-metaboxes">
<?php
// Product attributes - taxonomies and custom, ordered, with visibility and variation attributes set.
$attributes = $product_object->get_attributes( 'edit' );
$i = -1;
foreach ( $attributes as $attribute ) {
$i++;
$metabox_class = array();
if ( $attribute->is_taxonomy() ) {
$metabox_class[] = 'taxonomy';
$metabox_class[] = $attribute->get_name();
}
include __DIR__ . '/html-product-attribute.php';
}
?>
</div>
<div class="toolbar">
<span class="expand-close">
<a href="#" class="expand_all"><?php esc_html_e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php esc_html_e( 'Close', 'woocommerce' ); ?></a>
</span>
<button type="button" class="button save_attributes button-primary"><?php esc_html_e( 'Save attributes', 'woocommerce' ); ?></button>
</div>
<?php do_action( 'woocommerce_product_options_attributes' ); ?>
</div>

View File

@ -0,0 +1,189 @@
<?php
/**
* Product general data panel.
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<div id="general_product_data" class="panel woocommerce_options_panel">
<div class="options_group show_if_external">
<?php
woocommerce_wp_text_input(
array(
'id' => '_product_url',
'value' => is_callable( array( $product_object, 'get_product_url' ) ) ? $product_object->get_product_url( 'edit' ) : '',
'label' => __( 'Product URL', 'woocommerce' ),
'placeholder' => 'https://',
'description' => __( 'Enter the external URL to the product.', 'woocommerce' ),
)
);
woocommerce_wp_text_input(
array(
'id' => '_button_text',
'value' => is_callable( array( $product_object, 'get_button_text' ) ) ? $product_object->get_button_text( 'edit' ) : '',
'label' => __( 'Button text', 'woocommerce' ),
'placeholder' => _x( 'Buy product', 'placeholder', 'woocommerce' ),
'description' => __( 'This text will be shown on the button linking to the external product.', 'woocommerce' ),
)
);
do_action( 'woocommerce_product_options_external' );
?>
</div>
<div class="options_group pricing show_if_simple show_if_external hidden">
<?php
woocommerce_wp_text_input(
array(
'id' => '_regular_price',
'value' => $product_object->get_regular_price( 'edit' ),
'label' => __( 'Regular price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')',
'data_type' => 'price',
)
);
woocommerce_wp_text_input(
array(
'id' => '_sale_price',
'value' => $product_object->get_sale_price( 'edit' ),
'data_type' => 'price',
'label' => __( 'Sale price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')',
'description' => '<a href="#" class="sale_schedule">' . __( 'Schedule', 'woocommerce' ) . '</a>',
)
);
$sale_price_dates_from_timestamp = $product_object->get_date_on_sale_from( 'edit' ) ? $product_object->get_date_on_sale_from( 'edit' )->getOffsetTimestamp() : false;
$sale_price_dates_to_timestamp = $product_object->get_date_on_sale_to( 'edit' ) ? $product_object->get_date_on_sale_to( 'edit' )->getOffsetTimestamp() : false;
$sale_price_dates_from = $sale_price_dates_from_timestamp ? date_i18n( 'Y-m-d', $sale_price_dates_from_timestamp ) : '';
$sale_price_dates_to = $sale_price_dates_to_timestamp ? date_i18n( 'Y-m-d', $sale_price_dates_to_timestamp ) : '';
echo '<p class="form-field sale_price_dates_fields">
<label for="_sale_price_dates_from">' . esc_html__( 'Sale price dates', 'woocommerce' ) . '</label>
<input type="text" class="short" name="_sale_price_dates_from" id="_sale_price_dates_from" value="' . esc_attr( $sale_price_dates_from ) . '" placeholder="' . esc_html( _x( 'From&hellip;', 'placeholder', 'woocommerce' ) ) . ' YYYY-MM-DD" maxlength="10" pattern="' . 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])' ) ) . '" />
<input type="text" class="short" name="_sale_price_dates_to" id="_sale_price_dates_to" value="' . esc_attr( $sale_price_dates_to ) . '" placeholder="' . esc_html( _x( 'To&hellip;', 'placeholder', 'woocommerce' ) ) . ' YYYY-MM-DD" maxlength="10" pattern="' . 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])' ) ) . '" />
<a href="#" class="description cancel_sale_schedule">' . esc_html__( 'Cancel', 'woocommerce' ) . '</a>' . wc_help_tip( __( 'The sale will start at 00:00:00 of "From" date and end at 23:59:59 of "To" date.', 'woocommerce' ) ) . '
</p>';
do_action( 'woocommerce_product_options_pricing' );
?>
</div>
<div class="options_group show_if_downloadable hidden">
<div class="form-field downloadable_files">
<label><?php esc_html_e( 'Downloadable files', 'woocommerce' ); ?></label>
<table class="widefat">
<thead>
<tr>
<th class="sort">&nbsp;</th>
<th><?php esc_html_e( 'Name', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'This is the name of the download shown to the customer.', 'woocommerce' ) ); ?></th>
<th colspan="2"><?php esc_html_e( 'File URL', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'This is the URL or absolute path to the file which customers will get access to. URLs entered here should already be encoded.', 'woocommerce' ) ); ?></th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<?php
$downloadable_files = $product_object->get_downloads( 'edit' );
if ( $downloadable_files ) {
foreach ( $downloadable_files as $key => $file ) {
include __DIR__ . '/html-product-download.php';
}
}
?>
</tbody>
<tfoot>
<tr>
<th colspan="5">
<a href="#" class="button insert" data-row="
<?php
$key = '';
$file = array(
'file' => '',
'name' => '',
);
ob_start();
require __DIR__ . '/html-product-download.php';
echo esc_attr( ob_get_clean() );
?>
"><?php esc_html_e( 'Add File', 'woocommerce' ); ?></a>
</th>
</tr>
</tfoot>
</table>
</div>
<?php
woocommerce_wp_text_input(
array(
'id' => '_download_limit',
'value' => -1 === $product_object->get_download_limit( 'edit' ) ? '' : $product_object->get_download_limit( 'edit' ),
'label' => __( 'Download limit', 'woocommerce' ),
'placeholder' => __( 'Unlimited', 'woocommerce' ),
'description' => __( 'Leave blank for unlimited re-downloads.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => '1',
'min' => '0',
),
)
);
woocommerce_wp_text_input(
array(
'id' => '_download_expiry',
'value' => -1 === $product_object->get_download_expiry( 'edit' ) ? '' : $product_object->get_download_expiry( 'edit' ),
'label' => __( 'Download expiry', 'woocommerce' ),
'placeholder' => __( 'Never', 'woocommerce' ),
'description' => __( 'Enter the number of days before a download link expires, or leave blank.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => '1',
'min' => '0',
),
)
);
do_action( 'woocommerce_product_options_downloads' );
?>
</div>
<?php if ( wc_tax_enabled() ) : ?>
<div class="options_group show_if_simple show_if_external show_if_variable">
<?php
woocommerce_wp_select(
array(
'id' => '_tax_status',
'value' => $product_object->get_tax_status( 'edit' ),
'label' => __( 'Tax status', 'woocommerce' ),
'options' => array(
'taxable' => __( 'Taxable', 'woocommerce' ),
'shipping' => __( 'Shipping only', 'woocommerce' ),
'none' => _x( 'None', 'Tax status', 'woocommerce' ),
),
'desc_tip' => 'true',
'description' => __( 'Define whether or not the entire product is taxable, or just the cost of shipping it.', 'woocommerce' ),
)
);
woocommerce_wp_select(
array(
'id' => '_tax_class',
'value' => $product_object->get_tax_class( 'edit' ),
'label' => __( 'Tax class', 'woocommerce' ),
'options' => wc_get_product_tax_class_options(),
'desc_tip' => 'true',
'description' => __( 'Choose a tax class for this product. Tax classes are used to apply different tax rates specific to certain types of product.', 'woocommerce' ),
)
);
do_action( 'woocommerce_product_options_tax' );
?>
</div>
<?php endif; ?>
<?php do_action( 'woocommerce_product_options_general_product_data' ); ?>
</div>

View File

@ -0,0 +1,131 @@
<?php
/**
* Displays the inventory tab in the product data meta box.
*
* @package WooCommerce\Admin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div id="inventory_product_data" class="panel woocommerce_options_panel hidden">
<div class="options_group">
<?php
if ( wc_product_sku_enabled() ) {
woocommerce_wp_text_input(
array(
'id' => '_sku',
'value' => $product_object->get_sku( 'edit' ),
'label' => '<abbr title="' . esc_attr__( 'Stock Keeping Unit', 'woocommerce' ) . '">' . esc_html__( 'SKU', 'woocommerce' ) . '</abbr>',
'desc_tip' => true,
'description' => __( 'SKU refers to a Stock-keeping unit, a unique identifier for each distinct product and service that can be purchased.', 'woocommerce' ),
)
);
}
do_action( 'woocommerce_product_options_sku' );
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
woocommerce_wp_checkbox(
array(
'id' => '_manage_stock',
'value' => $product_object->get_manage_stock( 'edit' ) ? 'yes' : 'no',
'wrapper_class' => 'show_if_simple show_if_variable',
'label' => __( 'Manage stock?', 'woocommerce' ),
'description' => __( 'Enable stock management at product level', 'woocommerce' ),
)
);
do_action( 'woocommerce_product_options_stock' );
echo '<div class="stock_fields show_if_simple show_if_variable">';
woocommerce_wp_text_input(
array(
'id' => '_stock',
'value' => wc_stock_amount( $product_object->get_stock_quantity( 'edit' ) ),
'label' => __( 'Stock quantity', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Stock quantity. If this is a variable product this value will be used to control stock for all variations, unless you define stock at variation level.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => 'any',
),
'data_type' => 'stock',
)
);
echo '<input type="hidden" name="_original_stock" value="' . esc_attr( wc_stock_amount( $product_object->get_stock_quantity( 'edit' ) ) ) . '" />';
woocommerce_wp_select(
array(
'id' => '_backorders',
'value' => $product_object->get_backorders( 'edit' ),
'label' => __( 'Allow backorders?', 'woocommerce' ),
'options' => wc_get_product_backorder_options(),
'desc_tip' => true,
'description' => __( 'If managing stock, this controls whether or not backorders are allowed. If enabled, stock quantity can go below 0.', 'woocommerce' ),
)
);
woocommerce_wp_text_input(
array(
'id' => '_low_stock_amount',
'value' => $product_object->get_low_stock_amount( 'edit' ),
'placeholder' => sprintf(
/* translators: %d: Amount of stock left */
esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ),
esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) )
),
'label' => __( 'Low stock threshold', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'When product stock reaches this amount you will be notified by email. It is possible to define different values for each variation individually. The shop default value can be set in Settings > Products > Inventory.', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array(
'step' => 'any',
),
)
);
do_action( 'woocommerce_product_options_stock_fields' );
echo '</div>';
}
woocommerce_wp_select(
array(
'id' => '_stock_status',
'value' => $product_object->get_stock_status( 'edit' ),
'wrapper_class' => 'stock_status_field hide_if_variable hide_if_external hide_if_grouped',
'label' => __( 'Stock status', 'woocommerce' ),
'options' => wc_get_product_stock_status_options(),
'desc_tip' => true,
'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ),
)
);
do_action( 'woocommerce_product_options_stock_status' );
?>
</div>
<div class="options_group show_if_simple show_if_variable">
<?php
woocommerce_wp_checkbox(
array(
'id' => '_sold_individually',
'value' => $product_object->get_sold_individually( 'edit' ) ? 'yes' : 'no',
'wrapper_class' => 'show_if_simple show_if_variable',
'label' => __( 'Sold individually', 'woocommerce' ),
'description' => __( 'Enable this to only allow one of this item to be bought in a single order', 'woocommerce' ),
)
);
do_action( 'woocommerce_product_options_sold_individually' );
?>
</div>
<?php do_action( 'woocommerce_product_options_inventory_product_data' ); ?>
</div>

Some files were not shown because too many files have changed in this diff Show More