class-vc-license.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. die( '-1' );
  4. }
  5. /**
  6. * WPBakery WPBakery Page Builder Plugin
  7. *
  8. * @package WPBakeryPageBuilder
  9. *
  10. */
  11. /**
  12. * Manage license
  13. *
  14. * Activation/deactivation is done via support portal and does not use Envato username and
  15. * api_key anymore
  16. */
  17. class Vc_License {
  18. /**
  19. * Option name where license key is stored
  20. *
  21. * @var string
  22. */
  23. protected static $license_key_option = 'js_composer_purchase_code';
  24. /**
  25. * Option name where license key token is stored
  26. *
  27. * @var string
  28. */
  29. protected static $license_key_token_option = 'license_key_token';
  30. /**
  31. * @var string
  32. */
  33. protected static $support_host = 'https://support.wpbakery.com';
  34. /**
  35. * @var string
  36. */
  37. public $error = null;
  38. public function init() {
  39. if ( 'vc-updater' === vc_get_param( 'page' ) ) {
  40. $activate = vc_get_param( 'activate' );
  41. $deactivate = vc_get_param( 'deactivate' );
  42. if ( $activate ) {
  43. $this->finishActivationDeactivation( true, $activate );
  44. } elseif ( $deactivate ) {
  45. $this->finishActivationDeactivation( false, $deactivate );
  46. }
  47. }
  48. add_action( 'wp_ajax_vc_get_activation_url', array(
  49. $this,
  50. 'startActivationResponse',
  51. ) );
  52. add_action( 'wp_ajax_vc_get_deactivation_url', array(
  53. $this,
  54. 'startDeactivationResponse',
  55. ) );
  56. add_action( 'wp_ajax_nopriv_vc_check_license_key', array(
  57. vc_license(),
  58. 'checkLicenseKeyFromRemote',
  59. ) );
  60. }
  61. /**
  62. * Output notice
  63. *
  64. * @param string $message
  65. * @param bool $success
  66. */
  67. public function outputNotice( $message, $success = true ) {
  68. echo sprintf( '<div class="%s"><p>%s</p></div>', (bool) $success ? 'updated' : 'error', esc_html( $message ) );
  69. }
  70. /**
  71. * Show error
  72. *
  73. * @param string $error
  74. */
  75. public function showError( $error ) {
  76. $this->error = $error;
  77. add_action( 'admin_notices', array(
  78. $this,
  79. 'outputLastError',
  80. ) );
  81. }
  82. /**
  83. * Output last error
  84. */
  85. public function outputLastError() {
  86. $this->outputNotice( $this->error, false );
  87. }
  88. /**
  89. * Output successful activation message
  90. */
  91. public function outputActivatedSuccess() {
  92. $this->outputNotice( esc_html__( 'WPBakery Page Builder successfully activated.', 'js_composer' ), true );
  93. }
  94. /**
  95. * Output successful deactivation message
  96. */
  97. public function outputDeactivatedSuccess() {
  98. $this->outputNotice( esc_html__( 'WPBakery Page Builder successfully deactivated.', 'js_composer' ), true );
  99. }
  100. /**
  101. * Finish pending activation/deactivation
  102. *
  103. * 1) Make API call to support portal
  104. * 2) Receive success status and license key
  105. * 3) Set new license key
  106. *
  107. * @param bool $activation
  108. * @param string $user_token
  109. *
  110. * @return bool
  111. */
  112. public function finishActivationDeactivation( $activation, $user_token ) {
  113. if ( ! $this->isValidToken( $user_token ) ) {
  114. $this->showError( esc_html__( 'Token is not valid or has expired', 'js_composer' ) );
  115. return false;
  116. }
  117. if ( $activation ) {
  118. $url = self::$support_host . '/finish-license-activation';
  119. } else {
  120. $url = self::$support_host . '/finish-license-deactivation';
  121. }
  122. $params = array(
  123. 'body' => array( 'token' => $user_token ),
  124. 'timeout' => 30,
  125. );
  126. // FIX SSL SNI
  127. $filter_add = true;
  128. if ( function_exists( 'curl_version' ) ) {
  129. $version = curl_version();
  130. if ( version_compare( $version['version'], '7.18', '>=' ) ) {
  131. $filter_add = false;
  132. }
  133. }
  134. if ( $filter_add ) {
  135. add_filter( 'https_ssl_verify', '__return_false' );
  136. }
  137. $response = wp_remote_post( $url, $params );
  138. if ( $filter_add ) {
  139. remove_filter( 'https_ssl_verify', '__return_false' );
  140. }
  141. if ( is_wp_error( $response ) ) {
  142. $this->showError( sprintf( esc_html__( '%s. Please try again.', 'js_composer' ), $response->get_error_message() ) );
  143. return false;
  144. }
  145. if ( 200 !== $response['response']['code'] ) {
  146. $this->showError( sprintf( esc_html__( 'Server did not respond with OK: %s', 'js_composer' ), $response['response']['code'] ) );
  147. return false;
  148. }
  149. $json = json_decode( $response['body'], true );
  150. if ( ! $json || ! isset( $json['status'] ) ) {
  151. $this->showError( esc_html__( 'Invalid response structure. Please contact us for support.', 'js_composer' ) );
  152. return false;
  153. }
  154. if ( ! $json['status'] ) {
  155. $this->showError( esc_html__( 'Something went wrong. Please contact us for support.', 'js_composer' ) );
  156. return false;
  157. }
  158. if ( $activation ) {
  159. if ( ! isset( $json['license_key'] ) || ! $this->isValidFormat( $json['license_key'] ) ) {
  160. $this->showError( esc_html__( 'Invalid response structure. Please contact us for support.', 'js_composer' ) );
  161. return false;
  162. }
  163. $this->setLicenseKey( $json['license_key'] );
  164. add_action( 'admin_notices', array(
  165. $this,
  166. 'outputActivatedSuccess',
  167. ) );
  168. } else {
  169. $this->setLicenseKey( '' );
  170. add_action( 'admin_notices', array(
  171. $this,
  172. 'outputDeactivatedSuccess',
  173. ) );
  174. }
  175. $this->setLicenseKeyToken( '' );
  176. return true;
  177. }
  178. /**
  179. * @return boolean
  180. */
  181. public function isActivated() {
  182. return (bool) $this->getLicenseKey();
  183. }
  184. /**
  185. * Check license key from remote
  186. *
  187. * Function is used by support portal to check if VC w/ specific license is still installed
  188. */
  189. public function checkLicenseKeyFromRemote() {
  190. $license_key = vc_request_param( 'license_key' );
  191. if ( ! $this->isValid( $license_key ) ) {
  192. $response = array(
  193. 'status' => false,
  194. 'error' => esc_html__( 'Invalid license key', 'js_composer' ),
  195. );
  196. } else {
  197. $response = array( 'status' => true );
  198. }
  199. die( wp_json_encode( $response ) );
  200. }
  201. /**
  202. * Generate action URL
  203. *
  204. * @return string
  205. */
  206. public function generateActivationUrl() {
  207. $token = sha1( $this->newLicenseKeyToken() );
  208. $url = esc_url( self::getSiteUrl() );
  209. $redirect = esc_url( vc_updater()->getUpdaterUrl() );
  210. return sprintf( '%s/activate-license?token=%s&url=%s&redirect=%s', self::$support_host, $token, $url, $redirect );
  211. }
  212. /**
  213. * Generate action URL
  214. *
  215. * @return string
  216. */
  217. public function generateDeactivationUrl() {
  218. $license_key = $this->getLicenseKey();
  219. $token = sha1( $this->newLicenseKeyToken() );
  220. $url = esc_url( self::getSiteUrl() );
  221. $redirect = esc_url( vc_updater()->getUpdaterUrl() );
  222. return sprintf( '%s/deactivate-license?license_key=%s&token=%s&url=%s&redirect=%s', self::$support_host, $license_key, $token, $url, $redirect );
  223. }
  224. /**
  225. * Start activation process and output redirect URL as JSON
  226. */
  227. public function startActivationResponse() {
  228. vc_user_access()->checkAdminNonce()->validateDie()->wpAny( 'manage_options' )->validateDie()->part( 'settings' )->can( 'vc-updater-tab' )->validateDie();
  229. $response = array(
  230. 'status' => true,
  231. 'url' => $this->generateActivationUrl(),
  232. );
  233. die( wp_json_encode( $response ) );
  234. }
  235. /**
  236. * Start deactivation process and output redirect URL as JSON
  237. */
  238. public function startDeactivationResponse() {
  239. vc_user_access()->checkAdminNonce()->validateDie( 'Failed nonce check' )->wpAny( 'manage_options' )->validateDie( 'Failed access check' )->part( 'settings' )->can( 'vc-updater-tab' )
  240. ->validateDie( 'Failed access check #2' );
  241. $response = array(
  242. 'status' => true,
  243. 'url' => $this->generateDeactivationUrl(),
  244. );
  245. die( wp_json_encode( $response ) );
  246. }
  247. /**
  248. * Set license key
  249. *
  250. * @param string $license_key
  251. */
  252. public function setLicenseKey( $license_key ) {
  253. if ( vc_is_network_plugin() ) {
  254. update_site_option( 'wpb_js_' . self::$license_key_option, $license_key );
  255. } else {
  256. vc_settings()->set( self::$license_key_option, $license_key );
  257. }
  258. }
  259. /**
  260. * Get license key
  261. *
  262. * @return string
  263. */
  264. public function getLicenseKey() {
  265. if ( vc_is_network_plugin() ) {
  266. $value = get_site_option( 'wpb_js_' . self::$license_key_option );
  267. } else {
  268. $value = vc_settings()->get( self::$license_key_option );
  269. }
  270. return $value;
  271. }
  272. /**
  273. * Check if specified license key is valid
  274. *
  275. * @param string $license_key
  276. *
  277. * @return bool
  278. */
  279. public function isValid( $license_key ) {
  280. return $license_key === $this->getLicenseKey();
  281. }
  282. /**
  283. * Set up license activation notice if needed
  284. *
  285. * Don't show notice on dev environment
  286. */
  287. public function setupReminder() {
  288. if ( self::isDevEnvironment() ) {
  289. return;
  290. }
  291. $version1 = isset( $_COOKIE['vchideactivationmsg_vc11'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['vchideactivationmsg_vc11'] ) ) : '';
  292. if ( ! $this->isActivated() && ( empty( $version1 ) || version_compare( $version1, WPB_VC_VERSION, '<' ) ) && ! ( vc_is_network_plugin() && is_network_admin() ) ) {
  293. add_action( 'admin_notices', array(
  294. $this,
  295. 'adminNoticeLicenseActivation',
  296. ) );
  297. }
  298. }
  299. /**
  300. * Check if current enviroment is dev
  301. *
  302. * Environment is considered dev if host is:
  303. * - ip address
  304. * - tld is local, dev, wp, test, example, localhost or invalid
  305. * - no tld (localhost, custom hosts)
  306. *
  307. * @param string $host Hostname to check. If null, use HTTP_HOST
  308. *
  309. * @return boolean
  310. */
  311. public static function isDevEnvironment( $host = null ) {
  312. if ( ! $host ) {
  313. $host = self::getSiteUrl();
  314. }
  315. $chunks = explode( '.', $host );
  316. if ( 1 === count( $chunks ) ) {
  317. return true;
  318. }
  319. if ( in_array( end( $chunks ), array(
  320. 'local',
  321. 'dev',
  322. 'wp',
  323. 'test',
  324. 'example',
  325. 'localhost',
  326. 'invalid',
  327. ), true ) ) {
  328. return true;
  329. }
  330. if ( preg_match( '/^[0-9\.]+$/', $host ) ) {
  331. return true;
  332. }
  333. return false;
  334. }
  335. public function adminNoticeLicenseActivation() {
  336. if ( vc_is_network_plugin() ) {
  337. update_site_option( 'wpb_js_composer_license_activation_notified', 'yes' );
  338. } else {
  339. vc_settings()->set( 'composer_license_activation_notified', 'yes' );
  340. }
  341. $redirect = esc_url( vc_updater()->getUpdaterUrl() );
  342. $first_tag = 'style';
  343. $second_tag = 'script';
  344. // @codingStandardsIgnoreStart
  345. ?>
  346. <<?php echo esc_attr( $first_tag ); ?>>
  347. .vc_license-activation-notice {
  348. position: relative;
  349. }
  350. </<?php echo esc_attr( $first_tag ); ?>>
  351. <<?php echo esc_attr( $second_tag ); ?>>
  352. (function ( $ ) {
  353. var setCookie = function ( c_name, value, exdays ) {
  354. var exdate = new Date();
  355. exdate.setDate( exdate.getDate() + exdays );
  356. var c_value = encodeURIComponent( value ) + ((null === exdays) ? "" : "; expires=" + exdate.toUTCString());
  357. document.cookie = c_name + "=" + c_value;
  358. };
  359. $( document ).off( 'click.vc-notice-dismiss' ).on( 'click.vc-notice-dismiss',
  360. '.vc-notice-dismiss',
  361. function ( e ) {
  362. e.preventDefault();
  363. var $el = $( this ).closest(
  364. '#vc_license-activation-notice' );
  365. $el.fadeTo( 100, 0, function () {
  366. $el.slideUp( 100, function () {
  367. $el.remove();
  368. } );
  369. } );
  370. setCookie( 'vchideactivationmsg_vc11',
  371. '<?php echo esc_attr( WPB_VC_VERSION ); ?>',
  372. 30 );
  373. } );
  374. })( window.jQuery );
  375. </<?php echo esc_attr( $second_tag ); ?>>
  376. <?php
  377. echo '<div class="updated vc_license-activation-notice" id="vc_license-activation-notice"><p>' . sprintf( esc_html__( 'Hola! Would you like to receive automatic updates and unlock premium support? Please %sactivate your copy%s of WPBakery Page Builder.', 'js_composer' ), '<a href="' . esc_url( wp_nonce_url( $redirect ) ) . '">', '</a>' ) . '</p>' . '<button type="button" class="notice-dismiss vc-notice-dismiss"><span class="screen-reader-text">' . esc_html__( 'Dismiss this notice.', 'js_composer' ) . '</span></button></div>';
  378. // @codingStandardsIgnoreEnd
  379. }
  380. /**
  381. * Get license key token
  382. *
  383. * @return string
  384. */
  385. public function getLicenseKeyToken() {
  386. $value = vc_is_network_plugin() ? get_site_option( self::$license_key_token_option ) : get_option( self::$license_key_token_option );
  387. return $value;
  388. }
  389. /**
  390. * Set license key token
  391. *
  392. * @param string $token
  393. *
  394. * @return string
  395. */
  396. public function setLicenseKeyToken( $token ) {
  397. if ( vc_is_network_plugin() ) {
  398. $value = update_site_option( self::$license_key_token_option, $token );
  399. } else {
  400. $value = update_option( self::$license_key_token_option, $token );
  401. }
  402. return $value;
  403. }
  404. /**
  405. * Return new license key token
  406. *
  407. * Token is used to change license key from remote location
  408. *
  409. * Format is: timestamp|20-random-characters
  410. *
  411. * @return string
  412. */
  413. public function generateLicenseKeyToken() {
  414. $token = current_time( 'timestamp' ) . '|' . vc_random_string( 20 );
  415. return $token;
  416. }
  417. /**
  418. * Generate and set new license key token
  419. *
  420. * @return string
  421. */
  422. public function newLicenseKeyToken() {
  423. $token = $this->generateLicenseKeyToken();
  424. $this->setLicenseKeyToken( $token );
  425. return $token;
  426. }
  427. /**
  428. * Check if specified license key token is valid
  429. *
  430. * @param string $token_to_check SHA1 hashed token
  431. * @param int $ttl_in_seconds Time to live in seconds. Default = 20min
  432. *
  433. * @return boolean
  434. */
  435. public function isValidToken( $token_to_check, $ttl_in_seconds = 1200 ) {
  436. $token = $this->getLicenseKeyToken();
  437. if ( ! $token_to_check || sha1( $token ) !== $token_to_check ) {
  438. return false;
  439. }
  440. $chunks = explode( '|', $token );
  441. if ( intval( $chunks[0] ) < ( current_time( 'timestamp' ) - $ttl_in_seconds ) ) {
  442. return false;
  443. }
  444. return true;
  445. }
  446. /**
  447. * Check if license key format is valid
  448. *
  449. * license key is version 4 UUID, that have form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
  450. * where x is any hexadecimal digit and y is one of 8, 9, A, or B.
  451. *
  452. * @param string $license_key
  453. *
  454. * @return boolean
  455. */
  456. public function isValidFormat( $license_key ) {
  457. $pattern = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i';
  458. return (bool) preg_match( $pattern, $license_key );
  459. }
  460. /**
  461. * @return string|void
  462. */
  463. /**
  464. * @return string|void
  465. */
  466. public static function getSiteUrl() {
  467. if ( vc_is_network_plugin() ) {
  468. return network_site_url();
  469. } else {
  470. return site_url();
  471. }
  472. }
  473. }