plugin updates

This commit is contained in:
Tony Volpe
2024-02-21 16:19:46 +00:00
parent c72f206574
commit 21d4c85c00
1214 changed files with 102269 additions and 179257 deletions

View File

@@ -1,78 +0,0 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
/**
* Block template registry.
*/
final class BlockTemplateRegistry {
/**
* Class instance.
*
* @var BlockTemplateRegistry|null
*/
private static $instance = null;
/**
* Templates.
*
* @var array
*/
protected $templates = array();
/**
* Get the instance of the class.
*/
public static function get_instance(): BlockTemplateRegistry {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Register a single template.
*
* @param BlockTemplateInterface $template Template to register.
*
* @throws \ValueError If a template with the same ID already exists.
*/
public function register( BlockTemplateInterface $template ) {
$id = $template->get_id();
if ( isset( $this->templates[ $id ] ) ) {
throw new \ValueError( 'A template with the specified ID already exists in the registry.' );
}
/**
* Fires when a template is registered.
*
* @param BlockTemplateInterface $template Template that was registered.
*
* @since 8.2.0
*/
do_action( 'woocommerce_block_template_register', $template );
$this->templates[ $id ] = $template;
}
/**
* Get the registered templates.
*/
public function get_all_registered(): array {
return $this->templates;
}
/**
* Get a single registered template.
*
* @param string $id ID of the template.
*/
public function get_registered( $id ): BlockTemplateInterface {
return isset( $this->templates[ $id ] ) ? $this->templates[ $id ] : null;
}
}

View File

@@ -1,53 +0,0 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry;
/**
* Block template controller.
*/
class BlockTemplatesController {
/**
* Block template registry
*
* @var BlockTemplateRegistry
*/
private $block_template_registry;
/**
* Block template transformer.
*
* @var TemplateTransformer
*/
private $template_transformer;
/**
* Init.
*/
public function init( $block_template_registry, $template_transformer ) {
$this->block_template_registry = $block_template_registry;
$this->template_transformer = $template_transformer;
add_action( 'rest_api_init', array( $this, 'register_templates' ) );
}
/**
* Register templates in the blocks endpoint.
*/
public function register_templates() {
$templates = $this->block_template_registry->get_all_registered();
foreach ( $templates as $template ) {
add_filter( 'pre_get_block_templates', function( $query_result, $query, $template_type ) use( $template ) {
if ( ! isset( $query['area'] ) || $query['area'] !== $template->get_area() ) {
return $query_result;
}
$wp_block_template = $this->template_transformer->transform( $template );
$query_result[] = $wp_block_template;
return $query_result;
}, 10, 3 );
}
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry;
use Automattic\WooCommerce\Admin\BlockTemplates\BlockTemplateInterface;
/**
* Template transformer.
*/
class TemplateTransformer {
/**
* Transform the WooCommerceBlockTemplate to a WP_Block_Template.
*
* @param object $block_template The product template.
*/
public function transform( BlockTemplateInterface $block_template ): \WP_Block_Template {
$template = new \WP_Block_Template();
$template->id = $block_template->get_id();
$template->theme = 'woocommerce/woocommerce';
$template->content = $block_template->get_formatted_template();
$template->source = 'plugin';
$template->slug = $block_template->get_id();
$template->type = 'wp_template';
$template->title = $block_template->get_title();
$template->description = $block_template->get_description();
$template->status = 'publish';
$template->has_theme_file = true;
$template->origin = 'plugin';
$template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
$template->area = $block_template->get_area();
return $template;
}
}

View File

@@ -131,4 +131,19 @@ abstract class AbstractBlockTemplate implements BlockTemplateInterface {
return $inner_blocks_formatted_template;
}
/**
* Get the template as JSON like array.
*
* @return array The JSON.
*/
public function to_json(): array {
return array(
'id' => $this->get_id(),
'title' => $this->get_title(),
'description' => $this->get_description(),
'area' => $this->get_area(),
'blockTemplates' => $this->get_formatted_template(),
);
}
}

View File

@@ -210,25 +210,35 @@ class BlockTemplateLogger {
}
/**
* Get all template events for a given template.
* Get all template events for a given template as a JSON like array.
*
* @param string $template_id Template ID.
*/
public function get_formatted_template_events( string $template_id ): array {
public function template_events_to_json( 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();
return $this->to_json( $template_events );
}
/**
* Get all template events as a JSON like array.
*
* @param array $template_events Template events.
*
* @return array The JSON.
*/
private function to_json( array $template_events ): array {
$json = array();
foreach ( $template_events as $template_event ) {
$container = $template_event['container'];
$block = $template_event['block'];
$formatted_template_events[] = array(
$json[] = array(
'level' => $template_event['level'],
'event_type' => $template_event['event_type'],
'message' => $template_event['message'],
@@ -246,7 +256,7 @@ class BlockTemplateLogger {
);
}
return $formatted_template_events;
return $json;
}
/**
@@ -311,7 +321,7 @@ class BlockTemplateLogger {
* @param array $template_events Template events.
*/
private function generate_template_events_hash( array $template_events ): string {
return md5( wp_json_encode( $template_events ) );
return md5( wp_json_encode( $this->to_json( $template_events ) ) );
}
/**

View File

@@ -8,7 +8,6 @@ namespace Automattic\WooCommerce\Internal\Admin;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\PageController;
use Automattic\WooCommerce\Admin\PluginsHelper;
use Automattic\WooCommerce\Internal\Admin\BlockTemplateRegistry\BlockTemplatesController;
use Automattic\WooCommerce\Internal\Admin\ProductReviews\Reviews;
use Automattic\WooCommerce\Internal\Admin\ProductReviews\ReviewsCommentsOverrides;
use Automattic\WooCommerce\Internal\Admin\Settings;
@@ -73,7 +72,6 @@ class Loader {
wc_get_container()->get( Reviews::class );
wc_get_container()->get( ReviewsCommentsOverrides::class );
wc_get_container()->get( BlockTemplatesController::class );
add_filter( 'admin_body_class', array( __CLASS__, 'add_admin_body_classes' ) );
add_filter( 'admin_title', array( __CLASS__, 'update_admin_title' ) );

View File

@@ -86,7 +86,7 @@ class FileController {
* Class FileController
*/
public function __construct() {
$this->log_directory = trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) );
$this->log_directory = trailingslashit( Constants::get_constant( 'WC_LOG_DIR' ) );
}
/**

View File

@@ -3,8 +3,7 @@
namespace Automattic\WooCommerce\Internal\Admin\Logging;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\FileController;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ File, FileController };
use WC_Log_Handler;
/**
@@ -18,11 +17,19 @@ class LogHandlerFileV2 extends WC_Log_Handler {
*/
private $file_controller;
/**
* Instance of the Settings class.
*
* @var Settings
*/
private $settings;
/**
* LogHandlerFileV2 class.
*/
public function __construct() {
$this->file_controller = wc_get_container()->get( FileController::class );
$this->settings = wc_get_container()->get( Settings::class );
}
/**
@@ -73,20 +80,17 @@ class LogHandlerFileV2 extends WC_Log_Handler {
$time_string = static::format_time( $timestamp );
$level_string = strtoupper( $level );
// Remove line breaks so the whole entry is on one line in the file.
$formatted_message = str_replace( PHP_EOL, ' ', $message );
unset( $context['source'] );
if ( ! empty( $context ) ) {
if ( isset( $context['backtrace'] ) && true === filter_var( $context['backtrace'], FILTER_VALIDATE_BOOLEAN ) ) {
$context['backtrace'] = static::get_backtrace();
}
$formatted_context = wp_json_encode( $context );
$formatted_message .= " CONTEXT: $formatted_context";
$formatted_context = wp_json_encode( $context );
$message .= " CONTEXT: $formatted_context";
}
$entry = "$time_string $level_string $formatted_message";
$entry = "$time_string $level_string $message";
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/** This filter is documented in includes/abstracts/abstract-wc-log-handler.php */
@@ -152,6 +156,63 @@ class LogHandlerFileV2 extends WC_Log_Handler {
return sanitize_title( $source );
}
/**
* Delete all logs from a specific source.
*
* @param string $source The source of the log entries.
*
* @return int The number of files that were deleted.
*/
public function clear( string $source ): int {
$source = File::sanitize_source( $source );
$files = $this->file_controller->get_files(
array(
'source' => $source,
)
);
if ( is_wp_error( $files ) || count( $files ) < 1 ) {
return 0;
}
$file_ids = array_map(
fn( $file ) => $file->get_file_id(),
$files
);
$deleted = $this->file_controller->delete_files( $file_ids );
if ( $deleted > 0 ) {
$this->handle(
time(),
'info',
sprintf(
esc_html(
// translators: %1$s is a number of log files, %2$s is a slug-style name for a file.
_n(
'%1$s log file from source %2$s was deleted.',
'%1$s log files from source %2$s were deleted.',
$deleted,
'woocommerce'
)
),
number_format_i18n( $deleted ),
sprintf(
'<code>%s</code>',
esc_html( $source )
)
),
array(
'source' => 'wc_logger',
'backtrace' => true,
)
);
}
return $deleted;
}
/**
* Delete all logs older than a specified timestamp.
*
@@ -172,7 +233,7 @@ class LogHandlerFileV2 extends WC_Log_Handler {
)
);
if ( is_wp_error( $files ) ) {
if ( is_wp_error( $files ) || count( $files ) < 1 ) {
return 0;
}
@@ -181,12 +242,8 @@ class LogHandlerFileV2 extends WC_Log_Handler {
$files
);
$deleted = $this->file_controller->delete_files( $file_ids );
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/** This filter is documented in includes/class-wc-logger.php. */
$retention_days = absint( apply_filters( 'woocommerce_logger_days_to_retain_logs', 30 ) );
// phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
$deleted = $this->file_controller->delete_files( $file_ids );
$retention_days = $this->settings->get_retention_period();
if ( $deleted > 0 ) {
$this->handle(

View File

@@ -4,7 +4,7 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Internal\Admin\Logging;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2;
use Automattic\WooCommerce\Internal\Admin\Logging\{ LogHandlerFileV2, Settings };
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ File, FileController, FileListTable, SearchListTable };
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
use WC_Admin_Status;
@@ -26,6 +26,13 @@ class PageController {
*/
private $file_controller;
/**
* Instance of Settings.
*
* @var Settings
*/
private $settings;
/**
* Instance of FileListTable or SearchListTable.
*
@@ -39,13 +46,16 @@ class PageController {
* @internal
*
* @param FileController $file_controller Instance of FileController.
* @param Settings $settings Instance of Settings.
*
* @return void
*/
final public function init(
FileController $file_controller
FileController $file_controller,
Settings $settings
): void {
$this->file_controller = $file_controller;
$this->settings = $settings;
$this->init_hooks();
}
@@ -56,8 +66,61 @@ class PageController {
* @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' ) );
self::add_action( 'load-woocommerce_page_wc-status', array( $this, 'maybe_do_logs_tab_action' ), 2 );
self::add_action( 'wc_logs_load_tab', array( $this, 'setup_screen_options' ) );
self::add_action( 'wc_logs_load_tab', array( $this, 'handle_list_table_bulk_actions' ) );
self::add_action( 'wc_logs_load_tab', array( $this, 'notices' ) );
}
/**
* Determine if the current tab on the Status page is Logs, and if so, fire an action.
*
* @return void
*/
private function maybe_do_logs_tab_action(): void {
$is_logs_tab = 'logs' === filter_input( INPUT_GET, 'tab' );
if ( $is_logs_tab ) {
$params = $this->get_query_params( array( 'view' ) );
/**
* Action fires when the Logs tab starts loading.
*
* @param string $view The current view within the Logs tab.
*
* @since 8.6.0
*/
do_action( 'wc_logs_load_tab', $params['view'] );
}
}
/**
* Notices to display on Logs screens.
*
* @return void
*/
private function notices() {
if ( ! $this->settings->logging_is_enabled() ) {
add_action(
'admin_notices',
function() {
?>
<div class="notice notice-warning">
<p>
<?php
printf(
// translators: %s is a URL to another admin screen.
wp_kses_post( __( 'Logging is disabled. It can be enabled in <a href="%s">Logs Settings</a>.', 'woocommerce' ) ),
esc_url( add_query_arg( 'view', 'settings', $this->get_logs_tab_url() ) )
);
?>
</p>
</div>
<?php
}
);
}
}
/**
@@ -75,40 +138,83 @@ class PageController {
);
}
/**
* 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();
$handler = $this->settings->get_default_handler();
$params = $this->get_query_params( array( 'view' ) );
$this->render_section_nav();
if ( 'settings' === $params['view'] ) {
$this->settings->render_form();
return;
}
switch ( $handler ) {
case LogHandlerFileV2::class:
$this->render_filev2();
break;
case 'WC_Log_Handler_DB':
return;
case WC_Log_Handler_DB::class:
WC_Admin_Status::status_logs_db();
break;
default:
return;
case WC_Log_Handler_File::class:
WC_Admin_Status::status_logs_file();
break;
return;
}
/**
* Action fires only if there is not a built-in rendering method for the current default log handler.
*
* This is intended as a way for extensions to render log views for custom handlers.
*
* @param string $handler
*
* @since 8.6.0
*/
do_action( 'wc_logs_render_page', $handler );
}
/**
* Render navigation to switch between logs browsing and settings.
*
* @return void
*/
private function render_section_nav(): void {
$params = $this->get_query_params( array( 'view' ) );
$browse_url = $this->get_logs_tab_url();
$settings_url = add_query_arg( 'view', 'settings', $this->get_logs_tab_url() );
?>
<ul class="subsubsub">
<li>
<?php
printf(
'<a href="%1$s"%2$s>%3$s</a>',
esc_url( $browse_url ),
'settings' !== $params['view'] ? ' class="current"' : '',
esc_html__( 'Browse', 'woocommerce' )
);
?>
|
</li>
<li>
<?php
printf(
'<a href="%1$s"%2$s>%3$s</a>',
esc_url( $settings_url ),
'settings' === $params['view'] ? ' class="current"' : '',
esc_html__( 'Settings', 'woocommerce' )
);
?>
</li>
</ul>
<br class="clear">
<?php
}
/**
@@ -294,6 +400,17 @@ class PageController {
?>
<?php endwhile; ?>
</section>
<script>
// Clear the line number hash and highlight with a click.
document.documentElement.addEventListener( 'click', ( event ) => {
if ( window.location.hash && ! event.target.classList.contains( 'line-anchor' ) ) {
let scrollPos = document.documentElement.scrollTop;
window.location.hash = '';
document.documentElement.scrollTop = scrollPos;
history.replaceState( null, '', window.location.pathname + window.location.search );
}
} );
</script>
<?php
}
@@ -303,7 +420,7 @@ class PageController {
* @return void
*/
private function render_search_results_view(): void {
$params = $this->get_query_params( array( 'order', 'orderby', 'search', 'source', 'view' ) );
$params = $this->get_query_params( array( 'view' ) );
$list_table = $this->get_list_table( $params['view'] );
$list_table->prepare_items();
@@ -380,7 +497,7 @@ class PageController {
'view' => array(
'filter' => FILTER_VALIDATE_REGEXP,
'options' => array(
'regexp' => '/^(list_files|single_file|search_results)$/',
'regexp' => '/^(list_files|single_file|search_results|settings)$/',
'default' => $defaults['view'],
),
),
@@ -423,17 +540,18 @@ class PageController {
/**
* Register screen options for the logging views.
*
* @param string $view The current view within the Logs tab.
*
* @return void
*/
private function setup_screen_options(): void {
$params = $this->get_query_params( array( 'view' ) );
$handler = $this->get_default_handler();
private function setup_screen_options( string $view ): void {
$handler = $this->settings->get_default_handler();
$list_table = null;
switch ( $handler ) {
case LogHandlerFileV2::class:
if ( in_array( $params['view'], array( 'list_files', 'search_results' ), true ) ) {
$list_table = $this->get_list_table( $params['view'] );
if ( in_array( $view, array( 'list_files', 'search_results' ), true ) ) {
$list_table = $this->get_list_table( $view );
}
break;
case 'WC_Log_Handler_DB':
@@ -458,22 +576,24 @@ class PageController {
/**
* Process bulk actions initiated from the log file list table.
*
* @param string $view The current view within the Logs tab.
*
* @return void
*/
private function handle_list_table_bulk_actions(): void {
private function handle_list_table_bulk_actions( string $view ): void {
// Bail if we're not using the file handler.
if ( LogHandlerFileV2::class !== $this->get_default_handler() ) {
if ( LogHandlerFileV2::class !== $this->settings->get_default_handler() ) {
return;
}
$params = $this->get_query_params( array( 'file_id', 'view' ) );
$params = $this->get_query_params( array( 'file_id' ) );
// Bail if this is not the list table view.
if ( 'list_files' !== $params['view'] ) {
if ( 'list_files' !== $view ) {
return;
}
$action = $this->get_list_table( $params['view'] )->current_action();
$action = $this->get_list_table( $view )->current_action();
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : $this->get_logs_tab_url();
@@ -562,10 +682,9 @@ class PageController {
* @return string
*/
private function format_line( string $line, int $line_number ): string {
$severity_levels = WC_Log_Levels::get_all_severity_levels();
$classes = array( 'line' );
$classes = array( 'line' );
$line = esc_html( trim( $line ) );
$line = esc_html( $line );
if ( empty( $line ) ) {
$line = '&nbsp;';
}
@@ -583,11 +702,11 @@ class PageController {
$has_timestamp = true;
}
if ( isset( $segments[1] ) && in_array( strtolower( $segments[1] ), $severity_levels, true ) ) {
if ( isset( $segments[1] ) && WC_Log_Levels::is_valid_level( strtolower( $segments[1] ) ) ) {
$segments[1] = sprintf(
'<span class="%1$s">%2$s</span>',
esc_attr( 'log-level log-level--' . strtolower( $segments[1] ) ),
esc_html( $segments[1] )
esc_html( WC_Log_Levels::get_level_label( strtolower( $segments[1] ) ) )
);
$has_level = true;
}
@@ -600,7 +719,7 @@ class PageController {
$context = json_decode( $maybe_json, false, 512, JSON_THROW_ON_ERROR );
$message_chunks[1] = sprintf(
'<details><summary>%1$s</summary><pre>%2$s</pre></details>',
'<details><summary>%1$s</summary>%2$s</details>',
esc_html__( 'Additional context', 'woocommerce' ),
wp_json_encode( $context, JSON_PRETTY_PRINT )
);

View File

@@ -0,0 +1,449 @@
<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Internal\Admin\Logging;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2;
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
use WC_Admin_Settings;
use WC_Log_Handler, WC_Log_Handler_DB, WC_Log_Handler_File, WC_Log_Levels;
/**
* Settings class.
*/
class Settings {
use AccessiblePrivateMethods;
/**
* Default values for logging settings.
*
* @const array
*/
private const DEFAULTS = array(
'logging_enabled' => true,
'default_handler' => LogHandlerFileV2::class,
'retention_period_days' => 30,
'level_threshold' => 'none',
'file_entry_collapse_lines' => true,
);
/**
* The prefix for settings keys used in the options table.
*
* @const string
*/
private const PREFIX = 'woocommerce_logs_';
/**
* Class Settings.
*/
public function __construct() {
self::add_action( 'wc_logs_load_tab', array( $this, 'save_settings' ) );
}
/**
* The definitions used by WC_Admin_Settings to render and save settings controls.
*
* @return array
*/
private function get_settings_definitions(): array {
$settings = array(
'start' => array(
'title' => __( 'Logs settings', 'woocommerce' ),
'id' => self::PREFIX . 'settings',
'type' => 'title',
),
'logging_enabled' => array(
'title' => __( 'Logger', 'woocommerce' ),
'desc' => __( 'Enable logging', 'woocommerce' ),
'id' => self::PREFIX . 'logging_enabled',
'type' => 'checkbox',
'value' => $this->logging_is_enabled() ? 'yes' : 'no',
'default' => self::DEFAULTS['logging_enabled'] ? 'yes' : 'no',
'autoload' => false,
),
'default_handler' => array(),
'retention_period_days' => array(),
'level_threshold' => array(),
'end' => array(
'id' => self::PREFIX . 'settings',
'type' => 'sectionend',
),
);
if ( true === $this->logging_is_enabled() ) {
$settings['default_handler'] = $this->get_default_handler_setting_definition();
$settings['retention_period_days'] = $this->get_retention_period_days_setting_definition();
$settings['level_threshold'] = $this->get_level_threshold_setting_definition();
}
$default_handler = $this->get_default_handler();
if ( in_array( $default_handler, array( LogHandlerFileV2::class, WC_Log_Handler_File::class ), true ) ) {
$settings += $this->get_filesystem_settings_definitions();
} elseif ( WC_Log_Handler_DB::class === $default_handler ) {
$settings += $this->get_database_settings_definitions();
}
return $settings;
}
/**
* The definition for the default_handler setting.
*
* @return array
*/
private function get_default_handler_setting_definition(): array {
$handler_options = array(
LogHandlerFileV2::class => __( 'File system (default)', 'woocommerce' ),
WC_Log_Handler_DB::class => __( 'Database (not recommended on live sites)', 'woocommerce' ),
);
/**
* Filter the list of logging handlers that can be set as the default handler.
*
* @param array $handler_options An associative array of class_name => description.
*
* @since 8.6.0
*/
$handler_options = apply_filters( 'woocommerce_logger_handler_options', $handler_options );
$current_value = $this->get_default_handler();
if ( ! array_key_exists( $current_value, $handler_options ) ) {
$handler_options[ $current_value ] = $current_value;
}
$desc = array();
$desc[] = __( 'Note that if this setting is changed, any log entries that have already been recorded will remain stored in their current location, but will not migrate.', 'woocommerce' );
$hardcoded = ! is_null( Constants::get_constant( 'WC_LOG_HANDLER' ) );
if ( $hardcoded ) {
$desc[] = sprintf(
// translators: %s is the name of a code variable.
__( 'This setting cannot be changed here because it is defined in the %s constant.', 'woocommerce' ),
'<code>WC_LOG_HANDLER</code>'
);
}
return array(
'title' => __( 'Log storage', 'woocommerce' ),
'desc_tip' => __( 'This determines where log entries are saved.', 'woocommerce' ),
'id' => self::PREFIX . 'default_handler',
'type' => 'radio',
'value' => $current_value,
'default' => self::DEFAULTS['default_handler'],
'autoload' => false,
'options' => $handler_options,
'disabled' => $hardcoded ? array_keys( $handler_options ) : array(),
'desc' => implode( '<br><br>', $desc ),
'desc_at_end' => true,
);
}
/**
* The definition for the retention_period_days setting.
*
* @return array
*/
private function get_retention_period_days_setting_definition(): array {
$custom_attributes = array(
'min' => 1,
'step' => 1,
);
$hardcoded = has_filter( 'woocommerce_logger_days_to_retain_logs' );
$desc = '';
if ( $hardcoded ) {
$custom_attributes['disabled'] = 'true';
$desc = sprintf(
// translators: %s is the name of a filter hook.
__( 'This setting cannot be changed here because it is being set by a filter on the %s hook.', 'woocommerce' ),
'<code>woocommerce_logger_days_to_retain_logs</code>'
);
}
return array(
'title' => __( 'Retention period', 'woocommerce' ),
'desc_tip' => __( 'This sets how many days log entries will be kept before being auto-deleted.', 'woocommerce' ),
'id' => self::PREFIX . 'retention_period_days',
'type' => 'number',
'value' => $this->get_retention_period(),
'default' => self::DEFAULTS['retention_period_days'],
'autoload' => false,
'custom_attributes' => $custom_attributes,
'css' => 'width:70px;',
'row_class' => 'logs-retention-period-days',
'suffix' => sprintf(
' %s',
__( 'days', 'woocommerce' ),
),
'desc' => $desc,
);
}
/**
* The definition for the level_threshold setting.
*
* @return array
*/
private function get_level_threshold_setting_definition(): array {
$hardcoded = ! is_null( Constants::get_constant( 'WC_LOG_THRESHOLD' ) );
$desc = '';
if ( $hardcoded ) {
$desc = sprintf(
// translators: %1$s is the name of a code variable. %2$s is the name of a file.
__( 'This setting cannot be changed here because it is defined in the %1$s constant, probably in your %2$s file.', 'woocommerce' ),
'<code>WC_LOG_THRESHOLD</code>',
'<b>wp-config.php</b>'
);
}
$labels = WC_Log_Levels::get_all_level_labels();
$labels['none'] = __( 'None', 'woocommerce' );
$custom_attributes = array();
if ( $hardcoded ) {
$custom_attributes['disabled'] = 'true';
}
return array(
'title' => __( 'Level threshold', 'woocommerce' ),
'desc_tip' => __( 'This sets the minimum severity level of logs that will be stored. Lower severity levels will be ignored. "None" means all logs will be stored.', 'woocommerce' ),
'id' => self::PREFIX . 'level_threshold',
'type' => 'select',
'value' => $this->get_level_threshold(),
'default' => self::DEFAULTS['level_threshold'],
'autoload' => false,
'options' => $labels,
'custom_attributes' => $custom_attributes,
'css' => 'width:auto;',
'desc' => $desc,
);
}
/**
* The definitions used by WC_Admin_Settings to render settings related to filesystem log handlers.
*
* @return array
*/
private function get_filesystem_settings_definitions(): array {
$location_info = array();
$directory = trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) );
$location_info[] = sprintf(
// translators: %s is a location in the filesystem.
__( 'Log files are stored in this directory: %s', 'woocommerce' ),
sprintf(
'<code>%s</code>',
esc_html( $directory )
)
);
if ( ! wp_is_writable( $directory ) ) {
$location_info[] = __( '⚠️ This directory does not appear to be writable.', 'woocommerce' );
}
$location_info[] = sprintf(
// translators: %1$s is a code variable. %2$s is the name of a file.
__( 'Change the location by defining the %1$s constant in your %2$s file with a new path.', 'woocommerce' ),
'<code>WC_LOG_DIR</code>',
'<code>wp-config.php</code>'
);
return array(
'file_start' => array(
'title' => __( 'File system settings', 'woocommerce' ),
'id' => self::PREFIX . 'settings',
'type' => 'title',
),
'log_directory' => array(
'type' => 'info',
'text' => implode( "\n\n", $location_info ),
),
'entry_format' => array(),
'file_end' => array(
'id' => self::PREFIX . 'settings',
'type' => 'sectionend',
),
);
}
/**
* The definitions used by WC_Admin_Settings to render settings related to database log handlers.
*
* @return array
*/
private function get_database_settings_definitions(): array {
global $wpdb;
$table = "{$wpdb->prefix}woocommerce_log";
$location_info = sprintf(
// translators: %s is a location in the filesystem.
__( 'Log entries are stored in this database table: %s', 'woocommerce' ),
"<code>$table</code>"
);
return array(
'file_start' => array(
'title' => __( 'Database settings', 'woocommerce' ),
'id' => self::PREFIX . 'settings',
'type' => 'title',
),
'database_table' => array(
'type' => 'info',
'text' => $location_info,
),
'file_end' => array(
'id' => self::PREFIX . 'settings',
'type' => 'sectionend',
),
);
}
/**
* Handle the submission of the settings form and update the settings values.
*
* @param string $view The current view within the Logs tab.
*
* @return void
*/
private function save_settings( string $view ): void {
$is_saving = 'settings' === $view && isset( $_POST['save_settings'] );
if ( $is_saving ) {
check_admin_referer( self::PREFIX . 'settings' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to manage logging settings.', 'woocommerce' ) );
}
$settings = $this->get_settings_definitions();
WC_Admin_Settings::save_fields( $settings );
}
}
/**
* Render the settings page.
*
* @return void
*/
public function render_form(): void {
$settings = $this->get_settings_definitions();
?>
<form id="mainform" class="wc-logs-settings" method="post">
<?php WC_Admin_Settings::output_fields( $settings ); ?>
<?php
/**
* Action fires after the built-in logging settings controls have been rendered.
*
* This is intended as a way to allow other logging settings controls to be added by extensions.
*
* @param bool $enabled True if logging is currently enabled.
*
* @since 8.6.0
*/
do_action( 'wc_logs_settings_form_fields', $this->logging_is_enabled() );
?>
<?php wp_nonce_field( self::PREFIX . 'settings' ); ?>
<?php submit_button( __( 'Save changes', 'woocommerce' ), 'primary', 'save_settings' ); ?>
</form>
<?php
}
/**
* Determine the current value of the logging_enabled setting.
*
* @return bool
*/
public function logging_is_enabled(): bool {
$key = self::PREFIX . 'logging_enabled';
$enabled = WC_Admin_Settings::get_option( $key, self::DEFAULTS['logging_enabled'] );
$enabled = filter_var( $enabled, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
if ( is_null( $enabled ) ) {
$enabled = self::DEFAULTS['logging_enabled'];
}
return $enabled;
}
/**
* Determine the current value of the default_handler setting.
*
* @return string
*/
public function get_default_handler(): string {
$key = self::PREFIX . 'default_handler';
$handler = Constants::get_constant( 'WC_LOG_HANDLER' );
if ( is_null( $handler ) ) {
$handler = WC_Admin_Settings::get_option( $key );
}
if ( ! class_exists( $handler ) || ! is_a( $handler, 'WC_Log_Handler_Interface', true ) ) {
$handler = self::DEFAULTS['default_handler'];
}
return $handler;
}
/**
* Determine the current value of the retention_period_days setting.
*
* @return int
*/
public function get_retention_period(): int {
$key = self::PREFIX . 'retention_period_days';
$retention_period = self::DEFAULTS['retention_period_days'];
if ( has_filter( 'woocommerce_logger_days_to_retain_logs' ) ) {
/**
* Filter the retention period of log entries.
*
* @param int $days The number of days to retain log entries.
*
* @since 3.4.0
*/
$retention_period = apply_filters( 'woocommerce_logger_days_to_retain_logs', $retention_period );
} else {
$retention_period = WC_Admin_Settings::get_option( $key );
}
$retention_period = absint( $retention_period );
if ( $retention_period < 1 ) {
$retention_period = self::DEFAULTS['retention_period_days'];
}
return $retention_period;
}
/**
* Determine the current value of the level_threshold setting.
*
* @return string
*/
public function get_level_threshold(): string {
$key = self::PREFIX . 'level_threshold';
$threshold = Constants::get_constant( 'WC_LOG_THRESHOLD' );
if ( is_null( $threshold ) ) {
$threshold = WC_Admin_Settings::get_option( $key );
}
if ( ! WC_Log_Levels::is_valid_level( $threshold ) ) {
$threshold = self::DEFAULTS['level_threshold'];
}
return $threshold;
}
}

View File

@@ -16,6 +16,20 @@ class Marketing {
use CouponsMovedTrait;
/**
* Constant representing the key for the submenu name value in the global $submenu array.
*
* @var int
*/
const SUBMENU_NAME_KEY = 0;
/**
* Constant representing the key for the submenu location value in the global $submenu array.
*
* @var int
*/
const SUBMENU_LOCATION_KEY = 2;
/**
* Class instance.
*
@@ -44,6 +58,9 @@ class Marketing {
add_action( 'admin_menu', array( $this, 'register_pages' ), 5 );
add_action( 'admin_menu', array( $this, 'add_parent_menu_item' ), 6 );
// Overwrite submenu default ordering for marketing menu. High priority gives plugins the chance to register their own menu items.
add_action( 'admin_menu', array( $this, 'reorder_marketing_submenu' ), 99 );
add_filter( 'woocommerce_admin_shared_settings', array( $this, 'component_settings' ), 30 );
}
@@ -140,6 +157,67 @@ class Marketing {
}
}
/**
* Order marketing menu items alphabeticaly.
* Overview should be first, and Coupons should be second, followed by other marketing menu items.
*
* @return void
*/
public function reorder_marketing_submenu() {
global $submenu;
if ( ! isset( $submenu['woocommerce-marketing'] ) ) {
return;
}
$marketing_submenu = $submenu['woocommerce-marketing'];
$new_menu_order = array();
// Overview should be first.
$overview_key = array_search( 'Overview', array_column( $marketing_submenu, self::SUBMENU_NAME_KEY ), true );
if ( false === $overview_key ) {
/*
* If Overview is not found we may be on a site witha different language.
* We can use a fallback and try to find the overview page by its path.
*/
$overview_key = array_search( 'admin.php?page=wc-admin&path=/marketing', array_column( $marketing_submenu, self::SUBMENU_LOCATION_KEY ), true );
}
if ( false !== $overview_key ) {
$new_menu_order[] = $marketing_submenu[ $overview_key ];
array_splice( $marketing_submenu, $overview_key, 1 );
}
// Coupons should be second.
$coupons_key = array_search( 'Coupons', array_column( $marketing_submenu, self::SUBMENU_NAME_KEY ), true );
if ( false === $coupons_key ) {
/*
* If Coupons is not found we may be on a site witha different language.
* We can use a fallback and try to find the coupons page by its path.
*/
$coupons_key = array_search( 'edit.php?post_type=shop_coupon', array_column( $marketing_submenu, self::SUBMENU_LOCATION_KEY ), true );
}
if ( false !== $coupons_key ) {
$new_menu_order[] = $marketing_submenu[ $coupons_key ];
array_splice( $marketing_submenu, $coupons_key, 1 );
}
// Sort the rest of the items alphabetically.
usort(
$marketing_submenu,
function( $a, $b ) {
return strcmp( $a[0], $b[0] );
}
);
$new_menu_order = array_merge( $new_menu_order, $marketing_submenu );
$submenu['woocommerce-marketing'] = $new_menu_order; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
/**
* Add settings for marketing feature.
*

View File

@@ -14,13 +14,6 @@ namespace Automattic\WooCommerce\Internal\Admin\Marketing;
* @since x.x.x
*/
class MarketingSpecs {
/**
* Name of recommended plugins transient.
*
* @var string
*/
const RECOMMENDED_PLUGINS_TRANSIENT = 'wc_marketing_recommended_plugins';
/**
* Name of knowledge base post transient.
*
@@ -28,111 +21,6 @@ class MarketingSpecs {
*/
const KNOWLEDGE_BASE_TRANSIENT = 'wc_marketing_knowledge_base';
/**
* 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 Woo.com store.
*
* @var string
*/
const MARKETING_CHANNEL_SUBCATEGORY_SLUG = 'sales-channels';
/**
* Load recommended plugins from Woo.com
*
* @return array
*/
public function get_recommended_plugins(): array {
$plugins = get_transient( self::RECOMMENDED_PLUGINS_TRANSIENT );
if ( false === $plugins ) {
$request = wp_remote_get(
'https://woocommerce.com/wp-json/wccom/marketing-tab/1.3/recommendations.json',
array(
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
)
);
$plugins = [];
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
$plugins = json_decode( $request['body'], true );
}
set_transient(
self::RECOMMENDED_PLUGINS_TRANSIENT,
$plugins,
// Expire transient in 15 minutes if remote get failed.
// Cache an empty result to avoid repeated failed requests.
empty( $plugins ) ? 900 : 3 * DAY_IN_SECONDS
);
}
return array_values( $plugins );
}
/**
* Return only the recommended marketing channels from Woo.com.
*
* @return array
*/
public function get_recommended_marketing_channels(): array {
return array_filter( $this->get_recommended_plugins(), [ $this, 'is_marketing_channel_plugin' ] );
}
/**
* Return all recommended marketing extensions EXCEPT the marketing channels from Woo.com.
*
* @return array
*/
public function get_recommended_marketing_extensions_excluding_channels(): array {
return array_filter(
$this->get_recommended_plugins(),
function ( array $plugin_data ) {
return $this->is_marketing_plugin( $plugin_data ) && ! $this->is_marketing_channel_plugin( $plugin_data );
}
);
}
/**
* Returns whether a plugin is a marketing extension.
*
* @param array $plugin_data The plugin properties returned by the API.
*
* @return bool
*/
protected function is_marketing_plugin( array $plugin_data ): bool {
$categories = $plugin_data['categories'] ?? [];
return in_array( self::MARKETING_EXTENSION_CATEGORY_SLUG, $categories, true );
}
/**
* Returns whether a plugin is a marketing channel.
*
* @param array $plugin_data The plugin properties returned by the API.
*
* @return bool
*/
protected function is_marketing_channel_plugin( array $plugin_data ): bool {
if ( ! $this->is_marketing_plugin( $plugin_data ) ) {
return false;
}
$subcategories = $plugin_data['subcategories'] ?? [];
foreach ( $subcategories as $subcategory ) {
if ( isset( $subcategory['slug'] ) && self::MARKETING_CHANNEL_SUBCATEGORY_SLUG === $subcategory['slug'] ) {
return true;
}
}
return false;
}
/**
* Load knowledge base posts from Woo.com
*
@@ -165,21 +53,21 @@ class MarketingSpecs {
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
)
);
$posts = [];
$posts = array();
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
$raw_posts = json_decode( $request['body'], true );
foreach ( $raw_posts as $raw_post ) {
$post = [
$post = array(
'title' => html_entity_decode( $raw_post['title']['rendered'] ),
'date' => $raw_post['date_gmt'],
'link' => $raw_post['link'],
'author_name' => isset( $raw_post['author_name'] ) ? html_entity_decode( $raw_post['author_name'] ) : '',
'author_avatar' => isset( $raw_post['author_avatar_url'] ) ? $raw_post['author_avatar_url'] : '',
];
);
$featured_media = $raw_post['_embedded']['wp:featuredmedia'] ?? [];
$featured_media = isset( $raw_post['_embedded']['wp:featuredmedia'] ) && is_array( $raw_post['_embedded']['wp:featuredmedia'] ) ? $raw_post['_embedded']['wp:featuredmedia'] : array();
if ( count( $featured_media ) > 0 ) {
$image = current( $featured_media );
$post['image'] = add_query_arg(

View File

@@ -10,6 +10,7 @@ use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox;
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\OrderAttribution;
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\TaxonomiesMetaBox;
use Automattic\WooCommerce\Internal\Features\FeaturesController;
use Automattic\WooCommerce\Utilities\OrderUtil;
use WC_Order;
/**
@@ -250,6 +251,15 @@ class Edit {
'high'
);
// Add customer history meta box if analytics is enabled.
if ( 'yes' !== get_option( 'woocommerce_analytics_enabled' ) ) {
return;
}
if ( ! OrderUtil::is_order_edit_screen() ) {
return;
}
/**
* Customer history meta box.
*
@@ -260,7 +270,7 @@ class Edit {
add_meta_box(
'woocommerce-customer-history',
__( 'Customer history', 'woocommerce' ),
function( $post_or_order ) use ( $customer_history_meta_box ) {
function ( $post_or_order ) use ( $customer_history_meta_box ) {
$order = $post_or_order instanceof WC_Order ? $post_or_order : wc_get_order( $post_or_order );
if ( $order instanceof WC_Order ) {
$customer_history_meta_box->output( $order );

View File

@@ -372,7 +372,7 @@ class ListTable extends WP_List_Table {
'type' => $this->order_type,
);
foreach ( array( 'status', 's', 'm', '_customer_user' ) as $query_var ) {
foreach ( array( 'status', 's', 'm', '_customer_user', 'search-filter' ) as $query_var ) {
$this->request[ $query_var ] = sanitize_text_field( wp_unslash( $_REQUEST[ $query_var ] ?? '' ) );
}
@@ -532,6 +532,11 @@ class ListTable extends WP_List_Table {
$this->order_query_args['s'] = $search_term;
$this->has_filter = true;
}
$filter = trim( sanitize_text_field( $this->request['search-filter'] ) );
if ( ! empty( $filter ) ) {
$this->order_query_args['search_filter'] = $filter;
}
}
/**
@@ -541,8 +546,22 @@ class ListTable extends WP_List_Table {
* @return array
*/
public function get_views() {
$view_links = array();
/**
* Filters the list of available list table view links before the actual query runs.
* This can be used to, e.g., remove counts from the links.
*
* @since 8.6.0
*
* @param string[] $views An array of available list table view links.
*/
$view_links = apply_filters( 'woocommerce_before_' . $this->order_type . '_list_table_view_links', $view_links );
if ( ! empty( $view_links ) ) {
return $view_links;
}
$view_counts = array();
$view_links = array();
$statuses = $this->get_visible_statuses();
$current = ! empty( $this->request['status'] ) ? sanitize_text_field( $this->request['status'] ) : 'all';
$all_count = 0;
@@ -620,6 +639,24 @@ class ListTable extends WP_List_Table {
* @return boolean TRUE when the blank state should be rendered, FALSE otherwise.
*/
private function should_render_blank_state(): bool {
/**
* Whether we should render a blank state so that custom count queries can be used.
*
* @since 8.6.0
*
* @param null $should_render_blank_state `null` will use the built-in counts. Sending a boolean will short-circuit that path.
* @param object ListTable The current instance of the class.
*/
$should_render_blank_state = apply_filters(
'woocommerce_' . $this->order_type . '_list_table_should_render_blank_state',
null,
$this
);
if ( is_bool( $should_render_blank_state ) ) {
return $should_render_blank_state;
}
return ( ! $this->has_filter ) && 0 === $this->count_orders_by_status( array_keys( $this->get_visible_statuses() ) );
}
@@ -719,11 +756,22 @@ class ListTable extends WP_List_Table {
private function months_filter() {
// XXX: [review] we may prefer to move this logic outside of the ListTable class.
/**
* Filters whether to remove the 'Months' drop-down from the order list table.
*
* @since 8.6.0
*
* @param bool $disable Whether to disable the drop-down. Default false.
*/
if ( apply_filters( 'woocommerce_' . $this->order_type . '_list_table_disable_months_filter', false ) ) {
return;
}
global $wp_locale;
global $wpdb;
$orders_table = esc_sql( OrdersTableDataStore::get_orders_table_name() );
$utc_offset = wc_timezone_offset();
$utc_offset = wc_timezone_offset();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$order_dates = $wpdb->get_results(
@@ -1347,7 +1395,7 @@ class ListTable extends WP_List_Table {
* @return int Number of orders that were trashed.
*/
private function do_delete( array $ids, bool $force_delete = false ): int {
$changed = 0;
$changed = 0;
foreach ( $ids as $id ) {
$order = wc_get_order( $id );
@@ -1537,4 +1585,55 @@ class ListTable extends WP_List_Table {
return $html;
}
/**
* Renders the search box with various options to limit order search results.
*
* @param string $text The search button text.
* @param string $input_id The search input ID.
*
* @return void
*/
public function search_box( $text, $input_id ) {
if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) {
return;
}
$input_id = $input_id . '-search-input';
if ( ! empty( $_REQUEST['orderby'] ) ) {
echo '<input type="hidden" name="orderby" value="' . esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) ) . '" />';
}
if ( ! empty( $_REQUEST['order'] ) ) {
echo '<input type="hidden" name="order" value="' . esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) . '" />';
}
?>
<p class="search-box">
<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_html( $text ); ?>:</label>
<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="s" value="<?php _admin_search_query(); ?>" />
<?php $this->search_filter(); ?>
<?php submit_button( $text, '', '', false, array( 'id' => 'search-submit' ) ); ?>
</p>
<?php
}
/**
* Renders the search filter dropdown.
*
* @return void
*/
private function search_filter() {
$options = array(
'order_id' => __( 'Order ID', 'woocommerce' ),
'customer_email' => __( 'Customer Email', 'woocommerce' ),
'customers' => __( 'Customers', 'woocommerce' ),
'products' => __( 'Products', 'woocommerce' ),
'all' => __( 'All', 'woocommerce' ),
);
?>
<select name="search-filter" id="order-search-filter">
<?php foreach ( $options as $value => $label ) { ?>
<option value="<?php echo esc_attr( wp_unslash( sanitize_text_field( $value ) ) ); ?>" <?php selected( $value, sanitize_text_field( wp_unslash( $_REQUEST['search-filter'] ?? 'all' ) ) ); ?>><?php echo esc_html( $label ); ?></option>
<?php
}
}
}

View File

@@ -2,7 +2,7 @@
namespace Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes;
use Automattic\WooCommerce\Internal\Traits\OrderAttributionMeta;
use Automattic\WooCommerce\Admin\API\Reports\Customers\Query as CustomersQuery;
use WC_Order;
/**
@@ -12,8 +12,6 @@ use WC_Order;
*/
class CustomerHistory {
use OrderAttributionMeta;
/**
* Output the customer history template for the order.
*
@@ -22,34 +20,47 @@ class CustomerHistory {
* @return void
*/
public function output( WC_Order $order ): void {
$this->display_customer_history( $order->get_customer_id(), $order->get_billing_email() );
}
// No history when adding a new order.
if ( 'auto-draft' === $order->get_status() ) {
return;
}
/**
* Display the customer history template for the customer.
*
* @param int $customer_id The customer ID.
* @param string $billing_email The customer billing email.
*
* @return void
*/
private function display_customer_history( int $customer_id, string $billing_email ): void {
$has_customer_id = false;
if ( $customer_id ) {
$has_customer_id = true;
$args = $this->get_customer_history( $customer_id );
} elseif ( $billing_email ) {
$args = $this->get_customer_history( $billing_email );
} else {
$args = array(
'order_count' => 0,
'total_spent' => 0,
'average_spent' => 0,
$customer_history = null;
if ( method_exists( $order, 'get_report_customer_id' ) ) {
$customer_history = $this->get_customer_history( $order->get_report_customer_id() );
}
if ( ! $customer_history ) {
$customer_history = array(
'orders_count' => 0,
'total_spend' => 0,
'avg_order_value' => 0,
);
}
$args['has_customer_id'] = $has_customer_id;
wc_get_template( 'order/customer-history.php', $args );
wc_get_template( 'order/customer-history.php', $customer_history );
}
/**
* Get the order history for the customer (data matches Customers report).
*
* @param int $customer_report_id The reports customer ID (not necessarily User ID).
*
* @return array|null Order count, total spend, and average spend per order.
*/
private function get_customer_history( $customer_report_id ): ?array {
$args = array(
'customers' => array( $customer_report_id ),
// If unset, these params have default values that affect the results.
'order_after' => null,
'order_before' => null,
);
$customers_query = new CustomersQuery( $args );
$customer_data = $customers_query->get_data();
return $customer_data->data[0] ?? null;
}
}

View File

@@ -79,6 +79,6 @@ class OrderAttribution {
// Only show more details toggle if there is more than just the origin.
'has_more_details' => array( 'origin' ) !== array_keys( $meta ),
);
wc_get_template( 'order/attribution-data-fields.php', $template_data );
wc_get_template( 'order/attribution-details.php', $template_data );
}
}

View File

@@ -97,7 +97,7 @@ abstract class Component {
*/
public static function get_argument_from_path( $arguments, $path, $delimiter = '.' ) {
$path_keys = explode( $delimiter, $path );
$num_keys = count( $path_keys );
$num_keys = false !== $path_keys ? count( $path_keys ) : 0;
$val = $arguments;
for ( $i = 0; $i < $num_keys; $i++ ) {

View File

@@ -64,7 +64,25 @@ class MailchimpScheduler {
return false;
}
$response = $this->make_request( $profile_data['store_email'] );
$country_code = WC()->countries->get_base_country();
$country_name = WC()->countries->countries[ $country_code ] ?? 'N/A';
$state = WC()->countries->get_base_state();
$state_name = WC()->countries->states[ $country_code ][ $state ] ?? 'N/A';
$address = array(
// Setting N/A for addr1, city, state, zipcode and country as they are
// required fields. Setting '' doesn't work.
'addr1' => 'N/A',
'addr2' => '',
'city' => 'N/A',
'state' => $state_name,
'zip' => 'N/A',
'country' => $country_name,
);
$response = $this->make_request( $profile_data['store_email'], $address );
if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
$this->handle_request_error();
return false;
@@ -85,10 +103,11 @@ class MailchimpScheduler {
*
* @internal
* @param string $store_email Email address to subscribe.
* @param array $address Store address.
*
* @return mixed
*/
public function make_request( $store_email ) {
public function make_request( $store_email, $address ) {
if ( true === defined( 'WP_ENVIRONMENT_TYPE' ) && 'development' === constant( 'WP_ENVIRONMENT_TYPE' ) ) {
$subscribe_endpoint = self::SUBSCRIBE_ENDPOINT_DEV;
} else {
@@ -101,7 +120,8 @@ class MailchimpScheduler {
'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
'method' => 'POST',
'body' => array(
'email' => $store_email,
'email' => $store_email,
'address' => $address,
),
)
);

View File

@@ -240,7 +240,19 @@ class Settings {
$settings['isWooPayEligible'] = WCPayPromotionInit::is_woopay_eligible();
$settings['gutenberg_version'] = defined( 'GUTENBERG_VERSION' ) ? constant( 'GUTENBERG_VERSION' ) : 0;
$has_gutenberg = is_plugin_active( 'gutenberg/gutenberg.php' );
$gutenberg_version = '';
if ( $has_gutenberg ) {
if ( defined( 'GUTENBERG_VERSION' ) ) {
$gutenberg_version = GUTENBERG_VERSION;
}
if ( ! $gutenberg_version ) {
$gutenberg_data = get_plugin_data( WP_PLUGIN_DIR . '/gutenberg/gutenberg.php' );
$gutenberg_version = $gutenberg_data['Version'];
}
}
$settings['gutenberg_version'] = $has_gutenberg ? $gutenberg_version : 0;
return $settings;
}

View File

@@ -36,6 +36,34 @@ class WCPaymentGatewayPreInstallWCPayPromotion extends \WC_Payment_Gateway {
$this->method_description = $wc_pay_spec->content;
$this->has_fields = false;
// Set the promotion pseudo-gateway support features.
// If the promotion spec provides the supports property, use it.
if ( property_exists( $wc_pay_spec, 'supports' ) ) {
$this->supports = $wc_pay_spec->supports;
} else {
// Otherwise, use the default supported features in line with WooPayments ones.
// We include all features here, even if some of them are behind settings, since this is for info only.
$this->supports = array(
// Regular features.
'products',
'refunds',
// Subscriptions features.
'subscriptions',
'multiple_subscriptions',
'subscription_cancellation',
'subscription_reactivation',
'subscription_suspension',
'subscription_amount_changes',
'subscription_date_changes',
'subscription_payment_method_change_admin',
'subscription_payment_method_change_customer',
'subscription_payment_method_change',
// Saved cards features.
'tokenization',
'add_payment_method',
);
}
// Get setting values.
$this->enabled = false;