class-wc-install.php 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351
  1. <?php
  2. /**
  3. * Installation related functions and actions.
  4. *
  5. * @package WooCommerce/Classes
  6. * @version 3.0.0
  7. */
  8. defined( 'ABSPATH' ) || exit;
  9. /**
  10. * WC_Install Class.
  11. */
  12. class WC_Install {
  13. /**
  14. * DB updates and callbacks that need to be run per version.
  15. *
  16. * @var array
  17. */
  18. private static $db_updates = array(
  19. '2.0.0' => array(
  20. 'wc_update_200_file_paths',
  21. 'wc_update_200_permalinks',
  22. 'wc_update_200_subcat_display',
  23. 'wc_update_200_taxrates',
  24. 'wc_update_200_line_items',
  25. 'wc_update_200_images',
  26. 'wc_update_200_db_version',
  27. ),
  28. '2.0.9' => array(
  29. 'wc_update_209_brazillian_state',
  30. 'wc_update_209_db_version',
  31. ),
  32. '2.1.0' => array(
  33. 'wc_update_210_remove_pages',
  34. 'wc_update_210_file_paths',
  35. 'wc_update_210_db_version',
  36. ),
  37. '2.2.0' => array(
  38. 'wc_update_220_shipping',
  39. 'wc_update_220_order_status',
  40. 'wc_update_220_variations',
  41. 'wc_update_220_attributes',
  42. 'wc_update_220_db_version',
  43. ),
  44. '2.3.0' => array(
  45. 'wc_update_230_options',
  46. 'wc_update_230_db_version',
  47. ),
  48. '2.4.0' => array(
  49. 'wc_update_240_options',
  50. 'wc_update_240_shipping_methods',
  51. 'wc_update_240_api_keys',
  52. 'wc_update_240_refunds',
  53. 'wc_update_240_db_version',
  54. ),
  55. '2.4.1' => array(
  56. 'wc_update_241_variations',
  57. 'wc_update_241_db_version',
  58. ),
  59. '2.5.0' => array(
  60. 'wc_update_250_currency',
  61. 'wc_update_250_db_version',
  62. ),
  63. '2.6.0' => array(
  64. 'wc_update_260_options',
  65. 'wc_update_260_termmeta',
  66. 'wc_update_260_zones',
  67. 'wc_update_260_zone_methods',
  68. 'wc_update_260_refunds',
  69. 'wc_update_260_db_version',
  70. ),
  71. '3.0.0' => array(
  72. 'wc_update_300_grouped_products',
  73. 'wc_update_300_settings',
  74. 'wc_update_300_product_visibility',
  75. 'wc_update_300_db_version',
  76. ),
  77. '3.1.0' => array(
  78. 'wc_update_310_downloadable_products',
  79. 'wc_update_310_old_comments',
  80. 'wc_update_310_db_version',
  81. ),
  82. '3.1.2' => array(
  83. 'wc_update_312_shop_manager_capabilities',
  84. 'wc_update_312_db_version',
  85. ),
  86. '3.2.0' => array(
  87. 'wc_update_320_mexican_states',
  88. 'wc_update_320_db_version',
  89. ),
  90. '3.3.0' => array(
  91. 'wc_update_330_image_options',
  92. 'wc_update_330_webhooks',
  93. 'wc_update_330_product_stock_status',
  94. 'wc_update_330_set_default_product_cat',
  95. 'wc_update_330_clear_transients',
  96. 'wc_update_330_set_paypal_sandbox_credentials',
  97. 'wc_update_330_db_version',
  98. ),
  99. '3.4.0' => array(
  100. 'wc_update_340_states',
  101. 'wc_update_340_state',
  102. 'wc_update_340_last_active',
  103. 'wc_update_340_db_version',
  104. ),
  105. '3.4.3' => array(
  106. 'wc_update_343_cleanup_foreign_keys',
  107. 'wc_update_343_db_version',
  108. ),
  109. );
  110. /**
  111. * Background update class.
  112. *
  113. * @var object
  114. */
  115. private static $background_updater;
  116. /**
  117. * Hook in tabs.
  118. */
  119. public static function init() {
  120. add_action( 'init', array( __CLASS__, 'check_version' ), 5 );
  121. add_action( 'init', array( __CLASS__, 'init_background_updater' ), 5 );
  122. add_action( 'admin_init', array( __CLASS__, 'install_actions' ) );
  123. add_filter( 'plugin_action_links_' . WC_PLUGIN_BASENAME, array( __CLASS__, 'plugin_action_links' ) );
  124. add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_row_meta' ), 10, 2 );
  125. add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) );
  126. add_filter( 'cron_schedules', array( __CLASS__, 'cron_schedules' ) );
  127. add_action( 'woocommerce_plugin_background_installer', array( __CLASS__, 'background_installer' ), 10, 2 );
  128. add_action( 'woocommerce_theme_background_installer', array( __CLASS__, 'theme_background_installer' ), 10, 1 );
  129. }
  130. /**
  131. * Init background updates
  132. */
  133. public static function init_background_updater() {
  134. include_once dirname( __FILE__ ) . '/class-wc-background-updater.php';
  135. self::$background_updater = new WC_Background_Updater();
  136. }
  137. /**
  138. * Check WooCommerce version and run the updater is required.
  139. *
  140. * This check is done on all requests and runs if the versions do not match.
  141. */
  142. public static function check_version() {
  143. if ( ! defined( 'IFRAME_REQUEST' ) && version_compare( get_option( 'woocommerce_version' ), WC()->version, '<' ) ) {
  144. self::install();
  145. do_action( 'woocommerce_updated' );
  146. }
  147. }
  148. /**
  149. * Install actions when a update button is clicked within the admin area.
  150. *
  151. * This function is hooked into admin_init to affect admin only.
  152. */
  153. public static function install_actions() {
  154. if ( ! empty( $_GET['do_update_woocommerce'] ) ) { // WPCS: input var ok, CSRF ok.
  155. self::update();
  156. WC_Admin_Notices::add_notice( 'update' );
  157. }
  158. if ( ! empty( $_GET['force_update_woocommerce'] ) ) { // WPCS: input var ok, CSRF ok.
  159. $blog_id = get_current_blog_id();
  160. do_action( 'wp_' . $blog_id . '_wc_updater_cron' );
  161. wp_safe_redirect( admin_url( 'admin.php?page=wc-settings' ) );
  162. exit;
  163. }
  164. }
  165. /**
  166. * Install WC.
  167. */
  168. public static function install() {
  169. if ( ! is_blog_installed() ) {
  170. return;
  171. }
  172. // Check if we are not already running this routine.
  173. if ( 'yes' === get_transient( 'wc_installing' ) ) {
  174. return;
  175. }
  176. // If we made it till here nothing is running yet, lets set the transient now.
  177. set_transient( 'wc_installing', 'yes', MINUTE_IN_SECONDS * 10 );
  178. wc_maybe_define_constant( 'WC_INSTALLING', true );
  179. self::remove_admin_notices();
  180. self::create_options();
  181. self::create_tables();
  182. self::create_roles();
  183. self::setup_environment();
  184. self::create_terms();
  185. self::create_cron_jobs();
  186. self::create_files();
  187. self::maybe_enable_setup_wizard();
  188. self::update_wc_version();
  189. self::maybe_update_db_version();
  190. delete_transient( 'wc_installing' );
  191. do_action( 'woocommerce_flush_rewrite_rules' );
  192. do_action( 'woocommerce_installed' );
  193. }
  194. /**
  195. * Reset any notices added to admin.
  196. *
  197. * @since 3.2.0
  198. */
  199. private static function remove_admin_notices() {
  200. include_once dirname( __FILE__ ) . '/admin/class-wc-admin-notices.php';
  201. WC_Admin_Notices::remove_all_notices();
  202. }
  203. /**
  204. * Setup WC environment - post types, taxonomies, endpoints.
  205. *
  206. * @since 3.2.0
  207. */
  208. private static function setup_environment() {
  209. WC_Post_types::register_post_types();
  210. WC_Post_types::register_taxonomies();
  211. WC()->query->init_query_vars();
  212. WC()->query->add_endpoints();
  213. WC_API::add_endpoint();
  214. WC_Auth::add_endpoint();
  215. }
  216. /**
  217. * Is this a brand new WC install?
  218. *
  219. * @since 3.2.0
  220. * @return boolean
  221. */
  222. private static function is_new_install() {
  223. return is_null( get_option( 'woocommerce_version', null ) ) && is_null( get_option( 'woocommerce_db_version', null ) );
  224. }
  225. /**
  226. * Is a DB update needed?
  227. *
  228. * @since 3.2.0
  229. * @return boolean
  230. */
  231. private static function needs_db_update() {
  232. $current_db_version = get_option( 'woocommerce_db_version', null );
  233. $updates = self::get_db_update_callbacks();
  234. return ! is_null( $current_db_version ) && version_compare( $current_db_version, max( array_keys( $updates ) ), '<' );
  235. }
  236. /**
  237. * See if we need the wizard or not.
  238. *
  239. * @since 3.2.0
  240. */
  241. private static function maybe_enable_setup_wizard() {
  242. if ( apply_filters( 'woocommerce_enable_setup_wizard', self::is_new_install() ) ) {
  243. WC_Admin_Notices::add_notice( 'install' );
  244. set_transient( '_wc_activation_redirect', 1, 30 );
  245. }
  246. }
  247. /**
  248. * See if we need to show or run database updates during install.
  249. *
  250. * @since 3.2.0
  251. */
  252. private static function maybe_update_db_version() {
  253. if ( self::needs_db_update() ) {
  254. if ( apply_filters( 'woocommerce_enable_auto_update_db', false ) ) {
  255. self::init_background_updater();
  256. self::update();
  257. } else {
  258. WC_Admin_Notices::add_notice( 'update' );
  259. }
  260. } else {
  261. self::update_db_version();
  262. }
  263. }
  264. /**
  265. * Update WC version to current.
  266. */
  267. private static function update_wc_version() {
  268. delete_option( 'woocommerce_version' );
  269. add_option( 'woocommerce_version', WC()->version );
  270. }
  271. /**
  272. * Get list of DB update callbacks.
  273. *
  274. * @since 3.0.0
  275. * @return array
  276. */
  277. public static function get_db_update_callbacks() {
  278. return self::$db_updates;
  279. }
  280. /**
  281. * Push all needed DB updates to the queue for processing.
  282. */
  283. private static function update() {
  284. $current_db_version = get_option( 'woocommerce_db_version' );
  285. $logger = wc_get_logger();
  286. $update_queued = false;
  287. foreach ( self::get_db_update_callbacks() as $version => $update_callbacks ) {
  288. if ( version_compare( $current_db_version, $version, '<' ) ) {
  289. foreach ( $update_callbacks as $update_callback ) {
  290. $logger->info(
  291. sprintf( 'Queuing %s - %s', $version, $update_callback ),
  292. array( 'source' => 'wc_db_updates' )
  293. );
  294. self::$background_updater->push_to_queue( $update_callback );
  295. $update_queued = true;
  296. }
  297. }
  298. }
  299. if ( $update_queued ) {
  300. self::$background_updater->save()->dispatch();
  301. }
  302. }
  303. /**
  304. * Update DB version to current.
  305. *
  306. * @param string|null $version New WooCommerce DB version or null.
  307. */
  308. public static function update_db_version( $version = null ) {
  309. delete_option( 'woocommerce_db_version' );
  310. add_option( 'woocommerce_db_version', is_null( $version ) ? WC()->version : $version );
  311. }
  312. /**
  313. * Add more cron schedules.
  314. *
  315. * @param array $schedules List of WP scheduled cron jobs.
  316. * @return array
  317. */
  318. public static function cron_schedules( $schedules ) {
  319. $schedules['monthly'] = array(
  320. 'interval' => 2635200,
  321. 'display' => __( 'Monthly', 'woocommerce' ),
  322. );
  323. return $schedules;
  324. }
  325. /**
  326. * Create cron jobs (clear them first).
  327. */
  328. private static function create_cron_jobs() {
  329. wp_clear_scheduled_hook( 'woocommerce_scheduled_sales' );
  330. wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
  331. wp_clear_scheduled_hook( 'woocommerce_cleanup_sessions' );
  332. wp_clear_scheduled_hook( 'woocommerce_cleanup_personal_data' );
  333. wp_clear_scheduled_hook( 'woocommerce_cleanup_logs' );
  334. wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
  335. wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' );
  336. $ve = get_option( 'gmt_offset' ) > 0 ? '-' : '+';
  337. wp_schedule_event( strtotime( '00:00 tomorrow ' . $ve . absint( get_option( 'gmt_offset' ) ) . ' HOURS' ), 'daily', 'woocommerce_scheduled_sales' );
  338. $held_duration = get_option( 'woocommerce_hold_stock_minutes', '60' );
  339. if ( '' !== $held_duration ) {
  340. wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
  341. }
  342. wp_schedule_event( time(), 'daily', 'woocommerce_cleanup_personal_data' );
  343. wp_schedule_event( time() + ( 3 * HOUR_IN_SECONDS ), 'daily', 'woocommerce_cleanup_logs' );
  344. wp_schedule_event( time() + ( 6 * HOUR_IN_SECONDS ), 'twicedaily', 'woocommerce_cleanup_sessions' );
  345. wp_schedule_event( strtotime( 'first tuesday of next month' ), 'monthly', 'woocommerce_geoip_updater' );
  346. wp_schedule_event( time() + 10, apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' ), 'woocommerce_tracker_send_event' );
  347. // Trigger GeoLite2 database download after 5 minutes.
  348. wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS * 5 ), 'woocommerce_geoip_updater' );
  349. }
  350. /**
  351. * Create pages that the plugin relies on, storing page IDs in variables.
  352. */
  353. public static function create_pages() {
  354. include_once dirname( __FILE__ ) . '/admin/wc-admin-functions.php';
  355. $pages = apply_filters(
  356. 'woocommerce_create_pages', array(
  357. 'shop' => array(
  358. 'name' => _x( 'shop', 'Page slug', 'woocommerce' ),
  359. 'title' => _x( 'Shop', 'Page title', 'woocommerce' ),
  360. 'content' => '',
  361. ),
  362. 'cart' => array(
  363. 'name' => _x( 'cart', 'Page slug', 'woocommerce' ),
  364. 'title' => _x( 'Cart', 'Page title', 'woocommerce' ),
  365. 'content' => '[' . apply_filters( 'woocommerce_cart_shortcode_tag', 'woocommerce_cart' ) . ']',
  366. ),
  367. 'checkout' => array(
  368. 'name' => _x( 'checkout', 'Page slug', 'woocommerce' ),
  369. 'title' => _x( 'Checkout', 'Page title', 'woocommerce' ),
  370. 'content' => '[' . apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) . ']',
  371. ),
  372. 'myaccount' => array(
  373. 'name' => _x( 'my-account', 'Page slug', 'woocommerce' ),
  374. 'title' => _x( 'My account', 'Page title', 'woocommerce' ),
  375. 'content' => '[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']',
  376. ),
  377. )
  378. );
  379. foreach ( $pages as $key => $page ) {
  380. wc_create_page( esc_sql( $page['name'] ), 'woocommerce_' . $key . '_page_id', $page['title'], $page['content'], ! empty( $page['parent'] ) ? wc_get_page_id( $page['parent'] ) : '' );
  381. }
  382. }
  383. /**
  384. * Default options.
  385. *
  386. * Sets up the default options used on the settings page.
  387. */
  388. private static function create_options() {
  389. // Include settings so that we can run through defaults.
  390. include_once dirname( __FILE__ ) . '/admin/class-wc-admin-settings.php';
  391. $settings = WC_Admin_Settings::get_settings_pages();
  392. foreach ( $settings as $section ) {
  393. if ( ! method_exists( $section, 'get_settings' ) ) {
  394. continue;
  395. }
  396. $subsections = array_unique( array_merge( array( '' ), array_keys( $section->get_sections() ) ) );
  397. foreach ( $subsections as $subsection ) {
  398. foreach ( $section->get_settings( $subsection ) as $value ) {
  399. if ( isset( $value['default'] ) && isset( $value['id'] ) ) {
  400. $autoload = isset( $value['autoload'] ) ? (bool) $value['autoload'] : true;
  401. add_option( $value['id'], $value['default'], '', ( $autoload ? 'yes' : 'no' ) );
  402. }
  403. }
  404. }
  405. }
  406. // Define other defaults if not in setting screens.
  407. add_option( 'woocommerce_single_image_width', '600', '', 'yes' );
  408. add_option( 'woocommerce_thumbnail_image_width', '300', '', 'yes' );
  409. add_option( 'woocommerce_checkout_highlight_required_fields', 'yes', '', 'yes' );
  410. add_option( 'woocommerce_demo_store', 'no', '', 'no' );
  411. }
  412. /**
  413. * Add the default terms for WC taxonomies - product types and order statuses. Modify this at your own risk.
  414. */
  415. public static function create_terms() {
  416. $taxonomies = array(
  417. 'product_type' => array(
  418. 'simple',
  419. 'grouped',
  420. 'variable',
  421. 'external',
  422. ),
  423. 'product_visibility' => array(
  424. 'exclude-from-search',
  425. 'exclude-from-catalog',
  426. 'featured',
  427. 'outofstock',
  428. 'rated-1',
  429. 'rated-2',
  430. 'rated-3',
  431. 'rated-4',
  432. 'rated-5',
  433. ),
  434. );
  435. foreach ( $taxonomies as $taxonomy => $terms ) {
  436. foreach ( $terms as $term ) {
  437. if ( ! get_term_by( 'name', $term, $taxonomy ) ) { // @codingStandardsIgnoreLine.
  438. wp_insert_term( $term, $taxonomy );
  439. }
  440. }
  441. }
  442. $woocommerce_default_category = (int) get_option( 'default_product_cat', 0 );
  443. if ( ! $woocommerce_default_category || ! term_exists( $woocommerce_default_category, 'product_cat' ) ) {
  444. $default_product_cat_id = 0;
  445. $default_product_cat_slug = sanitize_title( _x( 'Uncategorized', 'Default category slug', 'woocommerce' ) );
  446. $default_product_cat = get_term_by( 'slug', $default_product_cat_slug, 'product_cat' ); // @codingStandardsIgnoreLine.
  447. if ( $default_product_cat ) {
  448. $default_product_cat_id = absint( $default_product_cat->term_taxonomy_id );
  449. } else {
  450. $result = wp_insert_term( _x( 'Uncategorized', 'Default category slug', 'woocommerce' ), 'product_cat', array( 'slug' => $default_product_cat_slug ) );
  451. if ( ! is_wp_error( $result ) && ! empty( $result['term_taxonomy_id'] ) ) {
  452. $default_product_cat_id = absint( $result['term_taxonomy_id'] );
  453. }
  454. }
  455. if ( $default_product_cat_id ) {
  456. update_option( 'default_product_cat', $default_product_cat_id );
  457. }
  458. }
  459. }
  460. /**
  461. * Set up the database tables which the plugin needs to function.
  462. *
  463. * Tables:
  464. * woocommerce_attribute_taxonomies - Table for storing attribute taxonomies - these are user defined
  465. * woocommerce_termmeta - Term meta table - sadly WordPress does not have termmeta so we need our own
  466. * woocommerce_downloadable_product_permissions - Table for storing user and guest download permissions.
  467. * KEY(order_id, product_id, download_id) used for organizing downloads on the My Account page
  468. * woocommerce_order_items - Order line items are stored in a table to make them easily queryable for reports
  469. * woocommerce_order_itemmeta - Order line item meta is stored in a table for storing extra data.
  470. * woocommerce_tax_rates - Tax Rates are stored inside 2 tables making tax queries simple and efficient.
  471. * woocommerce_tax_rate_locations - Each rate can be applied to more than one postcode/city hence the second table.
  472. */
  473. private static function create_tables() {
  474. global $wpdb;
  475. $wpdb->hide_errors();
  476. require_once ABSPATH . 'wp-admin/includes/upgrade.php';
  477. /**
  478. * Before updating with DBDELTA, remove any primary keys which could be
  479. * modified due to schema updates.
  480. */
  481. if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}woocommerce_downloadable_product_permissions';" ) ) {
  482. if ( ! $wpdb->get_var( "SHOW COLUMNS FROM `{$wpdb->prefix}woocommerce_downloadable_product_permissions` LIKE 'permission_id';" ) ) {
  483. $wpdb->query( "ALTER TABLE {$wpdb->prefix}woocommerce_downloadable_product_permissions DROP PRIMARY KEY, ADD `permission_id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT;" );
  484. }
  485. }
  486. dbDelta( self::get_schema() );
  487. $index_exists = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->comments} WHERE column_name = 'comment_type' and key_name = 'woo_idx_comment_type'" );
  488. if ( is_null( $index_exists ) ) {
  489. // Add an index to the field comment_type to improve the response time of the query
  490. // used by WC_Comments::wp_count_comments() to get the number of comments by type.
  491. $wpdb->query( "ALTER TABLE {$wpdb->comments} ADD INDEX woo_idx_comment_type (comment_type)" );
  492. }
  493. // Get tables data types and check it matches before adding constraint.
  494. $download_log_columns = $wpdb->get_results( "SHOW COLUMNS FROM {$wpdb->prefix}wc_download_log WHERE Field = 'permission_id'", ARRAY_A );
  495. $download_log_column_type = '';
  496. if ( isset( $download_log_columns[0]['Type'] ) ) {
  497. $download_log_column_type = $download_log_columns[0]['Type'];
  498. }
  499. $download_permissions_columns = $wpdb->get_results( "SHOW COLUMNS FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE Field = 'permission_id'", ARRAY_A );
  500. $download_permissions_column_type = '';
  501. if ( isset( $download_permissions_columns[0]['Type'] ) ) {
  502. $download_permissions_column_type = $download_permissions_columns[0]['Type'];
  503. }
  504. // Add constraint to download logs if the columns matches.
  505. if ( ! empty( $download_permissions_column_type ) && ! empty( $download_log_column_type ) && $download_permissions_column_type === $download_log_column_type ) {
  506. $fk_result = $wpdb->get_row( "
  507. SELECT COUNT(*) AS fk_count
  508. FROM information_schema.TABLE_CONSTRAINTS
  509. WHERE CONSTRAINT_SCHEMA = '{$wpdb->dbname}'
  510. AND CONSTRAINT_NAME = 'fk_wc_download_log_permission_id'
  511. AND CONSTRAINT_TYPE = 'FOREIGN KEY'
  512. AND TABLE_NAME = '{$wpdb->prefix}wc_download_log'
  513. " );
  514. if ( 0 === (int) $fk_result->fk_count ) {
  515. $wpdb->query( "
  516. ALTER TABLE `{$wpdb->prefix}wc_download_log`
  517. ADD CONSTRAINT `fk_wc_download_log_permission_id`
  518. FOREIGN KEY (`permission_id`)
  519. REFERENCES `{$wpdb->prefix}woocommerce_downloadable_product_permissions` (`permission_id`) ON DELETE CASCADE;
  520. " );
  521. }
  522. }
  523. }
  524. /**
  525. * Get Table schema.
  526. *
  527. * See https://github.com/woocommerce/woocommerce/wiki/Database-Description/
  528. *
  529. * A note on indexes; Indexes have a maximum size of 767 bytes. Historically, we haven't need to be concerned about that.
  530. * As of WordPress 4.2, however, we moved to utf8mb4, which uses 4 bytes per character. This means that an index which
  531. * used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
  532. *
  533. * Changing indexes may cause duplicate index notices in logs due to https://core.trac.wordpress.org/ticket/34870 but dropping
  534. * indexes first causes too much load on some servers/larger DB.
  535. *
  536. * When adding or removing a table, make sure to update the list of tables in WC_Install::get_tables().
  537. *
  538. * @return string
  539. */
  540. private static function get_schema() {
  541. global $wpdb;
  542. $collate = '';
  543. if ( $wpdb->has_cap( 'collation' ) ) {
  544. $collate = $wpdb->get_charset_collate();
  545. }
  546. $tables = "
  547. CREATE TABLE {$wpdb->prefix}woocommerce_sessions (
  548. session_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  549. session_key char(32) NOT NULL,
  550. session_value longtext NOT NULL,
  551. session_expiry BIGINT UNSIGNED NOT NULL,
  552. PRIMARY KEY (session_key),
  553. UNIQUE KEY session_id (session_id)
  554. ) $collate;
  555. CREATE TABLE {$wpdb->prefix}woocommerce_api_keys (
  556. key_id BIGINT UNSIGNED NOT NULL auto_increment,
  557. user_id BIGINT UNSIGNED NOT NULL,
  558. description varchar(200) NULL,
  559. permissions varchar(10) NOT NULL,
  560. consumer_key char(64) NOT NULL,
  561. consumer_secret char(43) NOT NULL,
  562. nonces longtext NULL,
  563. truncated_key char(7) NOT NULL,
  564. last_access datetime NULL default null,
  565. PRIMARY KEY (key_id),
  566. KEY consumer_key (consumer_key),
  567. KEY consumer_secret (consumer_secret)
  568. ) $collate;
  569. CREATE TABLE {$wpdb->prefix}woocommerce_attribute_taxonomies (
  570. attribute_id BIGINT UNSIGNED NOT NULL auto_increment,
  571. attribute_name varchar(200) NOT NULL,
  572. attribute_label varchar(200) NULL,
  573. attribute_type varchar(20) NOT NULL,
  574. attribute_orderby varchar(20) NOT NULL,
  575. attribute_public int(1) NOT NULL DEFAULT 1,
  576. PRIMARY KEY (attribute_id),
  577. KEY attribute_name (attribute_name(20))
  578. ) $collate;
  579. CREATE TABLE {$wpdb->prefix}woocommerce_downloadable_product_permissions (
  580. permission_id BIGINT UNSIGNED NOT NULL auto_increment,
  581. download_id varchar(36) NOT NULL,
  582. product_id BIGINT UNSIGNED NOT NULL,
  583. order_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
  584. order_key varchar(200) NOT NULL,
  585. user_email varchar(200) NOT NULL,
  586. user_id BIGINT UNSIGNED NULL,
  587. downloads_remaining varchar(9) NULL,
  588. access_granted datetime NOT NULL default '0000-00-00 00:00:00',
  589. access_expires datetime NULL default null,
  590. download_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
  591. PRIMARY KEY (permission_id),
  592. KEY download_order_key_product (product_id,order_id,order_key(16),download_id),
  593. KEY download_order_product (download_id,order_id,product_id),
  594. KEY order_id (order_id)
  595. ) $collate;
  596. CREATE TABLE {$wpdb->prefix}woocommerce_order_items (
  597. order_item_id BIGINT UNSIGNED NOT NULL auto_increment,
  598. order_item_name TEXT NOT NULL,
  599. order_item_type varchar(200) NOT NULL DEFAULT '',
  600. order_id BIGINT UNSIGNED NOT NULL,
  601. PRIMARY KEY (order_item_id),
  602. KEY order_id (order_id)
  603. ) $collate;
  604. CREATE TABLE {$wpdb->prefix}woocommerce_order_itemmeta (
  605. meta_id BIGINT UNSIGNED NOT NULL auto_increment,
  606. order_item_id BIGINT UNSIGNED NOT NULL,
  607. meta_key varchar(255) default NULL,
  608. meta_value longtext NULL,
  609. PRIMARY KEY (meta_id),
  610. KEY order_item_id (order_item_id),
  611. KEY meta_key (meta_key(32))
  612. ) $collate;
  613. CREATE TABLE {$wpdb->prefix}woocommerce_tax_rates (
  614. tax_rate_id BIGINT UNSIGNED NOT NULL auto_increment,
  615. tax_rate_country varchar(2) NOT NULL DEFAULT '',
  616. tax_rate_state varchar(200) NOT NULL DEFAULT '',
  617. tax_rate varchar(8) NOT NULL DEFAULT '',
  618. tax_rate_name varchar(200) NOT NULL DEFAULT '',
  619. tax_rate_priority BIGINT UNSIGNED NOT NULL,
  620. tax_rate_compound int(1) NOT NULL DEFAULT 0,
  621. tax_rate_shipping int(1) NOT NULL DEFAULT 1,
  622. tax_rate_order BIGINT UNSIGNED NOT NULL,
  623. tax_rate_class varchar(200) NOT NULL DEFAULT '',
  624. PRIMARY KEY (tax_rate_id),
  625. KEY tax_rate_country (tax_rate_country),
  626. KEY tax_rate_state (tax_rate_state(2)),
  627. KEY tax_rate_class (tax_rate_class(10)),
  628. KEY tax_rate_priority (tax_rate_priority)
  629. ) $collate;
  630. CREATE TABLE {$wpdb->prefix}woocommerce_tax_rate_locations (
  631. location_id BIGINT UNSIGNED NOT NULL auto_increment,
  632. location_code varchar(200) NOT NULL,
  633. tax_rate_id BIGINT UNSIGNED NOT NULL,
  634. location_type varchar(40) NOT NULL,
  635. PRIMARY KEY (location_id),
  636. KEY tax_rate_id (tax_rate_id),
  637. KEY location_type_code (location_type(10),location_code(20))
  638. ) $collate;
  639. CREATE TABLE {$wpdb->prefix}woocommerce_shipping_zones (
  640. zone_id BIGINT UNSIGNED NOT NULL auto_increment,
  641. zone_name varchar(200) NOT NULL,
  642. zone_order BIGINT UNSIGNED NOT NULL,
  643. PRIMARY KEY (zone_id)
  644. ) $collate;
  645. CREATE TABLE {$wpdb->prefix}woocommerce_shipping_zone_locations (
  646. location_id BIGINT UNSIGNED NOT NULL auto_increment,
  647. zone_id BIGINT UNSIGNED NOT NULL,
  648. location_code varchar(200) NOT NULL,
  649. location_type varchar(40) NOT NULL,
  650. PRIMARY KEY (location_id),
  651. KEY location_id (location_id),
  652. KEY location_type_code (location_type(10),location_code(20))
  653. ) $collate;
  654. CREATE TABLE {$wpdb->prefix}woocommerce_shipping_zone_methods (
  655. zone_id BIGINT UNSIGNED NOT NULL,
  656. instance_id BIGINT UNSIGNED NOT NULL auto_increment,
  657. method_id varchar(200) NOT NULL,
  658. method_order BIGINT UNSIGNED NOT NULL,
  659. is_enabled tinyint(1) NOT NULL DEFAULT '1',
  660. PRIMARY KEY (instance_id)
  661. ) $collate;
  662. CREATE TABLE {$wpdb->prefix}woocommerce_payment_tokens (
  663. token_id BIGINT UNSIGNED NOT NULL auto_increment,
  664. gateway_id varchar(200) NOT NULL,
  665. token text NOT NULL,
  666. user_id BIGINT UNSIGNED NOT NULL DEFAULT '0',
  667. type varchar(200) NOT NULL,
  668. is_default tinyint(1) NOT NULL DEFAULT '0',
  669. PRIMARY KEY (token_id),
  670. KEY user_id (user_id)
  671. ) $collate;
  672. CREATE TABLE {$wpdb->prefix}woocommerce_payment_tokenmeta (
  673. meta_id BIGINT UNSIGNED NOT NULL auto_increment,
  674. payment_token_id BIGINT UNSIGNED NOT NULL,
  675. meta_key varchar(255) NULL,
  676. meta_value longtext NULL,
  677. PRIMARY KEY (meta_id),
  678. KEY payment_token_id (payment_token_id),
  679. KEY meta_key (meta_key(32))
  680. ) $collate;
  681. CREATE TABLE {$wpdb->prefix}woocommerce_log (
  682. log_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  683. timestamp datetime NOT NULL,
  684. level smallint(4) NOT NULL,
  685. source varchar(200) NOT NULL,
  686. message longtext NOT NULL,
  687. context longtext NULL,
  688. PRIMARY KEY (log_id),
  689. KEY level (level)
  690. ) $collate;
  691. CREATE TABLE {$wpdb->prefix}wc_webhooks (
  692. webhook_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  693. status varchar(200) NOT NULL,
  694. name text NOT NULL,
  695. user_id BIGINT UNSIGNED NOT NULL,
  696. delivery_url text NOT NULL,
  697. secret text NOT NULL,
  698. topic varchar(200) NOT NULL,
  699. date_created datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  700. date_created_gmt datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  701. date_modified datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  702. date_modified_gmt datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  703. api_version smallint(4) NOT NULL,
  704. failure_count smallint(10) NOT NULL DEFAULT '0',
  705. pending_delivery tinyint(1) NOT NULL DEFAULT '0',
  706. PRIMARY KEY (webhook_id),
  707. KEY user_id (user_id)
  708. ) $collate;
  709. CREATE TABLE {$wpdb->prefix}wc_download_log (
  710. download_log_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  711. timestamp datetime NOT NULL,
  712. permission_id BIGINT UNSIGNED NOT NULL,
  713. user_id BIGINT UNSIGNED NULL,
  714. user_ip_address VARCHAR(100) NULL DEFAULT '',
  715. PRIMARY KEY (download_log_id),
  716. KEY permission_id (permission_id),
  717. KEY timestamp (timestamp)
  718. ) $collate;
  719. ";
  720. /**
  721. * Term meta is only needed for old installs and is now @deprecated by WordPress term meta.
  722. */
  723. if ( ! function_exists( 'get_term_meta' ) ) {
  724. $tables .= "
  725. CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
  726. meta_id BIGINT UNSIGNED NOT NULL auto_increment,
  727. woocommerce_term_id BIGINT UNSIGNED NOT NULL,
  728. meta_key varchar(255) default NULL,
  729. meta_value longtext NULL,
  730. PRIMARY KEY (meta_id),
  731. KEY woocommerce_term_id (woocommerce_term_id),
  732. KEY meta_key (meta_key(32))
  733. ) $collate;
  734. ";
  735. }
  736. return $tables;
  737. }
  738. /**
  739. * Return a list of WooCommerce tables. Used to make sure all WC tables are dropped when uninstalling the plugin
  740. * in a single site or multi site environment.
  741. *
  742. * @return array WC tables.
  743. */
  744. public static function get_tables() {
  745. global $wpdb;
  746. $tables = array(
  747. "{$wpdb->prefix}wc_download_log",
  748. "{$wpdb->prefix}wc_webhooks",
  749. "{$wpdb->prefix}woocommerce_api_keys",
  750. "{$wpdb->prefix}woocommerce_attribute_taxonomies",
  751. "{$wpdb->prefix}woocommerce_downloadable_product_permissions",
  752. "{$wpdb->prefix}woocommerce_log",
  753. "{$wpdb->prefix}woocommerce_order_itemmeta",
  754. "{$wpdb->prefix}woocommerce_order_items",
  755. "{$wpdb->prefix}woocommerce_payment_tokenmeta",
  756. "{$wpdb->prefix}woocommerce_payment_tokens",
  757. "{$wpdb->prefix}woocommerce_sessions",
  758. "{$wpdb->prefix}woocommerce_shipping_zone_locations",
  759. "{$wpdb->prefix}woocommerce_shipping_zone_methods",
  760. "{$wpdb->prefix}woocommerce_shipping_zones",
  761. "{$wpdb->prefix}woocommerce_tax_rate_locations",
  762. "{$wpdb->prefix}woocommerce_tax_rates",
  763. );
  764. if ( ! function_exists( 'get_term_meta' ) ) {
  765. // This table is only needed for old installs and is now @deprecated by WordPress term meta.
  766. $tables[] = "{$wpdb->prefix}woocommerce_termmeta";
  767. }
  768. /**
  769. * Filter the list of known WooCommerce tables.
  770. *
  771. * If WooCommerce plugins need to add new tables, they can inject them here.
  772. *
  773. * @param array $tables An array of WooCommerce-specific database table names.
  774. */
  775. $tables = apply_filters( 'woocommerce_install_get_tables', $tables );
  776. return $tables;
  777. }
  778. /**
  779. * Drop WooCommerce tables.
  780. *
  781. * @return void
  782. */
  783. public static function drop_tables() {
  784. global $wpdb;
  785. $tables = self::get_tables();
  786. foreach ( $tables as $table ) {
  787. $wpdb->query( "DROP TABLE IF EXISTS {$table}" ); // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared
  788. }
  789. }
  790. /**
  791. * Uninstall tables when MU blog is deleted.
  792. *
  793. * @param array $tables List of tables that will be deleted by WP.
  794. * @return string[]
  795. */
  796. public static function wpmu_drop_tables( $tables ) {
  797. return array_merge( $tables, self::get_tables() );
  798. }
  799. /**
  800. * Create roles and capabilities.
  801. */
  802. public static function create_roles() {
  803. global $wp_roles;
  804. if ( ! class_exists( 'WP_Roles' ) ) {
  805. return;
  806. }
  807. if ( ! isset( $wp_roles ) ) {
  808. $wp_roles = new WP_Roles(); // @codingStandardsIgnoreLine
  809. }
  810. // Dummy gettext calls to get strings in the catalog.
  811. /* translators: user role */
  812. _x( 'Customer', 'User role', 'woocommerce' );
  813. /* translators: user role */
  814. _x( 'Shop manager', 'User role', 'woocommerce' );
  815. // Customer role.
  816. add_role(
  817. 'customer',
  818. 'Customer',
  819. array(
  820. 'read' => true,
  821. )
  822. );
  823. // Shop manager role.
  824. add_role(
  825. 'shop_manager',
  826. 'Shop manager',
  827. array(
  828. 'level_9' => true,
  829. 'level_8' => true,
  830. 'level_7' => true,
  831. 'level_6' => true,
  832. 'level_5' => true,
  833. 'level_4' => true,
  834. 'level_3' => true,
  835. 'level_2' => true,
  836. 'level_1' => true,
  837. 'level_0' => true,
  838. 'read' => true,
  839. 'read_private_pages' => true,
  840. 'read_private_posts' => true,
  841. 'edit_users' => true,
  842. 'edit_posts' => true,
  843. 'edit_pages' => true,
  844. 'edit_published_posts' => true,
  845. 'edit_published_pages' => true,
  846. 'edit_private_pages' => true,
  847. 'edit_private_posts' => true,
  848. 'edit_others_posts' => true,
  849. 'edit_others_pages' => true,
  850. 'publish_posts' => true,
  851. 'publish_pages' => true,
  852. 'delete_posts' => true,
  853. 'delete_pages' => true,
  854. 'delete_private_pages' => true,
  855. 'delete_private_posts' => true,
  856. 'delete_published_pages' => true,
  857. 'delete_published_posts' => true,
  858. 'delete_others_posts' => true,
  859. 'delete_others_pages' => true,
  860. 'manage_categories' => true,
  861. 'manage_links' => true,
  862. 'moderate_comments' => true,
  863. 'upload_files' => true,
  864. 'export' => true,
  865. 'import' => true,
  866. 'list_users' => true,
  867. )
  868. );
  869. $capabilities = self::get_core_capabilities();
  870. foreach ( $capabilities as $cap_group ) {
  871. foreach ( $cap_group as $cap ) {
  872. $wp_roles->add_cap( 'shop_manager', $cap );
  873. $wp_roles->add_cap( 'administrator', $cap );
  874. }
  875. }
  876. }
  877. /**
  878. * Get capabilities for WooCommerce - these are assigned to admin/shop manager during installation or reset.
  879. *
  880. * @return array
  881. */
  882. private static function get_core_capabilities() {
  883. $capabilities = array();
  884. $capabilities['core'] = array(
  885. 'manage_woocommerce',
  886. 'view_woocommerce_reports',
  887. );
  888. $capability_types = array( 'product', 'shop_order', 'shop_coupon' );
  889. foreach ( $capability_types as $capability_type ) {
  890. $capabilities[ $capability_type ] = array(
  891. // Post type.
  892. "edit_{$capability_type}",
  893. "read_{$capability_type}",
  894. "delete_{$capability_type}",
  895. "edit_{$capability_type}s",
  896. "edit_others_{$capability_type}s",
  897. "publish_{$capability_type}s",
  898. "read_private_{$capability_type}s",
  899. "delete_{$capability_type}s",
  900. "delete_private_{$capability_type}s",
  901. "delete_published_{$capability_type}s",
  902. "delete_others_{$capability_type}s",
  903. "edit_private_{$capability_type}s",
  904. "edit_published_{$capability_type}s",
  905. // Terms.
  906. "manage_{$capability_type}_terms",
  907. "edit_{$capability_type}_terms",
  908. "delete_{$capability_type}_terms",
  909. "assign_{$capability_type}_terms",
  910. );
  911. }
  912. return $capabilities;
  913. }
  914. /**
  915. * Remove WooCommerce roles.
  916. */
  917. public static function remove_roles() {
  918. global $wp_roles;
  919. if ( ! class_exists( 'WP_Roles' ) ) {
  920. return;
  921. }
  922. if ( ! isset( $wp_roles ) ) {
  923. $wp_roles = new WP_Roles(); // @codingStandardsIgnoreLine
  924. }
  925. $capabilities = self::get_core_capabilities();
  926. foreach ( $capabilities as $cap_group ) {
  927. foreach ( $cap_group as $cap ) {
  928. $wp_roles->remove_cap( 'shop_manager', $cap );
  929. $wp_roles->remove_cap( 'administrator', $cap );
  930. }
  931. }
  932. remove_role( 'customer' );
  933. remove_role( 'shop_manager' );
  934. }
  935. /**
  936. * Create files/directories.
  937. */
  938. private static function create_files() {
  939. // Bypass if filesystem is read-only and/or non-standard upload system is used.
  940. if ( apply_filters( 'woocommerce_install_skip_create_files', false ) ) {
  941. return;
  942. }
  943. // Install files and folders for uploading files and prevent hotlinking.
  944. $upload_dir = wp_upload_dir();
  945. $download_method = get_option( 'woocommerce_file_download_method', 'force' );
  946. $files = array(
  947. array(
  948. 'base' => $upload_dir['basedir'] . '/woocommerce_uploads',
  949. 'file' => 'index.html',
  950. 'content' => '',
  951. ),
  952. array(
  953. 'base' => WC_LOG_DIR,
  954. 'file' => '.htaccess',
  955. 'content' => 'deny from all',
  956. ),
  957. array(
  958. 'base' => WC_LOG_DIR,
  959. 'file' => 'index.html',
  960. 'content' => '',
  961. ),
  962. );
  963. if ( 'redirect' !== $download_method ) {
  964. $files[] = array(
  965. 'base' => $upload_dir['basedir'] . '/woocommerce_uploads',
  966. 'file' => '.htaccess',
  967. 'content' => 'deny from all',
  968. );
  969. }
  970. foreach ( $files as $file ) {
  971. if ( wp_mkdir_p( $file['base'] ) && ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) ) {
  972. $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'w' ); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
  973. if ( $file_handle ) {
  974. fwrite( $file_handle, $file['content'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
  975. fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
  976. }
  977. }
  978. }
  979. }
  980. /**
  981. * Show action links on the plugin screen.
  982. *
  983. * @param mixed $links Plugin Action links.
  984. * @return array
  985. */
  986. public static function plugin_action_links( $links ) {
  987. $action_links = array(
  988. 'settings' => '<a href="' . admin_url( 'admin.php?page=wc-settings' ) . '" aria-label="' . esc_attr__( 'View WooCommerce settings', 'woocommerce' ) . '">' . esc_html__( 'Settings', 'woocommerce' ) . '</a>',
  989. );
  990. return array_merge( $action_links, $links );
  991. }
  992. /**
  993. * Show row meta on the plugin screen.
  994. *
  995. * @param mixed $links Plugin Row Meta.
  996. * @param mixed $file Plugin Base file.
  997. * @return array
  998. */
  999. public static function plugin_row_meta( $links, $file ) {
  1000. if ( WC_PLUGIN_BASENAME === $file ) {
  1001. $row_meta = array(
  1002. 'docs' => '<a href="' . esc_url( apply_filters( 'woocommerce_docs_url', 'https://docs.woocommerce.com/documentation/plugins/woocommerce/' ) ) . '" aria-label="' . esc_attr__( 'View WooCommerce documentation', 'woocommerce' ) . '">' . esc_html__( 'Docs', 'woocommerce' ) . '</a>',
  1003. 'apidocs' => '<a href="' . esc_url( apply_filters( 'woocommerce_apidocs_url', 'https://docs.woocommerce.com/wc-apidocs/' ) ) . '" aria-label="' . esc_attr__( 'View WooCommerce API docs', 'woocommerce' ) . '">' . esc_html__( 'API docs', 'woocommerce' ) . '</a>',
  1004. 'support' => '<a href="' . esc_url( apply_filters( 'woocommerce_support_url', 'https://woocommerce.com/my-account/tickets/' ) ) . '" aria-label="' . esc_attr__( 'Visit premium customer support', 'woocommerce' ) . '">' . esc_html__( 'Premium support', 'woocommerce' ) . '</a>',
  1005. );
  1006. return array_merge( $links, $row_meta );
  1007. }
  1008. return (array) $links;
  1009. }
  1010. /**
  1011. * Get slug from path and associate it with the path.
  1012. *
  1013. * @param array $plugins Associative array of plugin files to paths.
  1014. * @param string $key Plugin relative path. Example: woocommerce/woocommerce.php.
  1015. */
  1016. private static function associate_plugin_file( $plugins, $key ) {
  1017. $path = explode( '/', $key );
  1018. $filename = end( $path );
  1019. $plugins[ $filename ] = $key;
  1020. return $plugins;
  1021. }
  1022. /**
  1023. * Install a plugin from .org in the background via a cron job (used by
  1024. * installer - opt in).
  1025. *
  1026. * @param string $plugin_to_install_id Plugin ID.
  1027. * @param array $plugin_to_install Plugin information.
  1028. * @throws Exception If unable to proceed with plugin installation.
  1029. * @since 2.6.0
  1030. */
  1031. public static function background_installer( $plugin_to_install_id, $plugin_to_install ) {
  1032. // Explicitly clear the event.
  1033. wp_clear_scheduled_hook( 'woocommerce_plugin_background_installer', func_get_args() );
  1034. if ( ! empty( $plugin_to_install['repo-slug'] ) ) {
  1035. require_once ABSPATH . 'wp-admin/includes/file.php';
  1036. require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
  1037. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  1038. require_once ABSPATH . 'wp-admin/includes/plugin.php';
  1039. WP_Filesystem();
  1040. $skin = new Automatic_Upgrader_Skin();
  1041. $upgrader = new WP_Upgrader( $skin );
  1042. $installed_plugins = array_reduce( array_keys( get_plugins() ), array( __CLASS__, 'associate_plugin_file' ), array() );
  1043. $plugin_slug = $plugin_to_install['repo-slug'];
  1044. $plugin_file = isset( $plugin_to_install['file'] ) ? $plugin_to_install['file'] : $plugin_slug . '.php';
  1045. $installed = false;
  1046. $activate = false;
  1047. // See if the plugin is installed already.
  1048. if ( isset( $installed_plugins[ $plugin_file ] ) ) {
  1049. $installed = true;
  1050. $activate = ! is_plugin_active( $installed_plugins[ $plugin_file ] );
  1051. }
  1052. // Install this thing!
  1053. if ( ! $installed ) {
  1054. // Suppress feedback.
  1055. ob_start();
  1056. try {
  1057. $plugin_information = plugins_api(
  1058. 'plugin_information',
  1059. array(
  1060. 'slug' => $plugin_slug,
  1061. 'fields' => array(
  1062. 'short_description' => false,
  1063. 'sections' => false,
  1064. 'requires' => false,
  1065. 'rating' => false,
  1066. 'ratings' => false,
  1067. 'downloaded' => false,
  1068. 'last_updated' => false,
  1069. 'added' => false,
  1070. 'tags' => false,
  1071. 'homepage' => false,
  1072. 'donate_link' => false,
  1073. 'author_profile' => false,
  1074. 'author' => false,
  1075. ),
  1076. )
  1077. );
  1078. if ( is_wp_error( $plugin_information ) ) {
  1079. throw new Exception( $plugin_information->get_error_message() );
  1080. }
  1081. $package = $plugin_information->download_link;
  1082. $download = $upgrader->download_package( $package );
  1083. if ( is_wp_error( $download ) ) {
  1084. throw new Exception( $download->get_error_message() );
  1085. }
  1086. $working_dir = $upgrader->unpack_package( $download, true );
  1087. if ( is_wp_error( $working_dir ) ) {
  1088. throw new Exception( $working_dir->get_error_message() );
  1089. }
  1090. $result = $upgrader->install_package(
  1091. array(
  1092. 'source' => $working_dir,
  1093. 'destination' => WP_PLUGIN_DIR,
  1094. 'clear_destination' => false,
  1095. 'abort_if_destination_exists' => false,
  1096. 'clear_working' => true,
  1097. 'hook_extra' => array(
  1098. 'type' => 'plugin',
  1099. 'action' => 'install',
  1100. ),
  1101. )
  1102. );
  1103. if ( is_wp_error( $result ) ) {
  1104. throw new Exception( $result->get_error_message() );
  1105. }
  1106. $activate = true;
  1107. } catch ( Exception $e ) {
  1108. WC_Admin_Notices::add_custom_notice(
  1109. $plugin_to_install_id . '_install_error',
  1110. sprintf(
  1111. // translators: 1: plugin name, 2: error message, 3: URL to install plugin manually.
  1112. __( '%1$s could not be installed (%2$s). <a href="%3$s">Please install it manually by clicking here.</a>', 'woocommerce' ),
  1113. $plugin_to_install['name'],
  1114. $e->getMessage(),
  1115. esc_url( admin_url( 'index.php?wc-install-plugin-redirect=' . $plugin_slug ) )
  1116. )
  1117. );
  1118. }
  1119. // Discard feedback.
  1120. ob_end_clean();
  1121. }
  1122. wp_clean_plugins_cache();
  1123. // Activate this thing.
  1124. if ( $activate ) {
  1125. try {
  1126. add_action( 'add_option_mailchimp_woocommerce_plugin_do_activation_redirect', array( __CLASS__, 'remove_mailchimps_redirect' ), 10, 2 );
  1127. $result = activate_plugin( $installed ? $installed_plugins[ $plugin_file ] : $plugin_slug . '/' . $plugin_file );
  1128. if ( is_wp_error( $result ) ) {
  1129. throw new Exception( $result->get_error_message() );
  1130. }
  1131. } catch ( Exception $e ) {
  1132. WC_Admin_Notices::add_custom_notice(
  1133. $plugin_to_install_id . '_install_error',
  1134. sprintf(
  1135. // translators: 1: plugin name, 2: URL to WP plugin page.
  1136. __( '%1$s was installed but could not be activated. <a href="%2$s">Please activate it manually by clicking here.</a>', 'woocommerce' ),
  1137. $plugin_to_install['name'],
  1138. admin_url( 'plugins.php' )
  1139. )
  1140. );
  1141. }
  1142. }
  1143. }
  1144. }
  1145. /**
  1146. * Removes redirect added during MailChimp plugin's activation.
  1147. *
  1148. * @param string $option Option name.
  1149. * @param string $value Option value.
  1150. */
  1151. public static function remove_mailchimps_redirect( $option, $value ) {
  1152. // Remove this action to prevent infinite looping.
  1153. remove_action( 'add_option_mailchimp_woocommerce_plugin_do_activation_redirect', array( __CLASS__, 'remove_mailchimps_redirect' ) );
  1154. // Update redirect back to false.
  1155. update_option( 'mailchimp_woocommerce_plugin_do_activation_redirect', false );
  1156. }
  1157. /**
  1158. * Install a theme from .org in the background via a cron job (used by installer - opt in).
  1159. *
  1160. * @param string $theme_slug Theme slug.
  1161. * @throws Exception If unable to proceed with theme installation.
  1162. * @since 3.1.0
  1163. */
  1164. public static function theme_background_installer( $theme_slug ) {
  1165. // Explicitly clear the event.
  1166. wp_clear_scheduled_hook( 'woocommerce_theme_background_installer', func_get_args() );
  1167. if ( ! empty( $theme_slug ) ) {
  1168. // Suppress feedback.
  1169. ob_start();
  1170. try {
  1171. $theme = wp_get_theme( $theme_slug );
  1172. if ( ! $theme->exists() ) {
  1173. require_once ABSPATH . 'wp-admin/includes/file.php';
  1174. include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  1175. include_once ABSPATH . 'wp-admin/includes/theme.php';
  1176. WP_Filesystem();
  1177. $skin = new Automatic_Upgrader_Skin();
  1178. $upgrader = new Theme_Upgrader( $skin );
  1179. $api = themes_api(
  1180. 'theme_information', array(
  1181. 'slug' => $theme_slug,
  1182. 'fields' => array( 'sections' => false ),
  1183. )
  1184. );
  1185. $result = $upgrader->install( $api->download_link );
  1186. if ( is_wp_error( $result ) ) {
  1187. throw new Exception( $result->get_error_message() );
  1188. } elseif ( is_wp_error( $skin->result ) ) {
  1189. throw new Exception( $skin->result->get_error_message() );
  1190. } elseif ( is_null( $result ) ) {
  1191. throw new Exception( 'Unable to connect to the filesystem. Please confirm your credentials.' );
  1192. }
  1193. }
  1194. switch_theme( $theme_slug );
  1195. } catch ( Exception $e ) {
  1196. WC_Admin_Notices::add_custom_notice(
  1197. $theme_slug . '_install_error',
  1198. sprintf(
  1199. // translators: 1: theme slug, 2: error message, 3: URL to install theme manually.
  1200. __( '%1$s could not be installed (%2$s). <a href="%3$s">Please install it manually by clicking here.</a>', 'woocommerce' ),
  1201. $theme_slug,
  1202. $e->getMessage(),
  1203. esc_url( admin_url( 'update.php?action=install-theme&theme=' . $theme_slug . '&_wpnonce=' . wp_create_nonce( 'install-theme_' . $theme_slug ) ) )
  1204. )
  1205. );
  1206. }
  1207. // Discard feedback.
  1208. ob_end_clean();
  1209. }
  1210. }
  1211. }
  1212. WC_Install::init();