updates.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit; // Exit if accessed directly
  4. }
  5. if ( ! class_exists( 'acf_pro_updates' ) ) :
  6. class acf_pro_updates {
  7. /**
  8. * __construct
  9. *
  10. * Initialize filters, action, variables and includes
  11. *
  12. * @type function
  13. * @date 23/06/12
  14. * @since 5.0.0
  15. */
  16. function __construct() {
  17. // actions
  18. add_action( 'init', array( $this, 'init' ), 20 );
  19. }
  20. /**
  21. * init
  22. *
  23. * description
  24. *
  25. * @type function
  26. * @date 10/4/17
  27. * @since 5.5.10
  28. */
  29. function init() {
  30. // bail early if no show_updates.
  31. if ( ! acf_get_setting( 'show_updates' ) ) {
  32. return;
  33. }
  34. // bail early if not a plugin (included in theme).
  35. if ( ! acf_is_plugin_active() ) {
  36. return;
  37. }
  38. // register update
  39. acf_register_plugin_update(
  40. array(
  41. 'id' => 'pro',
  42. 'key' => acf_pro_get_license_key(),
  43. 'slug' => acf_get_setting( 'slug' ),
  44. 'basename' => acf_get_setting( 'basename' ),
  45. 'version' => acf_get_setting( 'version' ),
  46. )
  47. );
  48. add_action( 'admin_init', 'acf_pro_check_defined_license', 20 );
  49. add_action( 'current_screen', 'acf_pro_display_activation_error', 30 );
  50. // admin
  51. if ( is_admin() ) {
  52. add_action( 'in_plugin_update_message-' . acf_get_setting( 'basename' ), array( $this, 'modify_plugin_update_message' ), 10, 2 );
  53. }
  54. }
  55. /*
  56. * modify_plugin_update_message
  57. *
  58. * Displays an update message for plugin list screens.
  59. *
  60. * @type function
  61. * @date 14/06/2016
  62. * @since 5.3.8
  63. *
  64. * @param $message (string)
  65. * @param $plugin_data (array)
  66. * @param $r (object)
  67. * @return $message
  68. */
  69. function modify_plugin_update_message( $plugin_data, $response ) {
  70. // bail early if has key
  71. if ( acf_pro_get_license_key() ) {
  72. return;
  73. }
  74. // display message
  75. echo '<br />' . sprintf( __( 'To enable updates, please enter your license key on the <a href="%1$s">Updates</a> page. If you don\'t have a licence key, please see <a href="%2$s" target="_blank">details & pricing</a>.', 'acf' ), admin_url( 'edit.php?post_type=acf-field-group&page=acf-settings-updates' ), acf_add_url_utm_tags( 'https://www.advancedcustomfields.com/pro/', 'ACF upgrade', 'updates' ) );
  76. }
  77. }
  78. // initialize
  79. new acf_pro_updates();
  80. endif; // class_exists check
  81. /**
  82. * Check if a license is defined in wp-config.php and requires activation.
  83. * Also checks if the license key has been changed and reactivates.
  84. *
  85. * @date 29/09/2021
  86. * @since 5.11.0
  87. */
  88. function acf_pro_check_defined_license() {
  89. // Bail early if the license is not defined in wp-config.
  90. if ( ! defined( 'ACF_PRO_LICENSE' ) || empty( ACF_PRO_LICENSE ) || ! is_string( ACF_PRO_LICENSE ) ) {
  91. return;
  92. }
  93. // Bail early if no show_admin.
  94. if ( ! acf_get_setting( 'show_admin' ) ) {
  95. return;
  96. }
  97. // Check if we've been asked to clear the transient to retry activation.
  98. if ( acf_verify_nonce( 'acf_delete_activation_transient' ) || ( isset( $_REQUEST['acf_retry_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['acf_retry_nonce'] ) ), 'acf_retry_activation' ) ) ) {
  99. delete_transient( 'acf_activation_error' );
  100. } else {
  101. // If we've failed activation recently, check if the key has been changed, otherwise return.
  102. $activation_data = acf_pro_get_activation_failure_transient();
  103. if ( $activation_data && $activation_data['license'] === ACF_PRO_LICENSE ) {
  104. return;
  105. }
  106. }
  107. // If we're already activated, check if the defined license key has changed.
  108. $license = acf_pro_get_license();
  109. if ( $license ) {
  110. // Check the saved license key against the defined key.
  111. if ( acf_pro_get_license_key() !== ACF_PRO_LICENSE ) {
  112. // Deactivate if the key has changed.
  113. $deactivation_response = acf_pro_deactivate_license( true );
  114. // A connection error occurred while trying to deactivate.
  115. if ( is_wp_error( $deactivation_response ) ) {
  116. return acf_pro_set_activation_failure_transient( __( '<b>ACF Activation Error</b>. Your defined license key has changed, but an error occurred when connecting to activation server', 'acf' ) . ' <span class="description">(' . esc_html( $deactivation_response->get_error_message() ) . ').</span>', ACF_PRO_LICENSE );
  117. // A deactivation error occurred. Display the message returned by our API.
  118. } elseif ( ! $deactivation_response['success'] ) {
  119. return acf_pro_set_activation_failure_transient( __( '<b>ACF Activation Error</b>. Your defined license key has changed, but an error occurred when deactivating your old licence', 'acf' ) . ' <span class="description">(' . $deactivation_response['message'] . ').</span>', ACF_PRO_LICENSE );
  120. }
  121. } else {
  122. // Check if the licence has been marked as invalid during the update check.
  123. $basename = acf_get_setting( 'basename' );
  124. $update = acf_updates()->get_plugin_update( $basename );
  125. if ( isset( $update['license_valid'] ) && ! $update['license_valid'] ) {
  126. // Our site is not activated, so remove the license.
  127. acf_pro_update_license( '' );
  128. } else {
  129. // License key hasn't changed, we are activated and licence is still valid, return.
  130. return;
  131. }
  132. }
  133. }
  134. // Activate the defined key license.
  135. $activation_response = acf_pro_activate_license( ACF_PRO_LICENSE, true );
  136. $error_text = false;
  137. // A connection error occurred during activation
  138. if ( is_wp_error( $activation_response ) ) {
  139. $error_text = __( '<b>ACF Activation Error</b>. An error occurred when connecting to activation server', 'acf' ) . ' <span class="description">(' . esc_html( $activation_response->get_error_message() ) . ').</span>';
  140. // A deactivation error occurred. Display the message returned by our API.
  141. } elseif ( ! $activation_response['success'] ) {
  142. $error_text = __( '<b>ACF Activation Error</b>', 'acf' ) . ': <span class="description">' . $activation_response['message'] . '.</span>';
  143. } else {
  144. // Delete any previously saved activation error transient.
  145. delete_transient( 'acf_activation_error' );
  146. // Prefix connect API success message with ACF as we could be outside of the ACF admin and display message.
  147. acf_add_admin_notice( '<b>ACF </b>' . acf_esc_html( $activation_response['message'] ), 'success' );
  148. return;
  149. }
  150. return acf_pro_set_activation_failure_transient( $error_text, ACF_PRO_LICENSE );
  151. }
  152. /**
  153. * Set the automatic activation failure transient
  154. *
  155. * @date 11/10/2021
  156. * @since 5.11.0
  157. *
  158. * @param string $error_text string containing the error text message.
  159. * @param string $license_key the license key that was used during the failed activation.
  160. *
  161. * @return void
  162. */
  163. function acf_pro_set_activation_failure_transient( $error_text, $license_key ) {
  164. set_transient(
  165. 'acf_activation_error',
  166. array(
  167. 'error' => $error_text,
  168. 'license' => $license_key,
  169. ),
  170. HOUR_IN_SECONDS
  171. );
  172. }
  173. /**
  174. * Get the automatic activation failure transient
  175. *
  176. * @date 11/10/2021
  177. * @since 5.11.0
  178. *
  179. * @return array|false Activation failure transient array, or false if it's not set.
  180. */
  181. function acf_pro_get_activation_failure_transient() {
  182. return get_transient( 'acf_activation_error' );
  183. }
  184. /**
  185. * Display the stored activation error
  186. *
  187. * @date 11/10/2021
  188. * @since 5.11.0
  189. */
  190. function acf_pro_display_activation_error() {
  191. // Return if we're not in admin.
  192. if ( ! is_admin() ) {
  193. return;
  194. }
  195. // Return if the current user cannot view ACF settings.
  196. if ( ! acf_current_user_can_admin() ) {
  197. return;
  198. }
  199. // Check if the transient exists.
  200. $activation_data = acf_pro_get_activation_failure_transient();
  201. // Return if the transient does not exist.
  202. if ( ! $activation_data ) {
  203. return;
  204. }
  205. // Check if the license key is defined. If not, delete the transient.
  206. if ( ! defined( 'ACF_PRO_LICENSE' ) || empty( ACF_PRO_LICENSE ) || ! is_string( ACF_PRO_LICENSE ) ) {
  207. delete_transient( 'acf_activation_error' );
  208. return;
  209. }
  210. // Append a retry link if we're not already on the settings page.
  211. global $plugin_page;
  212. if ( ! $plugin_page || 'acf-settings-updates' !== $plugin_page ) {
  213. $nonce = wp_create_nonce( 'acf_retry_activation' );
  214. $check_again_url = admin_url( 'edit.php?post_type=acf-field-group&page=acf-settings-updates&acf_retry_nonce=' . $nonce );
  215. $activation_data['error'] = $activation_data['error'] . ' <a href="' . $check_again_url . '">' . __( 'Check Again', 'acf' ) . '</a>';
  216. }
  217. // Add a non-dismissible error message with the activation error.
  218. acf_add_admin_notice( acf_esc_html( $activation_data['error'] ), 'error', false );
  219. }
  220. /**
  221. * This function will return the license
  222. *
  223. * @type function
  224. * @date 20/09/2016
  225. * @since 5.4.0
  226. *
  227. * @return $license Activated license array
  228. */
  229. function acf_pro_get_license() {
  230. // get option
  231. $license = get_option( 'acf_pro_license' );
  232. // bail early if no value
  233. if ( ! $license ) {
  234. return false;
  235. }
  236. // decode
  237. $license = maybe_unserialize( base64_decode( $license ) );
  238. // bail early if corrupt
  239. if ( ! is_array( $license ) ) {
  240. return false;
  241. }
  242. // return
  243. return $license;
  244. }
  245. /**
  246. * An ACF specific getter to replace `home_url` in our licence checks to ensure we can avoid third party filters.
  247. *
  248. * @since 6.0.1
  249. *
  250. * @return string $home_url The output from home_url, sans known third party filters which cause licence activation issues.
  251. */
  252. function acf_get_home_url() {
  253. // Disable WPML's home url overrides for our license check.
  254. add_filter( 'wpml_get_home_url', 'acf_licence_wpml_intercept', 99, 2 );
  255. $home_url = home_url();
  256. // Re-enable WPML's home url overrides.
  257. remove_filter( 'wpml_get_home_url', 'acf_licence_wpml_intercept', 99 );
  258. return $home_url;
  259. }
  260. /**
  261. * Return the original home url inside ACF's home url getter.
  262. *
  263. * @since 6.0.1
  264. *
  265. * @param string $home_url the WPML converted home URL.
  266. * @param string $url the original home URL.
  267. *
  268. * @return string $url
  269. */
  270. function acf_licence_wpml_intercept( $home_url, $url ) {
  271. return $url;
  272. }
  273. /**
  274. * This function will return the license key
  275. *
  276. * @type function
  277. * @date 20/09/2016
  278. * @since 5.4.0
  279. *
  280. * @param boolean $skip_url_check Skip the check of the current site url.
  281. * @return string $license_key
  282. */
  283. function acf_pro_get_license_key( $skip_url_check = false ) {
  284. $license = acf_pro_get_license();
  285. $home_url = acf_get_home_url();
  286. // bail early if empty
  287. if ( ! $license || ! $license['key'] ) {
  288. return false;
  289. }
  290. // bail early if url has changed
  291. if ( ! $skip_url_check && acf_strip_protocol( $license['url'] ) !== acf_strip_protocol( $home_url ) ) {
  292. return false;
  293. }
  294. // return
  295. return $license['key'];
  296. }
  297. /**
  298. * This function will update the DB license
  299. *
  300. * @type function
  301. * @date 20/09/2016
  302. * @since 5.4.0
  303. *
  304. * @param string $key The license key
  305. * @return bool The result of the update_option call
  306. */
  307. function acf_pro_update_license( $key = '' ) {
  308. // vars
  309. $value = '';
  310. // key
  311. if ( $key ) {
  312. // vars
  313. $data = array(
  314. 'key' => $key,
  315. 'url' => acf_get_home_url(),
  316. );
  317. // encode
  318. $value = base64_encode( maybe_serialize( $data ) );
  319. }
  320. // re-register update (key has changed)
  321. acf_register_plugin_update(
  322. array(
  323. 'id' => 'pro',
  324. 'key' => $key,
  325. 'slug' => acf_get_setting( 'slug' ),
  326. 'basename' => acf_get_setting( 'basename' ),
  327. 'version' => acf_get_setting( 'version' ),
  328. )
  329. );
  330. // update
  331. return update_option( 'acf_pro_license', $value );
  332. }
  333. /**
  334. * Get count of registered ACF Blocks
  335. *
  336. * @return int
  337. */
  338. function acf_pro_get_registered_block_count() {
  339. return acf_get_store( 'block-types' )->count();
  340. }
  341. /**
  342. * Activates the submitted license key
  343. * Formally ACF_Admin_Updates::activate_pro_licence since 5.0.0
  344. *
  345. * @date 30/09/2021
  346. * @since 5.11.0
  347. *
  348. * @param string $license_key License key to activate
  349. * @param boolean $silent Return errors rather than displaying them
  350. * @return mixed $response A wp-error instance, or an array with a boolean success key, and string message key
  351. */
  352. function acf_pro_activate_license( $license_key, $silent = false ) {
  353. // Connect to API.
  354. $post = array(
  355. 'acf_license' => trim( $license_key ),
  356. 'acf_version' => acf_get_setting( 'version' ),
  357. 'wp_name' => get_bloginfo( 'name' ),
  358. 'wp_url' => acf_get_home_url(),
  359. 'wp_version' => get_bloginfo( 'version' ),
  360. 'wp_language' => get_bloginfo( 'language' ),
  361. 'wp_timezone' => get_option( 'timezone_string' ),
  362. 'php_version' => PHP_VERSION,
  363. 'block_count' => acf_pro_get_registered_block_count(),
  364. );
  365. $response = acf_updates()->request( 'v2/plugins/activate?p=pro', $post );
  366. // Check response is expected JSON array (not string).
  367. if ( is_string( $response ) ) {
  368. $response = new WP_Error( 'server_error', esc_html( $response ) );
  369. }
  370. // Display error.
  371. if ( is_wp_error( $response ) ) {
  372. if ( ! $silent ) {
  373. display_wp_activation_error( $response );
  374. }
  375. return $response;
  376. }
  377. $success = false;
  378. // On success.
  379. if ( $response['status'] == 1 ) {
  380. // Update license.
  381. acf_pro_update_license( $response['license'] );
  382. // Refresh plugins transient to fetch new update data.
  383. acf_updates()->refresh_plugins_transient();
  384. // Show notice.
  385. if ( ! $silent ) {
  386. acf_add_admin_notice( acf_esc_html( $response['message'] ), 'success' );
  387. }
  388. $success = true;
  389. // On failure.
  390. } else {
  391. // Show notice.
  392. if ( ! $silent ) {
  393. acf_add_admin_notice( acf_esc_html( $response['message'] ), 'warning' );
  394. }
  395. }
  396. // Return status array for automated activation error notices
  397. return array(
  398. 'success' => $success,
  399. 'message' => $response['message'],
  400. );
  401. }
  402. /**
  403. * Deactivates the registered license key.
  404. * Formally ACF_Admin_Updates::deactivate_pro_licence since 5.0.0
  405. *
  406. * @date 30/09/2021
  407. * @since 5.11.0
  408. *
  409. * @param bool $silent Return errors rather than displaying them
  410. * @return mixed $response A wp-error instance, or an array with a boolean success key, and string message key
  411. */
  412. function acf_pro_deactivate_license( $silent = false ) {
  413. // Get license key.
  414. $license = acf_pro_get_license_key( true );
  415. // Bail early if no key.
  416. if ( ! $license ) {
  417. return false;
  418. }
  419. // Connect to API.
  420. $post = array(
  421. 'acf_license' => $license,
  422. 'wp_url' => acf_get_home_url(),
  423. );
  424. $response = acf_updates()->request( 'v2/plugins/deactivate?p=pro', $post );
  425. // Check response is expected JSON array (not string).
  426. if ( is_string( $response ) ) {
  427. $response = new WP_Error( 'server_error', esc_html( $response ) );
  428. }
  429. // Display error.
  430. if ( is_wp_error( $response ) ) {
  431. if ( ! $silent ) {
  432. display_wp_activation_error( $response );
  433. }
  434. return $response;
  435. }
  436. // Remove license key from DB.
  437. acf_pro_update_license( '' );
  438. // Refresh plugins transient to fetch new update data.
  439. acf_updates()->refresh_plugins_transient();
  440. $success = $response['status'] == 1;
  441. if ( ! $silent ) {
  442. $notice_class = $success ? 'info' : 'warning';
  443. acf_add_admin_notice( acf_esc_html( $response['message'] ), $notice_class );
  444. }
  445. // Return status array for automated activation error notices
  446. return array(
  447. 'success' => $success,
  448. 'message' => $response['message'],
  449. );
  450. }
  451. /**
  452. * Adds an admin notice using the provided WP_Error.
  453. *
  454. * @date 14/1/19
  455. * @since 5.7.10
  456. *
  457. * @param WP_Error $wp_error The error to display.
  458. */
  459. function display_wp_activation_error( $wp_error ) {
  460. // Only show one error on page.
  461. if ( acf_has_done( 'display_wp_error' ) ) {
  462. return;
  463. }
  464. // Create new notice.
  465. acf_new_admin_notice(
  466. array(
  467. 'text' => __( '<b>ACF Activation Error</b>. Could not connect to activation server', 'acf' ) . ' <span class="description">(' . esc_html( $wp_error->get_error_message() ) . ').</span>',
  468. 'type' => 'error',
  469. )
  470. );
  471. }