Нет описания

class-wc-helper.php 51KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642
  1. <?php
  2. /**
  3. * WooCommerce Admin Helper
  4. *
  5. * @package WooCommerce\Admin\Helper
  6. */
  7. use Automattic\Jetpack\Constants;
  8. if ( ! defined( 'ABSPATH' ) ) {
  9. exit;
  10. }
  11. /**
  12. * WC_Helper Class
  13. *
  14. * The main entry-point for all things related to the Helper.
  15. */
  16. class WC_Helper {
  17. /**
  18. * A log object returned by wc_get_logger().
  19. *
  20. * @var $log
  21. */
  22. public static $log;
  23. /**
  24. * Get an absolute path to the requested helper view.
  25. *
  26. * @param string $view The requested view file.
  27. *
  28. * @return string The absolute path to the view file.
  29. */
  30. public static function get_view_filename( $view ) {
  31. return dirname( __FILE__ ) . "/views/$view";
  32. }
  33. /**
  34. * Loads the helper class, runs on init.
  35. */
  36. public static function load() {
  37. self::includes();
  38. add_action( 'current_screen', array( __CLASS__, 'current_screen' ) );
  39. add_action( 'woocommerce_helper_output', array( __CLASS__, 'render_helper_output' ) );
  40. add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
  41. add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
  42. do_action( 'woocommerce_helper_loaded' );
  43. }
  44. /**
  45. * Include supporting helper classes.
  46. */
  47. protected static function includes() {
  48. include_once dirname( __FILE__ ) . '/class-wc-helper-options.php';
  49. include_once dirname( __FILE__ ) . '/class-wc-helper-api.php';
  50. include_once dirname( __FILE__ ) . '/class-wc-helper-updater.php';
  51. include_once dirname( __FILE__ ) . '/class-wc-helper-plugin-info.php';
  52. include_once dirname( __FILE__ ) . '/class-wc-helper-compat.php';
  53. }
  54. /**
  55. * Render the helper section content based on context.
  56. */
  57. public static function render_helper_output() {
  58. $auth = WC_Helper_Options::get( 'auth' );
  59. $auth_user_data = WC_Helper_Options::get( 'auth_user_data' );
  60. // Return success/error notices.
  61. $notices = self::_get_return_notices();
  62. // No active connection.
  63. if ( empty( $auth['access_token'] ) ) {
  64. $connect_url = add_query_arg(
  65. array(
  66. 'page' => 'wc-addons',
  67. 'section' => 'helper',
  68. 'wc-helper-connect' => 1,
  69. 'wc-helper-nonce' => wp_create_nonce( 'connect' ),
  70. ),
  71. admin_url( 'admin.php' )
  72. );
  73. include self::get_view_filename( 'html-oauth-start.php' );
  74. return;
  75. }
  76. $disconnect_url = add_query_arg(
  77. array(
  78. 'page' => 'wc-addons',
  79. 'section' => 'helper',
  80. 'wc-helper-disconnect' => 1,
  81. 'wc-helper-nonce' => wp_create_nonce( 'disconnect' ),
  82. ),
  83. admin_url( 'admin.php' )
  84. );
  85. $current_filter = self::get_current_filter();
  86. $refresh_url = add_query_arg(
  87. array(
  88. 'page' => 'wc-addons',
  89. 'section' => 'helper',
  90. 'filter' => $current_filter,
  91. 'wc-helper-refresh' => 1,
  92. 'wc-helper-nonce' => wp_create_nonce( 'refresh' ),
  93. ),
  94. admin_url( 'admin.php' )
  95. );
  96. // Installed plugins and themes, with or without an active subscription.
  97. $woo_plugins = self::get_local_woo_plugins();
  98. $woo_themes = self::get_local_woo_themes();
  99. $site_id = absint( $auth['site_id'] );
  100. $subscriptions = self::get_subscriptions();
  101. $updates = WC_Helper_Updater::get_update_data();
  102. $subscriptions_product_ids = wp_list_pluck( $subscriptions, 'product_id' );
  103. foreach ( $subscriptions as &$subscription ) {
  104. $subscription['active'] = in_array( $site_id, $subscription['connections'] );
  105. $subscription['activate_url'] = add_query_arg(
  106. array(
  107. 'page' => 'wc-addons',
  108. 'section' => 'helper',
  109. 'filter' => $current_filter,
  110. 'wc-helper-activate' => 1,
  111. 'wc-helper-product-key' => $subscription['product_key'],
  112. 'wc-helper-product-id' => $subscription['product_id'],
  113. 'wc-helper-nonce' => wp_create_nonce( 'activate:' . $subscription['product_key'] ),
  114. ),
  115. admin_url( 'admin.php' )
  116. );
  117. $subscription['deactivate_url'] = add_query_arg(
  118. array(
  119. 'page' => 'wc-addons',
  120. 'section' => 'helper',
  121. 'filter' => $current_filter,
  122. 'wc-helper-deactivate' => 1,
  123. 'wc-helper-product-key' => $subscription['product_key'],
  124. 'wc-helper-product-id' => $subscription['product_id'],
  125. 'wc-helper-nonce' => wp_create_nonce( 'deactivate:' . $subscription['product_key'] ),
  126. ),
  127. admin_url( 'admin.php' )
  128. );
  129. $subscription['local'] = array(
  130. 'installed' => false,
  131. 'active' => false,
  132. 'version' => null,
  133. );
  134. $subscription['update_url'] = admin_url( 'update-core.php' );
  135. $local = wp_list_filter( array_merge( $woo_plugins, $woo_themes ), array( '_product_id' => $subscription['product_id'] ) );
  136. if ( ! empty( $local ) ) {
  137. $local = array_shift( $local );
  138. $subscription['local']['installed'] = true;
  139. $subscription['local']['version'] = $local['Version'];
  140. if ( 'plugin' == $local['_type'] ) {
  141. if ( is_plugin_active( $local['_filename'] ) ) {
  142. $subscription['local']['active'] = true;
  143. } elseif ( is_multisite() && is_plugin_active_for_network( $local['_filename'] ) ) {
  144. $subscription['local']['active'] = true;
  145. }
  146. // A magic update_url.
  147. $subscription['update_url'] = wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $local['_filename'], 'upgrade-plugin_' . $local['_filename'] );
  148. } elseif ( 'theme' == $local['_type'] ) {
  149. if ( in_array( $local['_stylesheet'], array( get_stylesheet(), get_template() ) ) ) {
  150. $subscription['local']['active'] = true;
  151. }
  152. // Another magic update_url.
  153. $subscription['update_url'] = wp_nonce_url( self_admin_url( 'update.php?action=upgrade-theme&theme=' . $local['_stylesheet'] ), 'upgrade-theme_' . $local['_stylesheet'] );
  154. }
  155. }
  156. $subscription['has_update'] = false;
  157. if ( $subscription['local']['installed'] && ! empty( $updates[ $subscription['product_id'] ] ) ) {
  158. $subscription['has_update'] = version_compare( $updates[ $subscription['product_id'] ]['version'], $subscription['local']['version'], '>' );
  159. }
  160. $subscription['download_primary'] = true;
  161. $subscription['download_url'] = 'https://woocommerce.com/my-account/downloads/';
  162. if ( ! $subscription['local']['installed'] && ! empty( $updates[ $subscription['product_id'] ] ) ) {
  163. $subscription['download_url'] = $updates[ $subscription['product_id'] ]['package'];
  164. }
  165. $subscription['actions'] = array();
  166. if ( $subscription['has_update'] && ! $subscription['expired'] ) {
  167. $action = array(
  168. /* translators: %s: version number */
  169. 'message' => sprintf( __( 'Version %s is <strong>available</strong>.', 'woocommerce' ), esc_html( $updates[ $subscription['product_id'] ]['version'] ) ),
  170. 'button_label' => __( 'Update', 'woocommerce' ),
  171. 'button_url' => $subscription['update_url'],
  172. 'status' => 'update-available',
  173. 'icon' => 'dashicons-update',
  174. );
  175. // Subscription is not active on this site.
  176. if ( ! $subscription['active'] ) {
  177. $action['message'] .= ' ' . __( 'To enable this update you need to <strong>activate</strong> this subscription.', 'woocommerce' );
  178. $action['button_label'] = null;
  179. $action['button_url'] = null;
  180. }
  181. $subscription['actions'][] = $action;
  182. }
  183. if ( $subscription['has_update'] && $subscription['expired'] ) {
  184. $action = array(
  185. /* translators: %s: version number */
  186. 'message' => sprintf( __( 'Version %s is <strong>available</strong>.', 'woocommerce' ), esc_html( $updates[ $subscription['product_id'] ]['version'] ) ),
  187. 'status' => 'expired',
  188. 'icon' => 'dashicons-info',
  189. );
  190. $action['message'] .= ' ' . __( 'To enable this update you need to <strong>purchase</strong> a new subscription.', 'woocommerce' );
  191. $action['button_label'] = __( 'Purchase', 'woocommerce' );
  192. $action['button_url'] = $subscription['product_url'];
  193. $subscription['actions'][] = $action;
  194. } elseif ( $subscription['expired'] && ! empty( $subscription['master_user_email'] ) ) {
  195. $action = array(
  196. 'message' => sprintf( __( 'This subscription has expired. Contact the owner to <strong>renew</strong> the subscription to receive updates and support.', 'woocommerce' ) ),
  197. 'status' => 'expired',
  198. 'icon' => 'dashicons-info',
  199. );
  200. $subscription['actions'][] = $action;
  201. } elseif ( $subscription['expired'] ) {
  202. $action = array(
  203. 'message' => sprintf( __( 'This subscription has expired. Please <strong>renew</strong> to receive updates and support.', 'woocommerce' ) ),
  204. 'button_label' => __( 'Renew', 'woocommerce' ),
  205. 'button_url' => 'https://woocommerce.com/my-account/my-subscriptions/',
  206. 'status' => 'expired',
  207. 'icon' => 'dashicons-info',
  208. );
  209. $subscription['actions'][] = $action;
  210. }
  211. if ( $subscription['expiring'] && ! $subscription['autorenew'] ) {
  212. $action = array(
  213. 'message' => __( 'Subscription is <strong>expiring</strong> soon.', 'woocommerce' ),
  214. 'button_label' => __( 'Enable auto-renew', 'woocommerce' ),
  215. 'button_url' => 'https://woocommerce.com/my-account/my-subscriptions/',
  216. 'status' => 'expired',
  217. 'icon' => 'dashicons-info',
  218. );
  219. $subscription['download_primary'] = false;
  220. $subscription['actions'][] = $action;
  221. } elseif ( $subscription['expiring'] ) {
  222. $action = array(
  223. 'message' => sprintf( __( 'This subscription is expiring soon. Please <strong>renew</strong> to continue receiving updates and support.', 'woocommerce' ) ),
  224. 'button_label' => __( 'Renew', 'woocommerce' ),
  225. 'button_url' => 'https://woocommerce.com/my-account/my-subscriptions/',
  226. 'status' => 'expired',
  227. 'icon' => 'dashicons-info',
  228. );
  229. $subscription['download_primary'] = false;
  230. $subscription['actions'][] = $action;
  231. }
  232. // Mark the first action primary.
  233. foreach ( $subscription['actions'] as $key => $action ) {
  234. if ( ! empty( $action['button_label'] ) ) {
  235. $subscription['actions'][ $key ]['primary'] = true;
  236. break;
  237. }
  238. }
  239. }
  240. // Break the by-ref.
  241. unset( $subscription );
  242. // Installed products without a subscription.
  243. $no_subscriptions = array();
  244. foreach ( array_merge( $woo_plugins, $woo_themes ) as $filename => $data ) {
  245. if ( in_array( $data['_product_id'], $subscriptions_product_ids ) ) {
  246. continue;
  247. }
  248. $data['_product_url'] = '#';
  249. $data['_has_update'] = false;
  250. if ( ! empty( $updates[ $data['_product_id'] ] ) ) {
  251. $data['_has_update'] = version_compare( $updates[ $data['_product_id'] ]['version'], $data['Version'], '>' );
  252. if ( ! empty( $updates[ $data['_product_id'] ]['url'] ) ) {
  253. $data['_product_url'] = $updates[ $data['_product_id'] ]['url'];
  254. } elseif ( ! empty( $data['PluginURI'] ) ) {
  255. $data['_product_url'] = $data['PluginURI'];
  256. }
  257. }
  258. $data['_actions'] = array();
  259. if ( $data['_has_update'] ) {
  260. $action = array(
  261. /* translators: %s: version number */
  262. 'message' => sprintf( __( 'Version %s is <strong>available</strong>. To enable this update you need to <strong>purchase</strong> a new subscription.', 'woocommerce' ), esc_html( $updates[ $data['_product_id'] ]['version'] ) ),
  263. 'button_label' => __( 'Purchase', 'woocommerce' ),
  264. 'button_url' => $data['_product_url'],
  265. 'status' => 'expired',
  266. 'icon' => 'dashicons-info',
  267. );
  268. $data['_actions'][] = $action;
  269. } else {
  270. $action = array(
  271. /* translators: 1: subscriptions docs 2: subscriptions docs */
  272. 'message' => sprintf( __( 'To receive updates and support for this extension, you need to <strong>purchase</strong> a new subscription or consolidate your extensions to one connected account by <strong><a href="%1$s" title="Sharing Docs">sharing</a> or <a href="%2$s" title="Transferring Docs">transferring</a></strong> this extension to this connected account.', 'woocommerce' ), 'https://docs.woocommerce.com/document/managing-woocommerce-com-subscriptions/#section-10', 'https://docs.woocommerce.com/document/managing-woocommerce-com-subscriptions/#section-5' ),
  273. 'button_label' => __( 'Purchase', 'woocommerce' ),
  274. 'button_url' => $data['_product_url'],
  275. 'status' => 'expired',
  276. 'icon' => 'dashicons-info',
  277. );
  278. $data['_actions'][] = $action;
  279. }
  280. $no_subscriptions[ $filename ] = $data;
  281. }
  282. // Update the user id if it came from a migrated connection.
  283. if ( empty( $auth['user_id'] ) ) {
  284. $auth['user_id'] = get_current_user_id();
  285. WC_Helper_Options::update( 'auth', $auth );
  286. }
  287. // Sort alphabetically.
  288. uasort( $subscriptions, array( __CLASS__, '_sort_by_product_name' ) );
  289. uasort( $no_subscriptions, array( __CLASS__, '_sort_by_name' ) );
  290. // Filters.
  291. self::get_filters_counts( $subscriptions ); // Warm it up.
  292. self::_filter( $subscriptions, self::get_current_filter() );
  293. // We have an active connection.
  294. include self::get_view_filename( 'html-main.php' );
  295. return;
  296. }
  297. /**
  298. * Get available subscriptions filters.
  299. *
  300. * @return array An array of filter keys and labels.
  301. */
  302. public static function get_filters() {
  303. $filters = array(
  304. 'all' => __( 'All', 'woocommerce' ),
  305. 'active' => __( 'Active', 'woocommerce' ),
  306. 'inactive' => __( 'Inactive', 'woocommerce' ),
  307. 'installed' => __( 'Installed', 'woocommerce' ),
  308. 'update-available' => __( 'Update Available', 'woocommerce' ),
  309. 'expiring' => __( 'Expiring Soon', 'woocommerce' ),
  310. 'expired' => __( 'Expired', 'woocommerce' ),
  311. 'download' => __( 'Download', 'woocommerce' ),
  312. );
  313. return $filters;
  314. }
  315. /**
  316. * Get counts data for the filters array.
  317. *
  318. * @param array $subscriptions The array of all available subscriptions.
  319. *
  320. * @return array Filter counts (filter => count).
  321. */
  322. public static function get_filters_counts( $subscriptions = null ) {
  323. static $filters;
  324. if ( isset( $filters ) ) {
  325. return $filters;
  326. }
  327. $filters = array_fill_keys( array_keys( self::get_filters() ), 0 );
  328. if ( empty( $subscriptions ) ) {
  329. return array();
  330. }
  331. foreach ( $filters as $key => $count ) {
  332. $_subs = $subscriptions;
  333. self::_filter( $_subs, $key );
  334. $filters[ $key ] = count( $_subs );
  335. }
  336. return $filters;
  337. }
  338. /**
  339. * Get current filter.
  340. *
  341. * @return string The current filter.
  342. */
  343. public static function get_current_filter() {
  344. $current_filter = 'all';
  345. $valid_filters = array_keys( self::get_filters() );
  346. if ( ! empty( $_GET['filter'] ) && in_array( wp_unslash( $_GET['filter'] ), $valid_filters ) ) {
  347. $current_filter = wc_clean( wp_unslash( $_GET['filter'] ) );
  348. }
  349. return $current_filter;
  350. }
  351. /**
  352. * Filter an array of subscriptions by $filter.
  353. *
  354. * @param array $subscriptions The subscriptions array, passed by ref.
  355. * @param string $filter The filter.
  356. */
  357. private static function _filter( &$subscriptions, $filter ) {
  358. switch ( $filter ) {
  359. case 'active':
  360. $subscriptions = wp_list_filter( $subscriptions, array( 'active' => true ) );
  361. break;
  362. case 'inactive':
  363. $subscriptions = wp_list_filter( $subscriptions, array( 'active' => false ) );
  364. break;
  365. case 'installed':
  366. foreach ( $subscriptions as $key => $subscription ) {
  367. if ( empty( $subscription['local']['installed'] ) ) {
  368. unset( $subscriptions[ $key ] );
  369. }
  370. }
  371. break;
  372. case 'update-available':
  373. $subscriptions = wp_list_filter( $subscriptions, array( 'has_update' => true ) );
  374. break;
  375. case 'expiring':
  376. $subscriptions = wp_list_filter( $subscriptions, array( 'expiring' => true ) );
  377. break;
  378. case 'expired':
  379. $subscriptions = wp_list_filter( $subscriptions, array( 'expired' => true ) );
  380. break;
  381. case 'download':
  382. foreach ( $subscriptions as $key => $subscription ) {
  383. if ( $subscription['local']['installed'] || $subscription['expired'] ) {
  384. unset( $subscriptions[ $key ] );
  385. }
  386. }
  387. break;
  388. }
  389. }
  390. /**
  391. * Enqueue admin scripts and styles.
  392. */
  393. public static function admin_enqueue_scripts() {
  394. $screen = get_current_screen();
  395. $screen_id = $screen ? $screen->id : '';
  396. $wc_screen_id = sanitize_title( __( 'WooCommerce', 'woocommerce' ) );
  397. if ( $wc_screen_id . '_page_wc-addons' === $screen_id && isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
  398. wp_enqueue_style( 'woocommerce-helper', WC()->plugin_url() . '/assets/css/helper.css', array(), Constants::get_constant( 'WC_VERSION' ) );
  399. wp_style_add_data( 'woocommerce-helper', 'rtl', 'replace' );
  400. }
  401. }
  402. /**
  403. * Various success/error notices.
  404. *
  405. * Runs during admin page render, so no headers/redirects here.
  406. *
  407. * @return array Array pairs of message/type strings with notices.
  408. */
  409. private static function _get_return_notices() {
  410. $return_status = isset( $_GET['wc-helper-status'] ) ? wc_clean( wp_unslash( $_GET['wc-helper-status'] ) ) : null;
  411. $notices = array();
  412. switch ( $return_status ) {
  413. case 'activate-success':
  414. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  415. $subscription = self::_get_subscriptions_from_product_id( $product_id );
  416. $notices[] = array(
  417. 'type' => 'updated',
  418. 'message' => sprintf(
  419. /* translators: %s: product name */
  420. __( '%s activated successfully. You will now receive updates for this product.', 'woocommerce' ),
  421. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>'
  422. ),
  423. );
  424. break;
  425. case 'activate-error':
  426. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  427. $subscription = self::_get_subscriptions_from_product_id( $product_id );
  428. $notices[] = array(
  429. 'type' => 'error',
  430. 'message' => sprintf(
  431. /* translators: %s: product name */
  432. __( 'An error has occurred when activating %s. Please try again later.', 'woocommerce' ),
  433. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>'
  434. ),
  435. );
  436. break;
  437. case 'deactivate-success':
  438. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  439. $subscription = self::_get_subscriptions_from_product_id( $product_id );
  440. $local = self::_get_local_from_product_id( $product_id );
  441. $message = sprintf(
  442. /* translators: %s: product name */
  443. __( 'Subscription for %s deactivated successfully. You will no longer receive updates for this product.', 'woocommerce' ),
  444. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>'
  445. );
  446. if ( $local && is_plugin_active( $local['_filename'] ) && current_user_can( 'activate_plugins' ) ) {
  447. $deactivate_plugin_url = add_query_arg(
  448. array(
  449. 'page' => 'wc-addons',
  450. 'section' => 'helper',
  451. 'filter' => self::get_current_filter(),
  452. 'wc-helper-deactivate-plugin' => 1,
  453. 'wc-helper-product-id' => $subscription['product_id'],
  454. 'wc-helper-nonce' => wp_create_nonce( 'deactivate-plugin:' . $subscription['product_id'] ),
  455. ),
  456. admin_url( 'admin.php' )
  457. );
  458. $message = sprintf(
  459. /* translators: %1$s: product name, %2$s: deactivate url */
  460. __( 'Subscription for %1$s deactivated successfully. You will no longer receive updates for this product. <a href="%2$s">Click here</a> if you wish to deactivate the plugin as well.', 'woocommerce' ),
  461. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>',
  462. esc_url( $deactivate_plugin_url )
  463. );
  464. }
  465. $notices[] = array(
  466. 'message' => $message,
  467. 'type' => 'updated',
  468. );
  469. break;
  470. case 'deactivate-error':
  471. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  472. $subscription = self::_get_subscriptions_from_product_id( $product_id );
  473. $notices[] = array(
  474. 'type' => 'error',
  475. 'message' => sprintf(
  476. /* translators: %s: product name */
  477. __( 'An error has occurred when deactivating the subscription for %s. Please try again later.', 'woocommerce' ),
  478. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>'
  479. ),
  480. );
  481. break;
  482. case 'deactivate-plugin-success':
  483. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  484. $subscription = self::_get_subscriptions_from_product_id( $product_id );
  485. $notices[] = array(
  486. 'type' => 'updated',
  487. 'message' => sprintf(
  488. /* translators: %s: product name */
  489. __( 'The extension %s has been deactivated successfully.', 'woocommerce' ),
  490. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>'
  491. ),
  492. );
  493. break;
  494. case 'deactivate-plugin-error':
  495. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  496. $subscription = self::_get_subscriptions_from_product_id( $product_id );
  497. $notices[] = array(
  498. 'type' => 'error',
  499. 'message' => sprintf(
  500. /* translators: %1$s: product name, %2$s: plugins screen url */
  501. __( 'An error has occurred when deactivating the extension %1$s. Please proceed to the <a href="%2$s">Plugins screen</a> to deactivate it manually.', 'woocommerce' ),
  502. '<strong>' . esc_html( $subscription['product_name'] ) . '</strong>',
  503. admin_url( 'plugins.php' )
  504. ),
  505. );
  506. break;
  507. case 'helper-connected':
  508. $notices[] = array(
  509. 'message' => __( 'You have successfully connected your store to WooCommerce.com', 'woocommerce' ),
  510. 'type' => 'updated',
  511. );
  512. break;
  513. case 'helper-disconnected':
  514. $notices[] = array(
  515. 'message' => __( 'You have successfully disconnected your store from WooCommerce.com', 'woocommerce' ),
  516. 'type' => 'updated',
  517. );
  518. break;
  519. case 'helper-refreshed':
  520. $notices[] = array(
  521. 'message' => __( 'Authentication and subscription caches refreshed successfully.', 'woocommerce' ),
  522. 'type' => 'updated',
  523. );
  524. break;
  525. }
  526. return $notices;
  527. }
  528. /**
  529. * Various early-phase actions with possible redirects.
  530. *
  531. * @param object $screen WP screen object.
  532. */
  533. public static function current_screen( $screen ) {
  534. $wc_screen_id = sanitize_title( __( 'WooCommerce', 'woocommerce' ) );
  535. if ( $wc_screen_id . '_page_wc-addons' !== $screen->id ) {
  536. return;
  537. }
  538. if ( empty( $_GET['section'] ) || 'helper' !== $_GET['section'] ) {
  539. return;
  540. }
  541. if ( ! empty( $_GET['wc-helper-connect'] ) ) {
  542. return self::_helper_auth_connect();
  543. }
  544. if ( ! empty( $_GET['wc-helper-return'] ) ) {
  545. return self::_helper_auth_return();
  546. }
  547. if ( ! empty( $_GET['wc-helper-disconnect'] ) ) {
  548. return self::_helper_auth_disconnect();
  549. }
  550. if ( ! empty( $_GET['wc-helper-refresh'] ) ) {
  551. return self::_helper_auth_refresh();
  552. }
  553. if ( ! empty( $_GET['wc-helper-activate'] ) ) {
  554. return self::_helper_subscription_activate();
  555. }
  556. if ( ! empty( $_GET['wc-helper-deactivate'] ) ) {
  557. return self::_helper_subscription_deactivate();
  558. }
  559. if ( ! empty( $_GET['wc-helper-deactivate-plugin'] ) ) {
  560. return self::_helper_plugin_deactivate();
  561. }
  562. }
  563. /**
  564. * Initiate a new OAuth connection.
  565. */
  566. private static function _helper_auth_connect() {
  567. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'connect' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  568. self::log( 'Could not verify nonce in _helper_auth_connect' );
  569. wp_die( 'Could not verify nonce' );
  570. }
  571. $redirect_uri = add_query_arg(
  572. array(
  573. 'page' => 'wc-addons',
  574. 'section' => 'helper',
  575. 'wc-helper-return' => 1,
  576. 'wc-helper-nonce' => wp_create_nonce( 'connect' ),
  577. ),
  578. admin_url( 'admin.php' )
  579. );
  580. $request = WC_Helper_API::post(
  581. 'oauth/request_token',
  582. array(
  583. 'body' => array(
  584. 'home_url' => home_url(),
  585. 'redirect_uri' => $redirect_uri,
  586. ),
  587. )
  588. );
  589. $code = wp_remote_retrieve_response_code( $request );
  590. if ( 200 !== $code ) {
  591. self::log( sprintf( 'Call to oauth/request_token returned a non-200 response code (%d)', $code ) );
  592. wp_die( 'Something went wrong' );
  593. }
  594. $secret = json_decode( wp_remote_retrieve_body( $request ) );
  595. if ( empty( $secret ) ) {
  596. self::log( sprintf( 'Call to oauth/request_token returned an invalid body: %s', wp_remote_retrieve_body( $request ) ) );
  597. wp_die( 'Something went wrong' );
  598. }
  599. /**
  600. * Fires when the Helper connection process is initiated.
  601. */
  602. do_action( 'woocommerce_helper_connect_start' );
  603. $connect_url = add_query_arg(
  604. array(
  605. 'home_url' => rawurlencode( home_url() ),
  606. 'redirect_uri' => rawurlencode( $redirect_uri ),
  607. 'secret' => rawurlencode( $secret ),
  608. ),
  609. WC_Helper_API::url( 'oauth/authorize' )
  610. );
  611. wp_redirect( esc_url_raw( $connect_url ) );
  612. die();
  613. }
  614. /**
  615. * Return from WooCommerce.com OAuth flow.
  616. */
  617. private static function _helper_auth_return() {
  618. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'connect' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  619. self::log( 'Could not verify nonce in _helper_auth_return' );
  620. wp_die( 'Something went wrong' );
  621. }
  622. // Bail if the user clicked deny.
  623. if ( ! empty( $_GET['deny'] ) ) {
  624. /**
  625. * Fires when the Helper connection process is denied/cancelled.
  626. */
  627. do_action( 'woocommerce_helper_denied' );
  628. wp_safe_redirect( admin_url( 'admin.php?page=wc-addons&section=helper' ) );
  629. die();
  630. }
  631. // We do need a request token...
  632. if ( empty( $_GET['request_token'] ) ) {
  633. self::log( 'Request token not found in _helper_auth_return' );
  634. wp_die( 'Something went wrong' );
  635. }
  636. // Obtain an access token.
  637. $request = WC_Helper_API::post(
  638. 'oauth/access_token',
  639. array(
  640. 'body' => array(
  641. 'request_token' => wp_unslash( $_GET['request_token'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  642. 'home_url' => home_url(),
  643. ),
  644. )
  645. );
  646. $code = wp_remote_retrieve_response_code( $request );
  647. if ( 200 !== $code ) {
  648. self::log( sprintf( 'Call to oauth/access_token returned a non-200 response code (%d)', $code ) );
  649. wp_die( 'Something went wrong' );
  650. }
  651. $access_token = json_decode( wp_remote_retrieve_body( $request ), true );
  652. if ( ! $access_token ) {
  653. self::log( sprintf( 'Call to oauth/access_token returned an invalid body: %s', wp_remote_retrieve_body( $request ) ) );
  654. wp_die( 'Something went wrong' );
  655. }
  656. WC_Helper_Options::update(
  657. 'auth',
  658. array(
  659. 'access_token' => $access_token['access_token'],
  660. 'access_token_secret' => $access_token['access_token_secret'],
  661. 'site_id' => $access_token['site_id'],
  662. 'user_id' => get_current_user_id(),
  663. 'updated' => time(),
  664. )
  665. );
  666. // Obtain the connected user info.
  667. if ( ! self::_flush_authentication_cache() ) {
  668. self::log( 'Could not obtain connected user info in _helper_auth_return' );
  669. WC_Helper_Options::update( 'auth', array() );
  670. wp_die( 'Something went wrong.' );
  671. }
  672. self::_flush_subscriptions_cache();
  673. self::_flush_updates_cache();
  674. /**
  675. * Fires when the Helper connection process has completed successfully.
  676. */
  677. do_action( 'woocommerce_helper_connected' );
  678. // Enable tracking when connected.
  679. if ( class_exists( 'WC_Tracker' ) ) {
  680. update_option( 'woocommerce_allow_tracking', 'yes' );
  681. WC_Tracker::send_tracking_data( true );
  682. }
  683. // If connecting through in-app purchase, redirects back to WooCommerce.com
  684. // for product installation.
  685. if ( ! empty( $_GET['wccom-install-url'] ) ) {
  686. wp_redirect( wp_unslash( $_GET['wccom-install-url'] ) );
  687. exit;
  688. }
  689. wp_safe_redirect(
  690. add_query_arg(
  691. array(
  692. 'page' => 'wc-addons',
  693. 'section' => 'helper',
  694. 'wc-helper-status' => 'helper-connected',
  695. ),
  696. admin_url( 'admin.php' )
  697. )
  698. );
  699. die();
  700. }
  701. /**
  702. * Disconnect from WooCommerce.com, clear OAuth tokens.
  703. */
  704. private static function _helper_auth_disconnect() {
  705. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'disconnect' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  706. self::log( 'Could not verify nonce in _helper_auth_disconnect' );
  707. wp_die( 'Could not verify nonce' );
  708. }
  709. /**
  710. * Fires when the Helper has been disconnected.
  711. */
  712. do_action( 'woocommerce_helper_disconnected' );
  713. $redirect_uri = add_query_arg(
  714. array(
  715. 'page' => 'wc-addons',
  716. 'section' => 'helper',
  717. 'wc-helper-status' => 'helper-disconnected',
  718. ),
  719. admin_url( 'admin.php' )
  720. );
  721. WC_Helper_API::post(
  722. 'oauth/invalidate_token',
  723. array(
  724. 'authenticated' => true,
  725. )
  726. );
  727. WC_Helper_Options::update( 'auth', array() );
  728. WC_Helper_Options::update( 'auth_user_data', array() );
  729. self::_flush_subscriptions_cache();
  730. self::_flush_updates_cache();
  731. wp_safe_redirect( $redirect_uri );
  732. die();
  733. }
  734. /**
  735. * User hit the Refresh button, clear all caches.
  736. */
  737. private static function _helper_auth_refresh() {
  738. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'refresh' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  739. self::log( 'Could not verify nonce in _helper_auth_refresh' );
  740. wp_die( 'Could not verify nonce' );
  741. }
  742. /**
  743. * Fires when Helper subscriptions are refreshed.
  744. */
  745. do_action( 'woocommerce_helper_subscriptions_refresh' );
  746. $redirect_uri = add_query_arg(
  747. array(
  748. 'page' => 'wc-addons',
  749. 'section' => 'helper',
  750. 'filter' => self::get_current_filter(),
  751. 'wc-helper-status' => 'helper-refreshed',
  752. ),
  753. admin_url( 'admin.php' )
  754. );
  755. self::_flush_authentication_cache();
  756. self::_flush_subscriptions_cache();
  757. self::_flush_updates_cache();
  758. wp_safe_redirect( $redirect_uri );
  759. die();
  760. }
  761. /**
  762. * Active a product subscription.
  763. */
  764. private static function _helper_subscription_activate() {
  765. $product_key = isset( $_GET['wc-helper-product-key'] ) ? wc_clean( wp_unslash( $_GET['wc-helper-product-key'] ) ) : '';
  766. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  767. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'activate:' . $product_key ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  768. self::log( 'Could not verify nonce in _helper_subscription_activate' );
  769. wp_die( 'Could not verify nonce' );
  770. }
  771. // Activate subscription.
  772. $activation_response = WC_Helper_API::post(
  773. 'activate',
  774. array(
  775. 'authenticated' => true,
  776. 'body' => wp_json_encode(
  777. array(
  778. 'product_key' => $product_key,
  779. )
  780. ),
  781. )
  782. );
  783. $activated = wp_remote_retrieve_response_code( $activation_response ) === 200;
  784. $body = json_decode( wp_remote_retrieve_body( $activation_response ), true );
  785. if ( ! $activated && ! empty( $body['code'] ) && 'already_connected' === $body['code'] ) {
  786. $activated = true;
  787. }
  788. if ( $activated ) {
  789. /**
  790. * Fires when the Helper activates a product successfully.
  791. *
  792. * @param int $product_id Product ID being activated.
  793. * @param string $product_key Subscription product key.
  794. * @param array $activation_response The response object from wp_safe_remote_request().
  795. */
  796. do_action( 'woocommerce_helper_subscription_activate_success', $product_id, $product_key, $activation_response );
  797. } else {
  798. /**
  799. * Fires when the Helper fails to activate a product.
  800. *
  801. * @param int $product_id Product ID being activated.
  802. * @param string $product_key Subscription product key.
  803. * @param array $activation_response The response object from wp_safe_remote_request().
  804. */
  805. do_action( 'woocommerce_helper_subscription_activate_error', $product_id, $product_key, $activation_response );
  806. }
  807. // Attempt to activate this plugin.
  808. $local = self::_get_local_from_product_id( $product_id );
  809. if ( $local && 'plugin' == $local['_type'] && current_user_can( 'activate_plugins' ) && ! is_plugin_active( $local['_filename'] ) ) {
  810. activate_plugin( $local['_filename'] );
  811. }
  812. self::_flush_subscriptions_cache();
  813. self::_flush_updates_cache();
  814. $redirect_uri = add_query_arg(
  815. array(
  816. 'page' => 'wc-addons',
  817. 'section' => 'helper',
  818. 'filter' => self::get_current_filter(),
  819. 'wc-helper-status' => $activated ? 'activate-success' : 'activate-error',
  820. 'wc-helper-product-id' => $product_id,
  821. ),
  822. admin_url( 'admin.php' )
  823. );
  824. wp_safe_redirect( $redirect_uri );
  825. die();
  826. }
  827. /**
  828. * Deactivate a product subscription.
  829. */
  830. private static function _helper_subscription_deactivate() {
  831. $product_key = isset( $_GET['wc-helper-product-key'] ) ? wc_clean( wp_unslash( $_GET['wc-helper-product-key'] ) ) : '';
  832. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  833. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'deactivate:' . $product_key ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  834. self::log( 'Could not verify nonce in _helper_subscription_deactivate' );
  835. wp_die( 'Could not verify nonce' );
  836. }
  837. $deactivation_response = WC_Helper_API::post(
  838. 'deactivate',
  839. array(
  840. 'authenticated' => true,
  841. 'body' => wp_json_encode(
  842. array(
  843. 'product_key' => $product_key,
  844. )
  845. ),
  846. )
  847. );
  848. $code = wp_remote_retrieve_response_code( $deactivation_response );
  849. $deactivated = 200 === $code;
  850. if ( $deactivated ) {
  851. /**
  852. * Fires when the Helper activates a product successfully.
  853. *
  854. * @param int $product_id Product ID being deactivated.
  855. * @param string $product_key Subscription product key.
  856. * @param array $deactivation_response The response object from wp_safe_remote_request().
  857. */
  858. do_action( 'woocommerce_helper_subscription_deactivate_success', $product_id, $product_key, $deactivation_response );
  859. } else {
  860. self::log( sprintf( 'Deactivate API call returned a non-200 response code (%d)', $code ) );
  861. /**
  862. * Fires when the Helper fails to activate a product.
  863. *
  864. * @param int $product_id Product ID being deactivated.
  865. * @param string $product_key Subscription product key.
  866. * @param array $deactivation_response The response object from wp_safe_remote_request().
  867. */
  868. do_action( 'woocommerce_helper_subscription_deactivate_error', $product_id, $product_key, $deactivation_response );
  869. }
  870. self::_flush_subscriptions_cache();
  871. $redirect_uri = add_query_arg(
  872. array(
  873. 'page' => 'wc-addons',
  874. 'section' => 'helper',
  875. 'filter' => self::get_current_filter(),
  876. 'wc-helper-status' => $deactivated ? 'deactivate-success' : 'deactivate-error',
  877. 'wc-helper-product-id' => $product_id,
  878. ),
  879. admin_url( 'admin.php' )
  880. );
  881. wp_safe_redirect( $redirect_uri );
  882. die();
  883. }
  884. /**
  885. * Deactivate a plugin.
  886. */
  887. private static function _helper_plugin_deactivate() {
  888. $product_id = isset( $_GET['wc-helper-product-id'] ) ? absint( $_GET['wc-helper-product-id'] ) : 0;
  889. $deactivated = false;
  890. if ( empty( $_GET['wc-helper-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['wc-helper-nonce'] ), 'deactivate-plugin:' . $product_id ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
  891. self::log( 'Could not verify nonce in _helper_plugin_deactivate' );
  892. wp_die( 'Could not verify nonce' );
  893. }
  894. if ( ! current_user_can( 'activate_plugins' ) ) {
  895. wp_die( 'You are not allowed to manage plugins on this site.' );
  896. }
  897. $local = wp_list_filter(
  898. array_merge(
  899. self::get_local_woo_plugins(),
  900. self::get_local_woo_themes()
  901. ),
  902. array( '_product_id' => $product_id )
  903. );
  904. // Attempt to deactivate this plugin or theme.
  905. if ( ! empty( $local ) ) {
  906. $local = array_shift( $local );
  907. if ( is_plugin_active( $local['_filename'] ) ) {
  908. deactivate_plugins( $local['_filename'] );
  909. }
  910. $deactivated = ! is_plugin_active( $local['_filename'] );
  911. }
  912. $redirect_uri = add_query_arg(
  913. array(
  914. 'page' => 'wc-addons',
  915. 'section' => 'helper',
  916. 'filter' => self::get_current_filter(),
  917. 'wc-helper-status' => $deactivated ? 'deactivate-plugin-success' : 'deactivate-plugin-error',
  918. 'wc-helper-product-id' => $product_id,
  919. ),
  920. admin_url( 'admin.php' )
  921. );
  922. wp_safe_redirect( $redirect_uri );
  923. die();
  924. }
  925. /**
  926. * Get a local plugin/theme entry from product_id.
  927. *
  928. * @param int $product_id The product id.
  929. *
  930. * @return array|bool The array containing the local plugin/theme data or false.
  931. */
  932. private static function _get_local_from_product_id( $product_id ) {
  933. $local = wp_list_filter(
  934. array_merge(
  935. self::get_local_woo_plugins(),
  936. self::get_local_woo_themes()
  937. ),
  938. array( '_product_id' => $product_id )
  939. );
  940. if ( ! empty( $local ) ) {
  941. return array_shift( $local );
  942. }
  943. return false;
  944. }
  945. /**
  946. * Checks whether current site has product subscription of a given ID.
  947. *
  948. * @since 3.7.0
  949. *
  950. * @param int $product_id The product id.
  951. *
  952. * @return bool Returns true if product subscription exists, false otherwise.
  953. */
  954. public static function has_product_subscription( $product_id ) {
  955. $subscription = self::_get_subscriptions_from_product_id( $product_id, true );
  956. return ! empty( $subscription );
  957. }
  958. /**
  959. * Get a subscription entry from product_id. If multiple subscriptions are
  960. * found with the same product id and $single is set to true, will return the
  961. * first one in the list, so you can use this method to get things like extension
  962. * name, version, etc.
  963. *
  964. * @param int $product_id The product id.
  965. * @param bool $single Whether to return a single subscription or all matching a product id.
  966. *
  967. * @return array|bool The array containing sub data or false.
  968. */
  969. private static function _get_subscriptions_from_product_id( $product_id, $single = true ) {
  970. $subscriptions = wp_list_filter( self::get_subscriptions(), array( 'product_id' => $product_id ) );
  971. if ( ! empty( $subscriptions ) ) {
  972. return $single ? array_shift( $subscriptions ) : $subscriptions;
  973. }
  974. return false;
  975. }
  976. /**
  977. * Obtain a list of data about locally installed Woo extensions.
  978. */
  979. public static function get_local_woo_plugins() {
  980. if ( ! function_exists( 'get_plugins' ) ) {
  981. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  982. }
  983. $plugins = get_plugins();
  984. /**
  985. * Check if plugins have WC headers, if not then clear cache and fetch again.
  986. * WC Headers will not be present if `wc_enable_wc_plugin_headers` hook was added after a `get_plugins` call -- for example when WC is activated/updated.
  987. * Also, get_plugins call is expensive so we should clear this cache very conservatively.
  988. */
  989. if ( ! empty( $plugins ) && ! array_key_exists( 'Woo', current( $plugins ) ) ) {
  990. wp_clean_plugins_cache( false );
  991. $plugins = get_plugins();
  992. }
  993. $woo_plugins = array();
  994. // Backwards compatibility for woothemes_queue_update().
  995. $_compat = array();
  996. if ( ! empty( $GLOBALS['woothemes_queued_updates'] ) ) {
  997. foreach ( $GLOBALS['woothemes_queued_updates'] as $_compat_plugin ) {
  998. $_compat[ $_compat_plugin->file ] = array(
  999. 'product_id' => $_compat_plugin->product_id,
  1000. 'file_id' => $_compat_plugin->file_id,
  1001. );
  1002. }
  1003. }
  1004. foreach ( $plugins as $filename => $data ) {
  1005. if ( empty( $data['Woo'] ) && ! empty( $_compat[ $filename ] ) ) {
  1006. $data['Woo'] = sprintf( '%d:%s', $_compat[ $filename ]['product_id'], $_compat[ $filename ]['file_id'] );
  1007. }
  1008. if ( empty( $data['Woo'] ) ) {
  1009. continue;
  1010. }
  1011. list( $product_id, $file_id ) = explode( ':', $data['Woo'] );
  1012. if ( empty( $product_id ) || empty( $file_id ) ) {
  1013. continue;
  1014. }
  1015. $data['_filename'] = $filename;
  1016. $data['_product_id'] = absint( $product_id );
  1017. $data['_file_id'] = $file_id;
  1018. $data['_type'] = 'plugin';
  1019. $data['slug'] = dirname( $filename );
  1020. $woo_plugins[ $filename ] = $data;
  1021. }
  1022. return $woo_plugins;
  1023. }
  1024. /**
  1025. * Get locally installed Woo themes.
  1026. */
  1027. public static function get_local_woo_themes() {
  1028. $themes = wp_get_themes();
  1029. $woo_themes = array();
  1030. foreach ( $themes as $theme ) {
  1031. $header = $theme->get( 'Woo' );
  1032. // Backwards compatibility for theme_info.txt.
  1033. if ( ! $header ) {
  1034. $txt = $theme->get_stylesheet_directory() . '/theme_info.txt';
  1035. if ( is_readable( $txt ) ) {
  1036. $txt = file_get_contents( $txt );
  1037. $txt = preg_split( '#\s#', $txt );
  1038. if ( count( $txt ) >= 2 ) {
  1039. $header = sprintf( '%d:%s', $txt[0], $txt[1] );
  1040. }
  1041. }
  1042. }
  1043. if ( empty( $header ) ) {
  1044. continue;
  1045. }
  1046. list( $product_id, $file_id ) = explode( ':', $header );
  1047. if ( empty( $product_id ) || empty( $file_id ) ) {
  1048. continue;
  1049. }
  1050. $data = array(
  1051. 'Name' => $theme->get( 'Name' ),
  1052. 'Version' => $theme->get( 'Version' ),
  1053. 'Woo' => $header,
  1054. '_filename' => $theme->get_stylesheet() . '/style.css',
  1055. '_stylesheet' => $theme->get_stylesheet(),
  1056. '_product_id' => absint( $product_id ),
  1057. '_file_id' => $file_id,
  1058. '_type' => 'theme',
  1059. );
  1060. $woo_themes[ $data['_filename'] ] = $data;
  1061. }
  1062. return $woo_themes;
  1063. }
  1064. /**
  1065. * Get the connected user's subscriptions.
  1066. *
  1067. * @return array
  1068. */
  1069. public static function get_subscriptions() {
  1070. $cache_key = '_woocommerce_helper_subscriptions';
  1071. $data = get_transient( $cache_key );
  1072. if ( false !== $data ) {
  1073. return $data;
  1074. }
  1075. // Obtain the connected user info.
  1076. $request = WC_Helper_API::get(
  1077. 'subscriptions',
  1078. array(
  1079. 'authenticated' => true,
  1080. )
  1081. );
  1082. if ( wp_remote_retrieve_response_code( $request ) !== 200 ) {
  1083. set_transient( $cache_key, array(), 15 * MINUTE_IN_SECONDS );
  1084. return array();
  1085. }
  1086. $data = json_decode( wp_remote_retrieve_body( $request ), true );
  1087. if ( empty( $data ) || ! is_array( $data ) ) {
  1088. $data = array();
  1089. }
  1090. set_transient( $cache_key, $data, 1 * HOUR_IN_SECONDS );
  1091. return $data;
  1092. }
  1093. /**
  1094. * Runs when any plugin is activated.
  1095. *
  1096. * Depending on the activated plugin attempts to look through available
  1097. * subscriptions and auto-activate one if possible, so the user does not
  1098. * need to visit the Helper UI at all after installing a new extension.
  1099. *
  1100. * @param string $filename The filename of the activated plugin.
  1101. */
  1102. public static function activated_plugin( $filename ) {
  1103. $plugins = self::get_local_woo_plugins();
  1104. // Not a local woo plugin.
  1105. if ( empty( $plugins[ $filename ] ) ) {
  1106. return;
  1107. }
  1108. // Make sure we have a connection.
  1109. $auth = WC_Helper_Options::get( 'auth' );
  1110. if ( empty( $auth ) ) {
  1111. return;
  1112. }
  1113. $plugin = $plugins[ $filename ];
  1114. $product_id = $plugin['_product_id'];
  1115. $subscriptions = self::_get_subscriptions_from_product_id( $product_id, false );
  1116. // No valid subscriptions for this product.
  1117. if ( empty( $subscriptions ) ) {
  1118. return;
  1119. }
  1120. $subscription = null;
  1121. foreach ( $subscriptions as $_sub ) {
  1122. // Don't attempt to activate expired subscriptions.
  1123. if ( $_sub['expired'] ) {
  1124. continue;
  1125. }
  1126. // No more sites available in this subscription.
  1127. if ( $_sub['sites_max'] && $_sub['sites_active'] >= $_sub['sites_max'] ) {
  1128. continue;
  1129. }
  1130. // Looks good.
  1131. $subscription = $_sub;
  1132. break;
  1133. }
  1134. // No valid subscription found.
  1135. if ( ! $subscription ) {
  1136. return;
  1137. }
  1138. $product_key = $subscription['product_key'];
  1139. $activation_response = WC_Helper_API::post(
  1140. 'activate',
  1141. array(
  1142. 'authenticated' => true,
  1143. 'body' => wp_json_encode(
  1144. array(
  1145. 'product_key' => $product_key,
  1146. )
  1147. ),
  1148. )
  1149. );
  1150. $activated = wp_remote_retrieve_response_code( $activation_response ) === 200;
  1151. $body = json_decode( wp_remote_retrieve_body( $activation_response ), true );
  1152. if ( ! $activated && ! empty( $body['code'] ) && 'already_connected' === $body['code'] ) {
  1153. $activated = true;
  1154. }
  1155. if ( $activated ) {
  1156. self::log( 'Auto-activated a subscription for ' . $filename );
  1157. /**
  1158. * Fires when the Helper activates a product successfully.
  1159. *
  1160. * @param int $product_id Product ID being activated.
  1161. * @param string $product_key Subscription product key.
  1162. * @param array $activation_response The response object from wp_safe_remote_request().
  1163. */
  1164. do_action( 'woocommerce_helper_subscription_activate_success', $product_id, $product_key, $activation_response );
  1165. } else {
  1166. self::log( 'Could not activate a subscription upon plugin activation: ' . $filename );
  1167. /**
  1168. * Fires when the Helper fails to activate a product.
  1169. *
  1170. * @param int $product_id Product ID being activated.
  1171. * @param string $product_key Subscription product key.
  1172. * @param array $activation_response The response object from wp_safe_remote_request().
  1173. */
  1174. do_action( 'woocommerce_helper_subscription_activate_error', $product_id, $product_key, $activation_response );
  1175. }
  1176. self::_flush_subscriptions_cache();
  1177. self::_flush_updates_cache();
  1178. }
  1179. /**
  1180. * Runs when any plugin is deactivated.
  1181. *
  1182. * When a user deactivates a plugin, attempt to deactivate any subscriptions
  1183. * associated with the extension.
  1184. *
  1185. * @param string $filename The filename of the deactivated plugin.
  1186. */
  1187. public static function deactivated_plugin( $filename ) {
  1188. $plugins = self::get_local_woo_plugins();
  1189. // Not a local woo plugin.
  1190. if ( empty( $plugins[ $filename ] ) ) {
  1191. return;
  1192. }
  1193. // Make sure we have a connection.
  1194. $auth = WC_Helper_Options::get( 'auth' );
  1195. if ( empty( $auth ) ) {
  1196. return;
  1197. }
  1198. $plugin = $plugins[ $filename ];
  1199. $product_id = $plugin['_product_id'];
  1200. $subscriptions = self::_get_subscriptions_from_product_id( $product_id, false );
  1201. $site_id = absint( $auth['site_id'] );
  1202. // No valid subscriptions for this product.
  1203. if ( empty( $subscriptions ) ) {
  1204. return;
  1205. }
  1206. $deactivated = 0;
  1207. foreach ( $subscriptions as $subscription ) {
  1208. // Don't touch subscriptions that aren't activated on this site.
  1209. if ( ! in_array( $site_id, $subscription['connections'], true ) ) {
  1210. continue;
  1211. }
  1212. $product_key = $subscription['product_key'];
  1213. $deactivation_response = WC_Helper_API::post(
  1214. 'deactivate',
  1215. array(
  1216. 'authenticated' => true,
  1217. 'body' => wp_json_encode(
  1218. array(
  1219. 'product_key' => $product_key,
  1220. )
  1221. ),
  1222. )
  1223. );
  1224. if ( wp_remote_retrieve_response_code( $deactivation_response ) === 200 ) {
  1225. $deactivated++;
  1226. /**
  1227. * Fires when the Helper activates a product successfully.
  1228. *
  1229. * @param int $product_id Product ID being deactivated.
  1230. * @param string $product_key Subscription product key.
  1231. * @param array $deactivation_response The response object from wp_safe_remote_request().
  1232. */
  1233. do_action( 'woocommerce_helper_subscription_deactivate_success', $product_id, $product_key, $deactivation_response );
  1234. } else {
  1235. /**
  1236. * Fires when the Helper fails to activate a product.
  1237. *
  1238. * @param int $product_id Product ID being deactivated.
  1239. * @param string $product_key Subscription product key.
  1240. * @param array $deactivation_response The response object from wp_safe_remote_request().
  1241. */
  1242. do_action( 'woocommerce_helper_subscription_deactivate_error', $product_id, $product_key, $deactivation_response );
  1243. }
  1244. }
  1245. if ( $deactivated ) {
  1246. self::log( sprintf( 'Auto-deactivated %d subscription(s) for %s', $deactivated, $filename ) );
  1247. self::_flush_subscriptions_cache();
  1248. self::_flush_updates_cache();
  1249. }
  1250. }
  1251. /**
  1252. * Various Helper-related admin notices.
  1253. */
  1254. public static function admin_notices() {
  1255. if ( apply_filters( 'woocommerce_helper_suppress_admin_notices', false ) ) {
  1256. return;
  1257. }
  1258. $screen = get_current_screen();
  1259. $screen_id = $screen ? $screen->id : '';
  1260. if ( 'update-core' !== $screen_id ) {
  1261. return;
  1262. }
  1263. // Don't nag if Woo doesn't have an update available.
  1264. if ( ! self::_woo_core_update_available() ) {
  1265. return;
  1266. }
  1267. // Add a note about available extension updates if Woo core has an update available.
  1268. $notice = self::_get_extensions_update_notice();
  1269. if ( ! empty( $notice ) ) {
  1270. echo '<div class="updated woocommerce-message"><p>' . $notice . '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
  1271. }
  1272. }
  1273. /**
  1274. * Get an update notice if one or more Woo extensions has an update available.
  1275. *
  1276. * @return string|null The update notice or null if everything is up to date.
  1277. */
  1278. private static function _get_extensions_update_notice() {
  1279. $plugins = self::get_local_woo_plugins();
  1280. $updates = WC_Helper_Updater::get_update_data();
  1281. $available = 0;
  1282. foreach ( $plugins as $data ) {
  1283. if ( empty( $updates[ $data['_product_id'] ] ) ) {
  1284. continue;
  1285. }
  1286. $product_id = $data['_product_id'];
  1287. if ( version_compare( $updates[ $product_id ]['version'], $data['Version'], '>' ) ) {
  1288. $available++;
  1289. }
  1290. }
  1291. if ( ! $available ) {
  1292. return;
  1293. }
  1294. return sprintf(
  1295. /* translators: %1$s: helper url, %2$d: number of extensions */
  1296. _n( 'Note: You currently have <a href="%1$s">%2$d paid extension</a> which should be updated first before updating WooCommerce.', 'Note: You currently have <a href="%1$s">%2$d paid extensions</a> which should be updated first before updating WooCommerce.', $available, 'woocommerce' ),
  1297. admin_url( 'admin.php?page=wc-addons&section=helper' ),
  1298. $available
  1299. );
  1300. }
  1301. /**
  1302. * Whether WooCommerce has an update available.
  1303. *
  1304. * @return bool True if a Woo core update is available.
  1305. */
  1306. private static function _woo_core_update_available() {
  1307. $updates = get_site_transient( 'update_plugins' );
  1308. if ( empty( $updates->response ) ) {
  1309. return false;
  1310. }
  1311. if ( empty( $updates->response['woocommerce/woocommerce.php'] ) ) {
  1312. return false;
  1313. }
  1314. $data = $updates->response['woocommerce/woocommerce.php'];
  1315. if ( version_compare( Constants::get_constant( 'WC_VERSION' ), $data->new_version, '>=' ) ) {
  1316. return false;
  1317. }
  1318. return true;
  1319. }
  1320. /**
  1321. * Flush subscriptions cache.
  1322. */
  1323. public static function _flush_subscriptions_cache() {
  1324. delete_transient( '_woocommerce_helper_subscriptions' );
  1325. }
  1326. /**
  1327. * Flush auth cache.
  1328. */
  1329. public static function _flush_authentication_cache() {
  1330. $request = WC_Helper_API::get(
  1331. 'oauth/me',
  1332. array(
  1333. 'authenticated' => true,
  1334. )
  1335. );
  1336. if ( wp_remote_retrieve_response_code( $request ) !== 200 ) {
  1337. return false;
  1338. }
  1339. $user_data = json_decode( wp_remote_retrieve_body( $request ), true );
  1340. if ( ! $user_data ) {
  1341. return false;
  1342. }
  1343. WC_Helper_Options::update(
  1344. 'auth_user_data',
  1345. array(
  1346. 'name' => $user_data['name'],
  1347. 'email' => $user_data['email'],
  1348. )
  1349. );
  1350. return true;
  1351. }
  1352. /**
  1353. * Flush updates cache.
  1354. */
  1355. private static function _flush_updates_cache() {
  1356. WC_Helper_Updater::flush_updates_cache();
  1357. }
  1358. /**
  1359. * Sort subscriptions by the product_name.
  1360. *
  1361. * @param array $a Subscription array.
  1362. * @param array $b Subscription array.
  1363. *
  1364. * @return int
  1365. */
  1366. public static function _sort_by_product_name( $a, $b ) {
  1367. return strcmp( $a['product_name'], $b['product_name'] );
  1368. }
  1369. /**
  1370. * Sort subscriptions by the Name.
  1371. *
  1372. * @param array $a Product array.
  1373. * @param array $b Product array.
  1374. *
  1375. * @return int
  1376. */
  1377. public static function _sort_by_name( $a, $b ) {
  1378. return strcmp( $a['Name'], $b['Name'] );
  1379. }
  1380. /**
  1381. * Log a helper event.
  1382. *
  1383. * @param string $message Log message.
  1384. * @param string $level Optional, defaults to info, valid levels: emergency|alert|critical|error|warning|notice|info|debug.
  1385. */
  1386. public static function log( $message, $level = 'info' ) {
  1387. if ( ! Constants::is_true( 'WP_DEBUG' ) ) {
  1388. return;
  1389. }
  1390. if ( ! isset( self::$log ) ) {
  1391. self::$log = wc_get_logger();
  1392. }
  1393. self::$log->log( $level, $message, array( 'source' => 'helper' ) );
  1394. }
  1395. }
  1396. WC_Helper::load();