PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.6.0
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.6.0
3.6.0 3.5.3 3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / vendor / themegrill / themegrill-sdk / src / Modules / Rollback.php
everest-forms / vendor / themegrill / themegrill-sdk / src / Modules Last commit date
Announcements.php 6 months ago Licenser.php 1 month ago Logger.php 6 months ago Notification.php 6 months ago Rollback.php 6 months ago ScriptLoader.php 6 months ago UninstallFeedback.php 6 months ago Updater.php 1 month ago
Rollback.php
416 lines
1 <?php
2 /**
3 * The rollback class for ThemeGrill SDK.
4 *
5 * @package ThemeGrillSDK
6 * @subpackage Rollback
7 * @copyright Copyright (c) 2025, ThemeGrill
8 * @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
9 * @since 1.0.0
10 */
11
12 namespace ThemeGrillSDK\Modules;
13
14 // Exit if accessed directly.
15 use ThemeGrillSDK\Common\AbstractModule;
16 use ThemeGrillSDK\Loader;
17 use ThemeGrillSDK\Product;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Rollback for ThemeGrill SDK.
25 */
26 class Rollback extends AbstractModule {
27
28 /**
29 * Add js scripts for themes rollback.
30 */
31 public function add_footer() {
32 $screen = get_current_screen();
33 if ( ! isset( $screen->parent_file ) ) {
34 return;
35 }
36 if ( 'themes.php' !== $screen->parent_file ) {
37 return;
38 }
39 if ( ! $this->product->is_theme() ) {
40 return;
41 }
42 $version = $this->get_rollback();
43 if ( empty( $version ) ) {
44 return;
45 }
46 ?>
47 <script type="text/javascript">
48 jQuery(document).ready(function ($) {
49 setInterval(checkTheme, 500);
50
51 function checkTheme() {
52 var theme = '<?php echo esc_attr( $this->product->get_slug() ); ?>-action';
53
54 if (jQuery('#' + theme).length > 0) {
55 if (jQuery('.theme-overlay.active').is(':visible')) {
56 if (jQuery('#' + theme + '-rollback').length === 0) {
57 jQuery('.theme-actions .active-theme').prepend('<a class="button" style="float:left" id="' + theme + '-rollback" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=' . $this->product->get_key() . '_rollback' ), $this->product->get_key() . '_rollback' ) ); ?>">Rollback to v<?php echo esc_attr( $version['version'] ); ?></a>')
58 }
59 }
60
61 }
62 }
63 })
64
65 </script>
66 <?php
67 }
68
69 /**
70 * Get the last rollback for this product.
71 *
72 * @return array The rollback version.
73 */
74 public function get_rollback() {
75 $rollback = array();
76 $versions = $this->get_api_versions();
77 $versions = apply_filters( $this->product->get_key() . '_rollbacks', $versions );
78 if ( empty( $versions ) ) {
79 return $rollback;
80 }
81 if ( $versions ) {
82 usort( $versions, array( $this, 'sort_rollback_array' ) );
83 foreach ( $versions as $version ) {
84 if ( isset( $version['version'] ) && isset( $version['url'] ) && version_compare( $this->product->get_version(), $version['version'], '>' ) ) {
85 $rollback = $version;
86 break;
87 }
88 }
89 }
90
91 return $rollback;
92 }
93
94 /**
95 * Get versions array from wp.org
96 *
97 * @return array Array of versions.
98 */
99 private function get_api_versions() {
100 $cache_key = $this->product->get_cache_key();
101 $cache_versions = get_transient( $cache_key );
102 if ( false === $cache_versions ) {
103 $versions = $this->get_remote_versions();
104 set_transient( $cache_key, $versions, 5 * DAY_IN_SECONDS );
105 } else {
106 $versions = is_array( $cache_versions ) ? $cache_versions : array();
107 }
108
109 return $versions;
110 }
111
112 /**
113 * Get remote versions zips.
114 *
115 * @return array Array of available versions.
116 */
117 private function get_remote_versions() {
118 $url = $this->get_versions_api_url();
119 if ( empty( $url ) ) {
120 return [];
121 }
122 $response = function_exists( 'vip_safe_wp_remote_get' )
123 ? vip_safe_wp_remote_get( $url )
124 : wp_remote_get( $url ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
125 if ( is_wp_error( $response ) ) {
126 return array();
127 }
128 $response = wp_remote_retrieve_body( $response );
129
130 if ( is_serialized( $response ) ) {
131 $response = maybe_unserialize( $response );
132 } else {
133 $response = json_decode( $response );
134 }
135
136 if ( ! is_object( $response ) ) {
137 return array();
138 }
139 if ( ! isset( $response->versions ) ) {
140 return array();
141 }
142
143 $versions = array();
144 foreach ( $response->versions as $key => $value ) {
145 $versions[] = array(
146 'version' => is_object( $value ) ? $value->version : $key,
147 'url' => is_object( $value ) ? $value->file : $value,
148 );
149 }
150
151 return $versions;
152 }
153
154 /**
155 * Return url where to check for versions.
156 *
157 * @return string Url where to check for versions.
158 */
159 private function get_versions_api_url() {
160 if ( $this->product->is_wordpress_available() && $this->product->is_plugin() ) {
161 return sprintf( 'https://api.wordpress.org/plugins/info/1.0/%s', $this->product->get_slug() );
162 }
163 if ( $this->product->is_wordpress_available() && $this->product->is_theme() ) {
164 return sprintf( 'https://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]=%s&request[fields][versions]=true', $this->product->get_slug() );
165 }
166 $license = $this->product->get_license();
167 if ( $this->product->requires_license() && strlen( $license ) < 10 ) {
168 return '';
169 }
170
171 return sprintf( '%slicense/versions/%s/%s/%s/%s', Product::API_URL, rawurlencode( $this->product->get_name() ), $license, urlencode( get_site_url() ), $this->product->get_version() );
172 }
173
174 /**
175 * Show the rollback links in the plugin page.
176 *
177 * @param array $links Plugin links.
178 *
179 * @return array $links Altered links.
180 */
181 public function add_rollback_link( $links ) {
182 $version = $this->get_rollback();
183 if ( empty( $version ) ) {
184 return $links;
185 }
186 $links[] = '<a href="' . wp_nonce_url( admin_url( 'admin-post.php?action=' . $this->product->get_key() . '_rollback' ), $this->product->get_key() . '_rollback' ) . '">' . sprintf( apply_filters( $this->product->get_key() . '_rollback_label', Loader::$labels['rollback']['cta'] ), $version['version'] ) . '</a>';
187
188 return $links;
189 }
190
191 /**
192 * Start the rollback operation.
193 */
194 public function start_rollback() {
195 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], $this->product->get_key() . '_rollback' ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
196 wp_nonce_ays( '' );
197 }
198
199 if ( $this->product->is_plugin() ) {
200 $this->start_rollback_plugin();
201
202 return;
203 }
204 if ( $this->product->is_theme() ) {
205 $this->start_rollback_theme();
206
207 return;
208 }
209 }
210
211 /**
212 * Start the rollback operation for the plugin.
213 */
214 private function start_rollback_plugin() {
215 $rollback = $this->get_rollback();
216 $plugin_transient = get_site_transient( 'update_plugins' );
217 $plugin_folder = $this->product->get_slug();
218 $plugin_file = $this->product->get_file();
219 $version = $rollback['version'];
220 $temp_array = array(
221 'slug' => $plugin_folder,
222 'new_version' => $version,
223 'package' => $rollback['url'],
224 );
225
226 $temp_object = (object) $temp_array;
227 $plugin_transient->response[ $plugin_folder . '/' . $plugin_file ] = $temp_object;
228 set_site_transient( 'update_plugins', $plugin_transient );
229
230 $transient = get_transient( $this->product->get_key() . '_warning_rollback' );
231
232 // Style fix for the api link that gets outside the content.
233 echo '<style>body#error-page{word-break:break-word;}</style>';
234
235 if ( false === $transient ) {
236 set_transient( $this->product->get_key() . '_warning_rollback', 'in progress', 30 );
237 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
238 $title = sprintf( apply_filters( $this->product->get_key() . '_rollback_message', 'Rolling back %s to v%s' ), $this->product->get_name(), $version );
239 $plugin = $plugin_folder . '/' . $plugin_file;
240 $nonce = 'upgrade-plugin_' . $plugin;
241 $url = 'update.php?action=upgrade-plugin&plugin=' . urlencode( $plugin );
242 $upgrader_skin = new \Plugin_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'plugin' ) );
243 $upgrader = new \Plugin_Upgrader( $upgrader_skin );
244 $upgrader->upgrade( $plugin );
245 delete_transient( $this->product->get_key() . '_warning_rollback' );
246 wp_die(
247 '',
248 esc_attr( $title ),
249 array(
250 'response' => 200,
251 )
252 );
253 }
254 }
255
256 /**
257 * Start the rollback operation for the theme.
258 */
259 private function start_rollback_theme() {
260 add_filter( 'update_theme_complete_actions', array( $this, 'alter_links_theme_upgrade' ) );
261 $rollback = $this->get_rollback();
262 $transient = get_site_transient( 'update_themes' );
263 $folder = $this->product->get_slug();
264 $version = $rollback['version'];
265 $temp_array = array(
266 'new_version' => $version,
267 'package' => $rollback['url'],
268 );
269
270 $transient->response[ $folder . '/style.css' ] = $temp_array;
271 set_site_transient( 'update_themes', $transient );
272
273 $transient = get_transient( $this->product->get_key() . '_warning_rollback' );
274
275 // Style fix for the api link that gets outside the content.
276 echo '<style>body#error-page{word-break:break-word;}</style>';
277
278 if ( false === $transient ) {
279 set_transient( $this->product->get_key() . '_warning_rollback', 'in progress', 30 );
280 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
281 $title = sprintf( apply_filters( $this->product->get_key() . '_rollback_message', 'Rolling back %s to v%s' ), $this->product->get_name(), $version );
282 $theme = $folder . '/style.css';
283 $nonce = 'upgrade-theme_' . $theme;
284 $url = 'update.php?action=upgrade-theme&theme=' . urlencode( $theme );
285
286 /**
287 * The rollback will attach a temporary theme for the rollback to the transient.
288 * However, when executing the upgrade for the attached theme we need to change the slug to the original theme slug.
289 * This is because it will use the slug to create a temp folder for the theme used during the upgrade.
290 */
291 add_filter(
292 'upgrader_package_options',
293 function ( $options ) use ( $folder, $theme ) {
294 if ( isset( $options['hook_extra']['theme'] ) && $options['hook_extra']['theme'] === $theme && isset( $options['hook_extra']['temp_backup']['slug'] ) ) {
295 $options['hook_extra']['temp_backup']['slug'] = $folder;
296 }
297
298 return $options;
299 }
300 );
301
302 $upgrader = new \Theme_Upgrader( new \Theme_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'theme' ) ) );
303 $upgrader->upgrade( $theme );
304 delete_transient( $this->product->get_key() . '_warning_rollback' );
305 wp_die(
306 '',
307 esc_attr( $title ),
308 array(
309 'response' => 200,
310 )
311 );
312 }
313 }
314
315 /**
316 * Alter links and remove duplicate customize message.
317 *
318 * @param array $links Array of old links.
319 *
320 * @return mixed Array of links.
321 */
322 public function alter_links_theme_upgrade( $links ) {
323 if ( isset( $links['preview'] ) ) {
324 $links['preview'] = str_replace( '<span aria-hidden="true">Customize</span>', '', $links['preview'] );
325 }
326
327 return $links;
328 }
329
330 /**
331 * Loads product object.
332 *
333 * @param Product $product Product object.
334 *
335 * @return bool Should we load the module?
336 */
337 public function can_load( $product ) {
338 if ( $this->is_from_partner( $product ) ) {
339 return false;
340 }
341 if ( $product->is_theme() && ! current_user_can( 'switch_themes' ) ) {
342 return false;
343 }
344
345 if ( $product->is_plugin() && ! current_user_can( 'install_plugins' ) ) {
346 return false;
347 }
348
349 return true;
350 }
351
352 /**
353 * Sort the rollbacks array in descending order.
354 *
355 * @param mixed $a First version to compare.
356 * @param mixed $b Second version to compare.
357 *
358 * @return bool Which version is greater?
359 */
360 public function sort_rollback_array( $a, $b ) {
361 return version_compare( $b['version'], $a['version'] );
362 }
363
364 /**
365 * Load module logic.
366 *
367 * @param Product $product Product object.
368 *
369 * @return $this Module object.
370 */
371 public function load( $product ) {
372 $this->product = $product;
373 $this->show_link();
374 $this->add_hooks();
375
376 return $this;
377 }
378
379 /**
380 * If product can be rolled back, show the link to rollback.
381 */
382 private function show_link() {
383 add_filter(
384 'plugin_action_links_' . plugin_basename( $this->product->get_basefile() ),
385 array(
386 $this,
387 'add_rollback_link',
388 )
389 );
390 }
391
392 /**
393 * Fires after the option has been updated.
394 *
395 * @param mixed $old_value The old option value.
396 * @param mixed $value The new option value.
397 * @param string $option Option name.
398 */
399 public function update_active_plugins_action( $old_value, $value, $option ) {
400 delete_site_transient( 'update_plugins' );
401 wp_cache_delete( 'plugins', 'plugins' );
402 }
403
404 /**
405 * Set the rollback hook. Strangely, this does not work if placed in the ThemeGrill_SDK_Rollback class, so it is being called from there instead.
406 */
407 public function add_hooks() {
408 add_action( 'admin_post_' . $this->product->get_key() . '_rollback', array( $this, 'start_rollback' ) );
409 add_action( 'admin_footer', array( $this, 'add_footer' ) );
410
411 // This hook will be invoked after the plugin activation.
412 // We use this to force an update of the cache so that Update is present immediate after a rollback.
413 add_action( 'update_option_active_plugins', array( $this, 'update_active_plugins_action' ), 10, 3 );
414 }
415 }
416