Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.23% covered (warning)
69.23%
36 / 52
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Marketplace_Webhook_Response
69.23% covered (warning)
69.23%
36 / 52
25.00% covered (danger)
25.00%
1 / 4
8.43
0.00% covered (danger)
0.00%
0 / 1
 register_routes
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
1
 create_item_permissions_check
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 create_item
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 is_plugin_inactive
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Marketplace webhook response endpoint.
4 *
5 * @package endpoints
6 */
7
8/**
9 * Marketplace_Webhook_Response class.
10 */
11class Marketplace_Webhook_Response extends WP_REST_Controller {
12
13    /**
14     * Namespace.
15     *
16     * @var string
17     */
18    protected $namespace = 'wpcomsh/v1';
19
20    /**
21     * Rest base.
22     *
23     * @var string
24     */
25    protected $rest_base = 'marketplace/license';
26
27    /**
28     * Registers the routes for the objects of the controller.
29     */
30    public function register_routes() {
31        register_rest_route(
32            $this->namespace,
33            '/' . $this->rest_base,
34            array(
35                'args' => array(
36                    'event_type'   => array(
37                        'description' => 'Subscription event type.',
38                        'type'        => 'string',
39                        'enum'        => array(
40                            'provision_license',
41                            'subscription_cancelled',
42                            'subscription_created',
43                            'subscription_domain_changed',
44                            'subscription_refunded',
45                            'subscription_renewed',
46                        ),
47                        'required'    => true,
48                    ),
49                    'product_slug' => array(
50                        'description' => 'Slug of the product for which webhook was called for.',
51                        'required'    => true,
52                        'pattern'     => '[\w\-]+',
53                        'type'        => 'string',
54                    ),
55                    'payload'      => array(
56                        'description' => 'Arbitrary webhook response data.',
57                        'required'    => true,
58                    ),
59                ),
60                array(
61                    'methods'             => WP_REST_Server::CREATABLE,
62                    'callback'            => array( $this, 'create_item' ),
63                    'permission_callback' => array( $this, 'create_item_permissions_check' ),
64                ),
65            )
66        );
67    }
68
69    /**
70     * Checks if a given request has access to create items.
71     *
72     * @param WP_REST_Request $request Full details about the request.
73     * @return bool True if the request has access, false otherwise.
74     */
75    public function create_item_permissions_check( $request ) { //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, VariableAnalysis.CodeAnalysis.VariableAnalysis
76        return method_exists( 'Automattic\Jetpack\Connection\Manager', 'verify_xml_rpc_signature' ) && ( new Automattic\Jetpack\Connection\Manager() )->verify_xml_rpc_signature();
77    }
78
79    /**
80     * Runs a filter and passes licensing payload to enable vendors to set their license.
81     *
82     * @param WP_REST_Request $request Full details about the request.
83     * @return WP_REST_Response|WP_Error WP_REST_Response on success, WP_Error on failure.
84     */
85    public function create_item( $request ) {
86        $params = $request->get_json_params();
87
88        // Check if the plugin is active before running license provisioning filter.
89        $plugin = $params['software_slug'] ?? null;
90        if ( $plugin && $this->is_plugin_inactive( $plugin ) ) {
91            return new WP_Error( 'plugin_not_active', "The plugin '{$plugin}' is not active on the site.", array( 'status' => 400 ) );
92        }
93
94        /**
95         * Fires when the site receives a response from a marketplace product webhook request.
96         *
97         * @param bool|WP_Error $result     Result to return. True on success, WP_Error on failure.
98         * @param mixed         $payload    Arbitrary webhook response data.
99         * @param string        $event_type Subscription event type.
100         */
101        $result = apply_filters( 'wpcom_marketplace_webhook_response_' . $params['product_slug'], true, $params['payload'], $params['event_type'] );
102
103        return rest_ensure_response( $result );
104    }
105
106    /**
107     * Checks if a plugin is active based on the active_plugins option.
108     * It only uses the folder name of the plugin to check if it's active.
109     *
110     * @param string $plugin Plugin slug.
111     * @return bool true if the plugin is inactive, false otherwise.
112     */
113    private function is_plugin_inactive( $plugin ) {
114        $active_plugins = (array) get_option( 'active_plugins', array() );
115
116        $folder_names = array_map(
117            function ( $plugin ) {
118                $pieces = explode( '/', $plugin );
119
120                return $pieces[0];
121            },
122            $active_plugins
123        );
124
125        return ! in_array( $plugin, $folder_names, true );
126    }
127}