Wrangle MarketingRecommendations as RemoteSpecsEngine (#44828)
* Fix potential recursion error, add MarketingRecommendations to extend RemoteSpecsEngine, add error handling, add tests * Changelog * Added spec evaluation and return default when empty * Update plugins/woocommerce/src/Admin/Features/MarketingRecommendations/Init.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> --------- Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
This commit is contained in:
parent
34a71f3963
commit
c601a0b197
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Wrangle MarketingRecommendations as RemoteSpecsEngine
|
|
@ -2,13 +2,15 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations;
|
namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marketing Recommendations engine.
|
* Marketing Recommendations engine.
|
||||||
* This goes through the specs and gets marketing recommendations.
|
* This goes through the specs and gets marketing recommendations.
|
||||||
*/
|
*/
|
||||||
class Init {
|
class Init extends RemoteSpecsEngine {
|
||||||
/**
|
/**
|
||||||
* Slug of the category specifying marketing extensions on the Woo.com store.
|
* Slug of the category specifying marketing extensions on the Woo.com store.
|
||||||
*
|
*
|
||||||
|
@ -54,6 +56,29 @@ class Init {
|
||||||
return $specs;
|
return $specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process specs.
|
||||||
|
*
|
||||||
|
* @param array|null $specs Marketing recommendations spec array.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function evaluate_specs( array $specs = null ) {
|
||||||
|
$suggestions = array();
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
foreach ( $specs as $spec ) {
|
||||||
|
try {
|
||||||
|
$suggestions[] = self::object_to_array( $spec );
|
||||||
|
} catch ( \Throwable $e ) {
|
||||||
|
$errors[] = $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'suggestions' => $suggestions,
|
||||||
|
'errors' => $errors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load recommended plugins from Woo.com
|
* Load recommended plugins from Woo.com
|
||||||
|
@ -62,13 +87,29 @@ class Init {
|
||||||
*/
|
*/
|
||||||
public static function get_recommended_plugins(): array {
|
public static function get_recommended_plugins(): array {
|
||||||
$specs = self::get_specs();
|
$specs = self::get_specs();
|
||||||
$result = array();
|
$results = self::evaluate_specs( $specs );
|
||||||
|
|
||||||
foreach ( $specs as $spec ) {
|
$specs_to_return = $results['suggestions'];
|
||||||
$result[] = self::object_to_array( $spec );
|
$specs_to_save = null;
|
||||||
|
|
||||||
|
if ( empty( $specs_to_return ) ) {
|
||||||
|
// When suggestions is empty, replace it with defaults and save for 3 hours.
|
||||||
|
$specs_to_save = DefaultMarketingRecommendations::get_all();
|
||||||
|
$specs_to_return = self::evaluate_specs( $specs_to_save )['suggestions'];
|
||||||
|
} elseif ( count( $results['errors'] ) > 0 ) {
|
||||||
|
// When suggestions is not empty but has errors, save it for 3 hours.
|
||||||
|
$specs_to_save = $specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
if ( $specs_to_save ) {
|
||||||
|
MarketingRecommendationsDataSourcePoller::get_instance()->set_specs_transient( $specs_to_save, 3 * HOUR_IN_SECONDS );
|
||||||
|
}
|
||||||
|
$errors = $results['errors'];
|
||||||
|
if ( ! empty( $errors ) ) {
|
||||||
|
self::log_errors( $errors );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $specs_to_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,16 +180,22 @@ class Init {
|
||||||
* This is used to convert the specs to an array so that they can be returned by the API.
|
* This is used to convert the specs to an array so that they can be returned by the API.
|
||||||
*
|
*
|
||||||
* @param mixed $obj Object to convert.
|
* @param mixed $obj Object to convert.
|
||||||
|
* @param array &$visited Reference to an array keeping track of all seen objects to detect circular references.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function object_to_array( $obj ) {
|
public static function object_to_array( $obj, &$visited = array() ) {
|
||||||
if ( is_object( $obj ) ) {
|
if ( is_object( $obj ) ) {
|
||||||
|
if ( in_array( $obj, $visited, true ) ) {
|
||||||
|
// Circular reference detected.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$visited[] = $obj;
|
||||||
$obj = (array) $obj;
|
$obj = (array) $obj;
|
||||||
}
|
}
|
||||||
if ( is_array( $obj ) ) {
|
if ( is_array( $obj ) ) {
|
||||||
$new = array();
|
$new = array();
|
||||||
foreach ( $obj as $key => $val ) {
|
foreach ( $obj as $key => $val ) {
|
||||||
$new[ $key ] = self::object_to_array( $val );
|
$new[ $key ] = self::object_to_array( $val, $visited );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$new = $obj;
|
$new = $obj;
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Tests\Internal\Admin\ShippingPartnerSuggestions;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Admin\DataSourcePoller;
|
||||||
|
use Automattic\WooCommerce\Admin\Features\MarketingRecommendations\Init;
|
||||||
|
use Automattic\WooCommerce\Admin\Features\MarketingRecommendations\DefaultMarketingRecommendations;
|
||||||
|
use Automattic\WooCommerce\Admin\Features\MarketingRecommendations\MarketingRecommendationsDataSourcePoller;
|
||||||
|
use WC_Unit_Test_Case;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class WC_Admin_Tests_MarketingRecommendations_Init
|
||||||
|
*
|
||||||
|
* @covers \Automattic\WooCommerce\Admin\Features\MarketingRecommendations\Init
|
||||||
|
*/
|
||||||
|
class InitTest extends WC_Unit_Test_Case {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up.
|
||||||
|
*/
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->user = $this->factory->user->create(
|
||||||
|
array(
|
||||||
|
'role' => 'administrator',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
delete_option( 'woocommerce_show_marketplace_suggestions' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tear down.
|
||||||
|
*/
|
||||||
|
public function tearDown(): void {
|
||||||
|
parent::tearDown();
|
||||||
|
MarketingRecommendationsDataSourcePoller::get_instance()->delete_specs_transient();
|
||||||
|
remove_all_filters( 'transient_woocommerce_admin_' . MarketingRecommendationsDataSourcePoller::ID . '_specs' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that default specs are provided when remote sources don't exist.
|
||||||
|
*/
|
||||||
|
public function test_get_default_specs() {
|
||||||
|
remove_all_filters( 'transient_woocommerce_admin_' . MarketingRecommendationsDataSourcePoller::ID . '_specs' );
|
||||||
|
add_filter(
|
||||||
|
DataSourcePoller::FILTER_NAME,
|
||||||
|
function() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$specs = Init::get_specs();
|
||||||
|
$defaults = DefaultMarketingRecommendations::get_all();
|
||||||
|
remove_all_filters( DataSourcePoller::FILTER_NAME );
|
||||||
|
$this->assertEquals( $defaults, $specs );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that specs are read from cache when they exist.
|
||||||
|
*/
|
||||||
|
public function test_specs_transient() {
|
||||||
|
set_transient(
|
||||||
|
'woocommerce_admin_' . MarketingRecommendationsDataSourcePoller::ID . '_specs',
|
||||||
|
array(
|
||||||
|
'en_US' => array(
|
||||||
|
array(
|
||||||
|
'id' => 'mock1',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'id' => 'mock2',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$specs = Init::get_specs();
|
||||||
|
$this->assertCount( 2, $specs );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that recursive objects does not cause an error and returns null for the recursive child.
|
||||||
|
*/
|
||||||
|
public function test_matching_suggestions() {
|
||||||
|
$node1 = new \StdClass();
|
||||||
|
$node2 = new \StdClass();
|
||||||
|
$node1->child = $node2;
|
||||||
|
$node2->child = $node1;
|
||||||
|
$result = Init::object_to_array( $node1 );
|
||||||
|
$this->assertEquals( array( 'child' => array( 'child' => null ) ), $result );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue