Merged in feature/117-dev-dev01 (pull request #8)
auto-patch 117-dev-dev01-2023-12-15T16_09_06 * auto-patch 117-dev-dev01-2023-12-15T16_09_06
This commit is contained in:
@@ -31,21 +31,21 @@ class AbstractBlock implements BlockInterface {
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $order = 10;
|
||||
private $order = 10000;
|
||||
|
||||
/**
|
||||
* The block attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $attributes = [];
|
||||
private $attributes = array();
|
||||
|
||||
/**
|
||||
* The block hide conditions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $hide_conditions = [];
|
||||
private $hide_conditions = array();
|
||||
|
||||
/**
|
||||
* The block hide conditions counter.
|
||||
@@ -54,6 +54,20 @@ class AbstractBlock implements BlockInterface {
|
||||
*/
|
||||
private $hide_conditions_counter = 0;
|
||||
|
||||
/**
|
||||
* The block disable conditions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $disable_conditions = array();
|
||||
|
||||
/**
|
||||
* The block disable conditions counter.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $disable_conditions_counter = 0;
|
||||
|
||||
/**
|
||||
* The block template that this block belongs to.
|
||||
*
|
||||
@@ -105,6 +119,12 @@ class AbstractBlock implements BlockInterface {
|
||||
$this->add_hide_condition( $hide_condition['expression'] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $config[ self::DISABLE_CONDITIONS_KEY ] ) ) {
|
||||
foreach ( $config[ self::DISABLE_CONDITIONS_KEY ] as $disable_condition ) {
|
||||
$this->add_disable_condition( $disable_condition['expression'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,9 +248,18 @@ class AbstractBlock implements BlockInterface {
|
||||
|
||||
// Storing the expression in an array to allow for future expansion
|
||||
// (such as adding the plugin that added the condition).
|
||||
$this->hide_conditions[ $key ] = [
|
||||
$this->hide_conditions[ $key ] = array(
|
||||
'expression' => $expression,
|
||||
];
|
||||
);
|
||||
|
||||
/**
|
||||
* Action called after a hide condition is added to a block.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
*
|
||||
* @since 8.4.0
|
||||
*/
|
||||
do_action( 'woocommerce_block_template_after_add_hide_condition', $this );
|
||||
|
||||
return $key;
|
||||
}
|
||||
@@ -242,6 +271,15 @@ class AbstractBlock implements BlockInterface {
|
||||
*/
|
||||
public function remove_hide_condition( string $key ) {
|
||||
unset( $this->hide_conditions[ $key ] );
|
||||
|
||||
/**
|
||||
* Action called after a hide condition is removed from a block.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
*
|
||||
* @since 8.4.0
|
||||
*/
|
||||
do_action( 'woocommerce_block_template_after_remove_hide_condition', $this );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,4 +288,41 @@ class AbstractBlock implements BlockInterface {
|
||||
public function get_hide_conditions(): array {
|
||||
return $this->hide_conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a disable condition to the block.
|
||||
*
|
||||
* The disable condition is a JavaScript-like expression that will be evaluated on the client to determine if the block should be hidden.
|
||||
* See [@woocommerce/expression-evaluation](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/expression-evaluation/README.md) for more details.
|
||||
*
|
||||
* @param string $expression An expression, which if true, will disable the block.
|
||||
*/
|
||||
public function add_disable_condition( string $expression ): string {
|
||||
$key = 'k' . $this->disable_conditions_counter;
|
||||
$this->disable_conditions_counter++;
|
||||
|
||||
// Storing the expression in an array to allow for future expansion
|
||||
// (such as adding the plugin that added the condition).
|
||||
$this->disable_conditions[ $key ] = array(
|
||||
'expression' => $expression,
|
||||
);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a disable condition from the block.
|
||||
*
|
||||
* @param string $key The key of the disable condition to remove.
|
||||
*/
|
||||
public function remove_disable_condition( string $key ) {
|
||||
unset( $this->disable_conditions[ $key ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the disable conditions of the block.
|
||||
*/
|
||||
public function get_disable_conditions(): array {
|
||||
return $this->disable_conditions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ trait BlockContainerTrait {
|
||||
*
|
||||
* @var BlockInterface[]
|
||||
*/
|
||||
private $inner_blocks = [];
|
||||
private $inner_blocks = array();
|
||||
|
||||
// phpcs doesn't take into account exceptions thrown by called methods.
|
||||
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
@@ -43,16 +43,7 @@ trait BlockContainerTrait {
|
||||
}
|
||||
|
||||
$is_detached = method_exists( $this, 'is_detached' ) && $this->is_detached();
|
||||
if ( $is_detached ) {
|
||||
BlockTemplateLogger::get_instance()->warning(
|
||||
'Block added to detached container. Block will not be included in the template, since the container will not be included in the template.',
|
||||
[
|
||||
'block' => $block,
|
||||
'container' => $this,
|
||||
'template' => $this->get_root_template(),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
if ( ! $is_detached ) {
|
||||
$this->get_root_template()->cache_block( $block );
|
||||
}
|
||||
|
||||
@@ -169,14 +160,6 @@ trait BlockContainerTrait {
|
||||
}
|
||||
);
|
||||
|
||||
BlockTemplateLogger::get_instance()->info(
|
||||
'Block removed from template.',
|
||||
[
|
||||
'block' => $block,
|
||||
'template' => $root_template,
|
||||
]
|
||||
);
|
||||
|
||||
$this->do_after_remove_block_action( $block );
|
||||
$this->do_after_remove_specific_block_action( $block );
|
||||
}
|
||||
@@ -237,12 +220,7 @@ trait BlockContainerTrait {
|
||||
*/
|
||||
do_action( 'woocommerce_block_template_after_add_block', $block );
|
||||
} catch ( \Exception $e ) {
|
||||
$this->handle_exception_doing_action(
|
||||
'Error after adding block to template.',
|
||||
'woocommerce_block_template_after_add_block',
|
||||
$block,
|
||||
$e
|
||||
);
|
||||
$this->do_after_add_block_error_action( $block, 'woocommerce_block_template_after_add_block', $e );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,15 +244,35 @@ trait BlockContainerTrait {
|
||||
*/
|
||||
do_action( "woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_add_block_{$block->get_id()}", $block );
|
||||
} catch ( \Exception $e ) {
|
||||
$this->handle_exception_doing_action(
|
||||
'Error after adding block to template.',
|
||||
"woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_add_block_{$block->get_id()}",
|
||||
$block,
|
||||
$e
|
||||
);
|
||||
$this->do_after_add_block_error_action( $block, "woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_add_block_{$block->get_id()}", $e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the `woocommerce_block_after_add_block_error` action.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
* @param string $action The action that threw the exception.
|
||||
* @param \Exception $e The exception.
|
||||
*/
|
||||
private function do_after_add_block_error_action( BlockInterface $block, string $action, \Exception $e ) {
|
||||
/**
|
||||
* Action called after an exception is thrown by a `woocommerce_block_template_after_add_block` action hook.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
* @param string $action The action that threw the exception.
|
||||
* @param \Exception $exception The exception.
|
||||
*
|
||||
* @since 8.4.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_block_template_after_add_block_error',
|
||||
$block,
|
||||
$action,
|
||||
$e,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the `woocommerce_block_template_after_remove_block` action.
|
||||
* Handle exceptions thrown by the action.
|
||||
@@ -295,12 +293,7 @@ trait BlockContainerTrait {
|
||||
*/
|
||||
do_action( 'woocommerce_block_template_after_remove_block', $block );
|
||||
} catch ( \Exception $e ) {
|
||||
$this->handle_exception_doing_action(
|
||||
'Error after removing block from template.',
|
||||
'woocommerce_block_template_after_remove_block',
|
||||
$block,
|
||||
$e
|
||||
);
|
||||
$this->do_after_remove_block_error_action( $block, 'woocommerce_block_template_after_remove_block', $e );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,33 +317,32 @@ trait BlockContainerTrait {
|
||||
*/
|
||||
do_action( "woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_remove_block_{$block->get_id()}", $block );
|
||||
} catch ( \Exception $e ) {
|
||||
$this->handle_exception_doing_action(
|
||||
'Error after removing block from template.',
|
||||
"woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_remove_block_{$block->get_id()}",
|
||||
$block,
|
||||
$e
|
||||
);
|
||||
$this->do_after_remove_block_error_action( $block, "woocommerce_block_template_area_{$this->get_root_template()->get_area()}_after_remove_block_{$block->get_id()}", $e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an exception thrown by an action.
|
||||
* Do the `woocommerce_block_after_remove_block_error` action.
|
||||
*
|
||||
* @param string $message The message.
|
||||
* @param string $action_tag The action tag.
|
||||
* @param BlockInterface $block The block.
|
||||
* @param \Exception $e The exception.
|
||||
* @param BlockInterface $block The block.
|
||||
* @param string $action The action that threw the exception.
|
||||
* @param \Exception $e The exception.
|
||||
*/
|
||||
private function handle_exception_doing_action( string $message, string $action_tag, BlockInterface $block, \Exception $e ) {
|
||||
BlockTemplateLogger::get_instance()->error(
|
||||
$message,
|
||||
[
|
||||
'exception' => $e,
|
||||
'action' => $action_tag,
|
||||
'container' => $this,
|
||||
'block' => $block,
|
||||
'template' => $this->get_root_template(),
|
||||
],
|
||||
private function do_after_remove_block_error_action( BlockInterface $block, string $action, \Exception $e ) {
|
||||
/**
|
||||
* Action called after an exception is thrown by a `woocommerce_block_template_after_remove_block` action hook.
|
||||
*
|
||||
* @param BlockInterface $block The block.
|
||||
* @param string $action The action that threw the exception.
|
||||
* @param \Exception $exception The exception.
|
||||
*
|
||||
* @since 8.4.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_block_template_after_remove_block_error',
|
||||
$block,
|
||||
$action,
|
||||
$e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,22 @@ trait BlockFormattedTemplateTrait {
|
||||
* @return array The block configuration as a formatted template.
|
||||
*/
|
||||
public function get_formatted_template(): array {
|
||||
$arr = [
|
||||
$arr = array(
|
||||
$this->get_name(),
|
||||
array_merge(
|
||||
$this->get_attributes(),
|
||||
[
|
||||
array(
|
||||
'_templateBlockId' => $this->get_id(),
|
||||
'_templateBlockOrder' => $this->get_order(),
|
||||
],
|
||||
! empty( $this->get_hide_conditions() ) ? [
|
||||
),
|
||||
! empty( $this->get_hide_conditions() ) ? array(
|
||||
'_templateBlockHideConditions' => $this->get_formatted_hide_conditions(),
|
||||
] : []
|
||||
) : array(),
|
||||
! empty( $this->get_disable_conditions() ) ? array(
|
||||
'_templateBlockDisableConditions' => $this->get_formatted_disable_conditions(),
|
||||
) : array(),
|
||||
),
|
||||
];
|
||||
);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
@@ -33,15 +36,31 @@ trait BlockFormattedTemplateTrait {
|
||||
* Get the block hide conditions formatted for inclusion in a formatted template.
|
||||
*/
|
||||
private function get_formatted_hide_conditions(): array {
|
||||
$formatted_hide_conditions = array_map(
|
||||
function( $hide_condition ) {
|
||||
return [
|
||||
'expression' => $hide_condition['expression'],
|
||||
];
|
||||
return $this->format_conditions( $this->get_hide_conditions() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block disable conditions formatted for inclusion in a formatted template.
|
||||
*/
|
||||
private function get_formatted_disable_conditions(): array {
|
||||
return $this->format_conditions( $this->get_disable_conditions() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats conditions in the expected format to include in the template.
|
||||
*
|
||||
* @param array $conditions The conditions to format.
|
||||
*/
|
||||
private function format_conditions( $conditions ): array {
|
||||
$formatted_expressions = array_map(
|
||||
function( $condition ) {
|
||||
return array(
|
||||
'expression' => $condition['expression'],
|
||||
);
|
||||
},
|
||||
array_values( $this->get_hide_conditions() )
|
||||
array_values( $conditions )
|
||||
);
|
||||
|
||||
return $formatted_hide_conditions;
|
||||
return $formatted_expressions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,68 @@ namespace Automattic\WooCommerce\Internal\Admin\BlockTemplates;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockContainerInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Logger for block template modifications.
|
||||
*/
|
||||
class BlockTemplateLogger {
|
||||
const BLOCK_ADDED = 'block_added';
|
||||
const BLOCK_REMOVED = 'block_removed';
|
||||
const BLOCK_MODIFIED = 'block_modified';
|
||||
const BLOCK_ADDED_TO_DETACHED_CONTAINER = 'block_added_to_detached_container';
|
||||
const HIDE_CONDITION_ADDED = 'hide_condition_added';
|
||||
const HIDE_CONDITION_REMOVED = 'hide_condition_removed';
|
||||
const HIDE_CONDITION_ADDED_TO_DETACHED_BLOCK = 'hide_condition_added_to_detached_block';
|
||||
const ERROR_AFTER_BLOCK_ADDED = 'error_after_block_added';
|
||||
const ERROR_AFTER_BLOCK_REMOVED = 'error_after_block_removed';
|
||||
|
||||
const LOG_HASH_TRANSIENT_BASE_NAME = 'wc_block_template_events_log_hash_';
|
||||
|
||||
/**
|
||||
* Event types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $event_types = array(
|
||||
self::BLOCK_ADDED => array(
|
||||
'level' => \WC_Log_Levels::DEBUG,
|
||||
'message' => 'Block added to template.',
|
||||
),
|
||||
self::BLOCK_REMOVED => array(
|
||||
'level' => \WC_Log_Levels::NOTICE,
|
||||
'message' => 'Block removed from template.',
|
||||
),
|
||||
self::BLOCK_MODIFIED => array(
|
||||
'level' => \WC_Log_Levels::NOTICE,
|
||||
'message' => 'Block modified in template.',
|
||||
),
|
||||
self::BLOCK_ADDED_TO_DETACHED_CONTAINER => array(
|
||||
'level' => \WC_Log_Levels::WARNING,
|
||||
'message' => 'Block added to detached container. Block will not be included in the template, since the container will not be included in the template.',
|
||||
),
|
||||
self::HIDE_CONDITION_ADDED => array(
|
||||
'level' => \WC_Log_Levels::NOTICE,
|
||||
'message' => 'Hide condition added to block.',
|
||||
),
|
||||
self::HIDE_CONDITION_REMOVED => array(
|
||||
'level' => \WC_Log_Levels::NOTICE,
|
||||
'message' => 'Hide condition removed from block.',
|
||||
),
|
||||
self::HIDE_CONDITION_ADDED_TO_DETACHED_BLOCK => array(
|
||||
'level' => \WC_Log_Levels::WARNING,
|
||||
'message' => 'Hide condition added to detached block. Block will not be included in the template, so the hide condition is not needed.',
|
||||
),
|
||||
self::ERROR_AFTER_BLOCK_ADDED => array(
|
||||
'level' => \WC_Log_Levels::WARNING,
|
||||
'message' => 'Error after block added to template.',
|
||||
),
|
||||
self::ERROR_AFTER_BLOCK_REMOVED => array(
|
||||
'level' => \WC_Log_Levels::WARNING,
|
||||
'message' => 'Error after block removed from template.',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
@@ -24,6 +81,27 @@ class BlockTemplateLogger {
|
||||
*/
|
||||
protected $logger = null;
|
||||
|
||||
/**
|
||||
* All template events.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $all_template_events = array();
|
||||
|
||||
/**
|
||||
* Templates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $templates = array();
|
||||
|
||||
/**
|
||||
* Threshold severity.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $threshold_severity = null;
|
||||
|
||||
/**
|
||||
* Get the singleton instance.
|
||||
*/
|
||||
@@ -40,44 +118,283 @@ class BlockTemplateLogger {
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->logger = wc_get_logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an informational message.
|
||||
*
|
||||
* @param string $message Message to log.
|
||||
* @param array $info Additional info to log.
|
||||
*/
|
||||
public function info( string $message, array $info = [] ) {
|
||||
$this->logger->info(
|
||||
$this->format_message( $message, $info ),
|
||||
[ 'source' => 'block_template' ]
|
||||
$threshold = get_option( 'woocommerce_block_template_logging_threshold', \WC_Log_Levels::WARNING );
|
||||
if ( ! \WC_Log_Levels::is_valid_level( $threshold ) ) {
|
||||
$threshold = \WC_Log_Levels::INFO;
|
||||
}
|
||||
|
||||
$this->threshold_severity = \WC_Log_Levels::get_level_severity( $threshold );
|
||||
|
||||
add_action(
|
||||
'woocommerce_block_template_after_add_block',
|
||||
function ( BlockInterface $block ) {
|
||||
$is_detached = method_exists( $block->get_parent(), 'is_detached' ) && $block->get_parent()->is_detached();
|
||||
|
||||
$this->log(
|
||||
$is_detached
|
||||
? $this::BLOCK_ADDED_TO_DETACHED_CONTAINER
|
||||
: $this::BLOCK_ADDED,
|
||||
$block,
|
||||
);
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_block_template_after_remove_block',
|
||||
function ( BlockInterface $block ) {
|
||||
$this->log(
|
||||
$this::BLOCK_REMOVED,
|
||||
$block,
|
||||
);
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_block_template_after_add_hide_condition',
|
||||
function ( BlockInterface $block ) {
|
||||
$this->log(
|
||||
$block->is_detached()
|
||||
? $this::HIDE_CONDITION_ADDED_TO_DETACHED_BLOCK
|
||||
: $this::HIDE_CONDITION_ADDED,
|
||||
$block,
|
||||
);
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_block_template_after_remove_hide_condition',
|
||||
function ( BlockInterface $block ) {
|
||||
$this->log(
|
||||
$this::HIDE_CONDITION_REMOVED,
|
||||
$block,
|
||||
);
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_block_template_after_add_block_error',
|
||||
function ( BlockInterface $block, string $action, \Exception $exception ) {
|
||||
$this->log(
|
||||
$this::ERROR_AFTER_BLOCK_ADDED,
|
||||
$block,
|
||||
array(
|
||||
'action' => $action,
|
||||
'exception' => $exception,
|
||||
),
|
||||
);
|
||||
},
|
||||
0,
|
||||
3
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_block_template_after_remove_block_error',
|
||||
function ( BlockInterface $block, string $action, \Exception $exception ) {
|
||||
$this->log(
|
||||
$this::ERROR_AFTER_BLOCK_REMOVED,
|
||||
$block,
|
||||
array(
|
||||
'action' => $action,
|
||||
'exception' => $exception,
|
||||
),
|
||||
);
|
||||
},
|
||||
0,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning message.
|
||||
* Get all template events for a given template.
|
||||
*
|
||||
* @param string $message Message to log.
|
||||
* @param array $info Additional info to log.
|
||||
* @param string $template_id Template ID.
|
||||
*/
|
||||
public function warning( string $message, array $info = [] ) {
|
||||
$this->logger->warning(
|
||||
$this->format_message( $message, $info ),
|
||||
[ 'source' => 'block_template' ]
|
||||
);
|
||||
public function get_formatted_template_events( string $template_id ): array {
|
||||
if ( ! isset( $this->all_template_events[ $template_id ] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$template_events = $this->all_template_events[ $template_id ];
|
||||
$template = $this->templates[ $template_id ];
|
||||
|
||||
$formatted_template_events = array();
|
||||
|
||||
foreach ( $template_events as $template_event ) {
|
||||
$container = $template_event['container'];
|
||||
$block = $template_event['block'];
|
||||
|
||||
$formatted_template_events[] = array(
|
||||
'level' => $template_event['level'],
|
||||
'event_type' => $template_event['event_type'],
|
||||
'message' => $template_event['message'],
|
||||
'container' => $container instanceof BlockInterface
|
||||
? array(
|
||||
'id' => $container->get_id(),
|
||||
'name' => $container->get_name(),
|
||||
)
|
||||
: null,
|
||||
'block' => array(
|
||||
'id' => $block->get_id(),
|
||||
'name' => $block->get_name(),
|
||||
),
|
||||
'additional_info' => $this->format_info( $template_event['additional_info'] ),
|
||||
);
|
||||
}
|
||||
|
||||
return $formatted_template_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error message.
|
||||
* Log all template events for a given template to the log file.
|
||||
*
|
||||
* @param string $message Message to log.
|
||||
* @param array $info Additional info to log.
|
||||
* @param string $template_id Template ID.
|
||||
*/
|
||||
public function error( string $message, array $info = [] ) {
|
||||
$this->logger->error(
|
||||
$this->format_message( $message, $info ),
|
||||
[ 'source' => 'block_template' ]
|
||||
public function log_template_events_to_file( string $template_id ) {
|
||||
if ( ! isset( $this->all_template_events[ $template_id ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template_events = $this->all_template_events[ $template_id ];
|
||||
|
||||
$hash = $this->generate_template_events_hash( $template_events );
|
||||
|
||||
if ( ! $this->has_template_events_changed( $template_id, $hash ) ) {
|
||||
// Nothing has changed since the last time this was logged,
|
||||
// so don't log it again.
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_template_events_log_hash( $template_id, $hash );
|
||||
|
||||
$template = $this->templates[ $template_id ];
|
||||
|
||||
foreach ( $template_events as $template_event ) {
|
||||
$info = array_merge(
|
||||
array(
|
||||
'template' => $template,
|
||||
'container' => $template_event['container'],
|
||||
'block' => $template_event['block'],
|
||||
),
|
||||
$template_event['additional_info']
|
||||
);
|
||||
|
||||
$message = $this->format_message( $template_event['message'], $info );
|
||||
|
||||
$this->logger->log(
|
||||
$template_event['level'],
|
||||
$message,
|
||||
array( 'source' => 'block_template' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the template events changed since the last time they were logged?
|
||||
*
|
||||
* @param string $template_id Template ID.
|
||||
* @param string $events_hash Events hash.
|
||||
*/
|
||||
private function has_template_events_changed( string $template_id, string $events_hash ) {
|
||||
$previous_hash = get_transient( self::LOG_HASH_TRANSIENT_BASE_NAME . $template_id );
|
||||
|
||||
return $previous_hash !== $events_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for a given set of template events.
|
||||
*
|
||||
* @param array $template_events Template events.
|
||||
*/
|
||||
private function generate_template_events_hash( array $template_events ): string {
|
||||
return md5( wp_json_encode( $template_events ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the template events hash for a given template.
|
||||
*
|
||||
* @param string $template_id Template ID.
|
||||
* @param string $hash Hash of template events.
|
||||
*/
|
||||
private function set_template_events_log_hash( string $template_id, string $hash ) {
|
||||
set_transient( self::LOG_HASH_TRANSIENT_BASE_NAME . $template_id, $hash );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an event.
|
||||
*
|
||||
* @param string $event_type Event type.
|
||||
* @param BlockInterface $block Block.
|
||||
* @param array $additional_info Additional info.
|
||||
*/
|
||||
private function log( string $event_type, BlockInterface $block, $additional_info = array() ) {
|
||||
if ( ! isset( self::$event_types[ $event_type ] ) ) {
|
||||
/* translators: 1: WC_Logger::log 2: level */
|
||||
wc_doing_it_wrong( __METHOD__, sprintf( __( '%1$s was called with an invalid event type "%2$s".', 'woocommerce' ), '<code>BlockTemplateLogger::log</code>', $event_type ), '8.4' );
|
||||
}
|
||||
|
||||
$event_type_info = isset( self::$event_types[ $event_type ] )
|
||||
? array_merge(
|
||||
self::$event_types[ $event_type ],
|
||||
array(
|
||||
'event_type' => $event_type,
|
||||
)
|
||||
)
|
||||
: array(
|
||||
'level' => \WC_Log_Levels::ERROR,
|
||||
'event_type' => $event_type,
|
||||
'message' => 'Unknown error.',
|
||||
);
|
||||
|
||||
if ( ! $this->should_handle( $event_type_info['level'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = $block->get_root_template();
|
||||
$container = $block->get_parent();
|
||||
|
||||
$this->add_template_event( $event_type_info, $template, $container, $block, $additional_info );
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the logger handle a given level?
|
||||
*
|
||||
* @param int $level Level to check.
|
||||
*/
|
||||
private function should_handle( $level ) {
|
||||
return $this->threshold_severity <= \WC_Log_Levels::get_level_severity( $level );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a template event.
|
||||
*
|
||||
* @param array $event_type_info Event type info.
|
||||
* @param BlockTemplateInterface $template Template.
|
||||
* @param ContainerInterface $container Container.
|
||||
* @param BlockInterface $block Block.
|
||||
* @param array $additional_info Additional info.
|
||||
*/
|
||||
private function add_template_event( array $event_type_info, BlockTemplateInterface $template, ContainerInterface $container, BlockInterface $block, array $additional_info = array() ) {
|
||||
$template_id = $template->get_id();
|
||||
|
||||
if ( ! isset( $this->all_template_events[ $template_id ] ) ) {
|
||||
$this->all_template_events[ $template_id ] = array();
|
||||
$this->templates[ $template_id ] = $template;
|
||||
}
|
||||
|
||||
$template_events = &$this->all_template_events[ $template_id ];
|
||||
|
||||
$template_events[] = array(
|
||||
'level' => $event_type_info['level'],
|
||||
'event_type' => $event_type_info['event_type'],
|
||||
'message' => $event_type_info['message'],
|
||||
'container' => $container,
|
||||
'block' => $block,
|
||||
'additional_info' => $additional_info,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,7 +404,7 @@ class BlockTemplateLogger {
|
||||
* @param string $message Message to log.
|
||||
* @param array $info Additional info to log.
|
||||
*/
|
||||
private function format_message( string $message, array $info = [] ): string {
|
||||
private function format_message( string $message, array $info = array() ): string {
|
||||
$formatted_message = sprintf(
|
||||
"%s\n%s",
|
||||
$message,
|
||||
@@ -137,12 +454,12 @@ class BlockTemplateLogger {
|
||||
* @param \Exception $exception Exception to format.
|
||||
*/
|
||||
private function format_exception( \Exception $exception ): array {
|
||||
return [
|
||||
return array(
|
||||
'message' => $exception->getMessage(),
|
||||
'source' => "{$exception->getFile()}: {$exception->getLine()}",
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
|
||||
'trace' => print_r( $this->format_exception_trace( $exception->getTrace() ), true ),
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +468,7 @@ class BlockTemplateLogger {
|
||||
* @param array $trace Exception trace to format.
|
||||
*/
|
||||
private function format_exception_trace( array $trace ): array {
|
||||
$formatted_trace = [];
|
||||
$formatted_trace = array();
|
||||
|
||||
foreach ( $trace as $source ) {
|
||||
$formatted_trace[] = "{$source['file']}: {$source['line']}";
|
||||
|
||||
@@ -133,7 +133,19 @@ class Homescreen {
|
||||
$zone = new \WC_Shipping_Zone();
|
||||
$zone->set_zone_name( $country_name );
|
||||
$zone->add_location( $country_code, 'country' );
|
||||
$zone->add_shipping_method( 'free_shipping' );
|
||||
|
||||
// Method creation has no default title, use the REST API to add a title.
|
||||
$instance_id = $zone->add_shipping_method( 'free_shipping' );
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/v2/shipping/zones/' . $zone->get_id() . '/methods/' . $instance_id );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'settings' => array(
|
||||
'title' => 'Free shipping',
|
||||
),
|
||||
)
|
||||
);
|
||||
rest_do_request( $request );
|
||||
|
||||
update_option( 'woocommerce_admin_created_default_shipping_zones', 'yes' );
|
||||
Shipping::delete_zone_count_transient();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2;
|
||||
|
||||
use WP_Filesystem_Direct;
|
||||
|
||||
/**
|
||||
* File class.
|
||||
*
|
||||
* An object representation of a single log file.
|
||||
*/
|
||||
class File {
|
||||
/**
|
||||
* The absolute path of the file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* The source property of the file, derived from the filename.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $source = '';
|
||||
|
||||
/**
|
||||
* The 0-based increment of the file, if it has been rotated. Derived from the filename. Can only be 0-9.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $rotation;
|
||||
|
||||
/**
|
||||
* The date the file was created, as a Unix timestamp, derived from the filename.
|
||||
*
|
||||
* @var int|false
|
||||
*/
|
||||
protected $created = false;
|
||||
|
||||
/**
|
||||
* The hash property of the file, derived from the filename.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hash = '';
|
||||
|
||||
/**
|
||||
* The file's resource handle when it is open.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $stream;
|
||||
|
||||
/**
|
||||
* Class File
|
||||
*
|
||||
* @param string $path The absolute path of the file.
|
||||
*/
|
||||
public function __construct( $path ) {
|
||||
global $wp_filesystem;
|
||||
if ( ! $wp_filesystem instanceof WP_Filesystem_Direct ) {
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
$this->parse_filename();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure open streams are closed.
|
||||
*/
|
||||
public function __destruct() {
|
||||
if ( is_resource( $this->stream ) ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose -- No suitable alternative.
|
||||
fclose( $this->stream );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the log filename to derive certain properties of the file.
|
||||
*
|
||||
* This makes assumptions about the structure of the log file's name. Using `-` to separate the name into segments,
|
||||
* if there are at least 5 segments, it assumes that the last segment is the hash, and the three segments before
|
||||
* that make up the date when the file was created in YYYY-MM-DD format. Any segments left after that are the
|
||||
* "source" that generated the log entries. If the filename doesn't have enough segments, it falls back to the
|
||||
* source and the hash both being the entire filename, and using the inode change time as the creation date.
|
||||
*
|
||||
* Example:
|
||||
* my-custom-plugin.2-2025-01-01-a1b2c3d4e5f.log
|
||||
* | | | |
|
||||
* 'my-custom-plugin' | '2025-01-01' |
|
||||
* (source) | (created) |
|
||||
* '2' 'a1b2c3d4e5f'
|
||||
* (rotation) (hash)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parse_filename(): void {
|
||||
$info = pathinfo( $this->path );
|
||||
$filename = $info['filename'];
|
||||
$segments = explode( '-', $filename );
|
||||
|
||||
if ( count( $segments ) >= 5 ) {
|
||||
$this->source = implode( '-', array_slice( $segments, 0, -4 ) );
|
||||
$this->created = strtotime( implode( '-', array_slice( $segments, -4, 3 ) ) );
|
||||
$this->hash = array_slice( $segments, -1 )[0];
|
||||
} else {
|
||||
$this->source = implode( '-', $segments );
|
||||
$this->created = filectime( $this->path );
|
||||
$this->hash = $this->source;
|
||||
}
|
||||
|
||||
$rotation_marker = strrpos( $this->source, '.', -1 );
|
||||
if ( false !== $rotation_marker ) {
|
||||
$rotation = substr( $this->source, -1 );
|
||||
if ( is_numeric( $rotation ) ) {
|
||||
$this->rotation = intval( $rotation );
|
||||
}
|
||||
|
||||
$this->source = substr( $this->source, 0, $rotation_marker );
|
||||
if ( count( $segments ) < 5 ) {
|
||||
$this->hash = $this->source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file represented by the class instance is a file and is readable.
|
||||
*
|
||||
* @global WP_Filesystem_Direct $wp_filesystem
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_readable(): bool {
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem->is_file( $this->path ) && $wp_filesystem->is_readable( $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file represented by the class instance is a file and is writable.
|
||||
*
|
||||
* @global WP_Filesystem_Direct $wp_filesystem
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_writable(): bool {
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem->is_file( $this->path ) && $wp_filesystem->is_writable( $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a read-only stream file this file.
|
||||
*
|
||||
* @return resource|false
|
||||
*/
|
||||
public function get_stream() {
|
||||
if ( ! is_resource( $this->stream ) ) {
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen -- No suitable alternative.
|
||||
$this->stream = fopen( $this->path, 'rb' );
|
||||
}
|
||||
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the file, with extension, but without full path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_basename(): string {
|
||||
return basename( $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's source property.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_source(): string {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's rotation property.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_rotation(): ?int {
|
||||
return $this->rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's hash property.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_hash(): string {
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's public ID.
|
||||
*
|
||||
* The file ID is the basename of the file without the hash part. It allows us to identify a file without revealing
|
||||
* its full name in the filesystem, so that it's difficult to access the file directly with an HTTP request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_id(): string {
|
||||
$file_id = $this->get_source();
|
||||
|
||||
if ( ! is_null( $this->get_rotation() ) ) {
|
||||
$file_id .= '.' . $this->get_rotation();
|
||||
}
|
||||
|
||||
if ( $this->get_source() !== $this->get_hash() ) {
|
||||
$file_id .= '-' . gmdate( 'Y-m-d', $this->get_created_timestamp() );
|
||||
}
|
||||
|
||||
return $file_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file's created property.
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function get_created_timestamp() {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of the last modification of the file, as a Unix timestamp. Or false if the file isn't readable.
|
||||
*
|
||||
* @global WP_Filesystem_Direct $wp_filesystem
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function get_modified_timestamp() {
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem->mtime( $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the file in bytes. Or false if the file isn't readable.
|
||||
*
|
||||
* @global WP_Filesystem_Direct $wp_filesystem
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function get_file_size() {
|
||||
global $wp_filesystem;
|
||||
|
||||
if ( ! $wp_filesystem->is_readable( $this->path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $wp_filesystem->size( $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the file from the filesystem.
|
||||
*
|
||||
* @global WP_Filesystem_Direct $wp_filesystem
|
||||
*
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function delete(): bool {
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem->delete( $this->path, false, 'f' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* FileController class.
|
||||
*/
|
||||
class FileController {
|
||||
/**
|
||||
* Default values for arguments for the get_files method.
|
||||
*
|
||||
* @const array
|
||||
*/
|
||||
public const DEFAULTS_GET_FILES = array(
|
||||
'offset' => 0,
|
||||
'order' => 'desc',
|
||||
'orderby' => 'modified',
|
||||
'per_page' => 20,
|
||||
'source' => '',
|
||||
);
|
||||
|
||||
/**
|
||||
* The absolute path to the log directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $log_directory;
|
||||
|
||||
/**
|
||||
* Class FileController
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->log_directory = trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of log files.
|
||||
*
|
||||
* @param array $args {
|
||||
* Optional. Arguments to filter and sort the files that are returned.
|
||||
*
|
||||
* @type int $offset Omit this number of files from the beginning of the list. Works with $per_page to do pagination.
|
||||
* @type string $order The sort direction. 'asc' or 'desc'. Defaults to 'desc'.
|
||||
* @type string $orderby The property to sort the list by. 'created', 'modified', 'source', 'size'. Defaults to 'modified'.
|
||||
* @type int $per_page The number of files to include in the list. Works with $offset to do pagination.
|
||||
* @type string $source Only include files from this source.
|
||||
* }
|
||||
* @param bool $count_only Optional. True to return a total count of the files.
|
||||
*
|
||||
* @return File[]|int|WP_Error
|
||||
*/
|
||||
public function get_files( array $args = array(), bool $count_only = false ) {
|
||||
$args = wp_parse_args( $args, self::DEFAULTS_GET_FILES );
|
||||
|
||||
$pattern = $args['source'] . '*.log';
|
||||
$paths = glob( $this->log_directory . $pattern );
|
||||
|
||||
if ( false === $paths ) {
|
||||
return new WP_Error(
|
||||
'wc_log_directory_error',
|
||||
__( 'Could not access the log file directory.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( true === $count_only ) {
|
||||
return count( $paths );
|
||||
}
|
||||
|
||||
$files = $this->convert_paths_to_objects( $paths );
|
||||
|
||||
$multi_sorter = function( $sort_sets, $order_sets ) {
|
||||
$comparison = 0;
|
||||
|
||||
while ( ! empty( $sort_sets ) ) {
|
||||
$set = array_shift( $sort_sets );
|
||||
$order = array_shift( $order_sets );
|
||||
|
||||
if ( 'desc' === $order ) {
|
||||
$comparison = $set[1] <=> $set[0];
|
||||
} else {
|
||||
$comparison = $set[0] <=> $set[1];
|
||||
}
|
||||
|
||||
if ( 0 !== $comparison ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $comparison;
|
||||
};
|
||||
|
||||
switch ( $args['orderby'] ) {
|
||||
case 'created':
|
||||
$sort_callback = function( $a, $b ) use ( $args, $multi_sorter ) {
|
||||
$sort_sets = array(
|
||||
array( $a->get_created_timestamp(), $b->get_created_timestamp() ),
|
||||
array( $a->get_source(), $b->get_source() ),
|
||||
array( $a->get_rotation() || -1, $b->get_rotation() || -1 ),
|
||||
);
|
||||
$order_sets = array( $args['order'], 'asc', 'asc' );
|
||||
return $multi_sorter( $sort_sets, $order_sets );
|
||||
};
|
||||
break;
|
||||
case 'modified':
|
||||
$sort_callback = function( $a, $b ) use ( $args, $multi_sorter ) {
|
||||
$sort_sets = array(
|
||||
array( $a->get_modified_timestamp(), $b->get_modified_timestamp() ),
|
||||
array( $a->get_source(), $b->get_source() ),
|
||||
array( $a->get_rotation() || -1, $b->get_rotation() || -1 ),
|
||||
);
|
||||
$order_sets = array( $args['order'], 'asc', 'asc' );
|
||||
return $multi_sorter( $sort_sets, $order_sets );
|
||||
};
|
||||
break;
|
||||
case 'source':
|
||||
$sort_callback = function( $a, $b ) use ( $args, $multi_sorter ) {
|
||||
$sort_sets = array(
|
||||
array( $a->get_source(), $b->get_source() ),
|
||||
array( $a->get_created_timestamp(), $b->get_created_timestamp() ),
|
||||
array( $a->get_rotation() || -1, $b->get_rotation() || -1 ),
|
||||
);
|
||||
$order_sets = array( $args['order'], 'desc', 'asc' );
|
||||
return $multi_sorter( $sort_sets, $order_sets );
|
||||
};
|
||||
break;
|
||||
case 'size':
|
||||
$sort_callback = function( $a, $b ) use ( $args, $multi_sorter ) {
|
||||
$sort_sets = array(
|
||||
array( $a->get_file_size(), $b->get_file_size() ),
|
||||
array( $a->get_source(), $b->get_source() ),
|
||||
array( $a->get_rotation() || -1, $b->get_rotation() || -1 ),
|
||||
);
|
||||
$order_sets = array( $args['order'], 'asc', 'asc' );
|
||||
return $multi_sorter( $sort_sets, $order_sets );
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
usort( $files, $sort_callback );
|
||||
|
||||
return array_slice( $files, $args['offset'], $args['per_page'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one or more File instances from an array of file IDs.
|
||||
*
|
||||
* @param array $file_ids An array of file IDs (file basename without the hash).
|
||||
*
|
||||
* @return File[]
|
||||
*/
|
||||
public function get_files_by_id( array $file_ids ): array {
|
||||
$paths = array();
|
||||
|
||||
foreach ( $file_ids as $file_id ) {
|
||||
$glob = glob( $this->log_directory . $file_id . '*.log' );
|
||||
|
||||
if ( is_array( $glob ) ) {
|
||||
$paths = array_merge( $paths, $glob );
|
||||
}
|
||||
}
|
||||
|
||||
$files = $this->convert_paths_to_objects( $paths );
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a File instance from a file ID.
|
||||
*
|
||||
* @param string $file_id A file ID (file basename without the hash).
|
||||
*
|
||||
* @return File|WP_Error
|
||||
*/
|
||||
public function get_file_by_id( string $file_id ) {
|
||||
$result = $this->get_files_by_id( array( $file_id ) );
|
||||
|
||||
if ( count( $result ) < 1 ) {
|
||||
return new WP_Error(
|
||||
'wc_log_file_error',
|
||||
esc_html__( 'This file does not exist.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
return reset( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get File instances for a given file ID and all of its related rotations.
|
||||
*
|
||||
* @param string $file_id A file ID (file basename without the hash).
|
||||
*
|
||||
* @return File[]|WP_Error An associative array where the rotation integer of the file is the key, and a "current"
|
||||
* key for the iteration of the file that hasn't been rotated (if it exists).
|
||||
*/
|
||||
public function get_file_rotations( string $file_id ) {
|
||||
$file = $this->get_file_by_id( $file_id );
|
||||
|
||||
if ( is_wp_error( $file ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$current = array();
|
||||
$rotations = array();
|
||||
|
||||
$source = $file->get_source();
|
||||
$created = gmdate( 'Y-m-d', $file->get_created_timestamp() );
|
||||
|
||||
if ( is_null( $file->get_rotation() ) ) {
|
||||
$current['current'] = $file;
|
||||
} else {
|
||||
$current_file_id = $source . '-' . $created;
|
||||
$result = $this->get_file_by_id( $current_file_id );
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$current['current'] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
$rotation_pattern = $this->log_directory . $source . '.[0123456789]-' . $created . '*.log';
|
||||
$rotation_paths = glob( $rotation_pattern );
|
||||
$rotation_files = $this->convert_paths_to_objects( $rotation_paths );
|
||||
foreach ( $rotation_files as $rotation_file ) {
|
||||
if ( $rotation_file->is_readable() ) {
|
||||
$rotations[ $rotation_file->get_rotation() ] = $rotation_file;
|
||||
}
|
||||
}
|
||||
|
||||
ksort( $rotations );
|
||||
|
||||
return array_merge( $current, $rotations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get an array of File instances.
|
||||
*
|
||||
* @param array $paths An array of absolute file paths.
|
||||
*
|
||||
* @return File[]
|
||||
*/
|
||||
private function convert_paths_to_objects( array $paths ): array {
|
||||
$files = array_map(
|
||||
function( $path ) {
|
||||
$file = new File( $path );
|
||||
return $file->is_readable() ? $file : null;
|
||||
},
|
||||
$paths
|
||||
);
|
||||
|
||||
return array_filter( $files );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of sources for existing log files.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_file_sources() {
|
||||
$paths = glob( $this->log_directory . '*.log' );
|
||||
if ( false === $paths ) {
|
||||
return new WP_Error(
|
||||
'wc_log_directory_error',
|
||||
__( 'Could not access the log file directory.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
$all_sources = array_map(
|
||||
function( $path ) {
|
||||
$file = new File( $path );
|
||||
return $file->is_readable() ? $file->get_source() : null;
|
||||
},
|
||||
$paths
|
||||
);
|
||||
|
||||
return array_unique( array_filter( $all_sources ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one or more files from the filesystem.
|
||||
*
|
||||
* @param array $file_ids An array of file IDs (file basename without the hash).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function delete_files( array $file_ids ): int {
|
||||
$deleted = 0;
|
||||
|
||||
$files = $this->get_files_by_id( $file_ids );
|
||||
foreach ( $files as $file ) {
|
||||
$result = false;
|
||||
if ( $file->is_readable() ) {
|
||||
$result = $file->delete();
|
||||
}
|
||||
|
||||
if ( true === $result ) {
|
||||
$deleted ++;
|
||||
}
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the source property of a log file.
|
||||
*
|
||||
* @param string $source The source property of a log file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_source( string $source ): string {
|
||||
return sanitize_file_name( $source );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\PageController;
|
||||
|
||||
use WP_List_Table;
|
||||
|
||||
/**
|
||||
* ListTable class.
|
||||
*/
|
||||
class ListTable extends WP_List_Table {
|
||||
/**
|
||||
* The user option key for saving the preferred number of files displayed per page.
|
||||
*
|
||||
* @const string
|
||||
*/
|
||||
public const PER_PAGE_USER_OPTION_KEY = 'woocommerce_logging_file_list_per_page';
|
||||
|
||||
/**
|
||||
* Instance of FileController.
|
||||
*
|
||||
* @var FileController
|
||||
*/
|
||||
private $file_controller;
|
||||
|
||||
/**
|
||||
* Instance of PageController.
|
||||
*
|
||||
* @var PageController
|
||||
*/
|
||||
private $page_controller;
|
||||
|
||||
/**
|
||||
* ListTable class.
|
||||
*
|
||||
* @param FileController $file_controller Instance of FileController.
|
||||
* @param PageController $page_controller Instance of PageController.
|
||||
*/
|
||||
public function __construct( FileController $file_controller, PageController $page_controller ) {
|
||||
$this->file_controller = $file_controller;
|
||||
$this->page_controller = $page_controller;
|
||||
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => 'log-file',
|
||||
'plural' => 'log-files',
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render message when there are no items.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function no_items(): void {
|
||||
esc_html_e( 'No log files found.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of bulk actions available for this table.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_bulk_actions(): array {
|
||||
return array(
|
||||
'delete' => esc_html__( 'Delete permanently', 'woocommerce' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing log sources for the filter dropdown.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_sources_list(): array {
|
||||
$sources = $this->file_controller->get_file_sources();
|
||||
if ( is_wp_error( $sources ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
sort( $sources );
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays extra controls between bulk actions and pagination.
|
||||
*
|
||||
* @param string $which The location of the tablenav being rendered. 'top' or 'bottom'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function extra_tablenav( $which ): void {
|
||||
$all_sources = $this->get_sources_list();
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
|
||||
$current_source = $this->file_controller->sanitize_source( wp_unslash( $_GET['source'] ?? '' ) );
|
||||
|
||||
?>
|
||||
<div class="alignleft actions">
|
||||
<?php if ( 'top' === $which ) : ?>
|
||||
<label for="filter-by-source" class="screen-reader-text"><?php esc_html_e( 'Filter by log source', 'woocommerce' ); ?></label>
|
||||
<select name="source" id="filter-by-source">
|
||||
<option<?php selected( $current_source, '' ); ?> value=""><?php esc_html_e( 'All sources', 'woocommerce' ); ?></option>
|
||||
<?php foreach ( $all_sources as $source ) : ?>
|
||||
<option<?php selected( $current_source, $source ); ?> value="<?php echo esc_attr( $source ); ?>">
|
||||
<?php echo esc_html( $source ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php
|
||||
submit_button(
|
||||
__( 'Filter', 'woocommerce' ),
|
||||
'',
|
||||
'filter_action',
|
||||
false,
|
||||
array(
|
||||
'id' => 'logs-filter-submit',
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the column header info.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepare_column_headers(): void {
|
||||
$this->_column_headers = array(
|
||||
$this->get_columns(),
|
||||
get_hidden_columns( $this->screen ),
|
||||
$this->get_sortable_columns(),
|
||||
$this->get_primary_column(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the list of items for displaying.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepare_items(): void {
|
||||
$per_page = $this->get_items_per_page(
|
||||
self::PER_PAGE_USER_OPTION_KEY,
|
||||
$this->file_controller::DEFAULTS_GET_FILES['per_page']
|
||||
);
|
||||
|
||||
$defaults = array(
|
||||
'per_page' => $per_page,
|
||||
'offset' => ( $this->get_pagenum() - 1 ) * $per_page,
|
||||
);
|
||||
$file_args = wp_parse_args( $this->page_controller->get_query_params(), $defaults );
|
||||
|
||||
$total_items = $this->file_controller->get_files( $file_args, true );
|
||||
if ( is_wp_error( $total_items ) ) {
|
||||
printf(
|
||||
'<div class="notice notice-warning"><p>%s</p></div>',
|
||||
esc_html( $total_items->get_error_message() )
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$total_pages = ceil( $total_items / $per_page );
|
||||
$items = $this->file_controller->get_files( $file_args );
|
||||
|
||||
$this->items = $items;
|
||||
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'per_page' => $per_page,
|
||||
'total_items' => $total_items,
|
||||
'total_pages' => $total_pages,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns(): array {
|
||||
$columns = array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'source' => esc_html__( 'Source', 'woocommerce' ),
|
||||
'created' => esc_html__( 'Date created', 'woocommerce' ),
|
||||
'modified' => esc_html__( 'Date modified', 'woocommerce' ),
|
||||
'size' => esc_html__( 'File size', 'woocommerce' ),
|
||||
);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of sortable columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_sortable_columns(): array {
|
||||
$sortable = array(
|
||||
'source' => array( 'source' ),
|
||||
'created' => array( 'created' ),
|
||||
'modified' => array( 'modified', true ),
|
||||
'size' => array( 'size' ),
|
||||
);
|
||||
|
||||
return $sortable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the checkbox column.
|
||||
*
|
||||
* @param File $item The current log file being rendered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $item ): string {
|
||||
ob_start();
|
||||
?>
|
||||
<input
|
||||
id="cb-select-<?php echo esc_attr( $item->get_file_id() ); ?>"
|
||||
type="checkbox"
|
||||
name="file_id[]"
|
||||
value="<?php echo esc_attr( $item->get_file_id() ); ?>"
|
||||
/>
|
||||
<label for="cb-select-<?php echo esc_attr( $item->get_hash() ); ?>">
|
||||
<span class="screen-reader-text">
|
||||
<?php
|
||||
printf(
|
||||
// translators: 1. a date, 2. a slug-style name for a file.
|
||||
esc_html__( 'Select the %1$s log file for %2$s', 'woocommerce' ),
|
||||
esc_html( gmdate( get_option( 'date_format' ), $item->get_created_timestamp() ) ),
|
||||
esc_html( $item->get_source() )
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
</label>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the source column.
|
||||
*
|
||||
* @param File $item The current log file being rendered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_source( $item ): string {
|
||||
$log_file = $item->get_file_id();
|
||||
$single_file_url = add_query_arg(
|
||||
array(
|
||||
'view' => 'single_file',
|
||||
'file_id' => $log_file,
|
||||
),
|
||||
$this->page_controller->get_logs_tab_url()
|
||||
);
|
||||
$rotation = '';
|
||||
if ( ! is_null( $item->get_rotation() ) ) {
|
||||
$rotation = sprintf(
|
||||
' – <span class="post-state">%d</span>',
|
||||
$item->get_rotation()
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a class="row-title" href="%1$s">%2$s</a>%3$s',
|
||||
esc_url( $single_file_url ),
|
||||
esc_html( $item->get_source() ),
|
||||
$rotation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the created column.
|
||||
*
|
||||
* @param File $item The current log file being rendered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_created( $item ): string {
|
||||
$timestamp = $item->get_created_timestamp();
|
||||
|
||||
return gmdate( 'Y-m-d', $timestamp );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modified column.
|
||||
*
|
||||
* @param File $item The current log file being rendered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_modified( $item ): string {
|
||||
$timestamp = $item->get_modified_timestamp();
|
||||
|
||||
return gmdate( 'Y-m-d H:i:s', $timestamp );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the size column.
|
||||
*
|
||||
* @param File $item The current log file being rendered.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_size( $item ): string {
|
||||
$size = $item->get_file_size();
|
||||
|
||||
return size_format( $size );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Logging;
|
||||
|
||||
use WC_Log_Handler_File;
|
||||
|
||||
/**
|
||||
* LogHandlerFileV2 class.
|
||||
*/
|
||||
class LogHandlerFileV2 extends WC_Log_Handler_File {}
|
||||
@@ -0,0 +1,530 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Logging;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ FileController, ListTable };
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use WC_Admin_Status;
|
||||
use WC_Log_Levels;
|
||||
|
||||
/**
|
||||
* PageController class.
|
||||
*/
|
||||
class PageController {
|
||||
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* Instance of FileController.
|
||||
*
|
||||
* @var FileController
|
||||
*/
|
||||
private $file_controller;
|
||||
|
||||
/**
|
||||
* Instance of ListTable.
|
||||
*
|
||||
* @var ListTable
|
||||
*/
|
||||
private $list_table;
|
||||
|
||||
/**
|
||||
* Initialize dependencies.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param FileController $file_controller Instance of FileController.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
final public function init(
|
||||
FileController $file_controller
|
||||
): void {
|
||||
$this->file_controller = $file_controller;
|
||||
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add callbacks to hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_hooks(): void {
|
||||
self::add_action( 'load-woocommerce_page_wc-status', array( $this, 'setup_screen_options' ) );
|
||||
self::add_action( 'load-woocommerce_page_wc-status', array( $this, 'handle_list_table_bulk_actions' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canonical URL for the Logs tab of the Status admin page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_logs_tab_url(): string {
|
||||
return add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-status',
|
||||
'tab' => 'logs',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the default log handler.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_handler(): string {
|
||||
$handler = Constants::get_constant( 'WC_LOG_HANDLER' );
|
||||
|
||||
if ( is_null( $handler ) || ! class_exists( $handler ) ) {
|
||||
$handler = \WC_Log_Handler_File::class;
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the "Logs" tab, depending on the current default log handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render(): void {
|
||||
$handler = $this->get_default_handler();
|
||||
|
||||
switch ( $handler ) {
|
||||
case LogHandlerFileV2::class:
|
||||
$params = $this->get_query_params();
|
||||
$this->render_filev2( $params );
|
||||
break;
|
||||
case 'WC_Log_Handler_DB':
|
||||
WC_Admin_Status::status_logs_db();
|
||||
break;
|
||||
default:
|
||||
WC_Admin_Status::status_logs_file();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the views for the FileV2 log handler.
|
||||
*
|
||||
* @param array $params Args for rendering the views.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_filev2( array $params = array() ): void {
|
||||
$view = $params['view'] ?? '';
|
||||
|
||||
switch ( $view ) {
|
||||
case 'list_files':
|
||||
default:
|
||||
$this->render_file_list_page( $params );
|
||||
break;
|
||||
case 'single_file':
|
||||
$this->render_single_file_page( $params );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the file list view.
|
||||
*
|
||||
* @param array $params Args for rendering the view.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_file_list_page( array $params = array() ): void {
|
||||
$defaults = $this->get_query_param_defaults();
|
||||
|
||||
?>
|
||||
<header id="logs-header" class="wc-logs-header">
|
||||
<h2>
|
||||
<?php esc_html_e( 'Browse log files', 'woocommerce' ); ?>
|
||||
</h2>
|
||||
</header>
|
||||
<form id="logs-list-table-form" method="get">
|
||||
<input type="hidden" name="page" value="wc-status" />
|
||||
<input type="hidden" name="tab" value="logs" />
|
||||
<?php foreach ( $params as $key => $value ) : ?>
|
||||
<?php if ( $value !== $defaults[ $key ] ) : ?>
|
||||
<input
|
||||
type="hidden"
|
||||
name="<?php echo esc_attr( $key ); ?>"
|
||||
value="<?php echo esc_attr( $value ); ?>"
|
||||
/>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php $this->get_list_table()->prepare_items(); ?>
|
||||
<?php $this->get_list_table()->display(); ?>
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the single file view.
|
||||
*
|
||||
* @param array $params Args for rendering the view.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_single_file_page( array $params ): void {
|
||||
$file = $this->file_controller->get_file_by_id( $params['file_id'] );
|
||||
|
||||
if ( is_wp_error( $file ) ) {
|
||||
?>
|
||||
<div class="notice notice-error notice-inline">
|
||||
<?php echo wp_kses_post( wpautop( $file->get_error_message() ) ); ?>
|
||||
<?php
|
||||
printf(
|
||||
'<p><a href="%1$s">%2$s</a></p>',
|
||||
esc_url( $this->get_logs_tab_url() ),
|
||||
esc_html__( 'Return to the file list.', 'woocommerce' )
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$rotations = $this->file_controller->get_file_rotations( $file->get_file_id() );
|
||||
$rotation_url_base = add_query_arg( 'view', 'single_file', $this->get_logs_tab_url() );
|
||||
|
||||
$delete_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'delete',
|
||||
'file_id' => array( $file->get_file_id() ),
|
||||
),
|
||||
wp_nonce_url( $this->get_logs_tab_url(), 'bulk-log-files' )
|
||||
);
|
||||
|
||||
$stream = $file->get_stream();
|
||||
$line_number = 1;
|
||||
|
||||
$delete_confirmation_js = sprintf(
|
||||
"return window.confirm( '%s' )",
|
||||
esc_js( __( 'Delete this log file permanently?', 'woocommerce' ) )
|
||||
);
|
||||
|
||||
?>
|
||||
<header id="logs-header" class="wc-logs-header">
|
||||
<h2>
|
||||
<?php
|
||||
printf(
|
||||
// translators: %s is the name of a log file.
|
||||
esc_html__( 'Viewing log file %s', 'woocommerce' ),
|
||||
sprintf(
|
||||
'<span class="file-id">%s</span>',
|
||||
esc_html( $file->get_file_id() )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</h2>
|
||||
<?php if ( count( $rotations ) > 1 ) : ?>
|
||||
<nav class="wc-logs-single-file-rotations">
|
||||
<h3><?php esc_html_e( 'File rotations:', 'woocommerce' ); ?></h3>
|
||||
<ul class="wc-logs-rotation-links">
|
||||
<?php if ( isset( $rotations['current'] ) ) : ?>
|
||||
<?php
|
||||
printf(
|
||||
'<li><a href="%1$s" class="button button-small button-%2$s">%3$s</a></li>',
|
||||
esc_url( add_query_arg( 'file_id', $rotations['current']->get_file_id(), $rotation_url_base ) ),
|
||||
$file->get_file_id() === $rotations['current']->get_file_id() ? 'primary' : 'secondary',
|
||||
esc_html__( 'Current', 'woocommerce' )
|
||||
);
|
||||
unset( $rotations['current'] );
|
||||
?>
|
||||
<?php endif; ?>
|
||||
<?php foreach ( $rotations as $rotation ) : ?>
|
||||
<?php
|
||||
printf(
|
||||
'<li><a href="%1$s" class="button button-small button-%2$s">%3$s</a></li>',
|
||||
esc_url( add_query_arg( 'file_id', $rotation->get_file_id(), $rotation_url_base ) ),
|
||||
$file->get_file_id() === $rotation->get_file_id() ? 'primary' : 'secondary',
|
||||
absint( $rotation->get_rotation() )
|
||||
);
|
||||
?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
<div class="wc-logs-single-file-actions">
|
||||
<?php
|
||||
// Delete button.
|
||||
printf(
|
||||
'<a href="%1$s" class="button button-secondary" onclick="%2$s">%3$s</a>',
|
||||
esc_url( $delete_url ),
|
||||
esc_attr( $delete_confirmation_js ),
|
||||
esc_html__( 'Delete permanently', 'woocommerce' )
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</header>
|
||||
<section id="logs-entries" class="wc-logs-entries">
|
||||
<?php while ( ! feof( $stream ) ) : ?>
|
||||
<?php
|
||||
$line = fgets( $stream );
|
||||
if ( is_string( $line ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- format_line does the escaping.
|
||||
echo $this->format_line( $line, $line_number );
|
||||
$line_number ++;
|
||||
}
|
||||
?>
|
||||
<?php endwhile; ?>
|
||||
</section>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default values for URL query params for FileV2 views.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_query_param_defaults(): array {
|
||||
return array(
|
||||
'file_id' => '',
|
||||
'order' => $this->file_controller::DEFAULTS_GET_FILES['order'],
|
||||
'orderby' => $this->file_controller::DEFAULTS_GET_FILES['orderby'],
|
||||
'source' => $this->file_controller::DEFAULTS_GET_FILES['source'],
|
||||
'view' => 'list_files',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and validate URL query params for FileV2 views.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_query_params(): array {
|
||||
$defaults = $this->get_query_param_defaults();
|
||||
$params = filter_input_array(
|
||||
INPUT_GET,
|
||||
array(
|
||||
'file_id' => array(
|
||||
'filter' => FILTER_CALLBACK,
|
||||
'options' => function( $file_id ) {
|
||||
return sanitize_file_name( $file_id );
|
||||
},
|
||||
),
|
||||
'order' => array(
|
||||
'filter' => FILTER_VALIDATE_REGEXP,
|
||||
'options' => array(
|
||||
'regexp' => '/^(asc|desc)$/i',
|
||||
'default' => $defaults['order'],
|
||||
),
|
||||
),
|
||||
'orderby' => array(
|
||||
'filter' => FILTER_VALIDATE_REGEXP,
|
||||
'options' => array(
|
||||
'regexp' => '/^(created|modified|source|size)$/',
|
||||
'default' => $defaults['orderby'],
|
||||
),
|
||||
),
|
||||
'source' => array(
|
||||
'filter' => FILTER_CALLBACK,
|
||||
'options' => function( $source ) {
|
||||
return $this->file_controller->sanitize_source( wp_unslash( $source ) );
|
||||
},
|
||||
),
|
||||
'view' => array(
|
||||
'filter' => FILTER_VALIDATE_REGEXP,
|
||||
'options' => array(
|
||||
'regexp' => '/^(list_files|single_file)$/',
|
||||
'default' => $defaults['view'],
|
||||
),
|
||||
),
|
||||
),
|
||||
false
|
||||
);
|
||||
$params = wp_parse_args( $params, $defaults );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and cache an instance of the list table.
|
||||
*
|
||||
* @return ListTable
|
||||
*/
|
||||
private function get_list_table(): ListTable {
|
||||
if ( $this->list_table instanceof ListTable ) {
|
||||
return $this->list_table;
|
||||
}
|
||||
|
||||
$this->list_table = new ListTable( $this->file_controller, $this );
|
||||
|
||||
return $this->list_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register screen options for the logging views.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setup_screen_options(): void {
|
||||
$params = $this->get_query_params();
|
||||
|
||||
if ( 'list_files' === $params['view'] ) {
|
||||
// Ensure list table columns are initialized early enough to enable column hiding.
|
||||
$this->get_list_table()->prepare_column_headers();
|
||||
|
||||
add_screen_option(
|
||||
'per_page',
|
||||
array(
|
||||
'default' => 20,
|
||||
'option' => ListTable::PER_PAGE_USER_OPTION_KEY,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process bulk actions initiated from the log file list table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_list_table_bulk_actions(): void {
|
||||
// Bail if this is not the list table view.
|
||||
$params = $this->get_query_params();
|
||||
if ( 'list_files' !== $params['view'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $this->get_list_table()->current_action();
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : $this->get_logs_tab_url();
|
||||
|
||||
if ( $action ) {
|
||||
check_admin_referer( 'bulk-log-files' );
|
||||
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to manage log files.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$sendback = remove_query_arg( array( 'deleted' ), wp_get_referer() );
|
||||
|
||||
// Multiple file_id[] params will be filtered separately, but assigned to $files as an array.
|
||||
$file_ids = filter_input(
|
||||
INPUT_GET,
|
||||
'file_id',
|
||||
FILTER_CALLBACK,
|
||||
array(
|
||||
'options' => function( $file ) {
|
||||
return sanitize_file_name( wp_unslash( $file ) );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! is_array( $file_ids ) || count( $file_ids ) < 1 ) {
|
||||
wp_safe_redirect( $sendback );
|
||||
exit;
|
||||
}
|
||||
|
||||
switch ( $action ) {
|
||||
case 'delete':
|
||||
$deleted = $this->file_controller->delete_files( $file_ids );
|
||||
$sendback = add_query_arg( 'deleted', $deleted, $sendback );
|
||||
|
||||
/**
|
||||
* If the delete action was triggered on the single file view, don't redirect back there
|
||||
* since the file doesn't exist anymore.
|
||||
*/
|
||||
$sendback = remove_query_arg( array( 'view', 'file_id' ), $sendback );
|
||||
break;
|
||||
}
|
||||
|
||||
$sendback = remove_query_arg( array( 'action', 'action2' ), $sendback );
|
||||
|
||||
wp_safe_redirect( $sendback );
|
||||
exit;
|
||||
} elseif ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
|
||||
$removable_args = array( '_wp_http_referer', '_wpnonce', 'action', 'action2', 'filter_action' );
|
||||
wp_safe_redirect( remove_query_arg( $removable_args, $request_uri ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$deleted = filter_input( INPUT_GET, 'deleted', FILTER_VALIDATE_INT );
|
||||
|
||||
if ( is_numeric( $deleted ) ) {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() use ( $deleted ) {
|
||||
?>
|
||||
<div class="notice notice-info is-dismissible">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
// translators: %s is a number of files.
|
||||
esc_html( _n( '%s log file deleted.', '%s log files deleted.', $deleted, 'woocommerce' ) ),
|
||||
esc_html( number_format_i18n( $deleted ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a log file line.
|
||||
*
|
||||
* @param string $text The unformatted log file line.
|
||||
* @param int $line_number The line number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function format_line( string $text, int $line_number ): string {
|
||||
$severity_levels = WC_Log_Levels::get_all_severity_levels();
|
||||
$classes = array( 'line' );
|
||||
|
||||
$text = esc_html( trim( $text ) );
|
||||
if ( empty( $text ) ) {
|
||||
$text = ' ';
|
||||
}
|
||||
|
||||
$segments = explode( ' ', $text, 3 );
|
||||
|
||||
if ( isset( $segments[0] ) && false !== strtotime( $segments[0] ) ) {
|
||||
$classes[] = 'log-entry';
|
||||
$segments[0] = sprintf(
|
||||
'<span class="log-timestamp">%s</span>',
|
||||
$segments[0]
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $segments[1] ) && in_array( strtolower( $segments[1] ), $severity_levels, true ) ) {
|
||||
$segments[1] = sprintf(
|
||||
'<span class="%1$s">%2$s</span>',
|
||||
esc_attr( 'log-level log-level--' . strtolower( $segments[1] ) ),
|
||||
esc_html( $segments[1] )
|
||||
);
|
||||
}
|
||||
|
||||
if ( count( $segments ) > 1 ) {
|
||||
$text = implode( ' ', $segments );
|
||||
}
|
||||
|
||||
$classes = implode( ' ', $classes );
|
||||
|
||||
return sprintf(
|
||||
'<span id="L%1$d" class="%2$s">%3$s%4$s</span>',
|
||||
absint( $line_number ),
|
||||
esc_attr( $classes ),
|
||||
sprintf(
|
||||
'<a href="#L%1$d" class="line-anchor"></a>',
|
||||
absint( $line_number )
|
||||
),
|
||||
sprintf(
|
||||
'<span class="line-content">%s</span>',
|
||||
wp_kses_post( $text )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Marketing Specs Handler
|
||||
*
|
||||
* Fetches the specifications for the marketing feature from WC.com API.
|
||||
* Fetches the specifications for the marketing feature from Woo.com API.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Marketing;
|
||||
@@ -29,21 +29,21 @@ class MarketingSpecs {
|
||||
const KNOWLEDGE_BASE_TRANSIENT = 'wc_marketing_knowledge_base';
|
||||
|
||||
/**
|
||||
* Slug of the category specifying marketing extensions on the WooCommerce.com store.
|
||||
* Slug of the category specifying marketing extensions on the Woo.com store.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARKETING_EXTENSION_CATEGORY_SLUG = 'marketing';
|
||||
|
||||
/**
|
||||
* Slug of the subcategory specifying marketing channels on the WooCommerce.com store.
|
||||
* Slug of the subcategory specifying marketing channels on the Woo.com store.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MARKETING_CHANNEL_SUBCATEGORY_SLUG = 'sales-channels';
|
||||
|
||||
/**
|
||||
* Load recommended plugins from WooCommerce.com
|
||||
* Load recommended plugins from Woo.com
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -76,7 +76,7 @@ class MarketingSpecs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only the recommended marketing channels from WooCommerce.com.
|
||||
* Return only the recommended marketing channels from Woo.com.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -85,7 +85,7 @@ class MarketingSpecs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all recommended marketing extensions EXCEPT the marketing channels from WooCommerce.com.
|
||||
* Return all recommended marketing extensions EXCEPT the marketing channels from Woo.com.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -134,7 +134,7 @@ class MarketingSpecs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load knowledge base posts from WooCommerce.com
|
||||
* Load knowledge base posts from Woo.com
|
||||
*
|
||||
* @param string|null $topic The topic of marketing knowledgebase to retrieve.
|
||||
* @return array
|
||||
|
||||
@@ -12,12 +12,20 @@ use Automattic\WooCommerce\Utilities\FeaturesUtil;
|
||||
*/
|
||||
class Marketplace {
|
||||
|
||||
const MARKETPLACE_TAB_SLUG = 'woo';
|
||||
|
||||
/**
|
||||
* Class initialization, to be executed when the class is resolved by the container.
|
||||
*/
|
||||
final public function init() {
|
||||
if ( FeaturesUtil::feature_is_enabled( 'marketplace' ) ) {
|
||||
add_action( 'admin_menu', array( $this, 'register_pages' ), 70 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
|
||||
// Add a Woo Marketplace link to the plugin install action links.
|
||||
add_filter( 'install_plugins_tabs', array( $this, 'add_woo_plugin_install_action_link' ) );
|
||||
add_action( 'install_plugins_pre_woo', array( $this, 'maybe_open_woo_tab' ) );
|
||||
add_action( 'admin_print_styles-plugin-install.php', array( $this, 'add_plugins_page_styles' ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +33,10 @@ class Marketplace {
|
||||
* Registers report pages.
|
||||
*/
|
||||
public function register_pages() {
|
||||
if ( ! function_exists( 'wc_admin_register_page' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$marketplace_pages = self::get_marketplace_pages();
|
||||
foreach ( $marketplace_pages as $marketplace_page ) {
|
||||
if ( ! is_null( $marketplace_page ) ) {
|
||||
@@ -53,4 +65,82 @@ class Marketplace {
|
||||
*/
|
||||
return apply_filters( 'woocommerce_marketplace_menu_items', $marketplace_pages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue update script.
|
||||
*
|
||||
* @param string $hook_suffix The current admin page.
|
||||
*/
|
||||
public function enqueue_scripts( $hook_suffix ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( 'woocommerce_page_wc-admin' !== $hook_suffix ) {
|
||||
return;
|
||||
};
|
||||
|
||||
if ( ! isset( $_GET['path'] ) || '/extensions' !== $_GET['path'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue WordPress updates script to enable plugin and theme installs and updates.
|
||||
wp_enqueue_script( 'updates' );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Woo Marketplace link to the plugin install action links.
|
||||
*
|
||||
* @param array $tabs Plugins list tabs.
|
||||
* @return array
|
||||
*/
|
||||
public function add_woo_plugin_install_action_link( $tabs ) {
|
||||
$tabs[ self::MARKETPLACE_TAB_SLUG ] = 'Woo';
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Woo tab when the user clicks on the Woo link in the plugin installer.
|
||||
*/
|
||||
public function maybe_open_woo_tab() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['tab'] ) || self::MARKETPLACE_TAB_SLUG !== $_GET['tab'] ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$woo_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-admin',
|
||||
'path' => '/extensions',
|
||||
'tab' => 'extensions',
|
||||
'ref' => 'plugins',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
|
||||
wp_safe_redirect( $woo_url );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add styles to the plugin install page.
|
||||
*/
|
||||
public function add_plugins_page_styles() {
|
||||
?>
|
||||
<style>
|
||||
.plugin-install-woo > a::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.33321 3H12.9999V7.66667H11.9999V4.70711L8.02009 8.68689L7.31299 7.97978L11.2928 4H8.33321V3Z' fill='%23646970'/%3E%3Cpath d='M6.33333 4.1665H4.33333C3.8731 4.1665 3.5 4.5396 3.5 4.99984V11.6665C3.5 12.1267 3.8731 12.4998 4.33333 12.4998H11C11.4602 12.4998 11.8333 12.1267 11.8333 11.6665V9.6665' stroke='%23646970'/%3E%3C/svg%3E%0A");
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
vertical-align: text-top;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.plugin-install-woo:hover > a::after {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.33321 3H12.9999V7.66667H11.9999V4.70711L8.02009 8.68689L7.31299 7.97978L11.2928 4H8.33321V3Z' fill='%23135E96'/%3E%3Cpath d='M6.33333 4.1665H4.33333C3.8731 4.1665 3.5 4.5396 3.5 4.99984V11.6665C3.5 12.1267 3.8731 12.4998 4.33333 12.4998H11C11.4602 12.4998 11.8333 12.1267 11.8333 11.6665V9.6665' stroke='%23135E96'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class AddFirstProduct {
|
||||
sprintf( __( 'Nice one; you\'ve created a WooCommerce store! Now it\'s time to add your first product and get ready to start selling.%s', 'woocommerce' ), '<br/><br/>' ),
|
||||
__( 'There are three ways to add your products: you can <strong>create products manually, import them at once via CSV file</strong>, or <strong>migrate them from another service</strong>.<br/><br/>', 'woocommerce' ),
|
||||
/* translators: %1$s is an open anchor tag (<a>) and %2$s is a close link tag (</a>). */
|
||||
sprintf( __( '%1$1sExplore our docs%2$2s for more information, or just get started!', 'woocommerce' ), '<a href="https://woocommerce.com/document/managing-products/?utm_source=help_panel&utm_medium=product">', '</a>' ),
|
||||
sprintf( __( '%1$1sExplore our docs%2$2s for more information, or just get started!', 'woocommerce' ), '<a href="https://woo.com/document/managing-products/?utm_source=help_panel&utm_medium=product">', '</a>' ),
|
||||
);
|
||||
|
||||
$additional_data = array(
|
||||
|
||||
@@ -48,7 +48,7 @@ class ChoosingTheme {
|
||||
$note->add_action(
|
||||
'visit-the-theme-marketplace',
|
||||
__( 'Visit the theme marketplace', 'woocommerce' ),
|
||||
'https://woocommerce.com/product-category/themes/?utm_source=inbox&utm_medium=product'
|
||||
'https://woo.com/product-category/themes/?utm_source=inbox&utm_medium=product'
|
||||
);
|
||||
return $note;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class CustomizeStoreWithBlocks {
|
||||
$note->add_action(
|
||||
'customize-store-with-blocks',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/posts/how-to-customize-your-online-store-with-woocommerce-blocks/?utm_source=inbox&utm_medium=product',
|
||||
'https://woo.com/posts/how-to-customize-your-online-store-with-woocommerce-blocks/?utm_source=inbox&utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_ACTIONED
|
||||
);
|
||||
return $note;
|
||||
|
||||
@@ -75,7 +75,7 @@ class CustomizingProductCatalog {
|
||||
$note->add_action(
|
||||
'day-after-first-product',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/document/woocommerce-customizer/?utm_source=inbox&utm_medium=product'
|
||||
'https://woo.com/document/woocommerce-customizer/?utm_source=inbox&utm_medium=product'
|
||||
);
|
||||
|
||||
return $note;
|
||||
|
||||
@@ -54,7 +54,7 @@ class EUVATNumber {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/products/eu-vat-number/?utm_medium=product',
|
||||
'https://woo.com/products/eu-vat-number/?utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_ACTIONED
|
||||
);
|
||||
return $note;
|
||||
|
||||
@@ -63,7 +63,7 @@ class EditProductsOnTheMove {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product'
|
||||
'https://woo.com/mobile/?utm_source=inbox&utm_medium=product'
|
||||
);
|
||||
|
||||
return $note;
|
||||
|
||||
@@ -54,7 +54,7 @@ class LaunchChecklist {
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/posts/pre-launch-checklist-the-essentials/?utm_source=inbox&utm_medium=product' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woo.com/posts/pre-launch-checklist-the-essentials/?utm_source=inbox&utm_medium=product' );
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ class MagentoMigration {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/posts/how-migrate-from-magento-to-woocommerce/?utm_source=inbox'
|
||||
'https://woo.com/posts/how-migrate-from-magento-to-woocommerce/?utm_source=inbox'
|
||||
);
|
||||
|
||||
return $note;
|
||||
|
||||
@@ -56,7 +56,7 @@ class ManageOrdersOnTheGo {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product'
|
||||
'https://woo.com/mobile/?utm_source=inbox&utm_medium=product'
|
||||
);
|
||||
|
||||
return $note;
|
||||
|
||||
@@ -20,7 +20,7 @@ use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
*
|
||||
* Note: This should probably live in the Jetpack plugin in the future.
|
||||
*
|
||||
* @see https://developer.woocommerce.com/2020/10/16/using-the-admin-notes-inbox-in-woocommerce/
|
||||
* @see https://developer.woo.com/2020/10/16/using-the-admin-notes-inbox-in-woocommerce/
|
||||
*/
|
||||
class MarketingJetpack {
|
||||
// Shared Note Traits.
|
||||
|
||||
@@ -71,7 +71,7 @@ class MigrateFromShopify {
|
||||
$note->add_action(
|
||||
'migrate-from-shopify',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/posts/migrate-from-shopify-to-woocommerce/?utm_source=inbox&utm_medium=product',
|
||||
'https://woo.com/posts/migrate-from-shopify-to-woocommerce/?utm_source=inbox&utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_ACTIONED
|
||||
);
|
||||
return $note;
|
||||
|
||||
@@ -47,7 +47,7 @@ class MobileApp {
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/mobile/?utm_medium=product' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woo.com/mobile/?utm_medium=product' );
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class OnboardingPayments {
|
||||
$note->add_action(
|
||||
'view-payment-gateways',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_medium=product',
|
||||
'https://woo.com/product-category/woocommerce-extensions/payment-gateways/?utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_ACTIONED,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -90,7 +90,7 @@ class OnlineClothingStore {
|
||||
$note->add_action(
|
||||
'online-clothing-store',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/posts/starting-an-online-clothing-store/?utm_source=inbox&utm_medium=product',
|
||||
'https://woo.com/posts/starting-an-online-clothing-store/?utm_source=inbox&utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_ACTIONED
|
||||
);
|
||||
return $note;
|
||||
|
||||
@@ -239,13 +239,13 @@ class OrderMilestones {
|
||||
return array(
|
||||
'name' => 'learn-more',
|
||||
'label' => __( 'Learn more', 'woocommerce' ),
|
||||
'query' => 'https://woocommerce.com/document/managing-orders/?utm_source=inbox&utm_medium=product',
|
||||
'query' => 'https://woo.com/document/managing-orders/?utm_source=inbox&utm_medium=product',
|
||||
);
|
||||
case 10:
|
||||
return array(
|
||||
'name' => 'browse',
|
||||
'label' => __( 'Browse', 'woocommerce' ),
|
||||
'query' => 'https://woocommerce.com/success-stories/?utm_source=inbox&utm_medium=product',
|
||||
'query' => 'https://woo.com/success-stories/?utm_source=inbox&utm_medium=product',
|
||||
);
|
||||
case 100:
|
||||
case 250:
|
||||
|
||||
@@ -72,7 +72,7 @@ class PaymentsMoreInfoNeeded {
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more here', 'woocommerce' ), 'https://woocommerce.com/payments/' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more here', 'woocommerce' ), 'https://woo.com/payments/' );
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class PerformanceOnMobile {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product'
|
||||
'https://woo.com/mobile/?utm_source=inbox&utm_medium=product'
|
||||
);
|
||||
|
||||
return $note;
|
||||
|
||||
@@ -51,7 +51,7 @@ class RealTimeOrderAlerts {
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/mobile/?utm_source=inbox&utm_medium=product' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woo.com/mobile/?utm_source=inbox&utm_medium=product' );
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class SellingOnlineCourses {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://woocommerce.com/posts/how-to-sell-online-courses-wordpress/?utm_source=inbox&utm_medium=product',
|
||||
'https://woo.com/posts/how-to-sell-online-courses-wordpress/?utm_source=inbox&utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_ACTIONED
|
||||
);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class TrackingOptIn {
|
||||
return;
|
||||
}
|
||||
|
||||
/* translators: 1: open link to WooCommerce.com settings, 2: open link to WooCommerce.com tracking documentation, 3: close link tag. */
|
||||
/* translators: 1: open link to Woo.com settings, 2: open link to Woo.com tracking documentation, 3: close link tag. */
|
||||
$content_format = __(
|
||||
'Gathering usage data allows us to improve WooCommerce. Your store will be considered as we evaluate new features, judge the quality of an update, or determine if an improvement makes sense. You can always visit the %1$sSettings%3$s and choose to stop sharing data. %2$sRead more%3$s about what data we collect.',
|
||||
'woocommerce'
|
||||
@@ -58,7 +58,7 @@ class TrackingOptIn {
|
||||
$note_content = sprintf(
|
||||
$content_format,
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=woocommerce_com' ) ) . '" target="_blank">',
|
||||
'<a href="https://woocommerce.com/usage-tracking?utm_medium=product" target="_blank">',
|
||||
'<a href="https://woo.com/usage-tracking?utm_medium=product" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class UnsecuredReportFiles {
|
||||
sprintf(
|
||||
/* translators: 1: opening analytics docs link tag. 2: closing link tag */
|
||||
__( 'Files that may contain %1$sstore analytics%2$s reports were found in your uploads directory - we recommend assessing and deleting any such files.', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/document/woocommerce-analytics/" target="_blank">',
|
||||
'<a href="https://woo.com/document/woocommerce-analytics/" target="_blank">',
|
||||
'</a>'
|
||||
)
|
||||
);
|
||||
@@ -48,7 +48,7 @@ class UnsecuredReportFiles {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn more', 'woocommerce' ),
|
||||
'https://developer.woocommerce.com/?p=10410',
|
||||
'https://developer.woo.com/2021/09/22/important-security-patch-released-in-woocommerce/',
|
||||
Note::E_WC_ADMIN_NOTE_UNACTIONED,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -118,7 +118,7 @@ class WooCommercePayments {
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_MARKETING );
|
||||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-admin' );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woocommerce.com/payments/?utm_medium=product', Note::E_WC_ADMIN_NOTE_UNACTIONED );
|
||||
$note->add_action( 'learn-more', __( 'Learn more', 'woocommerce' ), 'https://woo.com/payments/?utm_medium=product', Note::E_WC_ADMIN_NOTE_UNACTIONED );
|
||||
$note->add_action( 'get-started', __( 'Get started', 'woocommerce' ), wc_admin_url( '&action=setup-woocommerce-payments' ), Note::E_WC_ADMIN_NOTE_ACTIONED, true );
|
||||
$note->add_nonce_to_action( 'get-started', 'setup-woocommerce-payments', '' );
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class WooCommerceSubscriptions {
|
||||
$note->add_action(
|
||||
'learn-more',
|
||||
__( 'Learn More', 'woocommerce' ),
|
||||
'https://woocommerce.com/products/woocommerce-subscriptions/?utm_source=inbox&utm_medium=product',
|
||||
'https://woo.com/products/woocommerce-subscriptions/?utm_source=inbox&utm_medium=product',
|
||||
Note::E_WC_ADMIN_NOTE_UNACTIONED,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Admin (Dashboard) WooCommerce.com Extension Subscriptions Note Provider.
|
||||
* WooCommerce Admin (Dashboard) Woo.com Extension Subscriptions Note Provider.
|
||||
*
|
||||
* Adds notes to the merchant's inbox concerning WooCommerce.com extension subscriptions.
|
||||
* Adds notes to the merchant's inbox concerning Woo.com extension subscriptions.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Notes;
|
||||
@@ -129,7 +129,7 @@ class WooSubscriptionsNotes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we think the site is currently connected to WooCommerce.com.
|
||||
* Whether or not we think the site is currently connected to Woo.com.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -139,7 +139,7 @@ class WooSubscriptionsNotes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WooCommerce.com provided site ID for this site.
|
||||
* Returns the Woo.com provided site ID for this site.
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
@@ -187,7 +187,7 @@ class WooSubscriptionsNotes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a note prompting to connect to WooCommerce.com.
|
||||
* Adds a note prompting to connect to Woo.com.
|
||||
*/
|
||||
public function add_no_connection_note() {
|
||||
$note = self::get_note();
|
||||
@@ -195,11 +195,11 @@ class WooSubscriptionsNotes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WooCommerce.com connection note
|
||||
* Get the Woo.com connection note
|
||||
*/
|
||||
public static function get_note() {
|
||||
$note = new Note();
|
||||
$note->set_title( __( 'Connect to WooCommerce.com', 'woocommerce' ) );
|
||||
$note->set_title( __( 'Connect to Woo.com', 'woocommerce' ) );
|
||||
$note->set_content( __( 'Connect to get important product notifications and updates.', 'woocommerce' ) );
|
||||
$note->set_content_data( (object) array() );
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
@@ -358,7 +358,7 @@ class WooSubscriptionsNotes {
|
||||
$note->add_action(
|
||||
'enable-autorenew',
|
||||
__( 'Enable Autorenew', 'woocommerce' ),
|
||||
'https://woocommerce.com/my-account/my-subscriptions/?utm_medium=product'
|
||||
'https://woo.com/my-account/my-subscriptions/?utm_medium=product'
|
||||
);
|
||||
$note->set_content( $note_content );
|
||||
$note->set_content_data( $note_content_data );
|
||||
|
||||
@@ -44,7 +44,7 @@ class OnboardingSync {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send profile data to WooCommerce.com.
|
||||
* Send profile data to Woo.com.
|
||||
*/
|
||||
private function send_profile_data() {
|
||||
if ( 'yes' !== get_option( 'woocommerce_allow_tracking', 'no' ) ) {
|
||||
@@ -142,7 +142,7 @@ class OnboardingSync {
|
||||
! $task_list ||
|
||||
$task_list->is_hidden() ||
|
||||
! isset( $_SERVER['HTTP_REFERER'] ) ||
|
||||
0 !== strpos( $_SERVER['HTTP_REFERER'], 'https://woocommerce.com/checkout?utm_medium=product' ) // phpcs:ignore sanitization ok.
|
||||
0 !== strpos( $_SERVER['HTTP_REFERER'], 'https://woo.com/checkout?utm_medium=product' ) // phpcs:ignore sanitization ok.
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,9 +69,9 @@ class OnboardingThemes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort themes returned from WooCommerce.com
|
||||
* Sort themes returned from Woo.com
|
||||
*
|
||||
* @param array $themes Array of themes from WooCommerce.com.
|
||||
* @param array $themes Array of themes from Woo.com.
|
||||
* @return array
|
||||
*/
|
||||
public static function sort_woocommerce_themes( $themes ) {
|
||||
|
||||
@@ -237,6 +237,10 @@ class Edit {
|
||||
// Order updated message.
|
||||
$this->message = 1;
|
||||
|
||||
// Claim lock.
|
||||
$edit_lock = wc_get_container()->get( EditLock::class );
|
||||
$edit_lock->lock( $this->order );
|
||||
|
||||
$this->redirect_order( $this->order );
|
||||
}
|
||||
|
||||
@@ -385,6 +389,9 @@ class Edit {
|
||||
* @since 8.0.0
|
||||
*/
|
||||
do_action( 'order_edit_form_top', $this->order );
|
||||
|
||||
wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
|
||||
wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
|
||||
?>
|
||||
<input type="hidden" id="hiddenaction" name="action" value="<?php echo esc_attr( $form_action ); ?>"/>
|
||||
<input type="hidden" id="original_order_status" name="original_order_status" value="<?php echo esc_attr( $this->order->get_status() ); ?>"/>
|
||||
|
||||
@@ -166,9 +166,9 @@ class EditLock {
|
||||
* @return void
|
||||
*/
|
||||
public function render_dialog( $order ) {
|
||||
$locked = $this->is_locked_by_another_user( $order );
|
||||
$lock = $this->get_lock( $order );
|
||||
$user = get_user_by( 'id', $lock['user_id'] );
|
||||
$user = $lock ? get_user_by( 'id', $lock['user_id'] ) : false;
|
||||
$locked = $user && ( get_current_user_id() !== $user->ID );
|
||||
|
||||
$edit_url = wc_get_container()->get( \Automattic\WooCommerce\Internal\Admin\Orders\PageController::class )->get_edit_url( $order->get_id() );
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ class ListTable extends WP_List_Table {
|
||||
add_filter( 'set_screen_option_edit_' . $this->order_type . '_per_page', array( $this, 'set_items_per_page' ), 10, 3 );
|
||||
add_filter( 'default_hidden_columns', array( $this, 'default_hidden_columns' ), 10, 2 );
|
||||
add_action( 'admin_footer', array( $this, 'enqueue_scripts' ) );
|
||||
add_action( 'woocommerce_order_list_table_restrict_manage_orders', array( $this, 'customers_filter' ) );
|
||||
|
||||
$this->items_per_page();
|
||||
set_screen_options();
|
||||
@@ -281,7 +282,7 @@ class ListTable extends WP_List_Table {
|
||||
</h2>
|
||||
|
||||
<div class="woocommerce-BlankState-buttons">
|
||||
<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"><?php esc_html_e( 'Learn more about orders', 'woocommerce' ); ?></a>
|
||||
<a class="woocommerce-BlankState-cta button-primary button" target="_blank" href="https://woo.com/document/managing-orders/?utm_source=blankslate&utm_medium=product&utm_content=ordersdoc&utm_campaign=woocommerceplugin"><?php esc_html_e( 'Learn more about orders', 'woocommerce' ); ?></a>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
@@ -673,7 +674,6 @@ class ListTable extends WP_List_Table {
|
||||
ob_start();
|
||||
|
||||
$this->months_filter();
|
||||
$this->customers_filter();
|
||||
|
||||
/**
|
||||
* Fires before the "Filter" button on the list table for orders and other order types.
|
||||
|
||||
@@ -73,26 +73,32 @@ class ReviewsCommentsOverrides {
|
||||
* @return void
|
||||
*/
|
||||
protected function display_reviews_moved_notice() : void {
|
||||
$dismiss_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'wc-hide-notice' => urlencode( static::REVIEWS_MOVED_NOTICE_ID ),
|
||||
]
|
||||
),
|
||||
'woocommerce_hide_notices_nonce',
|
||||
'_wc_notice_nonce'
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="notice notice-info is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Product reviews have moved!', 'woocommerce' ); ?></strong></p>
|
||||
<p><?php esc_html_e( 'Product reviews can now be managed from Products > Reviews.', 'woocommerce' ); ?></p>
|
||||
<p class="submit">
|
||||
<a href="<?php echo esc_url( admin_url( 'edit.php?post_type=product&page=product-reviews' ) ); ?>" class="button-primary"><?php esc_html_e( 'Visit new location', 'woocommerce' ); ?></a>
|
||||
</p>
|
||||
<button type="button" class="notice-dismiss" onclick="window.location = '<?php echo esc_url( $dismiss_url ); ?>';"><span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'woocommerce' ); ?></span></button>
|
||||
</div>
|
||||
|
||||
<form action="<?php echo esc_url( admin_url( 'edit-comments.php' ) ); ?>" method="get">
|
||||
<input type="hidden" name="wc-hide-notice" value="<?php echo esc_attr( static::REVIEWS_MOVED_NOTICE_ID ); ?>" />
|
||||
|
||||
<?php if ( ! empty( $_GET['comment_status'] ) ): ?>
|
||||
<input type="hidden" name="comment_status" value="<?php echo esc_attr( $_GET['comment_status'] ); ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $_GET['paged'] ) ): ?>
|
||||
<input type="hidden" name="paged" value="<?php echo esc_attr( $_GET['paged'] ); ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php wp_nonce_field( 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ); ?>
|
||||
|
||||
<button type="submit" class="notice-dismiss">
|
||||
<span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'woocommerce' ); ?></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class DefaultFreeExtensions {
|
||||
'title' => __( 'Grow your store', 'woocommerce' ),
|
||||
'plugins' => array(
|
||||
self::get_plugin( 'mailpoet' ),
|
||||
self::get_plugin( 'codistoconnect' ),
|
||||
self::get_plugin( 'google-listings-and-ads' ),
|
||||
self::get_plugin( 'pinterest-for-woocommerce' ),
|
||||
self::get_plugin( 'facebook-for-woocommerce' ),
|
||||
@@ -50,7 +49,6 @@ class DefaultFreeExtensions {
|
||||
self::get_plugin( 'mailpoet:alt' ),
|
||||
self::get_plugin( 'mailchimp-for-woocommerce' ),
|
||||
self::get_plugin( 'klaviyo' ),
|
||||
self::get_plugin( 'creative-mail-by-constant-contact' ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
@@ -61,7 +59,6 @@ class DefaultFreeExtensions {
|
||||
self::get_plugin( 'tiktok-for-business' ),
|
||||
self::get_plugin( 'pinterest-for-woocommerce:alt' ),
|
||||
self::get_plugin( 'facebook-for-woocommerce:alt' ),
|
||||
self::get_plugin( 'codistoconnect:alt' ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
@@ -94,13 +91,13 @@ class DefaultFreeExtensions {
|
||||
*/
|
||||
public static function get_plugin( $slug ) {
|
||||
$plugins = array(
|
||||
'google-listings-and-ads' => array(
|
||||
'google-listings-and-ads' => array(
|
||||
'min_php_version' => '7.4',
|
||||
'name' => __( 'Google Listings & Ads', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Drive sales with %1$sGoogle Listings and Ads%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/google-listings-and-ads" target="_blank">',
|
||||
'<a href="https://woo.com/products/google-listings-and-ads" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/google.svg', WC_PLUGIN_FILE ),
|
||||
@@ -118,22 +115,22 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
),
|
||||
),
|
||||
'google-listings-and-ads:alt' => array(
|
||||
'google-listings-and-ads:alt' => array(
|
||||
'name' => __( 'Google Listings & Ads', 'woocommerce' ),
|
||||
'description' => __( 'Reach more shoppers and drive sales for your store. Integrate with Google to list your products for free and launch paid ad campaigns.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/google.svg', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=wc-admin&path=%2Fgoogle%2Fstart',
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'facebook-for-woocommerce' => array(
|
||||
'facebook-for-woocommerce' => array(
|
||||
'name' => __( 'Facebook for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'List products and create ads on Facebook and Instagram with <a href="https://woocommerce.com/products/facebook/">Facebook for WooCommerce</a>', 'woocommerce' ),
|
||||
'description' => __( 'List products and create ads on Facebook and Instagram with <a href="https://woo.com/products/facebook/">Facebook for WooCommerce</a>', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/facebook.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=wc-facebook',
|
||||
'is_visible' => false,
|
||||
'is_built_by_wc' => false,
|
||||
),
|
||||
'facebook-for-woocommerce:alt' => array(
|
||||
'facebook-for-woocommerce:alt' => array(
|
||||
'name' => __( 'Facebook for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'List products and create ads on Facebook and Instagram.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/facebook.png', WC_PLUGIN_FILE ),
|
||||
@@ -141,7 +138,7 @@ class DefaultFreeExtensions {
|
||||
'is_visible' => false,
|
||||
'is_built_by_wc' => false,
|
||||
),
|
||||
'pinterest-for-woocommerce' => array(
|
||||
'pinterest-for-woocommerce' => array(
|
||||
'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'Get your products in front of Pinners searching for ideas and things to buy.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/pinterest.png', WC_PLUGIN_FILE ),
|
||||
@@ -149,67 +146,41 @@ class DefaultFreeExtensions {
|
||||
'is_built_by_wc' => true,
|
||||
'min_php_version' => '7.3',
|
||||
),
|
||||
'pinterest-for-woocommerce:alt' => array(
|
||||
'pinterest-for-woocommerce:alt' => array(
|
||||
'name' => __( 'Pinterest for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'Get your products in front of Pinterest users searching for ideas and things to buy. Get started with Pinterest and make your entire product catalog browsable.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/pinterest.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=wc-admin&path=%2Fpinterest%2Flanding',
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'mailpoet' => array(
|
||||
'mailpoet' => array(
|
||||
'name' => __( 'MailPoet', 'woocommerce' ),
|
||||
'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/mailpoet.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=mailpoet-newsletters',
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'mailchimp-for-woocommerce' => array(
|
||||
'mailchimp-for-woocommerce' => array(
|
||||
'name' => __( 'Mailchimp', 'woocommerce' ),
|
||||
'description' => __( 'Send targeted campaigns, recover abandoned carts and much more with Mailchimp.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/mailchimp-for-woocommerce.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=mailchimp-woocommerce',
|
||||
'is_built_by_wc' => false,
|
||||
),
|
||||
'klaviyo' => array(
|
||||
'klaviyo' => array(
|
||||
'name' => __( 'Klaviyo', 'woocommerce' ),
|
||||
'description' => __( 'Grow and retain customers with intelligent, impactful email and SMS marketing automation and a consolidated view of customer interactions.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/klaviyo.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=klaviyo_settings',
|
||||
'is_built_by_wc' => false,
|
||||
),
|
||||
'creative-mail-by-constant-contact' => array(
|
||||
'name' => __( 'Creative Mail for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'Create on-brand store campaigns, fast email promotions and customer retargeting with Creative Mail.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/creative-mail-by-constant-contact.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=creativemail',
|
||||
'is_built_by_wc' => false,
|
||||
),
|
||||
'codistoconnect' => array(
|
||||
'name' => __( 'Codisto for WooCommerce', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Sell on Amazon, eBay, Walmart and more directly from WooCommerce with %1$sCodisto%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/pt-br/products/amazon-ebay-integration/?quid=c247a85321c9e93e7c3c6f1eb072e6e5" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/codistoconnect.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=codisto-settings',
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'codistoconnect:alt' => array(
|
||||
'name' => __( 'Codisto for WooCommerce', 'woocommerce' ),
|
||||
'description' => __( 'Sell on Amazon, eBay, Walmart and more directly from WooCommerce.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/codistoconnect.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=codisto-settings',
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'woocommerce-payments' => array(
|
||||
'woocommerce-payments' => array(
|
||||
'name' => __( 'WooPayments', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/wcpay.svg', WC_PLUGIN_FILE ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Accept credit cards and other popular payment methods with %1$sWooPayments%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/woocommerce-payments" target="_blank">',
|
||||
'<a href="https://woo.com/products/woocommerce-payments" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => array(
|
||||
@@ -418,13 +389,13 @@ class DefaultFreeExtensions {
|
||||
'is_built_by_wc' => true,
|
||||
'min_wp_version' => '5.9',
|
||||
),
|
||||
'woocommerce-services:shipping' => array(
|
||||
'woocommerce-services:shipping' => array(
|
||||
'name' => __( 'WooCommerce Shipping', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/woo.svg', WC_PLUGIN_FILE ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Print shipping labels with %1$sWooCommerce Shipping%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/shipping" target="_blank">',
|
||||
'<a href="https://woo.com/products/shipping" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => array(
|
||||
@@ -487,13 +458,13 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'woocommerce-services:tax' => array(
|
||||
'woocommerce-services:tax' => array(
|
||||
'name' => __( 'WooCommerce Tax', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/woo.svg', WC_PLUGIN_FILE ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Get automated sales tax with %1$sWooCommerce Tax%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/tax" target="_blank">',
|
||||
'<a href="https://woo.com/products/tax" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => array(
|
||||
@@ -569,13 +540,13 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'jetpack' => array(
|
||||
'jetpack' => array(
|
||||
'name' => __( 'Jetpack', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/jetpack.svg', WC_PLUGIN_FILE ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Enhance speed and security with %1$sJetpack%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/jetpack" target="_blank">',
|
||||
'<a href="https://woo.com/products/jetpack" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'is_visible' => array(
|
||||
@@ -592,13 +563,13 @@ class DefaultFreeExtensions {
|
||||
'is_built_by_wc' => false,
|
||||
'min_wp_version' => '6.0',
|
||||
),
|
||||
'mailpoet' => array(
|
||||
'mailpoet' => array(
|
||||
'name' => __( 'MailPoet', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/mailpoet.png', WC_PLUGIN_FILE ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Level up your email marketing with %1$sMailPoet%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/mailpoet" target="_blank">',
|
||||
'<a href="https://woo.com/products/mailpoet" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'manage_url' => 'admin.php?page=mailpoet-newsletters',
|
||||
@@ -615,14 +586,14 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'mailpoet:alt' => array(
|
||||
'mailpoet:alt' => array(
|
||||
'name' => __( 'MailPoet', 'woocommerce' ),
|
||||
'description' => __( 'Create and send purchase follow-up emails, newsletters, and promotional campaigns straight from your dashboard.', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/mailpoet.png', WC_PLUGIN_FILE ),
|
||||
'manage_url' => 'admin.php?page=mailpoet-newsletters',
|
||||
'is_built_by_wc' => true,
|
||||
),
|
||||
'tiktok-for-business' => array(
|
||||
'tiktok-for-business' => array(
|
||||
'name' => __( 'TikTok for WooCommerce', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/tiktok.svg', WC_PLUGIN_FILE ),
|
||||
'description' =>
|
||||
@@ -837,13 +808,13 @@ class DefaultFreeExtensions {
|
||||
),
|
||||
'is_built_by_wc' => false,
|
||||
),
|
||||
'tiktok-for-business:alt' => array(
|
||||
'tiktok-for-business:alt' => array(
|
||||
'name' => __( 'TikTok for WooCommerce', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/onboarding/tiktok.svg', WC_PLUGIN_FILE ),
|
||||
'description' => sprintf(
|
||||
/* translators: 1: opening product link tag. 2: closing link tag */
|
||||
__( 'Create ad campaigns and reach one billion global users with %1$sTikTok for WooCommerce%2$s', 'woocommerce' ),
|
||||
'<a href="https://woocommerce.com/products/tiktok-for-woocommerce" target="_blank">',
|
||||
'<a href="https://woo.com/products/tiktok-for-woocommerce" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'manage_url' => 'admin.php?page=tiktok',
|
||||
@@ -875,56 +846,56 @@ class DefaultFreeExtensions {
|
||||
'label' => __( 'Get paid with WooPayments', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-woo.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( "Securely accept payments and manage payment activity straight from your store's dashboard", 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/woocommerce-payments',
|
||||
'learn_more_link' => 'https://woo.com/products/woocommerce-payments',
|
||||
'install_priority' => 5,
|
||||
),
|
||||
'woocommerce-services:shipping' => array(
|
||||
'label' => __( 'Print shipping labels with WooCommerce Shipping', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-woo.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Print USPS and DHL labels directly from your dashboard and save on shipping.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/woocommerce-shipping',
|
||||
'learn_more_link' => 'https://woo.com/woocommerce-shipping',
|
||||
'install_priority' => 3,
|
||||
),
|
||||
'jetpack' => array(
|
||||
'label' => __( 'Boost content creation with Jetpack AI Assistant', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-jetpack.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Save time on content creation — unlock high-quality blog posts and pages using AI.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/jetpack',
|
||||
'learn_more_link' => 'https://woo.com/products/jetpack',
|
||||
'install_priority' => 8,
|
||||
),
|
||||
'pinterest-for-woocommerce' => array(
|
||||
'label' => __( 'Showcase your products with Pinterest', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-pinterest.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Get your products in front of a highly engaged audience.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/pinterest-for-woocommerce',
|
||||
'learn_more_link' => 'https://woo.com/products/pinterest-for-woocommerce',
|
||||
'install_priority' => 2,
|
||||
),
|
||||
'mailpoet' => array(
|
||||
'label' => __( 'Reach your customers with MailPoet', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-mailpoet.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Send purchase follow-up emails, newsletters, and promotional campaigns.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/mailpoet',
|
||||
'learn_more_link' => 'https://woo.com/products/mailpoet',
|
||||
'install_priority' => 7,
|
||||
),
|
||||
'tiktok-for-business' => array(
|
||||
'label' => __( 'Create ad campaigns with TikTok', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-tiktok.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Create advertising campaigns and reach one billion global users.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/tiktok-for-woocommerce',
|
||||
'learn_more_link' => 'https://woo.com/products/tiktok-for-woocommerce',
|
||||
'install_priority' => 1,
|
||||
),
|
||||
'google-listings-and-ads' => array(
|
||||
'label' => __( 'Drive sales with Google Listings & Ads', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-google.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Reach millions of active shoppers across Google with free product listings and ads.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/google-listings-and-ads',
|
||||
'learn_more_link' => 'https://woo.com/products/google-listings-and-ads',
|
||||
'install_priority' => 6,
|
||||
),
|
||||
'woocommerce-services:tax' => array(
|
||||
'label' => __( 'Get automated tax rates with WooCommerce Tax', 'woocommerce' ),
|
||||
'image_url' => plugins_url( '/assets/images/core-profiler/logo-woo.svg', WC_PLUGIN_FILE ),
|
||||
'description' => __( 'Automatically calculate how much sales tax should be collected – by city, country, or state.', 'woocommerce' ),
|
||||
'learn_more_link' => 'https://woocommerce.com/products/tax',
|
||||
'learn_more_link' => 'https://woo.com/products/tax',
|
||||
'install_priority' => 4,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -232,11 +232,16 @@ class Settings {
|
||||
$settings['allowMarketplaceSuggestions'] = WC_Marketplace_Suggestions::allow_suggestions();
|
||||
$settings['connectNonce'] = wp_create_nonce( 'connect' );
|
||||
$settings['wcpay_welcome_page_connect_nonce'] = wp_create_nonce( 'wcpay-connect' );
|
||||
$settings['wc_helper_nonces'] = array(
|
||||
'refresh' => wp_create_nonce( 'refresh' ),
|
||||
);
|
||||
|
||||
$settings['features'] = $this->get_features();
|
||||
|
||||
$settings['isWooPayEligible'] = WCPayPromotionInit::is_woopay_eligible();
|
||||
|
||||
$settings['gutenberg_version'] = defined( 'GUTENBERG_VERSION' ) ? constant( 'GUTENBERG_VERSION' ) : 0;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -442,15 +442,16 @@ class WCAdminAssets {
|
||||
* @param string $script_path_name The script path name.
|
||||
* @param string $script_name Filename of the script to load.
|
||||
* @param bool $need_translation Whether the script need translations.
|
||||
* @param array $dependencies Array of any extra dependencies. Note wc-admin and any application JS dependencies are automatically added by Dependency Extraction Webpack Plugin. Use this parameter to designate any extra dependencies.
|
||||
*/
|
||||
public static function register_script( $script_path_name, $script_name, $need_translation = false ) {
|
||||
public static function register_script( $script_path_name, $script_name, $need_translation = false, $dependencies = array() ) {
|
||||
$script_assets_filename = self::get_script_asset_filename( $script_path_name, $script_name );
|
||||
$script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/' . $script_assets_filename;
|
||||
|
||||
wp_enqueue_script(
|
||||
'wc-admin-' . $script_name,
|
||||
self::get_url( $script_path_name . '/' . $script_name, 'js' ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'] ),
|
||||
array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'], $dependencies ),
|
||||
self::get_file_version( 'js' ),
|
||||
true
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\WooCommercePayments;
|
||||
use Automattic\WooCommerce\Admin\WCAdminHelper;
|
||||
use Automattic\WooCommerce\Admin\PageController;
|
||||
use WC_Abstract_Order;
|
||||
|
||||
/**
|
||||
* Class WCPayWelcomePage
|
||||
@@ -12,8 +12,9 @@ use Automattic\WooCommerce\Admin\PageController;
|
||||
* @package Automattic\WooCommerce\Admin\Features
|
||||
*/
|
||||
class WcPayWelcomePage {
|
||||
const CACHE_TRANSIENT_NAME = 'wcpay_welcome_page_incentive';
|
||||
const HAD_WCPAY_OPTION_NAME = 'wcpay_was_in_use';
|
||||
const CACHE_TRANSIENT_NAME = 'wcpay_welcome_page_incentive';
|
||||
const HAS_ORDERS_TRANSIENT_NAME = 'wcpay_incentive_store_has_orders';
|
||||
const HAD_WCPAY_OPTION_NAME = 'wcpay_was_in_use';
|
||||
|
||||
/**
|
||||
* Plugin instance.
|
||||
@@ -275,6 +276,58 @@ class WcPayWelcomePage {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store has any paid orders.
|
||||
*
|
||||
* Currently, we look at the past 90 days and only consider orders
|
||||
* with status `wc-completed`, `wc-processing`, or `wc-refunded`.
|
||||
*
|
||||
* @return boolean Whether the store has any paid orders.
|
||||
*/
|
||||
private function has_orders(): bool {
|
||||
// First, get the stored value, if it exists.
|
||||
// This way we avoid costly DB queries and API calls.
|
||||
$has_orders = get_transient( self::HAS_ORDERS_TRANSIENT_NAME );
|
||||
if ( false !== $has_orders ) {
|
||||
return 'yes' === $has_orders;
|
||||
}
|
||||
|
||||
// We need to determine the value.
|
||||
// Start with the assumption that the store doesn't have orders in the timeframe we look at.
|
||||
$has_orders = false;
|
||||
// By default, we will check for new orders every 6 hours.
|
||||
$expiration = 6 * HOUR_IN_SECONDS;
|
||||
|
||||
// Get the latest completed, processing, or refunded order.
|
||||
$latest_order = wc_get_orders(
|
||||
array(
|
||||
'status' => array( 'wc-completed', 'wc-processing', 'wc-refunded' ),
|
||||
'limit' => 1,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
if ( ! empty( $latest_order ) ) {
|
||||
$latest_order = reset( $latest_order );
|
||||
// If the latest order is within the timeframe we look at, we consider the store to have orders.
|
||||
// Otherwise, it clearly doesn't have orders.
|
||||
if ( $latest_order instanceof WC_Abstract_Order
|
||||
&& strtotime( $latest_order->get_date_created() ) >= strtotime( '-90 days' ) ) {
|
||||
|
||||
$has_orders = true;
|
||||
|
||||
// For ultimate efficiency, we will check again after 90 days from the latest order
|
||||
// because in all that time we will consider the store to have orders regardless of new orders.
|
||||
$expiration = strtotime( $latest_order->get_date_created() ) + 90 * DAY_IN_SECONDS - time();
|
||||
}
|
||||
}
|
||||
|
||||
// Store the value for future use.
|
||||
set_transient( self::HAS_ORDERS_TRANSIENT_NAME, $has_orders ? 'yes' : 'no', $expiration );
|
||||
|
||||
return $has_orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current incentive has been manually dismissed.
|
||||
*
|
||||
@@ -331,19 +384,9 @@ class WcPayWelcomePage {
|
||||
'country' => WC()->countries->get_base_country(),
|
||||
// Store locale, e.g. `en_US`.
|
||||
'locale' => get_locale(),
|
||||
// WooCommerce active for duration in seconds.
|
||||
// WooCommerce store active for duration in seconds.
|
||||
'active_for' => WCAdminHelper::get_wcadmin_active_for_in_seconds(),
|
||||
// Whether the store has paid orders in the last 90 days.
|
||||
'has_orders' => ! empty(
|
||||
wc_get_orders(
|
||||
[
|
||||
'status' => [ 'wc-completed', 'wc-processing' ],
|
||||
'date_created' => '>=' . strtotime( '-90 days' ),
|
||||
'return' => 'ids',
|
||||
'limit' => 1,
|
||||
]
|
||||
)
|
||||
),
|
||||
'has_orders' => $this->has_orders(),
|
||||
// Whether the store has at least one payment gateway enabled.
|
||||
'has_payments' => ! empty( WC()->payment_gateways()->get_available_payment_gateways() ),
|
||||
'has_wcpay' => $this->has_wcpay(),
|
||||
|
||||
@@ -293,7 +293,7 @@ class CustomOrdersTableController {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option || 'no' === $value ) {
|
||||
if ( self::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION !== $option ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -306,13 +306,11 @@ class CustomOrdersTableController {
|
||||
if ( ! $tables_created ) {
|
||||
return 'no';
|
||||
}
|
||||
/**
|
||||
* Re-enable the following code once the COT to posts table sync is implemented (it's currently commented out to ease testing).
|
||||
|
||||
$sync_is_pending = 0 !== $this->data_synchronizer->get_current_orders_pending_sync_count();
|
||||
if ( $sync_is_pending ) {
|
||||
if ( $sync_is_pending && ! $this->changing_data_source_with_sync_pending_is_allowed() ) {
|
||||
throw new \Exception( "The authoritative table for orders storage can't be changed while there are orders out of sync" );
|
||||
}
|
||||
*/
|
||||
|
||||
return $value;
|
||||
}
|
||||
@@ -430,14 +428,13 @@ class CustomOrdersTableController {
|
||||
|
||||
$get_disabled = function() {
|
||||
$plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
$sync_status = $this->data_synchronizer->get_sync_status();
|
||||
$sync_complete = 0 === $sync_status['current_pending_count'];
|
||||
$sync_complete = 0 === $this->get_orders_pending_sync_count();
|
||||
$disabled = array();
|
||||
// Changing something here? might also want to look at `enable|disable` functions in CLIRunner.
|
||||
if ( count( array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] ) ) > 0 ) {
|
||||
$disabled = array( 'yes' );
|
||||
}
|
||||
if ( ! $sync_complete ) {
|
||||
if ( ! $sync_complete && ! $this->changing_data_source_with_sync_pending_is_allowed() ) {
|
||||
$disabled = array( 'yes', 'no' );
|
||||
}
|
||||
|
||||
@@ -475,22 +472,40 @@ class CustomOrdersTableController {
|
||||
};
|
||||
|
||||
$get_sync_message = function() {
|
||||
$sync_status = $this->data_synchronizer->get_sync_status();
|
||||
$sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) );
|
||||
$sync_enabled = $this->data_synchronizer->data_sync_is_enabled();
|
||||
$sync_message = array();
|
||||
$orders_pending_sync_count = $this->get_orders_pending_sync_count();
|
||||
$sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) );
|
||||
$sync_enabled = $this->data_synchronizer->data_sync_is_enabled();
|
||||
$sync_is_pending = $orders_pending_sync_count > 0;
|
||||
$sync_message = array();
|
||||
|
||||
$is_dangerous = $sync_is_pending && $this->changing_data_source_with_sync_pending_is_allowed();
|
||||
|
||||
if ( $is_dangerous ) {
|
||||
$sync_message[] = wp_kses_data(
|
||||
sprintf(
|
||||
// translators: %d: number of pending orders.
|
||||
_n(
|
||||
"There's %d order pending sync. <b>Switching data storage while sync is incomplete is dangerous and can lead to order data corruption or loss!</b>",
|
||||
'There are %d orders pending sync. <b>Switching data storage while sync is incomplete is dangerous and can lead to order data corruption or loss!</b>',
|
||||
$orders_pending_sync_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$orders_pending_sync_count,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $sync_enabled && $this->data_synchronizer->background_sync_is_enabled() ) {
|
||||
$sync_message[] = __( 'Background sync is enabled.', 'woocommerce' );
|
||||
}
|
||||
|
||||
if ( $sync_in_progress && $sync_status['current_pending_count'] > 0 ) {
|
||||
if ( $sync_in_progress && $sync_is_pending ) {
|
||||
$sync_message[] = sprintf(
|
||||
// translators: %d: number of pending orders.
|
||||
__( 'Currently syncing orders... %d pending', 'woocommerce' ),
|
||||
$sync_status['current_pending_count']
|
||||
$orders_pending_sync_count
|
||||
);
|
||||
} elseif ( $sync_status['current_pending_count'] > 0 ) {
|
||||
} elseif ( $sync_is_pending ) {
|
||||
$sync_now_url = add_query_arg(
|
||||
array(
|
||||
self::SYNC_QUERY_ARG => true,
|
||||
@@ -498,12 +513,20 @@ class CustomOrdersTableController {
|
||||
wc_get_container()->get( FeaturesController::class )->get_features_page_url()
|
||||
);
|
||||
|
||||
$sync_message[] = wp_kses_data(
|
||||
__(
|
||||
'You can switch order data storage <strong>only when the posts and orders tables are in sync</strong>.',
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
if ( ! $is_dangerous ) {
|
||||
$sync_message[] = wp_kses_data(
|
||||
sprintf(
|
||||
// translators: %d: number of pending orders.
|
||||
_n(
|
||||
"There's %d order pending sync. You can switch order data storage <strong>only when the posts and orders tables are in sync</strong>.",
|
||||
'There are %d orders pending sync. You can switch order data storage <strong>only when the posts and orders tables are in sync</strong>.',
|
||||
$orders_pending_sync_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$orders_pending_sync_count
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$sync_message[] = sprintf(
|
||||
'<a href="%1$s" class="button button-link">%2$s</a>',
|
||||
@@ -513,10 +536,10 @@ class CustomOrdersTableController {
|
||||
_n(
|
||||
'Sync %s pending order',
|
||||
'Sync %s pending orders',
|
||||
$sync_status['current_pending_count'],
|
||||
$orders_pending_sync_count,
|
||||
'woocommerce'
|
||||
),
|
||||
number_format_i18n( $sync_status['current_pending_count'] )
|
||||
number_format_i18n( $orders_pending_sync_count )
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -524,14 +547,50 @@ class CustomOrdersTableController {
|
||||
return implode( '<br />', $sync_message );
|
||||
};
|
||||
|
||||
$get_description_is_error = function() {
|
||||
$sync_is_pending = $this->get_orders_pending_sync_count() > 0;
|
||||
|
||||
return $sync_is_pending && $this->changing_data_source_with_sync_pending_is_allowed();
|
||||
};
|
||||
|
||||
return array(
|
||||
'id' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
|
||||
'title' => '',
|
||||
'type' => 'checkbox',
|
||||
'desc' => __( 'Enable compatibility mode (synchronizes orders to the posts table).', 'woocommerce' ),
|
||||
'value' => $get_value,
|
||||
'desc_tip' => $get_sync_message,
|
||||
'row_class' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
|
||||
'id' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
|
||||
'title' => '',
|
||||
'type' => 'checkbox',
|
||||
'desc' => __( 'Enable compatibility mode (synchronizes orders to the posts table).', 'woocommerce' ),
|
||||
'value' => $get_value,
|
||||
'desc_tip' => $get_sync_message,
|
||||
'description_is_error' => $get_description_is_error,
|
||||
'row_class' => DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value indicating if changing the authoritative data source for orders while there are orders pending synchronization is allowed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function changing_data_source_with_sync_pending_is_allowed(): bool {
|
||||
/**
|
||||
* Filter to allow changing where order data is stored, even when there are orders pending synchronization.
|
||||
*
|
||||
* DANGER! This filter is intended for usage when doing manual and automated testing in development environments only,
|
||||
* it should NEVER be used in production environments. Order data corruption or loss can happen!
|
||||
*
|
||||
* @param bool $allow True to allow changing order storage when there are orders pending synchronization, false to disallow.
|
||||
* @returns bool
|
||||
*
|
||||
* @since 8.3.0
|
||||
*/
|
||||
return apply_filters( 'wc_allow_changing_orders_storage_while_sync_is_pending', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of orders pending synchronization.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_orders_pending_sync_count(): int {
|
||||
return $this->data_synchronizer->get_sync_status()['current_pending_count'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,8 @@ class OrdersTableSearchQuery {
|
||||
* @param OrdersTableQuery $query The order query object.
|
||||
*/
|
||||
public function __construct( OrdersTableQuery $query ) {
|
||||
global $wpdb;
|
||||
$this->query = $query;
|
||||
$this->search_term = esc_sql( '%' . $wpdb->esc_like( urldecode( $query->get( 's' ) ) ) . '%' );
|
||||
$this->query = $query;
|
||||
$this->search_term = urldecode( $query->get( 's' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +80,7 @@ class OrdersTableSearchQuery {
|
||||
private function generate_where(): string {
|
||||
global $wpdb;
|
||||
$where = '';
|
||||
$possible_order_id = (string) absint( $this->query->get( 's' ) );
|
||||
$possible_order_id = (string) absint( $this->search_term );
|
||||
$order_table = $this->query->get_table_name( 'orders' );
|
||||
|
||||
// Support the passing of an order ID as the search term.
|
||||
@@ -96,7 +95,7 @@ class OrdersTableSearchQuery {
|
||||
search_query_items.order_item_name LIKE %s
|
||||
OR `$order_table`.id IN ( $meta_sub_query )
|
||||
",
|
||||
$this->search_term
|
||||
'%' . $wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
|
||||
return " ( $where ) ";
|
||||
@@ -123,7 +122,7 @@ WHERE search_query_meta.meta_key IN ( $meta_fields )
|
||||
AND search_query_meta.meta_value LIKE %s
|
||||
GROUP BY search_query_meta.order_id
|
||||
",
|
||||
$this->search_term
|
||||
'%' . $wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\PageController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ FileController, ListTable };
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
* LoggingServiceProvider class.
|
||||
*/
|
||||
class LoggingServiceProvider extends AbstractServiceProvider {
|
||||
/**
|
||||
* List services provided by this class.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $provides = array(
|
||||
FileController::class,
|
||||
PageController::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Registers services provided by this class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$this->share( FileController::class );
|
||||
|
||||
$this->share( PageController::class )->addArguments(
|
||||
array(
|
||||
FileController::class,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ namespace Automattic\WooCommerce\Internal\Features;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Analytics;
|
||||
use Automattic\WooCommerce\Admin\Features\Navigation\Init;
|
||||
use Automattic\WooCommerce\Admin\Features\NewProductManagementExperience;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
@@ -688,19 +686,21 @@ class FeaturesController {
|
||||
'id' => empty( $experimental_feature_ids ) ? 'features_options' : 'experimental_features_options',
|
||||
);
|
||||
|
||||
// Allow feature setting properties to be determined dynamically just before being rendered.
|
||||
$feature_settings = array_map(
|
||||
function( $feature_setting ) {
|
||||
foreach ( $feature_setting as $prop => $value ) {
|
||||
if ( is_callable( $value ) ) {
|
||||
$feature_setting[ $prop ] = call_user_func( $value );
|
||||
if ( $this->verify_did_woocommerce_init() ) {
|
||||
// Allow feature setting properties to be determined dynamically just before being rendered.
|
||||
$feature_settings = array_map(
|
||||
function( $feature_setting ) {
|
||||
foreach ( $feature_setting as $prop => $value ) {
|
||||
if ( is_callable( $value ) ) {
|
||||
$feature_setting[ $prop ] = call_user_func( $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $feature_setting;
|
||||
},
|
||||
$feature_settings
|
||||
);
|
||||
return $feature_setting;
|
||||
},
|
||||
$feature_settings
|
||||
);
|
||||
}
|
||||
|
||||
return $feature_settings;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\GroupInterface;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\SectionInterface;
|
||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlockTemplate;
|
||||
|
||||
/**
|
||||
* Block template class.
|
||||
*/
|
||||
abstract class AbstractProductFormTemplate extends AbstractBlockTemplate implements ProductFormTemplateInterface {
|
||||
/**
|
||||
* Get the template area.
|
||||
*/
|
||||
public function get_area(): string {
|
||||
return 'product-form';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a group block by ID.
|
||||
*
|
||||
* @param string $group_id The group block ID.
|
||||
* @throws \UnexpectedValueException If block is not of type GroupInterface.
|
||||
*/
|
||||
public function get_group_by_id( string $group_id ): ?GroupInterface {
|
||||
$group = $this->get_block( $group_id );
|
||||
if ( $group && ! $group instanceof GroupInterface ) {
|
||||
throw new \UnexpectedValueException( 'Block with specified ID is not a group.' );
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a section block by ID.
|
||||
*
|
||||
* @param string $section_id The section block ID.
|
||||
* @throws \UnexpectedValueException If block is not of type SectionInterface.
|
||||
*/
|
||||
public function get_section_by_id( string $section_id ): ?SectionInterface {
|
||||
$section = $this->get_block( $section_id );
|
||||
if ( $section && ! $section instanceof SectionInterface ) {
|
||||
throw new \UnexpectedValueException( 'Block with specified ID is not a section.' );
|
||||
}
|
||||
return $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a block by ID.
|
||||
*
|
||||
* @param string $block_id The block block ID.
|
||||
*/
|
||||
public function get_block_by_id( string $block_id ): ?BlockInterface {
|
||||
return $this->get_block( $block_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom block type to this template.
|
||||
*
|
||||
* @param array $block_config The block data.
|
||||
*/
|
||||
public function add_group( array $block_config ): GroupInterface {
|
||||
$block = new Group( $block_config, $this->get_root_template(), $this );
|
||||
return $this->add_inner_block( $block );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Group Block class.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\GroupInterface;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\SectionInterface;
|
||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockContainerTrait;
|
||||
|
||||
/**
|
||||
* Class for Group block.
|
||||
*/
|
||||
class Group extends ProductBlock implements GroupInterface {
|
||||
use BlockContainerTrait;
|
||||
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
/**
|
||||
* Group Block constructor.
|
||||
*
|
||||
* @param array $config The block configuration.
|
||||
* @param BlockTemplateInterface $root_template The block template that this block belongs to.
|
||||
* @param ContainerInterface|null $parent The parent block container.
|
||||
*
|
||||
* @throws \ValueError If the block configuration is invalid.
|
||||
* @throws \ValueError If the parent block container does not belong to the same template as the block.
|
||||
* @throws \InvalidArgumentException If blockName key and value are passed into block configuration.
|
||||
*/
|
||||
public function __construct( array $config, BlockTemplateInterface &$root_template, ContainerInterface &$parent = null ) {
|
||||
if ( ! empty( $config['blockName'] ) ) {
|
||||
throw new \InvalidArgumentException( 'Unexpected key "blockName", this defaults to "woocommerce/product-tab".' );
|
||||
}
|
||||
if ( $config['id'] && ( empty( $config['attributes'] ) || empty( $config['attributes']['id'] ) ) ) {
|
||||
$config['attributes'] = empty( $config['attributes'] ) ? array() : $config['attributes'];
|
||||
$config['attributes']['id'] = $config['id'];
|
||||
}
|
||||
parent::__construct( array_merge( array( 'blockName' => 'woocommerce/product-tab' ), $config ), $root_template, $parent );
|
||||
}
|
||||
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
|
||||
/**
|
||||
* Add a section block type to this template.
|
||||
*
|
||||
* @param array $block_config The block data.
|
||||
*/
|
||||
public function add_section( array $block_config ): SectionInterface {
|
||||
$block = new Section( $block_config, $this->get_root_template(), $this );
|
||||
return $this->add_inner_block( $block );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Product Block class.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
|
||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\AbstractBlock;
|
||||
use Automattic\WooCommerce\Internal\Admin\BlockTemplates\BlockContainerTrait;
|
||||
|
||||
/**
|
||||
* Class for Product block.
|
||||
*/
|
||||
class ProductBlock extends AbstractBlock implements ContainerInterface {
|
||||
use BlockContainerTrait;
|
||||
/**
|
||||
* Adds block to the section block.
|
||||
*
|
||||
* @param array $block_config The block data.
|
||||
*/
|
||||
public function &add_block( array $block_config ): BlockInterface {
|
||||
$block = new ProductBlock( $block_config, $this->get_root_template(), $this );
|
||||
return $this->add_inner_block( $block );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
/**
|
||||
* ProductVariationTemplate
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface;
|
||||
|
||||
/**
|
||||
* Simple Product Template.
|
||||
*/
|
||||
class ProductVariationTemplate extends AbstractProductFormTemplate implements ProductFormTemplateInterface {
|
||||
/**
|
||||
* The context name used to identify the editor.
|
||||
*/
|
||||
const GROUP_IDS = array(
|
||||
'GENERAL' => 'general',
|
||||
'PRICING' => 'pricing',
|
||||
'INVENTORY' => 'inventory',
|
||||
'SHIPPING' => 'shipping',
|
||||
);
|
||||
|
||||
/**
|
||||
* The option name used check whether the single variation notice has been dismissed.
|
||||
*/
|
||||
const SINGLE_VARIATION_NOTICE_DISMISSED_OPTION = 'woocommerce_single_variation_notice_dismissed';
|
||||
|
||||
/**
|
||||
* SimpleProductTemplate constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_group_blocks();
|
||||
$this->add_general_group_blocks();
|
||||
$this->add_pricing_group_blocks();
|
||||
$this->add_inventory_group_blocks();
|
||||
$this->add_shipping_group_blocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template ID.
|
||||
*/
|
||||
public function get_id(): string {
|
||||
return 'product-variation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template title.
|
||||
*/
|
||||
public function get_title(): string {
|
||||
return __( 'Product Variation Template', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template description.
|
||||
*/
|
||||
public function get_description(): string {
|
||||
return __( 'Template for the product variation form', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the group blocks to the template.
|
||||
*/
|
||||
private function add_group_blocks() {
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['GENERAL'],
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'General', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['PRICING'],
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Pricing', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['INVENTORY'],
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Inventory', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['SHIPPING'],
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Shipping', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the general group blocks to the template.
|
||||
*/
|
||||
private function add_general_group_blocks() {
|
||||
$general_group = $this->get_group_by_id( $this::GROUP_IDS['GENERAL'] );
|
||||
$general_group->add_block(
|
||||
array(
|
||||
'id' => 'general-single-variation-notice',
|
||||
'blockName' => 'woocommerce/product-single-variation-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( '<strong>You’re editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
'isDismissible' => true,
|
||||
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
|
||||
),
|
||||
)
|
||||
);
|
||||
// Basic Details Section.
|
||||
$basic_details = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-details-section',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Variation details', 'woocommerce' ),
|
||||
'description' => __( 'This info will be displayed on the product page, category pages, social media, and search results.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-note',
|
||||
'blockName' => 'woocommerce/product-summary-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'property' => 'description',
|
||||
'label' => __( 'Note <optional />', 'woocommerce' ),
|
||||
'helpText' => 'Enter an optional note displayed on the product page when customers select this variation.',
|
||||
),
|
||||
)
|
||||
);
|
||||
$basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-visibility',
|
||||
'blockName' => 'woocommerce/product-checkbox-field',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'property' => 'status',
|
||||
'label' => __( 'Hide in product catalog', 'woocommerce' ),
|
||||
'checkedValue' => 'private',
|
||||
'uncheckedValue' => 'publish',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Images section.
|
||||
$images_section = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-images-section',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Image', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag. */
|
||||
__( 'Drag images, upload new ones or select files from your library. For best results, use JPEG files that are 1000 by 1000 pixels or larger. %1$sHow to prepare images?%2$s', 'woocommerce' ),
|
||||
'<a href="https://woo.com/posts/how-to-take-professional-product-photos-top-tips" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$images_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-image',
|
||||
'blockName' => 'woocommerce/product-images-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'image',
|
||||
'multiple' => false,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Downloads section.
|
||||
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
|
||||
$general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-downloads-section',
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Downloads', 'woocommerce' ),
|
||||
'description' => __( "Add any files you'd like to make available for the customer to download after purchasing, such as instructions or warranty info.", 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-downloads',
|
||||
'blockName' => 'woocommerce/product-downloads-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the pricing group blocks to the template.
|
||||
*/
|
||||
private function add_pricing_group_blocks() {
|
||||
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
|
||||
$pricing_group->add_block(
|
||||
array(
|
||||
'id' => 'pricing-single-variation-notice',
|
||||
'blockName' => 'woocommerce/product-single-variation-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( '<strong>You’re editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
'isDismissible' => true,
|
||||
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
|
||||
),
|
||||
)
|
||||
);
|
||||
// Product Pricing Section.
|
||||
$product_pricing_section = $pricing_group->add_section(
|
||||
array(
|
||||
'id' => 'product-pricing-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Pricing', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag.*/
|
||||
__( 'Set a competitive price, put the product on sale, and manage tax calculations. %1$sHow to price your product?%2$s', 'woocommerce' ),
|
||||
'<a href="https://woo.com/posts/how-to-price-products-strategies-expert-tips/" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'blockGap' => 'unit-40',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_columns = $product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-group-pricing-columns',
|
||||
'blockName' => 'core/columns',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$pricing_column_1 = $pricing_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-group-pricing-column-1',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'templateLock' => 'all',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_1->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-regular-price',
|
||||
'blockName' => 'woocommerce/product-regular-price-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'name' => 'regular_price',
|
||||
'label' => __( 'Regular price', 'woocommerce' ),
|
||||
'isRequired' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_2 = $pricing_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-group-pricing-column-2',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'templateLock' => 'all',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_2->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-sale-price',
|
||||
'blockName' => 'woocommerce/product-sale-price-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Sale price', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-schedule-sale-fields',
|
||||
'blockName' => 'woocommerce/product-schedule-sale-fields',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
|
||||
$product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-tax-class',
|
||||
'blockName' => 'woocommerce/product-radio-field',
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Tax class', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
|
||||
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
|
||||
'<a href="https://woo.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'property' => 'tax_class',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => __( 'Same as main product', 'woocommerce' ),
|
||||
'value' => 'parent',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Standard', 'woocommerce' ),
|
||||
'value' => '',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Reduced rate', 'woocommerce' ),
|
||||
'value' => 'reduced-rate',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Zero rate', 'woocommerce' ),
|
||||
'value' => 'zero-rate',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inventory group blocks to the template.
|
||||
*/
|
||||
private function add_inventory_group_blocks() {
|
||||
$inventory_group = $this->get_group_by_id( $this::GROUP_IDS['INVENTORY'] );
|
||||
$inventory_group->add_block(
|
||||
array(
|
||||
'id' => 'inventory-single-variation-notice',
|
||||
'blockName' => 'woocommerce/product-single-variation-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( '<strong>You’re editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
'isDismissible' => true,
|
||||
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
|
||||
),
|
||||
)
|
||||
);
|
||||
// Product Inventory Section.
|
||||
$product_inventory_section = $inventory_group->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-inventory-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Inventory', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Inventory settings link opening tag. %2$s: Inventory settings link closing tag.*/
|
||||
__( 'Set up and manage inventory for this product, including status and available quantity. %1$sManage store inventory settings%2$s', 'woocommerce' ),
|
||||
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products§ion=inventory' ) . '" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'blockGap' => 'unit-40',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section = $product_inventory_section->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-inventory-inner-section',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-sku-field',
|
||||
'blockName' => 'woocommerce/product-sku-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-track-stock',
|
||||
'blockName' => 'woocommerce/product-toggle-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Track stock quantity for this product', 'woocommerce' ),
|
||||
'property' => 'manage_stock',
|
||||
'disabled' => 'yes' !== get_option( 'woocommerce_manage_stock' ),
|
||||
'disabledCopy' => sprintf(
|
||||
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
|
||||
__( 'Per your %1$sstore settings%2$s, inventory management is <strong>disabled</strong>.', 'woocommerce' ),
|
||||
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products§ion=inventory' ) . '" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_quantity_conditional = $product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-inventory-quantity-conditional-wrapper',
|
||||
'blockName' => 'woocommerce/conditional',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'mustMatch' => array(
|
||||
'manage_stock' => array( true ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_quantity_conditional->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-inventory-quantity',
|
||||
'blockName' => 'woocommerce/product-inventory-quantity-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_stock_status_conditional = $product_inventory_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-stock-status-conditional-wrapper',
|
||||
'blockName' => 'woocommerce/conditional',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'mustMatch' => array(
|
||||
'manage_stock' => array( false ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_stock_status_conditional->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-stock-status',
|
||||
'blockName' => 'woocommerce/product-radio-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Stock status', 'woocommerce' ),
|
||||
'property' => 'stock_status',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => __( 'In stock', 'woocommerce' ),
|
||||
'value' => 'instock',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Out of stock', 'woocommerce' ),
|
||||
'value' => 'outofstock',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'On backorder', 'woocommerce' ),
|
||||
'value' => 'onbackorder',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the shipping group blocks to the template.
|
||||
*/
|
||||
private function add_shipping_group_blocks() {
|
||||
$shipping_group = $this->get_group_by_id( $this::GROUP_IDS['SHIPPING'] );
|
||||
$shipping_group->add_block(
|
||||
array(
|
||||
'id' => 'shipping-single-variation-notice',
|
||||
'blockName' => 'woocommerce/product-single-variation-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( '<strong>You’re editing details specific to this variation.</strong> Some information, like description and images, will be inherited from the main product, <noticeLink><parentProductName/></noticeLink>.', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
'isDismissible' => true,
|
||||
'name' => $this::SINGLE_VARIATION_NOTICE_DISMISSED_OPTION,
|
||||
),
|
||||
)
|
||||
);
|
||||
// Virtual section.
|
||||
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
|
||||
$shipping_group->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-virtual-section',
|
||||
'order' => 20,
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-virtual',
|
||||
'blockName' => 'woocommerce/product-toggle-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'virtual',
|
||||
'checkedValue' => false,
|
||||
'uncheckedValue' => true,
|
||||
'label' => __( 'This variation requires shipping or pickup', 'woocommerce' ),
|
||||
'uncheckedHelp' => __( 'This variation will not trigger your customer\'s shipping calculator in cart or at checkout. This product also won\'t require your customers to enter their shipping details at checkout. <a href="https://woo.com/document/managing-products/#adding-a-virtual-product" target="_blank" rel="noreferrer">Read more about virtual products</a>.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
// Product Shipping Section.
|
||||
$product_fee_and_dimensions_section = $shipping_group->add_section(
|
||||
array(
|
||||
'id' => 'product-variation-fee-and-dimensions-section',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Fees & dimensions', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/
|
||||
__( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ),
|
||||
'<a href="https://woo.com/posts/how-to-calculate-shipping-costs-for-your-woocommerce-store/" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_fee_and_dimensions_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-shipping-class',
|
||||
'blockName' => 'woocommerce/product-shipping-class-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_fee_and_dimensions_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-shipping-dimensions',
|
||||
'blockName' => 'woocommerce/product-shipping-dimensions-fields',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Section Block class.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
|
||||
use Automattic\WooCommerce\Admin\BlockTemplates\ContainerInterface;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\SectionInterface;
|
||||
|
||||
/**
|
||||
* Class for Section block.
|
||||
*/
|
||||
class Section extends ProductBlock implements SectionInterface {
|
||||
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
/**
|
||||
* Section Block constructor.
|
||||
*
|
||||
* @param array $config The block configuration.
|
||||
* @param BlockTemplateInterface $root_template The block template that this block belongs to.
|
||||
* @param ContainerInterface|null $parent The parent block container.
|
||||
*
|
||||
* @throws \ValueError If the block configuration is invalid.
|
||||
* @throws \ValueError If the parent block container does not belong to the same template as the block.
|
||||
* @throws \InvalidArgumentException If blockName key and value are passed into block configuration.
|
||||
*/
|
||||
public function __construct( array $config, BlockTemplateInterface &$root_template, ContainerInterface &$parent = null ) {
|
||||
if ( ! empty( $config['blockName'] ) ) {
|
||||
throw new \InvalidArgumentException( 'Unexpected key "blockName", this defaults to "woocommerce/product-section".' );
|
||||
}
|
||||
parent::__construct( array_merge( array( 'blockName' => 'woocommerce/product-section' ), $config ), $root_template, $parent );
|
||||
}
|
||||
// phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
|
||||
/**
|
||||
* Add a section block type to this template.
|
||||
*
|
||||
* @param array $block_config The block data.
|
||||
*/
|
||||
public function add_section( array $block_config ): SectionInterface {
|
||||
$block = new Section( $block_config, $this->get_root_template(), $this );
|
||||
return $this->add_inner_block( $block );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,984 @@
|
||||
<?php
|
||||
/**
|
||||
* SimpleProductTemplate
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Features\ProductBlockEditor\ProductTemplates;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface;
|
||||
|
||||
/**
|
||||
* Simple Product Template.
|
||||
*/
|
||||
class SimpleProductTemplate extends AbstractProductFormTemplate implements ProductFormTemplateInterface {
|
||||
/**
|
||||
* The context name used to identify the editor.
|
||||
*/
|
||||
const GROUP_IDS = array(
|
||||
'GENERAL' => 'general',
|
||||
'ORGANIZATION' => 'organization',
|
||||
'PRICING' => 'pricing',
|
||||
'INVENTORY' => 'inventory',
|
||||
'SHIPPING' => 'shipping',
|
||||
'VARIATIONS' => 'variations',
|
||||
);
|
||||
|
||||
/**
|
||||
* SimpleProductTemplate constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_group_blocks();
|
||||
$this->add_general_group_blocks();
|
||||
$this->add_organization_group_blocks();
|
||||
$this->add_pricing_group_blocks();
|
||||
$this->add_inventory_group_blocks();
|
||||
$this->add_shipping_group_blocks();
|
||||
$this->add_variation_group_blocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template ID.
|
||||
*/
|
||||
public function get_id(): string {
|
||||
return 'simple-product';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template title.
|
||||
*/
|
||||
public function get_title(): string {
|
||||
return __( 'Simple Product Template', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template description.
|
||||
*/
|
||||
public function get_description(): string {
|
||||
return __( 'Template for the simple product form', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the group blocks to the template.
|
||||
*/
|
||||
private function add_group_blocks() {
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['GENERAL'],
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'General', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['ORGANIZATION'],
|
||||
'order' => 15,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Organization', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['PRICING'],
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Pricing', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['INVENTORY'],
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Inventory', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['SHIPPING'],
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Shipping', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
if ( Features::is_enabled( 'product-variation-management' ) ) {
|
||||
$this->add_group(
|
||||
array(
|
||||
'id' => $this::GROUP_IDS['VARIATIONS'],
|
||||
'order' => 50,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Variations', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the general group blocks to the template.
|
||||
*/
|
||||
private function add_general_group_blocks() {
|
||||
$general_group = $this->get_group_by_id( $this::GROUP_IDS['GENERAL'] );
|
||||
$general_group->add_block(
|
||||
array(
|
||||
'id' => 'product_variation_notice_general_tab',
|
||||
'blockName' => 'woocommerce/product-has-variations-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( 'This product has options, such as size or color. You can manage each variation\'s images, downloads, and other details individually.', 'woocommerce' ),
|
||||
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
),
|
||||
)
|
||||
);
|
||||
// Basic Details Section.
|
||||
$basic_details = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'basic-details',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Basic details', 'woocommerce' ),
|
||||
'description' => __( 'This info will be displayed on the product page, category pages, social media, and search results.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-name',
|
||||
'blockName' => 'woocommerce/product-name-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'name' => 'Product name',
|
||||
'autoFocus' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
$basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-summary',
|
||||
'blockName' => 'woocommerce/product-summary-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'property' => 'short_description',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_columns = $basic_details->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-columns',
|
||||
'blockName' => 'core/columns',
|
||||
'order' => 30,
|
||||
)
|
||||
);
|
||||
$pricing_column_1 = $pricing_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-column-1',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'templateLock' => 'all',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_1->add_block(
|
||||
array(
|
||||
'id' => 'product-regular-price',
|
||||
'blockName' => 'woocommerce/product-regular-price-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'name' => 'regular_price',
|
||||
'label' => __( 'List price', 'woocommerce' ),
|
||||
/* translators: PricingTab: This is a link tag to the pricing tab. */
|
||||
'help' => __( 'Manage more settings in <PricingTab>Pricing.</PricingTab>', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_2 = $pricing_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-column-2',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'templateLock' => 'all',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_2->add_block(
|
||||
array(
|
||||
'id' => 'product-sale-price',
|
||||
'blockName' => 'woocommerce/product-sale-price-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Sale price', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Description section.
|
||||
$description_section = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-description-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Description', 'woocommerce' ),
|
||||
'description' => __( 'What makes this product unique? What are its most important features? Enrich the product page by adding rich content using blocks.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$description_section->add_block(
|
||||
array(
|
||||
'id' => 'product-description',
|
||||
'blockName' => 'woocommerce/product-description-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
|
||||
// External/Affiliate section.
|
||||
if ( Features::is_enabled( 'product-external-affiliate' ) ) {
|
||||
$buy_button_section = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-buy-button-section',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Buy button', 'woocommerce' ),
|
||||
'description' => __( 'Add a link and choose a label for the button linked to a product sold elsewhere.', 'woocommerce' ),
|
||||
),
|
||||
'hideConditions' => array(
|
||||
array(
|
||||
'expression' => 'editedProduct.type !== "external"',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$buy_button_section->add_block(
|
||||
array(
|
||||
'id' => 'product-external-url',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'external_url',
|
||||
'label' => __( 'Link to the external product', 'woocommerce' ),
|
||||
'placeholder' => __( 'Enter the external URL to the product', 'woocommerce' ),
|
||||
'suffix' => true,
|
||||
'type' => array(
|
||||
'value' => 'url',
|
||||
'message' => __( 'Link to the external product is an invalid URL.', 'woocommerce' ),
|
||||
),
|
||||
'required' => __( 'Link to the external product is required.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$button_text_columns = $buy_button_section->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text-columns',
|
||||
'blockName' => 'core/columns',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
|
||||
$button_text_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text-column1',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 10,
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text',
|
||||
'blockName' => 'woocommerce/product-text-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'button_text',
|
||||
'label' => __( 'Buy button text', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$button_text_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-button-text-column2',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Images section.
|
||||
$images_section = $general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-images-section',
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Images', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag. */
|
||||
__( 'Drag images, upload new ones or select files from your library. For best results, use JPEG files that are 1000 by 1000 pixels or larger. %1$sHow to prepare images?%2$s', 'woocommerce' ),
|
||||
'<a href="https://woo.com/posts/how-to-take-professional-product-photos-top-tips" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$images_section->add_block(
|
||||
array(
|
||||
'id' => 'product-images',
|
||||
'blockName' => 'woocommerce/product-images-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'images' => array(),
|
||||
'property' => 'images',
|
||||
),
|
||||
)
|
||||
);
|
||||
// Downloads section.
|
||||
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
|
||||
$general_group->add_section(
|
||||
array(
|
||||
'id' => 'product-downloads-section',
|
||||
'order' => 50,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Downloads', 'woocommerce' ),
|
||||
'description' => __( "Add any files you'd like to make available for the customer to download after purchasing, such as instructions or warranty info.", 'woocommerce' ),
|
||||
),
|
||||
'hideConditions' => array(
|
||||
array(
|
||||
'expression' => 'editedProduct.type !== "simple"',
|
||||
),
|
||||
),
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-downloads',
|
||||
'blockName' => 'woocommerce/product-downloads-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the organization group blocks to the template.
|
||||
*/
|
||||
private function add_organization_group_blocks() {
|
||||
$organization_group = $this->get_group_by_id( $this::GROUP_IDS['ORGANIZATION'] );
|
||||
// Product Catalog Section.
|
||||
$product_catalog_section = $organization_group->add_section(
|
||||
array(
|
||||
'id' => 'product-catalog-section',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Product catalog', 'woocommerce' ),
|
||||
'description' => __( 'Help customers find this product by assigning it to categories, adding extra details, and managing its visibility in your store and other channels.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-categories',
|
||||
'blockName' => 'woocommerce/product-taxonomy-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'slug' => 'product_cat',
|
||||
'property' => 'categories',
|
||||
'label' => __( 'Categories', 'woocommerce' ),
|
||||
'createTitle' => __( 'Create new category', 'woocommerce' ),
|
||||
'dialogNameHelpText' => __( 'Shown to customers on the product page.', 'woocommerce' ),
|
||||
'parentTaxonomyText' => __( 'Parent category', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-tags',
|
||||
'blockName' => 'woocommerce/product-tag-field',
|
||||
'attributes' => array(
|
||||
'name' => 'tags',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-catalog-search-visibility',
|
||||
'blockName' => 'woocommerce/product-catalog-visibility-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Hide in product catalog', 'woocommerce' ),
|
||||
'visibility' => 'search',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-catalog-catalog-visibility',
|
||||
'blockName' => 'woocommerce/product-catalog-visibility-field',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Hide from search results', 'woocommerce' ),
|
||||
'visibility' => 'catalog',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-enable-product-reviews',
|
||||
'blockName' => 'woocommerce/product-checkbox-field',
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Enable product reviews', 'woocommerce' ),
|
||||
'property' => 'reviews_allowed',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-post-password',
|
||||
'blockName' => 'woocommerce/product-password-field',
|
||||
'order' => 50,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Require a password', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
// Attributes section.
|
||||
$product_catalog_section = $organization_group->add_section(
|
||||
array(
|
||||
'id' => 'product-attributes-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Attributes', 'woocommerce' ),
|
||||
'description' => __( 'Add descriptive pieces of information that customers can use to filter and search for this product. <a href="https://woo.com/document/managing-product-taxonomies/#product-attributes" target="_blank" rel="noreferrer">Learn more</a>.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_catalog_section->add_block(
|
||||
array(
|
||||
'id' => 'product-attributes',
|
||||
'blockName' => 'woocommerce/product-attributes-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the pricing group blocks to the template.
|
||||
*/
|
||||
private function add_pricing_group_blocks() {
|
||||
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
|
||||
$pricing_group->add_block(
|
||||
array(
|
||||
'id' => 'pricing-has-variations-notice',
|
||||
'blockName' => 'woocommerce/product-has-variations-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s price and other details individually.', 'woocommerce' ),
|
||||
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
),
|
||||
)
|
||||
);
|
||||
// Product Pricing Section.
|
||||
$product_pricing_section = $pricing_group->add_section(
|
||||
array(
|
||||
'id' => 'product-pricing-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Pricing', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Images guide link opening tag. %2$s: Images guide link closing tag.*/
|
||||
__( 'Set a competitive price, put the product on sale, and manage tax calculations. %1$sHow to price your product?%2$s', 'woocommerce' ),
|
||||
'<a href="https://woo.com/posts/how-to-price-products-strategies-expert-tips/" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'blockGap' => 'unit-40',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_columns = $product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-group-pricing-columns',
|
||||
'blockName' => 'core/columns',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$pricing_column_1 = $pricing_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-group-pricing-column-1',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'templateLock' => 'all',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_1->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-regular-price',
|
||||
'blockName' => 'woocommerce/product-regular-price-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'name' => 'regular_price',
|
||||
'label' => __( 'List price', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_2 = $pricing_columns->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-group-pricing-column-2',
|
||||
'blockName' => 'core/column',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'templateLock' => 'all',
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_column_2->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-sale-price',
|
||||
'blockName' => 'woocommerce/product-sale-price-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Sale price', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-schedule-sale-fields',
|
||||
'blockName' => 'woocommerce/product-schedule-sale-fields',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
$product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-sale-tax',
|
||||
'blockName' => 'woocommerce/product-radio-field',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Charge sales tax on', 'woocommerce' ),
|
||||
'property' => 'tax_status',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => __( 'Product and shipping', 'woocommerce' ),
|
||||
'value' => 'taxable',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Only shipping', 'woocommerce' ),
|
||||
'value' => 'shipping',
|
||||
),
|
||||
array(
|
||||
'label' => __( "Don't charge tax", 'woocommerce' ),
|
||||
'value' => 'none',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_advanced_block = $product_pricing_section->add_block(
|
||||
array(
|
||||
'id' => 'product-pricing-advanced',
|
||||
'blockName' => 'woocommerce/product-collapsible',
|
||||
'order' => 40,
|
||||
'attributes' => array(
|
||||
'toggleText' => __( 'Advanced', 'woocommerce' ),
|
||||
'initialCollapsed' => true,
|
||||
'persistRender' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
$pricing_advanced_block->add_block(
|
||||
array(
|
||||
'id' => 'product-tax-class',
|
||||
'blockName' => 'woocommerce/product-radio-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Tax class', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
|
||||
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
|
||||
'<a href="https://woo.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'property' => 'tax_class',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => __( 'Standard', 'woocommerce' ),
|
||||
'value' => '',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Reduced rate', 'woocommerce' ),
|
||||
'value' => 'reduced-rate',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Zero rate', 'woocommerce' ),
|
||||
'value' => 'zero-rate',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the inventory group blocks to the template.
|
||||
*/
|
||||
private function add_inventory_group_blocks() {
|
||||
$inventory_group = $this->get_group_by_id( $this::GROUP_IDS['INVENTORY'] );
|
||||
$inventory_group->add_block(
|
||||
array(
|
||||
'id' => 'product_variation_notice_inventory_tab',
|
||||
'blockName' => 'woocommerce/product-has-variations-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s inventory and other details individually.', 'woocommerce' ),
|
||||
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
),
|
||||
)
|
||||
);
|
||||
// Product Pricing Section.
|
||||
$product_inventory_section = $inventory_group->add_section(
|
||||
array(
|
||||
'id' => 'product-inventory-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Inventory', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Inventory settings link opening tag. %2$s: Inventory settings link closing tag.*/
|
||||
__( 'Set up and manage inventory for this product, including status and available quantity. %1$sManage store inventory settings%2$s', 'woocommerce' ),
|
||||
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products§ion=inventory' ) . '" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
'blockGap' => 'unit-40',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section = $product_inventory_section->add_section(
|
||||
array(
|
||||
'id' => 'product-inventory-inner-section',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-sku-field',
|
||||
'blockName' => 'woocommerce/product-sku-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-track-stock',
|
||||
'blockName' => 'woocommerce/product-toggle-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'label' => __( 'Track stock quantity for this product', 'woocommerce' ),
|
||||
'property' => 'manage_stock',
|
||||
'disabled' => 'yes' !== get_option( 'woocommerce_manage_stock' ),
|
||||
'disabledCopy' => sprintf(
|
||||
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
|
||||
__( 'Per your %1$sstore settings%2$s, inventory management is <strong>disabled</strong>.', 'woocommerce' ),
|
||||
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=products§ion=inventory' ) . '" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
),
|
||||
'hideConditions' => Features::is_enabled( 'product-external-affiliate' ) ? array(
|
||||
array(
|
||||
'expression' => 'editedProduct.type === "external"',
|
||||
),
|
||||
) : null,
|
||||
)
|
||||
);
|
||||
$product_inventory_quantity_conditional = $product_inventory_inner_section->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-quantity-conditional-wrapper',
|
||||
'blockName' => 'woocommerce/conditional',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'mustMatch' => array(
|
||||
'manage_stock' => array( true ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_quantity_conditional->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-quantity',
|
||||
'blockName' => 'woocommerce/product-inventory-quantity-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_stock_status_conditional = $product_inventory_section->add_block(
|
||||
array(
|
||||
'id' => 'product-stock-status-conditional-wrapper',
|
||||
'blockName' => 'woocommerce/conditional',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'mustMatch' => array(
|
||||
'manage_stock' => array( false ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_stock_status_conditional->add_block(
|
||||
array(
|
||||
'id' => 'product-stock-status',
|
||||
'blockName' => 'woocommerce/product-radio-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Stock status', 'woocommerce' ),
|
||||
'property' => 'stock_status',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => __( 'In stock', 'woocommerce' ),
|
||||
'value' => 'instock',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'Out of stock', 'woocommerce' ),
|
||||
'value' => 'outofstock',
|
||||
),
|
||||
array(
|
||||
'label' => __( 'On backorder', 'woocommerce' ),
|
||||
'value' => 'onbackorder',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_advanced = $product_inventory_section->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-advanced',
|
||||
'blockName' => 'woocommerce/product-collapsible',
|
||||
'order' => 30,
|
||||
'attributes' => array(
|
||||
'toggleText' => __( 'Advanced', 'woocommerce' ),
|
||||
'initialCollapsed' => true,
|
||||
'persistRender' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_inventory_advanced_wrapper = $product_inventory_advanced->add_block(
|
||||
array(
|
||||
'blockName' => 'woocommerce/product-section',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'blockGap' => 'unit-40',
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_out_of_stock_conditional = $product_inventory_advanced_wrapper->add_block(
|
||||
array(
|
||||
'id' => 'product-out-of-stock-conditional-wrapper',
|
||||
'blockName' => 'woocommerce/conditional',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'mustMatch' => array(
|
||||
'manage_stock' => array( true ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_out_of_stock_conditional->add_block(
|
||||
array(
|
||||
'id' => 'product-out-of-stock',
|
||||
'blockName' => 'woocommerce/product-radio-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'When out of stock', 'woocommerce' ),
|
||||
'property' => 'backorders',
|
||||
'options' => array(
|
||||
array(
|
||||
'label' => __( 'Allow purchases', 'woocommerce' ),
|
||||
'value' => 'yes',
|
||||
),
|
||||
array(
|
||||
'label' => __(
|
||||
'Allow purchases, but notify customers',
|
||||
'woocommerce'
|
||||
),
|
||||
'value' => 'notify',
|
||||
),
|
||||
array(
|
||||
'label' => __( "Don't allow purchases", 'woocommerce' ),
|
||||
'value' => 'no',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_out_of_stock_conditional->add_block(
|
||||
array(
|
||||
'id' => 'product-inventory-email',
|
||||
'blockName' => 'woocommerce/product-inventory-email-field',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
|
||||
$product_inventory_advanced_wrapper->add_block(
|
||||
array(
|
||||
'id' => 'product-limit-purchase',
|
||||
'blockName' => 'woocommerce/product-checkbox-field',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __(
|
||||
'Restrictions',
|
||||
'woocommerce'
|
||||
),
|
||||
'label' => __(
|
||||
'Limit purchases to 1 item per order',
|
||||
'woocommerce'
|
||||
),
|
||||
'property' => 'sold_individually',
|
||||
'tooltip' => __(
|
||||
'When checked, customers will be able to purchase only 1 item in a single order. This is particularly useful for items that have limited quantity, like art or handmade goods.',
|
||||
'woocommerce'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the shipping group blocks to the template.
|
||||
*/
|
||||
private function add_shipping_group_blocks() {
|
||||
$shipping_group = $this->get_group_by_id( $this::GROUP_IDS['SHIPPING'] );
|
||||
$shipping_group->add_block(
|
||||
array(
|
||||
'id' => 'product_variation_notice_shipping_tab',
|
||||
'blockName' => 'woocommerce/product-has-variations-notice',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s shipping settings and other details individually.', 'woocommerce' ),
|
||||
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
|
||||
'type' => 'info',
|
||||
),
|
||||
)
|
||||
);
|
||||
// Virtual section.
|
||||
if ( Features::is_enabled( 'product-virtual-downloadable' ) ) {
|
||||
$shipping_group->add_section(
|
||||
array(
|
||||
'id' => 'product-virtual-section',
|
||||
'order' => 10,
|
||||
'hideConditions' => array(
|
||||
array(
|
||||
'expression' => 'editedProduct.type !== "simple"',
|
||||
),
|
||||
),
|
||||
)
|
||||
)->add_block(
|
||||
array(
|
||||
'id' => 'product-virtual',
|
||||
'blockName' => 'woocommerce/product-toggle-field',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'property' => 'virtual',
|
||||
'checkedValue' => false,
|
||||
'uncheckedValue' => true,
|
||||
'label' => __( 'This product requires shipping or pickup', 'woocommerce' ),
|
||||
'uncheckedHelp' => __( 'This product will not trigger your customer\'s shipping calculator in cart or at checkout. This product also won\'t require your customers to enter their shipping details at checkout. <a href="https://woo.com/document/managing-products/#adding-a-virtual-product" target="_blank" rel="noreferrer">Read more about virtual products</a>.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
// Product Shipping Section.
|
||||
$product_fee_and_dimensions_section = $shipping_group->add_section(
|
||||
array(
|
||||
'id' => 'product-fee-and-dimensions-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Fees & dimensions', 'woocommerce' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/
|
||||
__( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ),
|
||||
'<a href="https://woo.com/posts/how-to-calculate-shipping-costs-for-your-woocommerce-store/" target="_blank" rel="noreferrer">',
|
||||
'</a>'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$product_fee_and_dimensions_section->add_block(
|
||||
array(
|
||||
'id' => 'product-shipping-class',
|
||||
'blockName' => 'woocommerce/product-shipping-class-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
$product_fee_and_dimensions_section->add_block(
|
||||
array(
|
||||
'id' => 'product-shipping-dimensions',
|
||||
'blockName' => 'woocommerce/product-shipping-dimensions-fields',
|
||||
'order' => 20,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the variation group blocks to the template.
|
||||
*/
|
||||
private function add_variation_group_blocks() {
|
||||
$variation_group = $this->get_group_by_id( $this::GROUP_IDS['VARIATIONS'] );
|
||||
if ( ! $variation_group ) {
|
||||
return;
|
||||
}
|
||||
$variation_fields = $variation_group->add_block(
|
||||
array(
|
||||
'id' => 'product_variation-field-group',
|
||||
'blockName' => 'woocommerce/product-variations-fields',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s: Sell your product in multiple variations like size or color. strong opening tag. %2$s: Sell your product in multiple variations like size or color. strong closing tag.*/
|
||||
__( '%1$sSell your product in multiple variations like size or color.%2$s Get started by adding options for the buyers to choose on the product page.', 'woocommerce' ),
|
||||
'<strong>',
|
||||
'</strong>'
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
$variation_options_section = $variation_fields->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-options-section',
|
||||
'blockName' => 'woocommerce/product-section',
|
||||
'order' => 10,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Variation options', 'woocommerce' ),
|
||||
'description' => __( 'Add and manage attributes used for product options, such as size and color.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
$variation_options_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-options',
|
||||
'blockName' => 'woocommerce/product-variations-options-field',
|
||||
)
|
||||
);
|
||||
$variation_section = $variation_fields->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-section',
|
||||
'blockName' => 'woocommerce/product-section',
|
||||
'order' => 20,
|
||||
'attributes' => array(
|
||||
'title' => __( 'Variations', 'woocommerce' ),
|
||||
'description' => __( 'Manage individual product combinations created from options.', 'woocommerce' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$variation_section->add_block(
|
||||
array(
|
||||
'id' => 'product-variation-items',
|
||||
'blockName' => 'woocommerce/product-variation-items-field',
|
||||
'order' => 10,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class MobileMessagingHandler {
|
||||
),
|
||||
self::prepare_utm_parameters( 'deeplinks_payments', $blog_id, $domain )
|
||||
),
|
||||
'https://woocommerce.com/mobile/payments'
|
||||
'https://woo.com/mobile/payments'
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
@@ -138,7 +138,7 @@ class MobileMessagingHandler {
|
||||
),
|
||||
self::prepare_utm_parameters( 'deeplinks_orders_details', $blog_id, $domain )
|
||||
),
|
||||
'https://woocommerce.com/mobile/orders/details'
|
||||
'https://woo.com/mobile/orders/details'
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
@@ -168,7 +168,7 @@ class MobileMessagingHandler {
|
||||
),
|
||||
self::prepare_utm_parameters( 'deeplinks_promote_app', $blog_id, $domain )
|
||||
),
|
||||
'https://woocommerce.com/mobile'
|
||||
'https://woo.com/mobile'
|
||||
);
|
||||
return sprintf(
|
||||
/* translators: 1: opening link tag 2: closing link tag. */
|
||||
@@ -182,7 +182,7 @@ class MobileMessagingHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares array of parameters used by WooCommerce.com for tracking.
|
||||
* Prepares array of parameters used by Woo.com for tracking.
|
||||
*
|
||||
* @param string $campaign name of the deep link campaign.
|
||||
* @param int|null $blog_id blog id of the current site.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Helpers for managing connection to WooCommerce.com.
|
||||
* Helpers for managing connection to Woo.com.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\WCCom;
|
||||
@@ -10,11 +10,11 @@ defined( 'ABSPATH' ) || exit;
|
||||
/**
|
||||
* Class WCConnectionHelper.
|
||||
*
|
||||
* Helpers for managing connection to WooCommerce.com.
|
||||
* Helpers for managing connection to Woo.com.
|
||||
*/
|
||||
final class ConnectionHelper {
|
||||
/**
|
||||
* Check if WooCommerce.com account is connected.
|
||||
* Check if Woo.com account is connected.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @return bool Whether account is connected.
|
||||
|
||||
Reference in New Issue
Block a user