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;
|
||||
|
||||
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Marketing Recommendations engine.
|
||||
* 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.
|
||||
*
|
||||
|
@ -54,6 +56,29 @@ class Init {
|
|||
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
|
||||
|
@ -62,13 +87,29 @@ class Init {
|
|||
*/
|
||||
public static function get_recommended_plugins(): array {
|
||||
$specs = self::get_specs();
|
||||
$result = array();
|
||||
$results = self::evaluate_specs( $specs );
|
||||
|
||||
foreach ( $specs as $spec ) {
|
||||
$result[] = self::object_to_array( $spec );
|
||||
$specs_to_return = $results['suggestions'];
|
||||
$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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
protected static function object_to_array( $obj ) {
|
||||
public static function object_to_array( $obj, &$visited = array() ) {
|
||||
if ( is_object( $obj ) ) {
|
||||
if ( in_array( $obj, $visited, true ) ) {
|
||||
// Circular reference detected.
|
||||
return null;
|
||||
}
|
||||
$visited[] = $obj;
|
||||
$obj = (array) $obj;
|
||||
}
|
||||
if ( is_array( $obj ) ) {
|
||||
$new = array();
|
||||
foreach ( $obj as $key => $val ) {
|
||||
$new[ $key ] = self::object_to_array( $val );
|
||||
$new[ $key ] = self::object_to_array( $val, $visited );
|
||||
}
|
||||
} else {
|
||||
$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