Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
8.70% covered (danger)
8.70%
8 / 92
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_JSON_API_Plugins_New_Endpoint
20.51% covered (danger)
20.51%
8 / 39
33.33% covered (danger)
33.33%
1 / 3
112.43
0.00% covered (danger)
0.00%
0 / 1
 validate_call
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 validate_input
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 install
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
72
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3use Automattic\Jetpack\Automatic_Install_Skin;
4
5if ( ! defined( 'ABSPATH' ) ) {
6    exit( 0 );
7}
8
9require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
10require_once ABSPATH . 'wp-admin/includes/file.php';
11
12/**
13 * Plugins new endpoint class.
14 *
15 * POST /sites/%s/plugins/new
16 *
17 * @phan-constructor-used-for-side-effects
18 */
19class Jetpack_JSON_API_Plugins_New_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
20    use Jetpack_JSON_API_Attachment_Ownership_Trait;
21
22    /**
23     * Needed capabilities.
24     *
25     * @var string
26     */
27    protected $needed_capabilities = 'install_plugins';
28
29    /**
30     * The action.
31     *
32     * @var string
33     */
34    protected $action = 'install';
35
36    /**
37     * Validate call.
38     *
39     * @param int    $_blog_id - the blog ID.
40     * @param string $capability - the capability.
41     * @param bool   $check_manage_active - check if manage is active.
42     *
43     * @return bool|WP_Error a WP_Error object or true if things are good.
44     */
45    protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) {
46        $validate = parent::validate_call( $_blog_id, $capability, $check_manage_active );
47        if ( is_wp_error( $validate ) ) {
48
49            // Lets delete the attachment... if the user doesn't have the right permissions to do things.
50            // Only clean up an upload the caller actually owns. This runs *after* the capability check
51            // has already failed, so without the ownership guard any connected user could name someone
52            // else's attachment and have it hard-deleted on their behalf.
53            $args = $this->input();
54            if ( isset( $args['zip'][0]['id'] ) && is_scalar( $args['zip'][0]['id'] ) ) {
55                $attachment_id = (int) $args['zip'][0]['id'];
56                if ( true === $this->validate_attachment_ownership( $attachment_id ) ) {
57                    wp_delete_attachment( $attachment_id, true );
58                }
59            }
60        }
61
62        return $validate;
63    }
64
65    /**
66     * No need to try to validate the plugin since we didn't pass one in.
67     *
68     * @param string $plugin - the plugin we're validating.
69     */
70    protected function validate_input( $plugin ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
71        $this->bulk    = false;
72        $this->plugins = array();
73    }
74
75    /**
76     * Install the plugin.
77     *
78     * @return bool|WP_Error
79     */
80    public function install() {
81        $args = $this->input();
82
83        if ( isset( $args['zip'][0]['id'] ) ) {
84            $plugin_attachment_id = $args['zip'][0]['id'];
85            $local_file           = get_attached_file( $plugin_attachment_id );
86            if ( ! $local_file ) {
87                return new WP_Error( 'local-file-does-not-exist' );
88            }
89            $skin     = new Automatic_Install_Skin();
90            $upgrader = new Plugin_Upgrader( $skin );
91
92            $pre_install_plugin_list = get_plugins();
93            $result                  = $upgrader->install( $local_file );
94
95            // clean up.
96            wp_delete_attachment( $plugin_attachment_id, true );
97
98            if ( is_wp_error( $result ) ) {
99                return $result;
100            }
101
102            $after_install_plugin_list = get_plugins();
103            $plugin                    = array_values( array_diff( array_keys( $after_install_plugin_list ), array_keys( $pre_install_plugin_list ) ) );
104
105            if ( ! $result ) {
106                $error_code = $skin->get_main_error_code();
107                $message    = $skin->get_main_error_message();
108                if ( empty( $message ) ) {
109                    $message = __( 'An unknown error occurred during installation', 'jetpack' );
110                }
111
112                if ( 'download_failed' === $error_code ) {
113                    $error_code = 'no_package';
114                }
115
116                return new WP_Error( $error_code, $message, 400 );
117            }
118
119            if ( empty( $plugin ) ) {
120                return new WP_Error( 'plugin_already_installed' );
121            }
122
123            $this->plugins           = $plugin;
124            $this->log[ $plugin[0] ] = $upgrader->skin->get_upgrade_messages();
125
126            return true;
127        }
128
129        return new WP_Error( 'no_plugin_installed' );
130    }
131}
132
133// POST /sites/%s/plugins/new
134new Jetpack_JSON_API_Plugins_New_Endpoint(
135    array(
136        'description'             => 'Install a plugin to a Jetpack site by uploading a zip file',
137        'group'                   => '__do_not_document',
138        'stat'                    => 'plugins:new',
139        'min_version'             => '1',
140        'max_version'             => '1.1',
141        'method'                  => 'POST',
142        'path'                    => '/sites/%s/plugins/new',
143        'path_labels'             => array(
144            '$site' => '(int|string) Site ID or domain',
145        ),
146        'request_format'          => array(
147            'zip' => '(array) Reference to an uploaded plugin package zip file.',
148        ),
149        'response_format'         => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
150        'allow_jetpack_site_auth' => true,
151        'example_request_data'    => array(
152            'headers' => array(
153                'authorization' => 'Bearer YOUR_API_TOKEN',
154            ),
155        ),
156        'example_request'         => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/new',
157    )
158);
159
160new Jetpack_JSON_API_Plugins_New_Endpoint(
161    array(
162        'description'             => 'Install a plugin to a Jetpack site by uploading a zip file',
163        'group'                   => '__do_not_document',
164        'stat'                    => 'plugins:new',
165        'min_version'             => '1.2',
166        'method'                  => 'POST',
167        'path'                    => '/sites/%s/plugins/new',
168        'path_labels'             => array(
169            '$site' => '(int|string) Site ID or domain',
170        ),
171        'request_format'          => array(
172            'zip' => '(array) Reference to an uploaded plugin package zip file.',
173        ),
174        'response_format'         => Jetpack_JSON_API_Plugins_Endpoint::$_response_format_v1_2,
175        'allow_jetpack_site_auth' => true,
176        'example_request_data'    => array(
177            'headers' => array(
178                'authorization' => 'Bearer YOUR_API_TOKEN',
179            ),
180        ),
181        'example_request'         => 'https://public-api.wordpress.com/rest/v1.2/sites/example.wordpress.org/plugins/new',
182    )
183);