\n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: 8bit\n"
12 | "POT-Creation-Date: 2022-04-25T05:38:16+00:00\n"
13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14 | "X-Generator: WP-CLI 2.6.0\n"
15 | "X-Domain: git-updater-pro\n"
16 |
17 | #. Plugin Name of the plugin
18 | msgid "Git Updater PRO"
19 | msgstr ""
20 |
21 | #. Plugin URI of the plugin
22 | msgid "https://github.com/afragen/git-updater-pro"
23 | msgstr ""
24 |
25 | #. Description of the plugin
26 | msgid "A Git Updater add-on plugin that unlocks PRO features of branch switching, remote installation of plugins and themes, REST API, Webhooks, WP-CLI, and more."
27 | msgstr ""
28 |
29 | #. Author of the plugin
30 | msgid "Andy Fragen"
31 | msgstr ""
32 |
33 | #. translators: 1: branch name, 2: jQuery dropdown, 3: closing tag
34 | #: src/Git_Updater_PRO/Branch.php:312
35 | #: src/Git_Updater_PRO/Branch.php:414
36 | msgid "Current branch is `%1$s`, try %2$sanother version%3$s"
37 | msgstr ""
38 |
39 | #: src/Git_Updater_PRO/Branch.php:320
40 | msgid "Choose a Version"
41 | msgstr ""
42 |
43 | #: src/Git_Updater_PRO/Branch.php:363
44 | #: src/Git_Updater_PRO/Branch.php:521
45 | msgid "No previous tags to rollback to."
46 | msgstr ""
47 |
48 | #: src/Git_Updater_PRO/Branch.php:367
49 | msgid "Install"
50 | msgstr ""
51 |
52 | #: src/Git_Updater_PRO/Branch.php:453
53 | msgid "Switch to branch "
54 | msgstr ""
55 |
56 | #: src/Git_Updater_PRO/Branch.php:513
57 | msgid "Switch to release "
58 | msgstr ""
59 |
60 | #: src/Git_Updater_PRO/Install.php:104
61 | #: src/Git_Updater_PRO/Install.php:318
62 | msgid "Install Plugin"
63 | msgstr ""
64 |
65 | #: src/Git_Updater_PRO/Install.php:107
66 | #: src/Git_Updater_PRO/Install.php:321
67 | msgid "Install Theme"
68 | msgstr ""
69 |
70 | #: src/Git_Updater_PRO/Install.php:163
71 | msgid "A repository URI is required."
72 | msgstr ""
73 |
74 | #: src/Git_Updater_PRO/Install.php:338
75 | msgid "Plugin"
76 | msgstr ""
77 |
78 | #: src/Git_Updater_PRO/Install.php:341
79 | msgid "Theme"
80 | msgstr ""
81 |
82 | #. translators: variable is 'Plugin' or 'Theme'
83 | #: src/Git_Updater_PRO/Install.php:353
84 | msgid "Git Updater Install %s"
85 | msgstr ""
86 |
87 | #. translators: variable is 'Plugin' or 'Theme'
88 | #: src/Git_Updater_PRO/Install.php:361
89 | msgid "%s URI"
90 | msgstr ""
91 |
92 | #: src/Git_Updater_PRO/Install.php:369
93 | msgid "Repository Branch"
94 | msgstr ""
95 |
96 | #: src/Git_Updater_PRO/Install.php:377
97 | msgid "Remote Repository Host"
98 | msgstr ""
99 |
100 | #: src/Git_Updater_PRO/Install.php:412
101 | msgid "URI is case sensitive."
102 | msgstr ""
103 |
104 | #: src/Git_Updater_PRO/Install.php:427
105 | msgid "Enter branch name or leave empty for `master`"
106 | msgstr ""
107 |
108 | #: src/Git_Updater_PRO/Install.php:477
109 | msgid "Activate"
110 | msgstr ""
111 |
112 | #: src/Git_Updater_PRO/Install.php:489
113 | msgctxt "This refers to a network activation in a multisite installation"
114 | msgid "Network Enable"
115 | msgstr ""
116 |
117 | #: src/Git_Updater_PRO/Remote_Management.php:58
118 | #: src/Git_Updater_PRO/Remote_Management.php:123
119 | msgid "Remote Management"
120 | msgstr ""
121 |
122 | #: src/Git_Updater_PRO/Remote_Management.php:92
123 | msgid "Reset REST API key"
124 | msgstr ""
125 |
126 | #: src/Git_Updater_PRO/Remote_Management.php:106
127 | msgid "REST API key reset."
128 | msgstr ""
129 |
130 | #: src/Git_Updater_PRO/Remote_Management.php:146
131 | msgid "Remote Management services should just work for plugins like MainWP, ManageWP, InfiniteWP, iThemes Sync and others."
132 | msgstr ""
133 |
134 | #. translators: %s: Link to Git Remote Updater repository
135 | #: src/Git_Updater_PRO/Remote_Management.php:153
136 | msgid "The Git Remote Updater plugin was specifically created to make the remote management of Git Updater supported plugins and themes much simpler. You will need the Site URL and REST API key to use with Git Remote Updater settings."
137 | msgstr ""
138 |
139 | #. translators: 1: home URL, 2: REST API key
140 | #: src/Git_Updater_PRO/Remote_Management.php:163
141 | msgid "Site URL: %1$s REST API key: %2$s"
142 | msgstr ""
143 |
144 | #. translators: 1: Link to wiki, 2: RESTful API URL
145 | #: src/Git_Updater_PRO/Remote_Management.php:174
146 | msgid "Please refer to the Git Updater Knowledge Base for complete list of attributes."
147 | msgstr ""
148 |
149 | #. translators: link to REST API endpoint for updating
150 | #: src/Git_Updater_PRO/Remote_Management.php:184
151 | msgid "REST API endpoints for webhook updating begin at: %s"
152 | msgstr ""
153 |
154 | #. translators: link to REST API endpoint for branch resetting
155 | #: src/Git_Updater_PRO/Remote_Management.php:194
156 | msgid "REST API endpoints for webhook branch resetting begin at: %s"
157 | msgstr ""
158 |
--------------------------------------------------------------------------------
/src/Git_Updater_PRO/Remote_Management.php:
--------------------------------------------------------------------------------
1 | ensure_api_key_is_set();
34 | }
35 |
36 | /**
37 | * Ensure api key is set.
38 | */
39 | public function ensure_api_key_is_set() {
40 | if ( ! self::$api_key ) {
41 | update_site_option( 'git_updater_api_key', md5( uniqid( \wp_rand(), true ) ) );
42 | }
43 | }
44 |
45 | /**
46 | * Load needed action/filter hooks.
47 | */
48 | public function load_hooks() {
49 | add_action( 'admin_init', [ $this, 'remote_management_page_init' ] );
50 |
51 | $this->add_settings_tabs();
52 | }
53 |
54 | /**
55 | * Adds Remote Management tab to Settings page.
56 | */
57 | public function add_settings_tabs() {
58 | $install_tabs = [ 'git_updater_remote_management' => esc_html__( 'Remote Management', 'git-updater-pro' ) ];
59 | add_filter(
60 | 'gu_add_settings_tabs',
61 | function ( $tabs ) use ( $install_tabs ) {
62 | return array_merge( $tabs, $install_tabs );
63 | }
64 | );
65 | add_filter(
66 | 'gu_add_admin_page',
67 | function ( $tab, $action ) {
68 | $this->add_admin_page( $tab, $action );
69 | },
70 | 10,
71 | 2
72 | );
73 | }
74 |
75 | /**
76 | * Add Settings page data via action hook.
77 | *
78 | * @uses 'gu_add_admin_page' action hook
79 | *
80 | * @param string $tab Tab name.
81 | * @param string $action Form action.
82 | */
83 | public function add_admin_page( $tab, $action ) {
84 | if ( 'git_updater_remote_management' === $tab ) {
85 | $action = add_query_arg( 'tab', $tab, $action );
86 | $this->admin_page_notices(); ?>
87 |
90 | true ], $action ); ?>
91 |
94 | ';
106 | esc_html_e( 'REST API key reset.', 'git-updater-pro' );
107 | echo '
';
108 | }
109 | }
110 |
111 | /**
112 | * Settings for Remote Management.
113 | */
114 | public function remote_management_page_init() {
115 | register_setting(
116 | 'git_updater_remote_management',
117 | 'git_updater_remote_settings',
118 | [ $this, 'sanitize' ]
119 | );
120 |
121 | add_settings_section(
122 | 'remote_management',
123 | esc_html__( 'Remote Management', 'git-updater-pro' ),
124 | [ $this, 'print_section_remote_management' ],
125 | 'git_updater_remote_settings'
126 | );
127 | }
128 |
129 | /**
130 | * Print the Remote Management text.
131 | */
132 | public function print_section_remote_management() {
133 | if ( empty( self::$api_key ) ) {
134 | $this->load_options();
135 | }
136 | $update_endpoint = add_query_arg(
137 | [ 'key' => self::$api_key ],
138 | home_url( 'wp-json/' . $this->get_class_vars( 'REST\REST_API', 'namespace' ) . '/update/' )
139 | );
140 | $branch_reset_endpoint = add_query_arg(
141 | [ 'key' => self::$api_key ],
142 | home_url( 'wp-json/' . $this->get_class_vars( 'REST\REST_API', 'namespace' ) . '/reset-branch/' )
143 | );
144 |
145 | echo '';
146 | esc_html_e( 'Remote Management services should just work for plugins like MainWP, ManageWP, InfiniteWP, iThemes Sync and others.', 'git-updater-pro' );
147 | echo '
';
148 |
149 | echo '';
150 | printf(
151 | wp_kses_post(
152 | /* translators: %s: Link to Git Remote Updater repository */
153 | __( 'The Git Remote Updater plugin was specifically created to make the remote management of Git Updater supported plugins and themes much simpler. You will need the Site URL and REST API key to use with Git Remote Updater settings.', 'git-updater-pro' )
154 | ),
155 | 'https://git-updater.com/knowledge-base/git-remote-updater/'
156 | );
157 | echo '
';
158 |
159 | echo '';
160 | printf(
161 | wp_kses_post(
162 | /* translators: 1: home URL, 2: REST API key */
163 | __( 'Site URL: %1$s REST API key: %2$s', 'git-updater-pro' )
164 | ),
165 | '' . esc_url( home_url() ) . ' ',
166 | '' . esc_attr( self::$api_key ) . ' '
167 | );
168 | echo '
';
169 |
170 | echo '';
171 | printf(
172 | wp_kses_post(
173 | /* translators: 1: Link to wiki, 2: RESTful API URL */
174 | __( 'Please refer to the Git Updater Knowledge Base for complete list of attributes.', 'git-updater-pro' )
175 | ),
176 | 'https://git-updater.com/knowledge-base/remote-management-restful-endpoints/'
177 | );
178 | echo '
';
179 |
180 | echo '';
181 | printf(
182 | wp_kses_post(
183 | /* translators: link to REST API endpoint for updating */
184 | __( 'REST API endpoints for webhook updating begin at: %s', 'git-updater-pro' )
185 | ),
186 | '' . esc_url( $update_endpoint ) . ' '
187 | );
188 | echo '
';
189 |
190 | echo '';
191 | printf(
192 | wp_kses_post(
193 | /* translators: link to REST API endpoint for branch resetting */
194 | __( 'REST API endpoints for webhook branch resetting begin at: %s', 'git-updater-pro' )
195 | ),
196 | '' . esc_url( $branch_reset_endpoint ) . ' '
197 | );
198 | echo '
';
199 | }
200 |
201 | /**
202 | * Reset RESTful API key.
203 | * Deleting site option will cause it to be re-created.
204 | *
205 | * @return bool
206 | */
207 | public function reset_api_key() {
208 | // phpcs:disable WordPress.Security.NonceVerification.Recommended
209 | if ( isset( $_REQUEST['tab'], $_REQUEST['git_updater_reset_api_key'] )
210 | && 'git_updater_remote_management' === sanitize_title_with_dashes( wp_unslash( $_REQUEST['tab'] ) )
211 | ) {
212 | $_POST = $_REQUEST;
213 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
214 | $_POST['_wp_http_referer'] = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : null;
215 | // phpcs:enable
216 | delete_site_option( 'git_updater_api_key' );
217 |
218 | return true;
219 | }
220 |
221 | return false;
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/Git_Updater_PRO/WP_CLI/CLI_Integration.php:
--------------------------------------------------------------------------------
1 |
36 | * : URI to the repo being installed
37 | *
38 | * [--branch=]
39 | * : String indicating the branch name to be installed
40 | * ---
41 | * default: master
42 | * ---
43 | *
44 | * [--token=]
45 | * : GitHub, Bitbucket, GitLab, or Gitea access token if not already saved
46 | * Bitbucket pseudo-token in format `username:password`
47 | *
48 | * [--slug=]
49 | * : Optional string indicating the plugin slug
50 |
51 | * [--github]
52 | * : Optional to denote a GitHub repository
53 | * Required when installing from a self-hosted GitHub installation
54 | *
55 | * [--bitbucket]
56 | * : Optional switch to denote a Bitbucket repository
57 | * Required when installing from a self-hosted Bitbucket installation
58 | *
59 | * [--gitlab]
60 | * : Optional switch to denote a GitLab repository
61 | * Required when installing from a self-hosted GitLab installation
62 | *
63 | * [--gitea]
64 | * : Optional switch to denote a Gitea repository
65 | * Required when installing from a Gitea installation
66 | *
67 | * [--gist]
68 | * : Optional switch to denote a GitHub Gist repository
69 | * Required when installing from a GitHub Gist installation
70 | *
71 | * [--zipfile]
72 | * : Optional switch to denote a Zipfile
73 | * Required when installing from a Zipfile
74 | *
75 | * ## EXAMPLES
76 | *
77 | * wp plugin install-git https://github.com/afragen/my-plugin
78 | *
79 | * wp plugin install-git https://github.com/afragen/my-plugin --branch=develop --github
80 | *
81 | * wp plugin install-git https://bitbucket.org/afragen/my-private-plugin --token=username:password
82 | *
83 | * wp plugin install-git https://github.com/afragen/my-private-plugin --token=lks9823evalki
84 | *
85 | * @param array $args An array of $uri.
86 | * @param array $assoc_args Array of optional arguments.
87 | *
88 | * @subcommand install-git
89 | */
90 | public function install_plugin( $args, $assoc_args ) {
91 | list($uri) = $args;
92 | $cli_config = $this->process_args( $uri, $assoc_args );
93 | ( new Install() )->install( 'plugin', $cli_config );
94 |
95 | $headers = parse_url( $uri, PHP_URL_PATH );
96 | $slug = basename( $headers );
97 | $this->process_branch( $cli_config, $slug );
98 | WP_CLI::success( sprintf( 'Plugin %s installed.', "'{$slug}'" ) );
99 | }
100 |
101 | /**
102 | * Install theme from GitHub, Bitbucket, GitLab, Gitea, Gist, or Zipfile using Git Updater PRO. Appropriate API plugin is required.
103 | *
104 | * ## OPTIONS
105 | *
106 | *
107 | * : URI to the repo being installed
108 | *
109 | * [--branch=]
110 | * : String indicating the branch name to be installed
111 | * ---
112 | * default: master
113 | * ---
114 | *
115 | * [--token=]
116 | * : GitHub, Bitbucket, GitLab, or Gitea access token if not already saved
117 | * Bitbucket pseudo-token in format `username:password`
118 | *
119 | * [--slug=]
120 | * : Optional string indicating the theme slug
121 | *
122 | * [--github]
123 | * : Optional to denote a GitHub repository
124 | * Required when installing from a self-hosted GitHub installation
125 | *
126 | * [--bitbucket]
127 | * : Optional switch to denote a Bitbucket repository
128 | * Required when installing from a self-hosted Bitbucket installation
129 | *
130 | * [--gitlab]
131 | * : Optional switch to denote a GitLab repository
132 | * Required when installing from a self-hosted GitLab installation
133 | *
134 | * [--gitea]
135 | * : Optional switch to denote a Gitea repository
136 | * Required when installing from a Gitea installation
137 | *
138 | * [--gist]
139 | * : Optional switch to denote a GitHub Gist repository
140 | * Required when installing from a GitHub Gist installation
141 | *
142 | * [--zipfile]
143 | * : Optional switch to denote a Zipfile
144 | * Required when installing from a Zipfile
145 | *
146 | * ## EXAMPLES
147 | *
148 | * wp theme install-git https://github.com/afragen/my-theme
149 | *
150 | * wp theme install-git https://bitbucket.org/afragen/my-theme --branch=develop --bitbucket
151 | *
152 | * wp theme install-git https://bitbucket.org/afragen/my-private-theme --token=username:password
153 | *
154 | * wp theme install-git https://github.com/afragen/my-private-theme --token=lks9823evalki
155 | *
156 | * @param array $args An array of $uri.
157 | * @param array $assoc_args Array of optional arguments.
158 | *
159 | * @subcommand install-git
160 | */
161 | public function install_theme( $args, $assoc_args ) {
162 | list($uri) = $args;
163 | $cli_config = $this->process_args( $uri, $assoc_args );
164 | ( new Install() )->install( 'theme', $cli_config );
165 |
166 | $headers = parse_url( $uri, PHP_URL_PATH );
167 | $slug = basename( $headers );
168 | $this->process_branch( $cli_config, $slug );
169 | WP_CLI::success( sprintf( 'Theme %s installed.', "'$slug'" ) );
170 | }
171 |
172 | /**
173 | * Branch switching via WP-CLI.
174 | *
175 | * ## OPTIONS
176 | *
177 | *
178 | * : Slug of the repo being installed
179 | *
180 | *
181 | * : String indicating the branch name to be installed
182 | * ---
183 | * default: master
184 | * ---
185 | *
186 | * ## EXAMPLES
187 | *
188 | * wp plugin branch-switch
189 | *
190 | * wp theme branch-switch
191 | *
192 | * @param string $args Repository slug.
193 | *
194 | * @subcommand branch-switch
195 | */
196 | public function branch_switch( $args = null ) {
197 | list( $slug, $branch ) = $args;
198 | $plugins = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs();
199 | $themes = Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs();
200 | $configs = array_merge( $plugins, $themes );
201 |
202 | $repo = isset( $configs[ $slug ] ) ? $configs[ $slug ] : false;
203 | if ( ! $repo ) {
204 | WP_CLI::error( sprintf( 'There is no repository with slug: %s installed.', "'{$slug}'" ) );
205 | exit;
206 | }
207 |
208 | $rest_api_key = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_class_vars( 'Remote_Management', 'api_key' );
209 | $api_url = add_query_arg(
210 | [
211 | 'key' => $rest_api_key,
212 | $repo->type => $repo->slug,
213 | 'branch' => $branch,
214 | 'override' => true,
215 | ],
216 | home_url( 'wp-json/' . Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_class_vars( 'REST\REST_API', 'namespace' ) . '/update/' )
217 | );
218 | $response = wp_remote_get( $api_url, [ 'timeout' => 10 ] );
219 |
220 | if ( is_wp_error( $response ) ) {
221 | WP_CLI::error( $response->errors['http_request_failed'][0] );
222 | exit;
223 | }
224 | if ( 200 === \wp_remote_retrieve_response_code( $response ) ) {
225 | WP_CLI::success( $response['body'] );
226 | } else {
227 | WP_CLI::warning( 'Branch switching resulted in an error.' );
228 | WP_CLI::warning( $response['body'] );
229 | }
230 | }
231 |
232 | /**
233 | * Process WP-CLI config data.
234 | *
235 | * @param string $uri URI to process.
236 | * @param array $assoc_args Args to process.
237 | *
238 | * @return array $cli_config
239 | */
240 | private function process_args( $uri, $assoc_args ) {
241 | $token = isset( $assoc_args['token'] ) ? $assoc_args['token'] : false;
242 | $cli_config = [];
243 | $cli_config['uri'] = $uri;
244 | $cli_config['private'] = $token;
245 | $cli_config['branch'] = isset( $assoc_args['branch'] ) ? $assoc_args['branch'] : 'master';
246 | $cli_config['slug'] = isset( $assoc_args['slug'] ) ? $assoc_args['slug'] : null;
247 |
248 | switch ( $assoc_args ) {
249 | case isset( $assoc_args['github'] ):
250 | $cli_config['git'] = 'github';
251 | break;
252 | case isset( $assoc_args['bitbucket'] ):
253 | $cli_config['git'] = 'bitbucket';
254 | break;
255 | case isset( $assoc_args['gitlab'] ):
256 | $cli_config['git'] = 'gitlab';
257 | break;
258 | case isset( $assoc_args['gitea'] ):
259 | $cli_config['git'] = 'gitea';
260 | break;
261 | case isset( $assoc_args['gist'] ):
262 | $cli_config['git'] = 'gist';
263 | break;
264 | case isset( $assoc_args['zipfile'] ):
265 | $cli_config['git'] = 'zipfile';
266 | break;
267 | }
268 |
269 | return $cli_config;
270 | }
271 |
272 | /**
273 | * Process branch setting for WP-CLI.
274 | *
275 | * @param array $cli_config Config args.
276 | * @param string $slug Repository slug.
277 | */
278 | private function process_branch( $cli_config, $slug ) {
279 | $branch_data['git_updater_branch'] = $cli_config['branch'];
280 | $branch_data['repo'] = $slug;
281 |
282 | ( new Branch() )->set_branch_on_install( $branch_data );
283 | }
284 | }
285 |
286 | /**
287 | * Use custom installer skins to display error messages.
288 | */
289 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
290 |
291 | /**
292 | * Class GitHub_Upgrader_CLI_Plugin_Installer_Skin
293 | */
294 | // phpcs:ignore
295 | class CLI_Plugin_Installer_Skin extends \Plugin_Installer_Skin {
296 |
297 | /** Skin feeback. */
298 | public function header() {
299 | }
300 |
301 | /** Skin footer. */
302 | public function footer() {
303 | }
304 |
305 | /**
306 | * Skin error.
307 | *
308 | * @param \stdClass $errors Error object.
309 | *
310 | * @return void
311 | */
312 | public function error( $errors ) {
313 | if ( is_wp_error( $errors ) ) {
314 | WP_CLI::error( $errors->get_error_message() . "\n" . $errors->get_error_data() );
315 | }
316 | }
317 |
318 | /**
319 | * Skin feedback.
320 | *
321 | * @param string $string Feedback message.
322 | * @param array ...$args Feedback args.
323 | *
324 | * @return void
325 | */
326 | public function feedback( $string, ...$args ) {
327 | }
328 | }
329 |
330 | /**
331 | * Class GitHub_Upgrader_CLI_Theme_Installer_Skin
332 | */
333 | // phpcs:ignore
334 | class CLI_Theme_Installer_Skin extends \Theme_Installer_Skin {
335 | /** Skin header. */
336 | public function header() {
337 | }
338 |
339 | /** Skin footer. */
340 | public function footer() {
341 | }
342 |
343 | /**
344 | * Skin error.
345 | *
346 | * @param \stdClass $errors Error object.
347 | *
348 | * @return void
349 | */
350 | public function error( $errors ) {
351 | if ( is_wp_error( $errors ) ) {
352 | WP_CLI::error( $errors->get_error_message() . "\n" . $errors->get_error_data() );
353 | }
354 | }
355 |
356 | /**
357 | * Skin feedback.
358 | *
359 | * @param string $string Feedback message.
360 | * @param array ...$args Feedback args.
361 | *
362 | * @return void
363 | */
364 | public function feedback( $string, ...$args ) {
365 | }
366 | }
367 |
--------------------------------------------------------------------------------
/src/Git_Updater_PRO/REST/REST_API.php:
--------------------------------------------------------------------------------
1 | true,
47 | 'methods' => \WP_REST_Server::READABLE,
48 | 'callback' => [ $this, 'test' ],
49 | 'permission_callback' => '__return_true',
50 | ]
51 | );
52 |
53 | register_rest_route(
54 | 'git-updater',
55 | 'namespace',
56 | [
57 | 'show_in_index' => true,
58 | 'methods' => \WP_REST_Server::READABLE,
59 | 'callback' => [ $this, 'get_namespace' ],
60 | 'permission_callback' => '__return_true',
61 | ]
62 | );
63 |
64 | register_rest_route(
65 | self::$namespace,
66 | 'repos',
67 | [
68 | 'show_in_index' => false,
69 | 'methods' => \WP_REST_Server::READABLE,
70 | 'callback' => [ $this, 'get_remote_repo_data' ],
71 | 'permission_callback' => '__return_true',
72 | 'args' => [
73 | 'key' => [
74 | 'default' => null,
75 | 'required' => true,
76 | 'validate_callback' => 'sanitize_text_field',
77 | ],
78 | ],
79 | ]
80 | );
81 |
82 | register_rest_route(
83 | self::$namespace,
84 | 'plugins-api',
85 | [
86 | 'show_in_index' => true,
87 | 'methods' => \WP_REST_Server::READABLE,
88 | 'callback' => [ $this, 'get_plugins_api_data' ],
89 | 'permission_callback' => '__return_true',
90 | 'args' => [
91 | 'slug' => [
92 | 'default' => false,
93 | 'required' => true,
94 | 'validate_callback' => 'sanitize_title_with_dashes',
95 | ],
96 | ],
97 | ]
98 | );
99 |
100 | $update_args = [
101 | 'key' => [
102 | 'default' => false,
103 | 'required' => true,
104 | 'validate_callback' => 'sanitize_text_field',
105 | ],
106 | 'plugin' => [
107 | 'default' => false,
108 | 'validate_callback' => 'sanitize_title_with_dashes',
109 | ],
110 | 'theme' => [
111 | 'default' => false,
112 | 'validate_callback' => 'sanitize_title_with_dashes',
113 | ],
114 | 'tag' => [
115 | 'default' => false,
116 | 'validate_callback' => 'sanitize_text_field',
117 | ],
118 | 'branch' => [
119 | 'default' => false,
120 | 'validate_callback' => 'sanitize_text_field',
121 | ],
122 | 'committish' => [
123 | 'default' => false,
124 | 'validate_callback' => 'sanitize_text_field',
125 | ],
126 | 'override' => [
127 | 'default' => false,
128 | ],
129 | ];
130 |
131 | register_rest_route(
132 | self::$namespace,
133 | 'update',
134 | [
135 | [
136 | 'show_in_index' => true,
137 | 'methods' => \WP_REST_Server::READABLE,
138 | 'callback' => [ new REST_Update(), 'process_request' ],
139 | 'permission_callback' => '__return_true',
140 | 'args' => $update_args,
141 | ],
142 | [
143 | 'show_in_index' => false,
144 | 'methods' => \WP_REST_Server::CREATABLE,
145 | 'callback' => [ new REST_Update(), 'process_request' ],
146 | 'permission_callback' => '__return_true',
147 | 'args' => $update_args,
148 | ],
149 | ]
150 | );
151 |
152 | register_rest_route(
153 | self::$namespace,
154 | 'reset-branch',
155 | [
156 | 'show_in_index' => true,
157 | 'methods' => \WP_REST_Server::READABLE,
158 | 'callback' => [ $this, 'reset_branch' ],
159 | 'permission_callback' => '__return_true',
160 | 'args' => [
161 | 'key' => [
162 | 'default' => null,
163 | 'required' => true,
164 | 'validate_callback' => 'sanitize_text_field',
165 | ],
166 | 'plugin' => [
167 | 'default' => false,
168 | 'validate_callback' => 'sanitize_title_with_dashes',
169 | ],
170 | 'theme' => [
171 | 'default' => false,
172 | 'validate_callback' => 'sanitize_title_with_dashes',
173 | ],
174 | ],
175 | ]
176 | );
177 |
178 | register_rest_route(
179 | 'github-updater/v1',
180 | 'test',
181 | [
182 | 'methods' => \WP_REST_Server::READABLE,
183 | 'callback' => [ $this, 'deprecated' ],
184 | 'permission_callback' => '__return_true',
185 | ]
186 | );
187 |
188 | register_rest_route(
189 | 'github-updater/v1',
190 | 'repos',
191 | [
192 | 'methods' => \WP_REST_Server::READABLE,
193 | 'callback' => [ $this, 'deprecated' ],
194 | 'permission_callback' => '__return_true',
195 | ]
196 | );
197 | register_rest_route(
198 | 'github-updater/v1',
199 | 'update',
200 | [
201 | [
202 | 'show_in_index' => false,
203 | 'methods' => \WP_REST_Server::READABLE,
204 | 'callback' => [ new REST_Update(), 'process_request' ],
205 | 'permission_callback' => '__return_true',
206 | 'args' => $update_args,
207 | ],
208 | [
209 | 'show_in_index' => false,
210 | 'methods' => \WP_REST_Server::CREATABLE,
211 | 'callback' => [ new REST_Update(), 'process_request' ],
212 | 'permission_callback' => '__return_true',
213 | 'args' => $update_args,
214 | ],
215 | ]
216 | );
217 | }
218 |
219 | /**
220 | * Return deprecation notice.
221 | *
222 | * @return array
223 | */
224 | public function deprecated() {
225 | $namespace = self::$namespace;
226 | return [
227 | 'success' => false,
228 | 'error' => "The 'github-updater/v1' REST route namespace has been deprecated. Please use '{$namespace}'",
229 | ];
230 | }
231 |
232 | /**
233 | * Simple REST endpoint return.
234 | *
235 | * @return string
236 | */
237 | public function test() {
238 | return 'Connected to Git Updater PRO!';
239 | }
240 |
241 | /**
242 | * Return current REST namespace.
243 | *
244 | * @return array
245 | */
246 | public function get_namespace() {
247 | return [ 'namespace' => self::$namespace ];
248 | }
249 |
250 | /**
251 | * Get repo data for Git Remote Updater.
252 | *
253 | * @param \WP_REST_Request $request REST API response.
254 | *
255 | * @return array
256 | */
257 | public function get_remote_repo_data( \WP_REST_Request $request ) {
258 | // Test for API key and exit if incorrect.
259 | if ( $this->get_class_vars( 'Remote_Management', 'api_key' ) !== $request->get_param( 'key' ) ) {
260 | return [ 'error' => 'Bad API key. No repo data for you.' ];
261 | }
262 | $gu_plugins = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs();
263 | $gu_themes = Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs();
264 | $gu_tokens = array_merge( $gu_plugins, $gu_themes );
265 |
266 | $plugin_updates = get_site_option( 'git_updater_plugin_updates' );
267 | $theme_updates = get_site_option( 'git_updater_theme_updates' );
268 |
269 | $site = $request->get_header( 'host' );
270 | $api_url = add_query_arg(
271 | [
272 | 'key' => $request->get_param( 'key' ),
273 | ],
274 | home_url( 'wp-json/' . self::$namespace . '/update/' )
275 | );
276 | foreach ( $gu_tokens as $token ) {
277 | $update_package = false;
278 | if ( 'plugin' === $token->type && array_key_exists( $token->file, (array) $plugin_updates ) ) {
279 | $update_package = $plugin_updates[ $token->file ];
280 | }
281 | if ( 'theme' === $token->type && array_key_exists( $token->slug, (array) $theme_updates ) ) {
282 | $update_package = $theme_updates[ $token->slug ];
283 | }
284 | $slugs[] = [
285 | 'slug' => $token->slug,
286 | 'type' => $token->type,
287 | 'primary_branch' => $token->primary_branch,
288 | 'branch' => $token->branch,
289 | 'version' => $token->local_version,
290 | 'update_package' => $update_package,
291 | ];
292 | }
293 | $json = [
294 | 'sites' => [
295 | 'site' => $site,
296 | 'restful_start' => $api_url,
297 | 'slugs' => $slugs,
298 | ],
299 | ];
300 |
301 | return $json;
302 | }
303 |
304 | /**
305 | * Get specific repo plugin API data.
306 | *
307 | * Returns data consistent with `plugins_api()` request.
308 | *
309 | * @param \WP_REST_Request $request REST API response.
310 | *
311 | * @return array|\WP_Error
312 | */
313 | public function get_plugins_api_data( \WP_REST_Request $request ) {
314 | $slug = $request->get_param( 'slug' );
315 | if ( ! $slug ) {
316 | return (object) [ 'error' => 'The REST request likely has an invalid query argument.' ];
317 | }
318 | $repo_cache = $this->get_repo_cache( $slug );
319 | $gu_plugins = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs();
320 |
321 | if ( ! \array_key_exists( $slug, $gu_plugins ) ) {
322 | return (object) [ 'error' => 'Specified plugin does not exist.' ];
323 | }
324 |
325 | add_filter( 'gu_disable_wpcron', '__return_false' );
326 | $repo_data = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_remote_repo_meta( $gu_plugins[ $slug ] );
327 |
328 | if ( ! is_object( $repo_data ) ) {
329 | return (object) [ 'error' => 'Plugin data response is incorrect.' ];
330 | }
331 |
332 | $plugins_api_data = [
333 | 'name' => $repo_data->name,
334 | 'slug' => $repo_data->slug,
335 | 'git' => $repo_data->git,
336 | 'type' => $repo_data->type,
337 | 'version' => $repo_data->remote_version,
338 | 'author' => $repo_data->author,
339 | 'contributors' => $repo_data->contributors,
340 | 'requires' => $repo_data->requires,
341 | 'tested' => $repo_data->tested,
342 | 'requires_php' => $repo_data->requires_php,
343 | 'sections' => $repo_data->sections,
344 | // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
345 | 'short_description' => substr( strip_tags( trim( $repo_data->sections['description'] ) ), 0, 175 ) . '...',
346 | 'primary_branch' => $repo_data->primary_branch,
347 | 'branch' => $repo_data->branch,
348 | 'download_link' => $repo_data->download_link,
349 | 'banners' => $repo_data->banners,
350 | 'icons' => $repo_data->icons,
351 | 'last_updated' => $repo_data->last_updated,
352 | 'num_ratings' => $repo_data->num_ratings,
353 | 'rating' => $repo_data->rating,
354 | 'active_installs' => $repo_data->downloaded,
355 | ];
356 |
357 | if ( $repo_data->release_asset ) {
358 | if ( property_exists( $repo_cache['release_asset_response'], 'browser_download_url' ) ) {
359 | $plugins_api_data['download_link'] = $repo_cache['release_asset_response']->browser_download_url;
360 | $plugins_api_data['active_installs'] = $repo_cache['release_asset_response']->download_count;
361 | } elseif ( $repo_cache['release_asset'] ) {
362 | $plugins_api_data['download_link'] = $repo_cache['release_asset'];
363 | }
364 | }
365 |
366 | return $plugins_api_data;
367 | }
368 |
369 | /**
370 | * Reset branch of plugin/theme by removing from saved options.
371 | *
372 | * @param \WP_REST_Request $request REST API response.
373 | *
374 | * @throws \UnexpectedValueException Under multiple bad or missing params.
375 | * @return void
376 | */
377 | public function reset_branch( \WP_REST_Request $request ) {
378 | $rest_update = new Rest_Update();
379 | $start = microtime( true );
380 |
381 | try {
382 | // Test for API key and exit if incorrect.
383 | if ( $this->get_class_vars( 'Remote_Management', 'api_key' ) !== $request->get_param( 'key' ) ) {
384 | throw new \UnexpectedValueException( 'Bad API key. No branch reset for you.' );
385 | }
386 |
387 | $plugin_slug = $request->get_param( 'plugin' );
388 | $theme_slug = $request->get_param( 'theme' );
389 | $options = $this->get_class_vars( 'Base', 'options' );
390 | $slug = ! empty( $plugin_slug ) ? $plugin_slug : $theme_slug;
391 |
392 | if ( empty( $plugin_slug ) && empty( $theme_slug ) || ! isset( $options[ $slug ] ) ) {
393 | throw new \UnexpectedValueException( 'No plugin or theme specified for branch reset.' );
394 | }
395 |
396 | $this->set_repo_cache( 'current_branch', '', $slug );
397 | unset( $options[ "current_branch_$slug" ] );
398 | update_site_option( 'git_updater', $options );
399 |
400 | $response = [
401 | 'success' => true,
402 | 'messages' => 'Reset to primary branch complete.',
403 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification
404 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms',
405 | ];
406 | $rest_update->log_exit( $response, 200 );
407 |
408 | } catch ( \Exception $e ) {
409 | $response = [
410 | 'success' => false,
411 | 'messages' => $e->getMessage(),
412 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification
413 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms',
414 | ];
415 | $rest_update->log_exit( $response, 417 );
416 | }
417 | }
418 | }
419 |
--------------------------------------------------------------------------------
/src/Git_Updater_PRO/Install.php:
--------------------------------------------------------------------------------
1 | / directly from Git Updater.
30 | */
31 | class Install {
32 | use GU_Trait, Basic_Auth_Loader;
33 |
34 | /**
35 | * Class options.
36 | *
37 | * @var array
38 | */
39 | protected static $install = [];
40 |
41 | /**
42 | * Hold local copy of Git Updater options.
43 | *
44 | * @var mixed
45 | */
46 | private static $options;
47 |
48 | /**
49 | * Hold local copy of installed APIs.
50 | *
51 | * @var mixed
52 | */
53 | private static $installed_apis;
54 |
55 | /**
56 | * Hold local copy of git servers.
57 | *
58 | * @var mixed
59 | */
60 | private static $git_servers;
61 |
62 | /**
63 | * Constructor.
64 | */
65 | public function __construct() {
66 | self::$options = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'options' );
67 | self::$installed_apis = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'installed_apis' );
68 | self::$git_servers = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'git_servers' );
69 | }
70 |
71 | /**
72 | * Let's set up the Install tabs.
73 | * Need class-wp-upgrader.php for upgrade classes.
74 | *
75 | * @return void
76 | */
77 | public function run() {
78 | $this->load_js();
79 | $this->add_settings_tabs();
80 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
81 | }
82 |
83 | /**
84 | * Load javascript for Install.
85 | *
86 | * @return void
87 | */
88 | public function load_js() {
89 | add_action(
90 | 'admin_enqueue_scripts',
91 | function () {
92 | wp_register_script( 'gu-install', plugins_url( basename( dirname( __DIR__, 2 ) ) . '/js/gu-install-vanilla.js' ), [], $this->get_plugin_version(), true );
93 | wp_enqueue_script( 'gu-install' );
94 | }
95 | );
96 | }
97 |
98 | /**
99 | * Adds Install tabs to Settings page.
100 | */
101 | public function add_settings_tabs() {
102 | $install_tabs = [];
103 | if ( current_user_can( 'install_plugins' ) ) {
104 | $install_tabs['git_updater_install_plugin'] = esc_html__( 'Install Plugin', 'git-updater-pro' );
105 | }
106 | if ( current_user_can( 'install_themes' ) ) {
107 | $install_tabs['git_updater_install_theme'] = esc_html__( 'Install Theme', 'git-updater-pro' );
108 | }
109 | add_filter(
110 | 'gu_add_settings_tabs',
111 | function ( $tabs ) use ( $install_tabs ) {
112 | return array_merge( $tabs, $install_tabs );
113 | }
114 | );
115 | add_action(
116 | 'gu_add_admin_page',
117 | function ( $tab ) {
118 | $this->add_admin_page( $tab );
119 | }
120 | );
121 | }
122 |
123 | /**
124 | * Add Settings page data via action hook.
125 | *
126 | * @uses 'gu_add_admin_page' action hook
127 | *
128 | * @param string $tab Name of tab.
129 | */
130 | public function add_admin_page( $tab ) {
131 | if ( 'git_updater_install_plugin' === $tab ) {
132 | $this->install( 'plugin' );
133 | $this->create_form( 'plugin' );
134 | }
135 | if ( 'git_updater_install_theme' === $tab ) {
136 | $this->install( 'theme' );
137 | $this->create_form( 'theme' );
138 | }
139 | }
140 |
141 | /**
142 | * Install remote plugin or theme.
143 | *
144 | * @param string $type plugin|theme.
145 | * @param array $config Array of data.
146 | *
147 | * @return bool
148 | */
149 | public function install( $type, $config = null ) {
150 | if ( self::is_wp_cli() ) {
151 | $this->set_install_post_data( $config );
152 | }
153 |
154 | // phpcs:disable WordPress.Security.NonceVerification.Missing
155 | if ( isset( $_POST['option_page'] ) && 'git_updater_install' === $_POST['option_page'] ) {
156 | if ( empty( $_POST['git_updater_branch'] ) ) {
157 | $_POST['git_updater_branch'] = 'master';
158 | }
159 |
160 | // Exit early if no repo entered.
161 | if ( empty( $_POST['git_updater_repo'] ) ) {
162 | echo '';
163 | esc_html_e( 'A repository URI is required.', 'git-updater-pro' );
164 | echo ' ';
165 |
166 | return false;
167 | }
168 |
169 | // Transform URI to owner/repo.
170 | $headers = $this->parse_header_uri( sanitize_text_field( wp_unslash( $_POST['git_updater_repo'] ) ) );
171 | $_POST['git_updater_repo'] = $headers['owner_repo'];
172 |
173 | self::$install = $this->sanitize( $_POST );
174 | // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
175 | self::$install['repo'] = self::$install['git_updater_install_repo'] = $headers['repo'];
176 | // phpcs:enable
177 |
178 | /*
179 | * Create GitHub endpoint.
180 | * Save Access Token if present.
181 | * Check for GitHub Self-Hosted.
182 | */
183 | if ( 'github' === self::$install['git_updater_api'] ) {
184 | self::$install = Singleton::get_instance( 'Fragen\Git_Updater\API\GitHub_API', $this, new \stdClass() )->remote_install( $headers, self::$install );
185 | }
186 |
187 | /**
188 | * Filter to create git host specific endpoint.
189 | *
190 | * @since 10.0.0
191 | * @param array self::$install Array of installation data.
192 | * @param array $headers Array of repo header data.
193 | */
194 | self::$install = apply_filters( 'gu_install_remote_install', self::$install, $headers );
195 |
196 | if ( isset( self::$install['options'] ) ) {
197 | $this->save_options_on_install( self::$install['options'] );
198 | }
199 |
200 | $url = self::$install['download_link'];
201 | $upgrader = $this->get_upgrader( $type, $url );
202 |
203 | // Ensure authentication headers are present for download packages.
204 | add_filter( 'http_request_args', [ $this, 'download_package' ], 15, 2 );
205 |
206 | // Install the repo from the $source urldecode() and save branch setting.
207 | if ( $upgrader && $upgrader->install( $url ) ) {
208 | ( new Branch() )->set_branch_on_install( self::$install );
209 | } else {
210 | return false;
211 | }
212 | }
213 |
214 | return true;
215 | }
216 |
217 | /**
218 | * Save options set during installation.
219 | *
220 | * @param array $install_options Array of options from remote install process.
221 | * @return void
222 | */
223 | private function save_options_on_install( $install_options ) {
224 | self::$options = array_merge( self::$options, $install_options );
225 | update_site_option( 'git_updater', self::$options );
226 | }
227 |
228 | /**
229 | * Set remote install data into $_POST.
230 | *
231 | * @param array $config Data for a remote install.
232 | */
233 | private function set_install_post_data( $config ) {
234 | if ( ! isset( $config['uri'] ) ) {
235 | return;
236 | }
237 |
238 | $headers = $this->parse_header_uri( $config['uri'] );
239 | $api = false !== strpos( $headers['host'], '.com' )
240 | ? rtrim( $headers['host'], '.com' )
241 | : rtrim( $headers['host'], '.org' );
242 |
243 | $api = isset( $config['git'] ) ? $config['git'] : $api;
244 |
245 | $_POST['git_updater_repo'] = $config['uri'];
246 | $_POST['git_updater_branch'] = $config['branch'];
247 | $_POST['git_updater_api'] = $api;
248 | $_POST['option_page'] = 'git_updater_install';
249 | $_POST[ "{$api}_access_token" ] = $config['private'] ?: null;
250 |
251 | if ( 'zipfile' === $config['git'] ) {
252 | $_POST['zipfile_slug'] = $config['slug'];
253 | }
254 | }
255 |
256 | /**
257 | * Get the appropriate upgrader for remote installation.
258 | *
259 | * @param string $type 'plugin' | 'theme'.
260 | * @param string $url URL of the repository to be installed.
261 | *
262 | * @return bool|\Plugin_Upgrader|\Theme_Upgrader
263 | */
264 | private function get_upgrader( $type, $url ) {
265 | $nonce = wp_nonce_url( $url );
266 | $upgrader = false;
267 |
268 | if ( 'plugin' === $type ) {
269 | $plugin = self::$install['repo'];
270 |
271 | // Create a new instance of Plugin_Upgrader.
272 | $skin = static::is_wp_cli()
273 | ? new CLI_Plugin_Installer_Skin()
274 | : new \Plugin_Installer_Skin( compact( 'type', 'url', 'nonce', 'plugin' ) );
275 | $upgrader = new \Plugin_Upgrader( $skin );
276 | }
277 |
278 | if ( 'theme' === $type ) {
279 | $theme = self::$install['repo'];
280 |
281 | // Create a new instance of Theme_Upgrader.
282 | $skin = static::is_wp_cli()
283 | ? new CLI_Theme_Installer_Skin()
284 | : new \Theme_Installer_Skin( compact( 'type', 'url', 'nonce', 'theme' ) );
285 | $upgrader = new \Theme_Upgrader( $skin );
286 | add_filter(
287 | 'install_theme_complete_actions',
288 | [
289 | $this,
290 | 'install_theme_complete_actions',
291 | ],
292 | 10,
293 | 3
294 | );
295 | }
296 |
297 | return $upgrader;
298 | }
299 |
300 | /**
301 | * Create Install Plugin or Install Theme page.
302 | *
303 | * @param string $type (plugin|theme).
304 | */
305 | public function create_form( $type ) {
306 | // Bail if installing.
307 | // phpcs:ignore WordPress.Security.NonceVerification.Missing
308 | if ( isset( $_POST['option_page'] ) && 'git_updater_install' === $_POST['option_page'] ) {
309 | return;
310 | }
311 |
312 | $this->register_settings( $type ); ?>
313 |
325 | get_running_git_servers();
394 | $servers_not_running = array_diff( array_flip( self::$git_servers ), $running_servers );
395 | if ( ! empty( $servers_not_running ) ) {
396 | foreach ( array_keys( $servers_not_running ) as $server ) {
397 | $class = 'Fragen\\Git_Updater\\API\\' . $server . '_API';
398 | Singleton::get_instance( $class, $this )->add_install_settings_fields( $type );
399 | }
400 | }
401 | }
402 |
403 | /**
404 | * Repo setting.
405 | */
406 | public function get_repo() {
407 | ?>
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
438 |
439 |
440 | $value ) : ?>
441 |
442 | >
443 |
444 |
445 |
446 |
447 |
448 |
449 | 'activate',
470 | // 'template' => rawurlencode( $template ),
471 | 'stylesheet' => rawurlencode( $stylesheet ),
472 | ],
473 | admin_url( 'themes.php' )
474 | );
475 | $activate_link = esc_url( wp_nonce_url( $activate_link, 'switch-theme_' . $stylesheet ) );
476 |
477 | $install_actions['activate'] = '' . esc_attr__( 'Activate', 'git-updater-pro' ) . ' ' . esc_attr__( 'Activate', 'git-updater-pro' ) . ' “' . $stylesheet . '” ';
478 |
479 | if ( is_network_admin() && current_user_can( 'manage_network_themes' ) ) {
480 | $network_activate_link = add_query_arg(
481 | [
482 | 'action' => 'enable',
483 | 'theme' => rawurlencode( $stylesheet ),
484 | ],
485 | network_admin_url( 'themes.php' )
486 | );
487 | $network_activate_link = esc_url( wp_nonce_url( $network_activate_link, 'enable-theme_' . $stylesheet ) );
488 |
489 | $install_actions['network_enable'] = '' . esc_attr_x( 'Network Enable', 'This refers to a network activation in a multisite installation', 'git-updater-pro' ) . ' ';
490 | unset( $install_actions['activate'] );
491 | }
492 | ksort( $install_actions );
493 |
494 | return $install_actions;
495 | }
496 | }
497 |
--------------------------------------------------------------------------------
/src/Git_Updater_PRO/REST/Rest_Update.php:
--------------------------------------------------------------------------------
1 | load_options();
63 | $this->upgrader_skin = new Rest_Upgrader_Skin();
64 |
65 | // phpcs:ignore WordPress.Security.NonceVerification
66 | self::$request = $this->sanitize( $_REQUEST );
67 | }
68 |
69 | /**
70 | * Update plugin.
71 | *
72 | * @param string $plugin_slug Plugin slug.
73 | * @param string $tag Plugin tag/branch.
74 | *
75 | * @throws \UnexpectedValueException Plugin not found or not updatable.
76 | */
77 | public function update_plugin( $plugin_slug, $tag = 'master' ) {
78 | $plugin = null;
79 | $is_plugin_active = false;
80 |
81 | foreach ( (array) Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs() as $config_entry ) {
82 | if ( $config_entry->slug === $plugin_slug ) {
83 | $plugin = $config_entry;
84 | break;
85 | }
86 | }
87 |
88 | if ( ! $plugin ) {
89 | throw new \UnexpectedValueException( 'Plugin not found or not updatable with Git Updater: ' . $plugin_slug );
90 | }
91 |
92 | if ( is_plugin_active( $plugin->file ) ) {
93 | $is_plugin_active = true;
94 | }
95 |
96 | Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_remote_repo_meta( $plugin );
97 | $repo_api = Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this )->get_repo_api( $plugin->git, $plugin );
98 |
99 | $update = [
100 | 'slug' => $plugin->slug,
101 | 'plugin' => $plugin->file,
102 | 'new_version' => null,
103 | 'url' => $plugin->uri,
104 | 'package' => $repo_api->construct_download_link( $tag ),
105 | ];
106 |
107 | add_filter(
108 | 'site_transient_update_plugins',
109 | function ( $current ) use ( $plugin, $update ) {
110 | // needed to fix PHP 7.4 warning.
111 | if ( ! \is_object( $current ) ) {
112 | $current = new \stdClass();
113 | $current->response = null;
114 | } elseif ( ! \property_exists( $current, 'response' ) ) {
115 | $current->response = null;
116 | }
117 |
118 | $current->response[ $plugin->file ] = (object) $update;
119 |
120 | return $current;
121 | }
122 | );
123 |
124 | // Add authentication header to download package.
125 | add_filter( 'http_request_args', [ Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this ), 'download_package' ], 15, 2 );
126 |
127 | $upgrader = new \Plugin_Upgrader( $this->upgrader_skin );
128 | $upgrader->upgrade( $plugin->file );
129 |
130 | if ( $is_plugin_active ) {
131 | $activate = is_multisite() ? activate_plugin( $plugin->file, null, true ) : activate_plugin( $plugin->file );
132 | if ( ! $activate ) {
133 | $this->upgrader_skin->messages[] = 'Plugin reactivated successfully.';
134 | }
135 | }
136 | }
137 |
138 | /**
139 | * Update a single theme.
140 | *
141 | * @param string $theme_slug Theme slug.
142 | * @param string $tag Theme tag/branch.
143 | *
144 | * @throws \UnexpectedValueException Theme not found or not updatable.
145 | */
146 | public function update_theme( $theme_slug, $tag = 'master' ) {
147 | $theme = null;
148 |
149 | foreach ( (array) Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs() as $config_entry ) {
150 | if ( $config_entry->slug === $theme_slug ) {
151 | $theme = $config_entry;
152 | break;
153 | }
154 | }
155 |
156 | if ( ! $theme ) {
157 | throw new \UnexpectedValueException( 'Theme not found or not updatable with Git Updater: ' . $theme_slug );
158 | }
159 |
160 | Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_remote_repo_meta( $theme );
161 | $repo_api = Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this )->get_repo_api( $theme->git, $theme );
162 |
163 | $update = [
164 | 'theme' => $theme->slug,
165 | 'new_version' => null,
166 | 'url' => $theme->uri,
167 | 'package' => $repo_api->construct_download_link( $tag ),
168 | ];
169 |
170 | add_filter(
171 | 'site_transient_update_themes',
172 | function ( $current ) use ( $theme, $update ) {
173 | // needed to fix PHP 7.4 warning.
174 | if ( ! \is_object( $current ) ) {
175 | $current = new \stdClass();
176 | $current->response = null;
177 | } elseif ( ! \property_exists( $current, 'response' ) ) {
178 | $current->response = null;
179 | }
180 |
181 | $current->response[ $theme->slug ] = $update;
182 |
183 | return $current;
184 | }
185 | );
186 |
187 | // Add authentication header to download package.
188 | add_filter( 'http_request_args', [ Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this ), 'download_package' ], 15, 2 );
189 |
190 | $upgrader = new \Theme_Upgrader( $this->upgrader_skin );
191 | $upgrader->upgrade( $theme->slug );
192 | }
193 |
194 | /**
195 | * Is there an error?
196 | */
197 | public function is_error() {
198 | return $this->upgrader_skin->error;
199 | }
200 |
201 | /**
202 | * Get messages during update.
203 | */
204 | public function get_messages() {
205 | return $this->upgrader_skin->messages;
206 | }
207 |
208 | /**
209 | * Process request.
210 | *
211 | * Relies on data in $_REQUEST, prints out json and exits.
212 | * If the request came through a webhook, and if the branch in the
213 | * webhook matches the branch specified by the url, use the latest
214 | * update available as specified in the webhook payload.
215 | *
216 | * @param \WP_REST_Request|null $request Request data from update webhook.
217 | *
218 | * @throws \UnexpectedValueException Under multiple bad or missing params.
219 | */
220 | public function process_request( $request = null ) {
221 | $args = $this->process_request_data( $request );
222 | extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
223 |
224 | $start = microtime( true );
225 | try {
226 | if ( ! $key
227 | || get_site_option( 'git_updater_api_key' ) !== $key
228 | ) {
229 | throw new \UnexpectedValueException( 'Bad API key.' );
230 | }
231 |
232 | /**
233 | * Allow access into the REST Update process.
234 | *
235 | * @since 10.0.0
236 | * @access public
237 | */
238 | do_action_deprecated( 'github_updater_pre_rest_process_request', [], '10.0.0', 'gu_pre_rest_process_request' );
239 |
240 | /**
241 | * Allow access into the REST Update process.
242 | *
243 | * @since 7.6.0
244 | * @access public
245 | */
246 | do_action( 'gu_pre_rest_process_request' );
247 |
248 | $this->get_webhook_source();
249 | $tag = $committish ? $committish : $tag;
250 | $current_branch = $this->get_local_branch( $plugin, $theme );
251 |
252 | if ( ! ( 0 === preg_match( self::$version_number_regex, $tag ) ) ) {
253 | $remote_branch = 'master';
254 | }
255 | if ( $branch ) {
256 | $tag = $branch;
257 | $remote_branch = $branch;
258 | }
259 | $remote_branch = isset( $remote_branch ) ? $remote_branch : $tag;
260 | $current_branch = $override ? $remote_branch : $current_branch;
261 | if ( $remote_branch !== $current_branch && ! $override ) {
262 | throw new \UnexpectedValueException( 'Webhook tag and current branch are not matching. Consider using `override` query arg.' );
263 | }
264 |
265 | if ( $plugin ) {
266 | $this->update_plugin( $plugin, $tag );
267 | } elseif ( $theme ) {
268 | $this->update_theme( $theme, $tag );
269 | } else {
270 | throw new \UnexpectedValueException( 'No plugin or theme specified for update.' );
271 | }
272 | } catch ( \Exception $e ) {
273 | $http_response = [
274 | 'success' => false,
275 | 'messages' => $e->getMessage(),
276 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification
277 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms',
278 | 'deprecated' => $deprecated,
279 | ];
280 | $this->log_exit( $http_response, 417 );
281 | }
282 |
283 | // Only set branch on successful update.
284 | if ( ! $this->is_error() ) {
285 | $slug = $plugin ? $plugin : false;
286 | $slug = $theme ? $theme : $slug;
287 | $file = $plugin ? $plugin . '.php' : 'style.css';
288 | $options = $this->get_class_vars( 'Base', 'options' );
289 | $cache = $this->get_repo_cache( $slug );
290 | $cache_key = 'ghu-' . md5( $slug );
291 |
292 | $cache['current_branch'] = $current_branch;
293 | unset( $cache[ $file ] );
294 | update_site_option( $cache_key, $cache );
295 |
296 | $options[ 'current_branch_' . $slug ] = $current_branch;
297 | update_site_option( 'git_updater', $options );
298 | }
299 |
300 | $response = [
301 | 'success' => true,
302 | 'messages' => $this->get_messages(),
303 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification
304 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms',
305 | 'deprecated' => $deprecated,
306 | ];
307 |
308 | if ( $this->is_error() ) {
309 | $response['success'] = false;
310 | $this->log_exit( $response, 417 );
311 | }
312 | $this->log_exit( $response, 200 );
313 | }
314 |
315 | /**
316 | * Process request data from REST API or RESTful endpoint.
317 | *
318 | * @param \WP_REST_Request|array $request Request data from update webhook.
319 | *
320 | * @return array
321 | */
322 | public function process_request_data( $request = null ) {
323 | if ( $request instanceof \WP_REST_Request ) {
324 | $params = $request->get_params();
325 | $slug = $params['plugin'] ?: $params['theme'];
326 | $params['tag'] = $params['tag'] ?: $this->get_primary_branch( $slug );
327 | extract( $params ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
328 | $override = false === $override ? false : true;
329 | // TODO: Update for PHP 7.0
330 | // $deprecated = strpos( $request->get_route(), ( new REST_API() )::$namespace ) ? false : true;
331 | $rest_api = new REST_API();
332 | $deprecated = strpos( $request->get_route(), $rest_api::$namespace ) ? false : true;
333 | if ( $deprecated ) {
334 | $this->upgrader_skin->feedback( ( new REST_API() )->deprecated()['error'] );
335 | }
336 | } else { // call from admin-ajax.php.
337 | $key = empty( self::$request['key'] ) ? false : self::$request['key'];
338 | $plugin = empty( self::$request['plugin'] ) ? false : self::$request['plugin'];
339 | $theme = empty( self::$request['theme'] ) ? false : self::$request['theme'];
340 | $tag = empty( self::$request['tag'] ) ? 'master' : self::$request['tag'];
341 | $committish = empty( self::$request['committish'] ) ? false : self::$request['committish'];
342 | $branch = empty( self::$request['branch'] ) ? false : self::$request['branch'];
343 | $override = empty( self::$request['override'] ) ? false : self::$request['override'];
344 | $override = false === $override ? false : true;
345 | $deprecated = 'Please update to using the new REST API endpoint. This is now deprecated.';
346 | }
347 |
348 | $args = compact( 'key', 'plugin', 'theme', 'tag', 'committish', 'branch', 'override', 'deprecated' );
349 |
350 | return $args;
351 | }
352 |
353 | /**
354 | * Returns the current branch of the local repository referenced in the webhook.
355 | *
356 | * @param string|bool $plugin Plugin slug or false.
357 | * @param string|bool $theme Theme slug or false.
358 | *
359 | * @return string $current_branch Default return is 'master'.
360 | */
361 | private function get_local_branch( $plugin, $theme ) {
362 | $repo = false;
363 | if ( $plugin ) {
364 | $repos = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs();
365 | $repo = isset( $repos[ $plugin ] ) ? $repos[ $plugin ] : false;
366 | }
367 | if ( $theme ) {
368 | $repos = Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs();
369 | $repo = isset( $repos[ $theme ] ) ? $repos[ $theme ] : false;
370 | }
371 | $current_branch = $repo ?
372 | ( new Branch() )->get_current_branch( $repo ) :
373 | 'master';
374 |
375 | return $current_branch;
376 | }
377 |
378 | /**
379 | * Returns the Primary Branch header if set, otherwise 'master'.
380 | *
381 | * @param string $slug Repository slug.
382 | *
383 | * @return string
384 | */
385 | private function get_primary_branch( $slug ) {
386 | $cache = $this->get_repo_cache( $slug );
387 | $primary_branch = isset( $cache[ $slug ]['PrimaryBranch'] ) ? $cache[ $slug ]['PrimaryBranch'] : 'master';
388 |
389 | return $primary_branch;
390 | }
391 |
392 | /**
393 | * Sets the source of the webhook to $_GET variable.
394 | */
395 | private function get_webhook_source() {
396 | switch ( $_SERVER ) {
397 | case isset( $_SERVER['HTTP_X_GITHUB_EVENT'] ):
398 | $webhook_source = 'GitHub webhook';
399 | break;
400 | case isset( $_SERVER['HTTP_X_EVENT_KEY'] ):
401 | $webhook_source = 'Bitbucket webhook';
402 | break;
403 | case isset( $_SERVER['HTTP_X_GITLAB_EVENT'] ):
404 | $webhook_source = 'GitLab webhook';
405 | break;
406 | case isset( $_SERVER['HTTP_X_GITEA_EVENT'] ):
407 | $webhook_source = 'Gitea webhook';
408 | break;
409 | default:
410 | $webhook_source = 'browser';
411 | break;
412 | }
413 | $_GET['webhook_source'] = $webhook_source;
414 | }
415 |
416 | /**
417 | * Append $response to debug.log and wp_die().
418 | *
419 | * 128 == JSON_PRETTY_PRINT
420 | * 64 == JSON_UNESCAPED_SLASHES
421 | *
422 | * @param array $response Response array.
423 | * @param int $code Response code.
424 | */
425 | public function log_exit( $response, $code ) {
426 | $json_encode_flags = 128 | 64;
427 |
428 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
429 | error_log( json_encode( $response, $json_encode_flags ) );
430 |
431 | /**
432 | * Action hook after processing REST process.
433 | *
434 | * @since 8.6.0
435 | *
436 | * @param array $response
437 | * @param int $code HTTP response.
438 | */
439 | do_action_deprecated( 'github_updater_post_rest_process_request', [ $response, $code ], '10.0.0', 'gu_post_rest_process_request' );
440 |
441 | /**
442 | * Action hook after processing REST process.
443 | *
444 | * @since 10.0.0
445 | *
446 | * @param array $response
447 | * @param int $code HTTP response.
448 | */
449 | do_action( 'gu_post_rest_process_request', $response, $code );
450 |
451 | unset( $response['success'] );
452 | // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
453 | if ( 200 === $code ) {
454 | wp_die( wp_send_json_success( $response, $code ) );
455 | } else {
456 | wp_die( wp_send_json_error( $response, $code ) );
457 | }
458 | // phpcs:enable
459 | }
460 | }
461 |
--------------------------------------------------------------------------------
/vendor/composer/InstalledVersions.php:
--------------------------------------------------------------------------------
1 |
7 | * Jordi Boggiano
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | namespace Composer;
14 |
15 | use Composer\Autoload\ClassLoader;
16 | use Composer\Semver\VersionParser;
17 |
18 | /**
19 | * This class is copied in every Composer installed project and available to all
20 | *
21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
22 | *
23 | * To require its presence, you can require `composer-runtime-api ^2.0`
24 | *
25 | * @final
26 | */
27 | class InstalledVersions
28 | {
29 | /**
30 | * @var mixed[]|null
31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
32 | */
33 | private static $installed;
34 |
35 | /**
36 | * @var bool|null
37 | */
38 | private static $canGetVendors;
39 |
40 | /**
41 | * @var array[]
42 | * @psalm-var array}>
43 | */
44 | private static $installedByVendor = array();
45 |
46 | /**
47 | * Returns a list of all package names which are present, either by being installed, replaced or provided
48 | *
49 | * @return string[]
50 | * @psalm-return list
51 | */
52 | public static function getInstalledPackages()
53 | {
54 | $packages = array();
55 | foreach (self::getInstalled() as $installed) {
56 | $packages[] = array_keys($installed['versions']);
57 | }
58 |
59 | if (1 === \count($packages)) {
60 | return $packages[0];
61 | }
62 |
63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
64 | }
65 |
66 | /**
67 | * Returns a list of all package names with a specific type e.g. 'library'
68 | *
69 | * @param string $type
70 | * @return string[]
71 | * @psalm-return list
72 | */
73 | public static function getInstalledPackagesByType($type)
74 | {
75 | $packagesByType = array();
76 |
77 | foreach (self::getInstalled() as $installed) {
78 | foreach ($installed['versions'] as $name => $package) {
79 | if (isset($package['type']) && $package['type'] === $type) {
80 | $packagesByType[] = $name;
81 | }
82 | }
83 | }
84 |
85 | return $packagesByType;
86 | }
87 |
88 | /**
89 | * Checks whether the given package is installed
90 | *
91 | * This also returns true if the package name is provided or replaced by another package
92 | *
93 | * @param string $packageName
94 | * @param bool $includeDevRequirements
95 | * @return bool
96 | */
97 | public static function isInstalled($packageName, $includeDevRequirements = true)
98 | {
99 | foreach (self::getInstalled() as $installed) {
100 | if (isset($installed['versions'][$packageName])) {
101 | return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
102 | }
103 | }
104 |
105 | return false;
106 | }
107 |
108 | /**
109 | * Checks whether the given package satisfies a version constraint
110 | *
111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
112 | *
113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
114 | *
115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality
116 | * @param string $packageName
117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
118 | * @return bool
119 | */
120 | public static function satisfies(VersionParser $parser, $packageName, $constraint)
121 | {
122 | $constraint = $parser->parseConstraints($constraint);
123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
124 |
125 | return $provided->matches($constraint);
126 | }
127 |
128 | /**
129 | * Returns a version constraint representing all the range(s) which are installed for a given package
130 | *
131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check
132 | * whether a given version of a package is installed, and not just whether it exists
133 | *
134 | * @param string $packageName
135 | * @return string Version constraint usable with composer/semver
136 | */
137 | public static function getVersionRanges($packageName)
138 | {
139 | foreach (self::getInstalled() as $installed) {
140 | if (!isset($installed['versions'][$packageName])) {
141 | continue;
142 | }
143 |
144 | $ranges = array();
145 | if (isset($installed['versions'][$packageName]['pretty_version'])) {
146 | $ranges[] = $installed['versions'][$packageName]['pretty_version'];
147 | }
148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) {
149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
150 | }
151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) {
152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
153 | }
154 | if (array_key_exists('provided', $installed['versions'][$packageName])) {
155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
156 | }
157 |
158 | return implode(' || ', $ranges);
159 | }
160 |
161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
162 | }
163 |
164 | /**
165 | * @param string $packageName
166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
167 | */
168 | public static function getVersion($packageName)
169 | {
170 | foreach (self::getInstalled() as $installed) {
171 | if (!isset($installed['versions'][$packageName])) {
172 | continue;
173 | }
174 |
175 | if (!isset($installed['versions'][$packageName]['version'])) {
176 | return null;
177 | }
178 |
179 | return $installed['versions'][$packageName]['version'];
180 | }
181 |
182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
183 | }
184 |
185 | /**
186 | * @param string $packageName
187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
188 | */
189 | public static function getPrettyVersion($packageName)
190 | {
191 | foreach (self::getInstalled() as $installed) {
192 | if (!isset($installed['versions'][$packageName])) {
193 | continue;
194 | }
195 |
196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) {
197 | return null;
198 | }
199 |
200 | return $installed['versions'][$packageName]['pretty_version'];
201 | }
202 |
203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
204 | }
205 |
206 | /**
207 | * @param string $packageName
208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
209 | */
210 | public static function getReference($packageName)
211 | {
212 | foreach (self::getInstalled() as $installed) {
213 | if (!isset($installed['versions'][$packageName])) {
214 | continue;
215 | }
216 |
217 | if (!isset($installed['versions'][$packageName]['reference'])) {
218 | return null;
219 | }
220 |
221 | return $installed['versions'][$packageName]['reference'];
222 | }
223 |
224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
225 | }
226 |
227 | /**
228 | * @param string $packageName
229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
230 | */
231 | public static function getInstallPath($packageName)
232 | {
233 | foreach (self::getInstalled() as $installed) {
234 | if (!isset($installed['versions'][$packageName])) {
235 | continue;
236 | }
237 |
238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
239 | }
240 |
241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
242 | }
243 |
244 | /**
245 | * @return array
246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
247 | */
248 | public static function getRootPackage()
249 | {
250 | $installed = self::getInstalled();
251 |
252 | return $installed[0]['root'];
253 | }
254 |
255 | /**
256 | * Returns the raw installed.php data for custom implementations
257 | *
258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
259 | * @return array[]
260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
261 | */
262 | public static function getRawData()
263 | {
264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
265 |
266 | if (null === self::$installed) {
267 | // only require the installed.php file if this file is loaded from its dumped location,
268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
269 | if (substr(__DIR__, -8, 1) !== 'C') {
270 | self::$installed = include __DIR__ . '/installed.php';
271 | } else {
272 | self::$installed = array();
273 | }
274 | }
275 |
276 | return self::$installed;
277 | }
278 |
279 | /**
280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations
281 | *
282 | * @return array[]
283 | * @psalm-return list}>
284 | */
285 | public static function getAllRawData()
286 | {
287 | return self::getInstalled();
288 | }
289 |
290 | /**
291 | * Lets you reload the static array from another file
292 | *
293 | * This is only useful for complex integrations in which a project needs to use
294 | * this class but then also needs to execute another project's autoloader in process,
295 | * and wants to ensure both projects have access to their version of installed.php.
296 | *
297 | * A typical case would be PHPUnit, where it would need to make sure it reads all
298 | * the data it needs from this class, then call reload() with
299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
300 | * the project in which it runs can then also use this class safely, without
301 | * interference between PHPUnit's dependencies and the project's dependencies.
302 | *
303 | * @param array[] $data A vendor/composer/installed.php data set
304 | * @return void
305 | *
306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
307 | */
308 | public static function reload($data)
309 | {
310 | self::$installed = $data;
311 | self::$installedByVendor = array();
312 | }
313 |
314 | /**
315 | * @return array[]
316 | * @psalm-return list}>
317 | */
318 | private static function getInstalled()
319 | {
320 | if (null === self::$canGetVendors) {
321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
322 | }
323 |
324 | $installed = array();
325 |
326 | if (self::$canGetVendors) {
327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
328 | if (isset(self::$installedByVendor[$vendorDir])) {
329 | $installed[] = self::$installedByVendor[$vendorDir];
330 | } elseif (is_file($vendorDir.'/composer/installed.php')) {
331 | $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
332 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
333 | self::$installed = $installed[count($installed) - 1];
334 | }
335 | }
336 | }
337 | }
338 |
339 | if (null === self::$installed) {
340 | // only require the installed.php file if this file is loaded from its dumped location,
341 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
342 | if (substr(__DIR__, -8, 1) !== 'C') {
343 | self::$installed = require __DIR__ . '/installed.php';
344 | } else {
345 | self::$installed = array();
346 | }
347 | }
348 | $installed[] = self::$installed;
349 |
350 | return $installed;
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/vendor/composer/ClassLoader.php:
--------------------------------------------------------------------------------
1 |
7 | * Jordi Boggiano
8 | *
9 | * For the full copyright and license information, please view the LICENSE
10 | * file that was distributed with this source code.
11 | */
12 |
13 | namespace Composer\Autoload;
14 |
15 | /**
16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17 | *
18 | * $loader = new \Composer\Autoload\ClassLoader();
19 | *
20 | * // register classes with namespaces
21 | * $loader->add('Symfony\Component', __DIR__.'/component');
22 | * $loader->add('Symfony', __DIR__.'/framework');
23 | *
24 | * // activate the autoloader
25 | * $loader->register();
26 | *
27 | * // to enable searching the include path (eg. for PEAR packages)
28 | * $loader->setUseIncludePath(true);
29 | *
30 | * In this example, if you try to use a class in the Symfony\Component
31 | * namespace or one of its children (Symfony\Component\Console for instance),
32 | * the autoloader will first look for the class under the component/
33 | * directory, and it will then fallback to the framework/ directory if not
34 | * found before giving up.
35 | *
36 | * This class is loosely based on the Symfony UniversalClassLoader.
37 | *
38 | * @author Fabien Potencier
39 | * @author Jordi Boggiano
40 | * @see https://www.php-fig.org/psr/psr-0/
41 | * @see https://www.php-fig.org/psr/psr-4/
42 | */
43 | class ClassLoader
44 | {
45 | /** @var ?string */
46 | private $vendorDir;
47 |
48 | // PSR-4
49 | /**
50 | * @var array[]
51 | * @psalm-var array>
52 | */
53 | private $prefixLengthsPsr4 = array();
54 | /**
55 | * @var array[]
56 | * @psalm-var array>
57 | */
58 | private $prefixDirsPsr4 = array();
59 | /**
60 | * @var array[]
61 | * @psalm-var array
62 | */
63 | private $fallbackDirsPsr4 = array();
64 |
65 | // PSR-0
66 | /**
67 | * @var array[]
68 | * @psalm-var array>
69 | */
70 | private $prefixesPsr0 = array();
71 | /**
72 | * @var array[]
73 | * @psalm-var array
74 | */
75 | private $fallbackDirsPsr0 = array();
76 |
77 | /** @var bool */
78 | private $useIncludePath = false;
79 |
80 | /**
81 | * @var string[]
82 | * @psalm-var array
83 | */
84 | private $classMap = array();
85 |
86 | /** @var bool */
87 | private $classMapAuthoritative = false;
88 |
89 | /**
90 | * @var bool[]
91 | * @psalm-var array
92 | */
93 | private $missingClasses = array();
94 |
95 | /** @var ?string */
96 | private $apcuPrefix;
97 |
98 | /**
99 | * @var self[]
100 | */
101 | private static $registeredLoaders = array();
102 |
103 | /**
104 | * @param ?string $vendorDir
105 | */
106 | public function __construct($vendorDir = null)
107 | {
108 | $this->vendorDir = $vendorDir;
109 | }
110 |
111 | /**
112 | * @return string[]
113 | */
114 | public function getPrefixes()
115 | {
116 | if (!empty($this->prefixesPsr0)) {
117 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
118 | }
119 |
120 | return array();
121 | }
122 |
123 | /**
124 | * @return array[]
125 | * @psalm-return array>
126 | */
127 | public function getPrefixesPsr4()
128 | {
129 | return $this->prefixDirsPsr4;
130 | }
131 |
132 | /**
133 | * @return array[]
134 | * @psalm-return array
135 | */
136 | public function getFallbackDirs()
137 | {
138 | return $this->fallbackDirsPsr0;
139 | }
140 |
141 | /**
142 | * @return array[]
143 | * @psalm-return array
144 | */
145 | public function getFallbackDirsPsr4()
146 | {
147 | return $this->fallbackDirsPsr4;
148 | }
149 |
150 | /**
151 | * @return string[] Array of classname => path
152 | * @psalm-return array
153 | */
154 | public function getClassMap()
155 | {
156 | return $this->classMap;
157 | }
158 |
159 | /**
160 | * @param string[] $classMap Class to filename map
161 | * @psalm-param array $classMap
162 | *
163 | * @return void
164 | */
165 | public function addClassMap(array $classMap)
166 | {
167 | if ($this->classMap) {
168 | $this->classMap = array_merge($this->classMap, $classMap);
169 | } else {
170 | $this->classMap = $classMap;
171 | }
172 | }
173 |
174 | /**
175 | * Registers a set of PSR-0 directories for a given prefix, either
176 | * appending or prepending to the ones previously set for this prefix.
177 | *
178 | * @param string $prefix The prefix
179 | * @param string[]|string $paths The PSR-0 root directories
180 | * @param bool $prepend Whether to prepend the directories
181 | *
182 | * @return void
183 | */
184 | public function add($prefix, $paths, $prepend = false)
185 | {
186 | if (!$prefix) {
187 | if ($prepend) {
188 | $this->fallbackDirsPsr0 = array_merge(
189 | (array) $paths,
190 | $this->fallbackDirsPsr0
191 | );
192 | } else {
193 | $this->fallbackDirsPsr0 = array_merge(
194 | $this->fallbackDirsPsr0,
195 | (array) $paths
196 | );
197 | }
198 |
199 | return;
200 | }
201 |
202 | $first = $prefix[0];
203 | if (!isset($this->prefixesPsr0[$first][$prefix])) {
204 | $this->prefixesPsr0[$first][$prefix] = (array) $paths;
205 |
206 | return;
207 | }
208 | if ($prepend) {
209 | $this->prefixesPsr0[$first][$prefix] = array_merge(
210 | (array) $paths,
211 | $this->prefixesPsr0[$first][$prefix]
212 | );
213 | } else {
214 | $this->prefixesPsr0[$first][$prefix] = array_merge(
215 | $this->prefixesPsr0[$first][$prefix],
216 | (array) $paths
217 | );
218 | }
219 | }
220 |
221 | /**
222 | * Registers a set of PSR-4 directories for a given namespace, either
223 | * appending or prepending to the ones previously set for this namespace.
224 | *
225 | * @param string $prefix The prefix/namespace, with trailing '\\'
226 | * @param string[]|string $paths The PSR-4 base directories
227 | * @param bool $prepend Whether to prepend the directories
228 | *
229 | * @throws \InvalidArgumentException
230 | *
231 | * @return void
232 | */
233 | public function addPsr4($prefix, $paths, $prepend = false)
234 | {
235 | if (!$prefix) {
236 | // Register directories for the root namespace.
237 | if ($prepend) {
238 | $this->fallbackDirsPsr4 = array_merge(
239 | (array) $paths,
240 | $this->fallbackDirsPsr4
241 | );
242 | } else {
243 | $this->fallbackDirsPsr4 = array_merge(
244 | $this->fallbackDirsPsr4,
245 | (array) $paths
246 | );
247 | }
248 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
249 | // Register directories for a new namespace.
250 | $length = strlen($prefix);
251 | if ('\\' !== $prefix[$length - 1]) {
252 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
253 | }
254 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
255 | $this->prefixDirsPsr4[$prefix] = (array) $paths;
256 | } elseif ($prepend) {
257 | // Prepend directories for an already registered namespace.
258 | $this->prefixDirsPsr4[$prefix] = array_merge(
259 | (array) $paths,
260 | $this->prefixDirsPsr4[$prefix]
261 | );
262 | } else {
263 | // Append directories for an already registered namespace.
264 | $this->prefixDirsPsr4[$prefix] = array_merge(
265 | $this->prefixDirsPsr4[$prefix],
266 | (array) $paths
267 | );
268 | }
269 | }
270 |
271 | /**
272 | * Registers a set of PSR-0 directories for a given prefix,
273 | * replacing any others previously set for this prefix.
274 | *
275 | * @param string $prefix The prefix
276 | * @param string[]|string $paths The PSR-0 base directories
277 | *
278 | * @return void
279 | */
280 | public function set($prefix, $paths)
281 | {
282 | if (!$prefix) {
283 | $this->fallbackDirsPsr0 = (array) $paths;
284 | } else {
285 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
286 | }
287 | }
288 |
289 | /**
290 | * Registers a set of PSR-4 directories for a given namespace,
291 | * replacing any others previously set for this namespace.
292 | *
293 | * @param string $prefix The prefix/namespace, with trailing '\\'
294 | * @param string[]|string $paths The PSR-4 base directories
295 | *
296 | * @throws \InvalidArgumentException
297 | *
298 | * @return void
299 | */
300 | public function setPsr4($prefix, $paths)
301 | {
302 | if (!$prefix) {
303 | $this->fallbackDirsPsr4 = (array) $paths;
304 | } else {
305 | $length = strlen($prefix);
306 | if ('\\' !== $prefix[$length - 1]) {
307 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
308 | }
309 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
310 | $this->prefixDirsPsr4[$prefix] = (array) $paths;
311 | }
312 | }
313 |
314 | /**
315 | * Turns on searching the include path for class files.
316 | *
317 | * @param bool $useIncludePath
318 | *
319 | * @return void
320 | */
321 | public function setUseIncludePath($useIncludePath)
322 | {
323 | $this->useIncludePath = $useIncludePath;
324 | }
325 |
326 | /**
327 | * Can be used to check if the autoloader uses the include path to check
328 | * for classes.
329 | *
330 | * @return bool
331 | */
332 | public function getUseIncludePath()
333 | {
334 | return $this->useIncludePath;
335 | }
336 |
337 | /**
338 | * Turns off searching the prefix and fallback directories for classes
339 | * that have not been registered with the class map.
340 | *
341 | * @param bool $classMapAuthoritative
342 | *
343 | * @return void
344 | */
345 | public function setClassMapAuthoritative($classMapAuthoritative)
346 | {
347 | $this->classMapAuthoritative = $classMapAuthoritative;
348 | }
349 |
350 | /**
351 | * Should class lookup fail if not found in the current class map?
352 | *
353 | * @return bool
354 | */
355 | public function isClassMapAuthoritative()
356 | {
357 | return $this->classMapAuthoritative;
358 | }
359 |
360 | /**
361 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
362 | *
363 | * @param string|null $apcuPrefix
364 | *
365 | * @return void
366 | */
367 | public function setApcuPrefix($apcuPrefix)
368 | {
369 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
370 | }
371 |
372 | /**
373 | * The APCu prefix in use, or null if APCu caching is not enabled.
374 | *
375 | * @return string|null
376 | */
377 | public function getApcuPrefix()
378 | {
379 | return $this->apcuPrefix;
380 | }
381 |
382 | /**
383 | * Registers this instance as an autoloader.
384 | *
385 | * @param bool $prepend Whether to prepend the autoloader or not
386 | *
387 | * @return void
388 | */
389 | public function register($prepend = false)
390 | {
391 | spl_autoload_register(array($this, 'loadClass'), true, $prepend);
392 |
393 | if (null === $this->vendorDir) {
394 | return;
395 | }
396 |
397 | if ($prepend) {
398 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
399 | } else {
400 | unset(self::$registeredLoaders[$this->vendorDir]);
401 | self::$registeredLoaders[$this->vendorDir] = $this;
402 | }
403 | }
404 |
405 | /**
406 | * Unregisters this instance as an autoloader.
407 | *
408 | * @return void
409 | */
410 | public function unregister()
411 | {
412 | spl_autoload_unregister(array($this, 'loadClass'));
413 |
414 | if (null !== $this->vendorDir) {
415 | unset(self::$registeredLoaders[$this->vendorDir]);
416 | }
417 | }
418 |
419 | /**
420 | * Loads the given class or interface.
421 | *
422 | * @param string $class The name of the class
423 | * @return true|null True if loaded, null otherwise
424 | */
425 | public function loadClass($class)
426 | {
427 | if ($file = $this->findFile($class)) {
428 | includeFile($file);
429 |
430 | return true;
431 | }
432 |
433 | return null;
434 | }
435 |
436 | /**
437 | * Finds the path to the file where the class is defined.
438 | *
439 | * @param string $class The name of the class
440 | *
441 | * @return string|false The path if found, false otherwise
442 | */
443 | public function findFile($class)
444 | {
445 | // class map lookup
446 | if (isset($this->classMap[$class])) {
447 | return $this->classMap[$class];
448 | }
449 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
450 | return false;
451 | }
452 | if (null !== $this->apcuPrefix) {
453 | $file = apcu_fetch($this->apcuPrefix.$class, $hit);
454 | if ($hit) {
455 | return $file;
456 | }
457 | }
458 |
459 | $file = $this->findFileWithExtension($class, '.php');
460 |
461 | // Search for Hack files if we are running on HHVM
462 | if (false === $file && defined('HHVM_VERSION')) {
463 | $file = $this->findFileWithExtension($class, '.hh');
464 | }
465 |
466 | if (null !== $this->apcuPrefix) {
467 | apcu_add($this->apcuPrefix.$class, $file);
468 | }
469 |
470 | if (false === $file) {
471 | // Remember that this class does not exist.
472 | $this->missingClasses[$class] = true;
473 | }
474 |
475 | return $file;
476 | }
477 |
478 | /**
479 | * Returns the currently registered loaders indexed by their corresponding vendor directories.
480 | *
481 | * @return self[]
482 | */
483 | public static function getRegisteredLoaders()
484 | {
485 | return self::$registeredLoaders;
486 | }
487 |
488 | /**
489 | * @param string $class
490 | * @param string $ext
491 | * @return string|false
492 | */
493 | private function findFileWithExtension($class, $ext)
494 | {
495 | // PSR-4 lookup
496 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
497 |
498 | $first = $class[0];
499 | if (isset($this->prefixLengthsPsr4[$first])) {
500 | $subPath = $class;
501 | while (false !== $lastPos = strrpos($subPath, '\\')) {
502 | $subPath = substr($subPath, 0, $lastPos);
503 | $search = $subPath . '\\';
504 | if (isset($this->prefixDirsPsr4[$search])) {
505 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
506 | foreach ($this->prefixDirsPsr4[$search] as $dir) {
507 | if (file_exists($file = $dir . $pathEnd)) {
508 | return $file;
509 | }
510 | }
511 | }
512 | }
513 | }
514 |
515 | // PSR-4 fallback dirs
516 | foreach ($this->fallbackDirsPsr4 as $dir) {
517 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
518 | return $file;
519 | }
520 | }
521 |
522 | // PSR-0 lookup
523 | if (false !== $pos = strrpos($class, '\\')) {
524 | // namespaced class name
525 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
526 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
527 | } else {
528 | // PEAR-like class name
529 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
530 | }
531 |
532 | if (isset($this->prefixesPsr0[$first])) {
533 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
534 | if (0 === strpos($class, $prefix)) {
535 | foreach ($dirs as $dir) {
536 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
537 | return $file;
538 | }
539 | }
540 | }
541 | }
542 | }
543 |
544 | // PSR-0 fallback dirs
545 | foreach ($this->fallbackDirsPsr0 as $dir) {
546 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
547 | return $file;
548 | }
549 | }
550 |
551 | // PSR-0 include paths.
552 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
553 | return $file;
554 | }
555 |
556 | return false;
557 | }
558 | }
559 |
560 | /**
561 | * Scope isolated include.
562 | *
563 | * Prevents access to $this/self from included files.
564 | *
565 | * @param string $file
566 | * @return void
567 | * @private
568 | */
569 | function includeFile($file)
570 | {
571 | include $file;
572 | }
573 |
--------------------------------------------------------------------------------
/src/Git_Updater_PRO/Branch.php:
--------------------------------------------------------------------------------
1 | get_class_vars( 'Fragen\Git_Updater\Base', 'options' );
48 | $this->base = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this );
49 | }
50 |
51 | /**
52 | * Get the current repo branch.
53 | *
54 | * @access public
55 | *
56 | * @param \stdClass $repo Repository object.
57 | *
58 | * @return mixed
59 | */
60 | public function get_current_branch( $repo ) {
61 | $cache = $this->get_repo_cache( $repo->slug );
62 | $current_branch = ! empty( $cache['current_branch'] )
63 | ? $cache['current_branch']
64 | : $repo->branch;
65 |
66 | return $current_branch;
67 | }
68 |
69 | /**
70 | * Update transient for rollback or branch switch.
71 | *
72 | * @param string $type plugin|theme.
73 | * @param \stdClass $repo Repo object.
74 | *
75 | * @return array $rollback Rollback transient.
76 | */
77 | public function set_rollback_transient( $type, $repo ) {
78 | $repo_api = Singleton::get_instance( 'API\API', $this )->get_repo_api( $repo->git, $repo );
79 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended
80 | $this->tag = isset( $_GET['rollback'] ) ? sanitize_text_field( wp_unslash( $_GET['rollback'] ) ) : false;
81 | $slug = 'plugin' === $type ? $repo->file : $repo->slug;
82 | $download_link = $repo_api->construct_download_link( $this->tag );
83 |
84 | /**
85 | * Filter download link so developers can point to specific ZipFile
86 | * to use as a download link during a branch switch.
87 | *
88 | * @since 8.6.0
89 | *
90 | * @param string $download_link Download URL.
91 | * @param /stdClass $repo
92 | * @param string $this->tag Branch or tag for rollback.
93 | */
94 | $download_link = apply_filters_deprecated( 'github_updater_post_construct_download_link', [ $download_link, $repo, $this->tag ], '10.0.0', 'gu_post_construct_download_link' );
95 |
96 | /**
97 | * Filter download link so developers can point to specific ZipFile
98 | * to use as a download link during a branch switch.
99 | *
100 | * @since 10.0.0
101 | *
102 | * @param string $download_link Download URL.
103 | * @param /stdClass $repo
104 | * @param string $this->tag Branch or tag for rollback.
105 | */
106 | $download_link = apply_filters( 'gu_post_construct_download_link', $download_link, $repo, $this->tag );
107 |
108 | $repo->download_link = $download_link;
109 | $rollback = [
110 | $type => $slug,
111 | 'new_version' => $this->tag,
112 | 'url' => $repo->uri,
113 | 'package' => $repo->download_link,
114 | 'branch' => $repo->branch,
115 | 'branches' => $repo->branches,
116 | 'type' => $repo->type,
117 | ];
118 |
119 | if ( 'plugin' === $type ) {
120 | $rollback['slug'] = $repo->slug;
121 | $rollback = (object) $rollback;
122 | }
123 |
124 | return $rollback;
125 | }
126 |
127 | /**
128 | * Set current branch on branch switch.
129 | * Exit early if not a rollback.
130 | *
131 | * @access public
132 | *
133 | * @param string $repo Repository slug.
134 | * @return void
135 | */
136 | public function set_branch_on_switch( $repo ) {
137 | $this->cache = $this->get_repo_cache( $repo );
138 |
139 | // phpcs:disable WordPress.Security.NonceVerification.Recommended
140 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
141 | $rollback = isset( $_GET['rollback'] ) ? wp_unslash( $_GET['rollback'] ) : false;
142 | // Exit early if not a rollback, ie normal update.
143 | if ( ! $rollback ) {
144 | return;
145 | }
146 |
147 | $tag_array = isset( $this->cache['tags'] ) && is_array( $this->cache['tags'] );
148 | $in_tag_array = $tag_array && in_array( $rollback, $this->cache['tags'], true );
149 | if ( $in_tag_array ) {
150 | $current_branch = isset( $this->cache[ $repo ]['PrimaryBranch'] ) ? $this->cache[ $repo ]['PrimaryBranch'] : 'master';
151 | }
152 |
153 | if ( ! $in_tag_array && isset( $_GET['action'], $this->cache['branches'] )
154 | && in_array( $_GET['action'], [ 'upgrade-plugin', 'upgrade-theme' ], true )
155 | ) {
156 | // phpcs:enable
157 | $current_branch = array_key_exists( $rollback, $this->cache['branches'] )
158 | ? sanitize_text_field( $rollback )
159 | : 'master';
160 | }
161 | if ( isset( $current_branch ) ) {
162 | $this->set_repo_cache( 'current_branch', $current_branch, $repo );
163 | self::$options[ 'current_branch_' . $repo ] = $current_branch;
164 | update_site_option( 'git_updater', self::$options );
165 | }
166 | }
167 |
168 | /**
169 | * Set current branch on install and update options.
170 | *
171 | * @access public
172 | *
173 | * @param array $install Array of install data.
174 | */
175 | public function set_branch_on_install( $install ) {
176 | $this->set_repo_cache( 'current_branch', $install['git_updater_branch'], $install['repo'] );
177 | self::$options[ 'current_branch_' . $install['repo'] ] = $install['git_updater_branch'];
178 | self::$options = isset( $install['options'] ) && is_array( $install['options'] )
179 | ? array_merge( self::$options, $install['options'] )
180 | : self::$options;
181 | update_site_option( 'git_updater', self::$options );
182 | }
183 |
184 | /**
185 | * Add branch switch row to plugins page.
186 | *
187 | * @param string $plugin_file Plugin file.
188 | * @param \stdClass $plugin_data Plugin repo data.
189 | *
190 | * @return bool
191 | */
192 | public function plugin_branch_switcher( $plugin_file, $plugin_data ) {
193 | if ( empty( self::$options['branch_switch'] ) ) {
194 | return false;
195 | }
196 | $plugin_obj = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this );
197 | $config = $this->get_class_vars( 'Fragen\Git_Updater\Plugin', 'config' );
198 | $plugin = $this->get_repo_slugs( dirname( $plugin_file ), $plugin_obj );
199 |
200 | $this->base->get_remote_repo_meta( $config[ $plugin['slug'] ] );
201 |
202 | $enclosure = $this->base->update_row_enclosure( $plugin_file, 'plugin', true );
203 | $nonced_update_url = wp_nonce_url(
204 | $this->base->get_update_url( 'plugin', 'upgrade-plugin', $plugin_file ),
205 | 'upgrade-plugin_' . $plugin_file
206 | );
207 |
208 | if ( ! empty( $plugin ) ) {
209 | $id = $plugin['slug'] . '-id';
210 | $branches = isset( $config[ $plugin['slug'] ]->branches )
211 | ? $config[ $plugin['slug'] ]->branches
212 | : null;
213 | } else {
214 | return false;
215 | }
216 |
217 | // Get current branch.
218 | $repo = $config[ $plugin['slug'] ];
219 | $branch = $this->get_current_branch( $repo );
220 |
221 | $branch_switch_data = [];
222 | $branch_switch_data['slug'] = $plugin['slug'];
223 | $branch_switch_data['nonced_update_url'] = $nonced_update_url;
224 | $branch_switch_data['id'] = $id;
225 | $branch_switch_data['branch'] = $branch;
226 | $branch_switch_data['branches'] = $branches;
227 | $branch_switch_data['release_asset'] = $repo->release_asset;
228 | $branch_switch_data['primary_branch'] = $repo->primary_branch;
229 |
230 | /*
231 | * Create after_plugin_row_
232 | */
233 | echo wp_kses_post( $enclosure['open'] );
234 | $this->make_branch_switch_row( $branch_switch_data, $config );
235 | echo wp_kses_post( $enclosure['close'] );
236 |
237 | return true;
238 | }
239 |
240 | /**
241 | * Create branch switcher row for theme multisite installation.
242 | *
243 | * @param string $theme_key Theme slug.
244 | * @param array $theme Array of theme data.
245 | *
246 | * @return bool
247 | */
248 | public function multisite_branch_switcher( $theme_key, $theme ) {
249 | if ( empty( self::$options['branch_switch'] ) ) {
250 | return false;
251 | }
252 |
253 | $config = $this->get_class_vars( 'Fragen\Git_Updater\Theme', 'config' );
254 |
255 | $this->base->get_remote_repo_meta( $config[ $theme_key ] );
256 |
257 | $enclosure = $this->base->update_row_enclosure( $theme_key, 'theme', true );
258 | $id = $theme_key . '-id';
259 | $branches = isset( $config[ $theme_key ]->branches )
260 | ? $config[ $theme_key ]->branches
261 | : null;
262 | $nonced_update_url = wp_nonce_url(
263 | $this->base->get_update_url( 'theme', 'upgrade-theme', $theme_key ),
264 | 'upgrade-theme_' . $theme_key
265 | );
266 |
267 | // Get current branch.
268 | $repo = $config[ $theme_key ];
269 | $branch = $this->get_current_branch( $repo );
270 |
271 | $branch_switch_data = [];
272 | $branch_switch_data['slug'] = $theme_key;
273 | $branch_switch_data['nonced_update_url'] = $nonced_update_url;
274 | $branch_switch_data['id'] = $id;
275 | $branch_switch_data['branch'] = $branch;
276 | $branch_switch_data['branches'] = $branches;
277 | $branch_switch_data['release_asset'] = $repo->release_asset;
278 | $branch_switch_data['primary_branch'] = $repo->primary_branch;
279 |
280 | /*
281 | * Create after_theme_row_
282 | */
283 | echo wp_kses_post( $enclosure['open'] );
284 | $this->make_branch_switch_row( $branch_switch_data, $config );
285 | echo wp_kses_post( $enclosure['close'] );
286 |
287 | return true;
288 | }
289 |
290 | /**
291 | * Display rollback/branch switcher for theme single site installation.
292 | *
293 | * @param \stdClass $theme Theme object.
294 | *
295 | * @return string
296 | */
297 | public function single_install_switcher( $theme ) {
298 | $nonced_update_url = wp_nonce_url(
299 | $this->base->get_update_url( 'theme', 'upgrade-theme', $theme->slug ),
300 | 'upgrade-theme_' . $theme->slug
301 | );
302 | $rollback_url = sprintf( '%s%s', $nonced_update_url, '&rollback=' );
303 |
304 | if ( ! isset( self::$options['branch_switch'] ) ) {
305 | return;
306 | }
307 |
308 | ob_start();
309 | if ( '1' === self::$options['branch_switch'] ) {
310 | printf(
311 | /* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */
312 | '' . esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'git-updater-pro' ),
313 | esc_attr( $theme->branch ),
314 | '',
315 | ' .
'
316 | );
317 | ?>
318 |
319 |
320 | …
321 | release_asset ) {
325 | unset( $theme->branches[ $theme->primary_branch ] );
326 | }
327 | if ( isset( $theme->branches ) ) {
328 | foreach ( array_keys( $theme->branches ) as $branch ) {
329 | echo '' . esc_attr( $branch ) . ' ';
330 | }
331 | }
332 | if ( ! empty( $theme->rollback ) ) {
333 | $rollback = array_keys( $theme->rollback );
334 | usort( $rollback, 'version_compare' );
335 | krsort( $rollback );
336 |
337 | /**
338 | * Filter to return the number of tagged releases (rollbacks) in branch switching.
339 | *
340 | * @since 10.0.0
341 | * @param int Number of rollbacks. Zero implies value not set.
342 | */
343 | $num_rollbacks = absint( apply_filters( 'gu_number_rollbacks', 0 ) );
344 |
345 | /**
346 | * Filter to return the number of tagged releases (rollbacks) in branch switching.
347 | *
348 | * @since 9.6.0
349 | * @param int Number of rollbacks. Zero implies value not set.
350 | */
351 | $num_rollbacks = 0 === $num_rollbacks ? apply_filters_deprecated( 'github_updater_number_rollbacks', [ 0 ], '10.0.0', 'gu_number_rollbacks' ) : $num_rollbacks;
352 |
353 | // Still only return last tag if using release assets.
354 | $rollback = 0 === $num_rollbacks || $theme->release_asset
355 | ? array_slice( $rollback, 0, 1 )
356 | : array_splice( $rollback, 0, $num_rollbacks, true );
357 |
358 | foreach ( $rollback as $tag ) {
359 | echo '' . esc_attr( $tag ) . ' ';
360 | }
361 | }
362 | if ( empty( $theme->rollback ) ) {
363 | echo '' . esc_html__( 'No previous tags to rollback to.', 'git-updater-pro' ) . ' ';
364 | }
365 | ?>
366 |
367 |
368 |
369 | rollback ) ? [] : $config[ $data['slug'] ]->rollback;
385 |
386 | // Make the branch switch row visually appear as if it is contained with the plugin/theme's row.
387 | // We have to use JS for this because of the way:
388 | // 1) the @class of the list table row is not filterabled; and
389 | // 2) the list table CSS is written.
390 | if ( 'plugin' === $config[ $data['slug'] ]->type ) {
391 | $data_attr = 'data-plugin';
392 | $file = $config[ $data['slug'] ]->file;
393 | } else {
394 | $data_attr = 'data-slug';
395 | $file = $config[ $data['slug'] ]->slug;
396 | }
397 | echo '';
409 |
410 | echo '';
411 | echo wp_kses_post( $this->base->get_git_icon( $file, true ) );
412 | printf(
413 | /* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */
414 | esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'git-updater-pro' ),
415 | esc_attr( $data['branch'] ),
416 | '',
417 | ' .'
418 | );
419 | echo '
';
420 |
421 | print '';
422 |
423 | // Disable branch switching to primary branch for release assets.
424 | if ( $data['release_asset'] ) {
425 | unset( $data['branches'][ $data['primary_branch'] ] );
426 | }
427 |
428 | /**
429 | * Filter out branches for release assets if desired.
430 | * Removes all branches from the branch switcher leaving only the tags.
431 | *
432 | * @since 10.0.0
433 | *
434 | * @return bool
435 | */
436 | $no_release_asset_branches = (bool) apply_filters( 'gu_no_release_asset_branches', false );
437 |
438 | /**
439 | * Filter out branches for release assets if desired.
440 | * Removes all branches from the branch switcher leaving only the tags.
441 | *
442 | * @since 9.9.1
443 | *
444 | * @return bool
445 | */
446 | $no_release_asset_branches = $no_release_asset_branches ?: (bool) apply_filters_deprecated( 'github_updater_no_release_asset_branches', [ false ], '10.0.0', 'gu_no_release_asset_branches' );
447 |
448 | $data['branches'] = $data['release_asset'] && $no_release_asset_branches ? [] : $data['branches'];
449 |
450 | if ( null !== $data['branches'] ) {
451 | foreach ( array_keys( $data['branches'] ) as $branch ) {
452 | printf(
453 | '%s ',
454 | esc_url( $data['nonced_update_url'] ),
455 | '&rollback=' . rawurlencode( $branch ),
456 | esc_attr( $branch )
457 | );
458 | }
459 | }
460 |
461 | if ( ! empty( $rollback ) ) {
462 | $rollback = array_keys( $rollback );
463 | usort( $rollback, 'version_compare' );
464 | krsort( $rollback );
465 |
466 | /**
467 | * Filter to return the number of tagged releases (rollbacks) in branch switching.
468 | *
469 | * @since 10.0.0
470 | * @param int Number of rollbacks. Zero implies value not set.
471 | */
472 | $num_rollbacks = absint( apply_filters( 'gu_number_rollbacks', 0 ) );
473 |
474 | /**
475 | * Filter to return the number of tagged releases (rollbacks) in branch switching.
476 | *
477 | * @since 9.6.0
478 | * @param int Number of rollbacks. Zero implies value not set.
479 | */
480 | $num_rollbacks = 0 === $num_rollbacks ? absint( apply_filters_deprecated( 'github_updater_number_rollbacks', [ 0 ], '10.0.0', 'gu_number_rollbacks' ) ) : $num_rollbacks;
481 |
482 | // Still only return last tag if using release assets.
483 | $rollback = 0 === $num_rollbacks || $data['release_asset']
484 | ? array_slice( $rollback, 0, 1 )
485 | : array_splice( $rollback, 0, $num_rollbacks, true );
486 |
487 | if ( $data['release_asset'] ) {
488 | /**
489 | * Filter release asset rollbacks.
490 | *
491 | * @since 10.0.0
492 | *
493 | * @return array
494 | */
495 | $release_asset_rollback = apply_filters( 'gu_release_asset_rollback', $rollback, $file );
496 |
497 | /**
498 | * Filter release asset rollbacks.
499 | *
500 | * @since 9.9.2
501 | *
502 | * @return array
503 | */
504 | $release_asset_rollback = apply_filters_deprecated( 'github_updater_release_asset_rollback', [ $rollback, $file ], '10.0.0', 'gu_release_asset_rollback' );
505 |
506 | if ( ! empty( $release_asset_rollback ) && is_array( $release_asset_rollback ) ) {
507 | $rollback = $release_asset_rollback;
508 | }
509 | }
510 |
511 | foreach ( $rollback as $tag ) {
512 | printf(
513 | '%s ',
514 | esc_url( $data['nonced_update_url'] ),
515 | '&rollback=' . rawurlencode( $tag ),
516 | esc_attr( $tag )
517 | );
518 | }
519 | }
520 | if ( empty( $rollback ) ) {
521 | echo '' . esc_html__( 'No previous tags to rollback to.', 'git-updater-pro' ) . ' ';
522 | }
523 |
524 | print ' ';
525 | }
526 |
527 | }
528 |
--------------------------------------------------------------------------------