PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 7.6.0-rc.2
WooCommerce v7.6.0-rc.2
10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / admin / class-wc-admin-addons.php
woocommerce / includes / admin Last commit date
helper 3 years ago importers 3 years ago list-tables 3 years ago marketplace-suggestions 3 years ago meta-boxes 3 years ago notes 3 years ago plugin-updates 5 years ago reports 3 years ago settings 3 years ago views 3 years ago class-wc-admin-addons.php 3 years ago class-wc-admin-api-keys-table-list.php 6 years ago class-wc-admin-api-keys.php 6 years ago class-wc-admin-assets.php 3 years ago class-wc-admin-attributes.php 3 years ago class-wc-admin-customize.php 5 years ago class-wc-admin-dashboard-setup.php 3 years ago class-wc-admin-dashboard.php 3 years ago class-wc-admin-duplicate-product.php 5 years ago class-wc-admin-exporters.php 3 years ago class-wc-admin-help.php 4 years ago class-wc-admin-importers.php 3 years ago class-wc-admin-log-table-list.php 5 years ago class-wc-admin-menus.php 3 years ago class-wc-admin-meta-boxes.php 3 years ago class-wc-admin-notices.php 3 years ago class-wc-admin-permalink-settings.php 5 years ago class-wc-admin-pointers.php 3 years ago class-wc-admin-post-types.php 3 years ago class-wc-admin-profile.php 4 years ago class-wc-admin-reports.php 5 years ago class-wc-admin-settings.php 3 years ago class-wc-admin-setup-wizard.php 4 years ago class-wc-admin-status.php 3 years ago class-wc-admin-taxonomies.php 3 years ago class-wc-admin-webhooks-table-list.php 4 years ago class-wc-admin-webhooks.php 3 years ago class-wc-admin.php 4 years ago wc-admin-functions.php 3 years ago wc-meta-box-functions.php 3 years ago
class-wc-admin-addons.php
1549 lines
1 <?php
2 /**
3 * Addons Page
4 *
5 * @package WooCommerce\Admin
6 * @version 2.5.0
7 */
8
9 use Automattic\Jetpack\Constants;
10 use Automattic\WooCommerce\Admin\RemoteInboxNotifications as PromotionRuleEngine;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * WC_Admin_Addons Class.
18 */
19 class WC_Admin_Addons {
20
21 /**
22 * Get featured for the addons screen
23 *
24 * @deprecated 5.9.0 No longer used in In-App Marketplace
25 *
26 * @return array of objects
27 */
28 public static function get_featured() {
29 $locale = get_user_locale();
30 $featured = self::get_locale_data_from_transient( 'wc_addons_featured_2', $locale );
31 if ( false === $featured ) {
32 $headers = array();
33 $auth = WC_Helper_Options::get( 'auth' );
34
35 if ( ! empty( $auth['access_token'] ) ) {
36 $headers['Authorization'] = 'Bearer ' . $auth['access_token'];
37 }
38
39 $raw_featured = wp_safe_remote_get(
40 'https://woocommerce.com/wp-json/wccom-extensions/1.0/featured',
41 array(
42 'headers' => $headers,
43 'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
44 )
45 );
46
47 if ( ! is_wp_error( $raw_featured ) ) {
48 $featured = json_decode( wp_remote_retrieve_body( $raw_featured ) );
49 if ( $featured ) {
50 self::set_locale_data_in_transient( 'wc_addons_featured_2', $featured, $locale, DAY_IN_SECONDS );
51 }
52 }
53 }
54
55 if ( is_object( $featured ) ) {
56 self::output_featured_sections( $featured->sections );
57 return $featured;
58 }
59 }
60
61 /**
62 * Render featured products and banners using WCCOM's the Featured 2.0 Endpoint
63 *
64 * @return void
65 */
66 public static function render_featured() {
67 $locale = get_user_locale();
68 $featured = self::get_locale_data_from_transient( 'wc_addons_featured', $locale );
69 if ( false === $featured ) {
70 $headers = array();
71 $auth = WC_Helper_Options::get( 'auth' );
72
73 if ( ! empty( $auth['access_token'] ) ) {
74 $headers['Authorization'] = 'Bearer ' . $auth['access_token'];
75 }
76
77 $parameter_string = '?' . http_build_query( array( 'locale' => get_user_locale() ) );
78 $country = WC()->countries->get_base_country();
79 if ( ! empty( $country ) ) {
80 $parameter_string = $parameter_string . '&' . http_build_query( array( 'country' => $country ) );
81 }
82
83 // Important: WCCOM Extensions API v2.0 is used.
84 $raw_featured = wp_safe_remote_get(
85 'https://woocommerce.com/wp-json/wccom-extensions/2.0/featured' . $parameter_string,
86 array(
87 'headers' => $headers,
88 'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
89 )
90 );
91
92 if ( is_wp_error( $raw_featured ) ) {
93 do_action( 'woocommerce_page_wc-addons_connection_error', $raw_featured->get_error_message() );
94
95 $message = self::is_ssl_error( $raw_featured->get_error_message() )
96 ? __( 'We encountered an SSL error. Please ensure your site supports TLS version 1.2 or above.', 'woocommerce' )
97 : $raw_featured->get_error_message();
98
99 self::output_empty( $message );
100
101 return;
102 }
103
104 $response_code = (int) wp_remote_retrieve_response_code( $raw_featured );
105 if ( 200 !== $response_code ) {
106 do_action( 'woocommerce_page_wc-addons_connection_error', $response_code );
107
108 /* translators: %d: HTTP error code. */
109 $message = sprintf(
110 esc_html(
111 /* translators: Error code */
112 __(
113 'Our request to the featured API got error code %d.',
114 'woocommerce'
115 )
116 ),
117 $response_code
118 );
119
120 self::output_empty( $message );
121
122 return;
123 }
124
125 $featured = json_decode( wp_remote_retrieve_body( $raw_featured ) );
126 if ( empty( $featured ) || ! is_array( $featured ) ) {
127 do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' );
128 $message = __( 'Our request to the featured API got a malformed response.', 'woocommerce' );
129 self::output_empty( $message );
130
131 return;
132 }
133
134 if ( $featured ) {
135 self::set_locale_data_in_transient( 'wc_addons_featured', $featured, $locale, DAY_IN_SECONDS );
136 }
137 }
138
139 self::output_featured( $featured );
140 }
141
142 /**
143 * Check if the error is due to an SSL error
144 *
145 * @param string $error_message Error message.
146 *
147 * @return bool True if SSL error, false otherwise
148 */
149 public static function is_ssl_error( $error_message ) {
150 return false !== stripos( $error_message, 'cURL error 35' );
151 }
152
153 /**
154 * Build url parameter string
155 *
156 * @param string $category Addon (sub) category.
157 * @param string $term Search terms.
158 * @param string $country Store country.
159 *
160 * @return string url parameter string
161 */
162 public static function build_parameter_string( $category, $term, $country ) {
163
164 $parameters = array(
165 'category' => $category,
166 'term' => $term,
167 'country' => $country,
168 'locale' => get_user_locale(),
169 );
170
171 return '?' . http_build_query( $parameters );
172 }
173
174 /**
175 * Call API to get extensions
176 *
177 * @param string $category Addon (sub) category.
178 * @param string $term Search terms.
179 * @param string $country Store country.
180 *
181 * @return object|WP_Error Object with products and promotions properties, or WP_Error
182 */
183 public static function get_extension_data( $category, $term, $country ) {
184 $parameters = self::build_parameter_string( $category, $term, $country );
185
186 $headers = array();
187 $auth = WC_Helper_Options::get( 'auth' );
188
189 if ( ! empty( $auth['access_token'] ) ) {
190 $headers['Authorization'] = 'Bearer ' . $auth['access_token'];
191 }
192
193 $raw_extensions = wp_safe_remote_get(
194 'https://woocommerce.com/wp-json/wccom-extensions/1.0/search' . $parameters,
195 array(
196 'headers' => $headers,
197 'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
198 )
199 );
200
201 if ( is_wp_error( $raw_extensions ) ) {
202 do_action( 'woocommerce_page_wc-addons_connection_error', $raw_extensions->get_error_message() );
203 return $raw_extensions;
204 }
205
206 $response_code = (int) wp_remote_retrieve_response_code( $raw_extensions );
207 if ( 200 !== $response_code ) {
208 do_action( 'woocommerce_page_wc-addons_connection_error', $response_code );
209 return new WP_Error(
210 'error',
211 sprintf(
212 esc_html(
213 /* translators: Error code */
214 __( 'Our request to the search API got response code %s.', 'woocommerce' )
215 ),
216 $response_code
217 )
218 );
219 }
220
221 $addons = json_decode( wp_remote_retrieve_body( $raw_extensions ) );
222
223 if ( ! is_object( $addons ) || ! isset( $addons->products ) ) {
224 do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' );
225 return new WP_Error( 'error', __( 'Our request to the search API got a malformed response.', 'woocommerce' ) );
226 }
227 return $addons;
228 }
229
230 /**
231 * Get sections for the addons screen
232 *
233 * @return array of objects
234 */
235 public static function get_sections() {
236 $locale = get_user_locale();
237 $addon_sections = self::get_locale_data_from_transient( 'wc_addons_sections', $locale );
238 if ( false === ( $addon_sections ) ) {
239 $parameter_string = '?' . http_build_query( array( 'locale' => get_user_locale() ) );
240 $raw_sections = wp_safe_remote_get(
241 'https://woocommerce.com/wp-json/wccom-extensions/1.0/categories' . $parameter_string,
242 array(
243 'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
244 )
245 );
246 if ( ! is_wp_error( $raw_sections ) ) {
247 $addon_sections = json_decode( wp_remote_retrieve_body( $raw_sections ) );
248 if ( $addon_sections ) {
249 self::set_locale_data_in_transient( 'wc_addons_sections', $addon_sections, $locale, WEEK_IN_SECONDS );
250 }
251 }
252 }
253 return apply_filters( 'woocommerce_addons_sections', $addon_sections );
254 }
255
256 /**
257 * Get section for the addons screen.
258 *
259 * @param string $section_id Required section ID.
260 *
261 * @return object|bool
262 */
263 public static function get_section( $section_id ) {
264 $sections = self::get_sections();
265 if ( isset( $sections[ $section_id ] ) ) {
266 return $sections[ $section_id ];
267 }
268 return false;
269 }
270
271
272 /**
273 * Get section content for the addons screen.
274 *
275 * @deprecated 5.9.0 No longer used in In-App Marketplace
276 *
277 * @param string $section_id Required section ID.
278 *
279 * @return array
280 */
281 public static function get_section_data( $section_id ) {
282 $section = self::get_section( $section_id );
283 $section_data = '';
284
285 if ( ! empty( $section->endpoint ) ) {
286 $section_data = get_transient( 'wc_addons_section_' . $section_id );
287 if ( false === $section_data ) {
288 $raw_section = wp_safe_remote_get(
289 esc_url_raw( $section->endpoint ),
290 array(
291 'user-agent' => 'WooCommerce/' . WC()->version . '; ' . get_bloginfo( 'url' ),
292 )
293 );
294
295 if ( ! is_wp_error( $raw_section ) ) {
296 $section_data = json_decode( wp_remote_retrieve_body( $raw_section ) );
297
298 if ( ! empty( $section_data->products ) ) {
299 set_transient( 'wc_addons_section_' . $section_id, $section_data, WEEK_IN_SECONDS );
300 }
301 }
302 }
303 }
304
305 return apply_filters( 'woocommerce_addons_section_data', $section_data->products, $section_id );
306 }
307
308 /**
309 * Handles the outputting of a contextually aware Storefront link (points to child themes if Storefront is already active).
310 *
311 * @deprecated 5.9.0 No longer used in In-App Marketplace
312 */
313 public static function output_storefront_button() {
314 $template = get_option( 'template' );
315 $stylesheet = get_option( 'stylesheet' );
316
317 if ( 'storefront' === $template ) {
318 if ( 'storefront' === $stylesheet ) {
319 $url = 'https://woocommerce.com/product-category/themes/storefront-child-theme-themes/';
320 $text = __( 'Need a fresh look? Try Storefront child themes', 'woocommerce' );
321 $utm_content = 'nostorefrontchildtheme';
322 } else {
323 $url = 'https://woocommerce.com/product-category/themes/storefront-child-theme-themes/';
324 $text = __( 'View more Storefront child themes', 'woocommerce' );
325 $utm_content = 'hasstorefrontchildtheme';
326 }
327 } else {
328 $url = 'https://woocommerce.com/storefront/';
329 $text = __( 'Need a theme? Try Storefront', 'woocommerce' );
330 $utm_content = 'nostorefront';
331 }
332
333 $url = add_query_arg(
334 array(
335 'utm_source' => 'addons',
336 'utm_medium' => 'product',
337 'utm_campaign' => 'woocommerceplugin',
338 'utm_content' => $utm_content,
339 ),
340 $url
341 );
342
343 echo '<a href="' . esc_url( $url ) . '" class="add-new-h2">' . esc_html( $text ) . '</a>' . "\n";
344 }
345
346 /**
347 * Handles the outputting of a banner block.
348 *
349 * @deprecated 5.9.0 No longer used in In-App Marketplace
350 *
351 * @param object $block Banner data.
352 */
353 public static function output_banner_block( $block ) {
354 ?>
355 <div class="addons-banner-block">
356 <h1><?php echo esc_html( $block->title ); ?></h1>
357 <p><?php echo esc_html( $block->description ); ?></p>
358 <div class="addons-banner-block-items">
359 <?php foreach ( $block->items as $item ) : ?>
360 <?php if ( self::show_extension( $item ) ) : ?>
361 <div class="addons-banner-block-item">
362 <div class="addons-banner-block-item-icon">
363 <img class="addons-img" src="<?php echo esc_url( $item->image ); ?>" />
364 </div>
365 <div class="addons-banner-block-item-content">
366 <h3><?php echo esc_html( $item->title ); ?></h3>
367 <p><?php echo esc_html( $item->description ); ?></p>
368 <?php
369 self::output_button(
370 $item->href,
371 $item->button,
372 'addons-button-solid',
373 $item->plugin
374 );
375 ?>
376 </div>
377 </div>
378 <?php endif; ?>
379 <?php endforeach; ?>
380 </div>
381 </div>
382 <?php
383 }
384
385 /**
386 * Handles the outputting of a column.
387 *
388 * @deprecated 5.9.0 No longer used in In-App Marketplace
389 *
390 * @param object $block Column data.
391 */
392 public static function output_column( $block ) {
393 if ( isset( $block->container ) && 'column_container_start' === $block->container ) {
394 ?>
395 <div class="addons-column-section">
396 <?php
397 }
398 if ( 'column_start' === $block->module ) {
399 ?>
400 <div class="addons-column">
401 <?php
402 } else {
403 ?>
404 </div>
405 <?php
406 }
407 if ( isset( $block->container ) && 'column_container_end' === $block->container ) {
408 ?>
409 </div>
410 <?php
411 }
412 }
413
414 /**
415 * Handles the outputting of a column block.
416 *
417 * @deprecated 5.9.0 No longer used in In-App Marketplace
418 *
419 * @param object $block Column block data.
420 */
421 public static function output_column_block( $block ) {
422 ?>
423 <div class="addons-column-block">
424 <h1><?php echo esc_html( $block->title ); ?></h1>
425 <p><?php echo esc_html( $block->description ); ?></p>
426 <?php foreach ( $block->items as $item ) : ?>
427 <?php if ( self::show_extension( $item ) ) : ?>
428 <div class="addons-column-block-item">
429 <div class="addons-column-block-item-icon">
430 <img class="addons-img" src="<?php echo esc_url( $item->image ); ?>" />
431 </div>
432 <div class="addons-column-block-item-content">
433 <h2><?php echo esc_html( $item->title ); ?></h2>
434 <?php
435 self::output_button(
436 $item->href,
437 $item->button,
438 'addons-button-solid',
439 $item->plugin
440 );
441 ?>
442 <p><?php echo esc_html( $item->description ); ?></p>
443 </div>
444 </div>
445 <?php endif; ?>
446 <?php endforeach; ?>
447 </div>
448
449 <?php
450 }
451
452 /**
453 * Handles the outputting of a small light block.
454 *
455 * @deprecated 5.9.0 No longer used in In-App Marketplace
456 *
457 * @param object $block Block data.
458 */
459 public static function output_small_light_block( $block ) {
460 ?>
461 <div class="addons-small-light-block">
462 <img class="addons-img" src="<?php echo esc_url( $block->image ); ?>" />
463 <div class="addons-small-light-block-content">
464 <h1><?php echo esc_html( $block->title ); ?></h1>
465 <p><?php echo esc_html( $block->description ); ?></p>
466 <div class="addons-small-light-block-buttons">
467 <?php foreach ( $block->buttons as $button ) : ?>
468 <?php
469 self::output_button(
470 $button->href,
471 $button->text,
472 'addons-button-solid'
473 );
474 ?>
475 <?php endforeach; ?>
476 </div>
477 </div>
478 </div>
479 <?php
480 }
481
482 /**
483 * Handles the outputting of a small dark block.
484 *
485 * @deprecated 5.9.0 No longer used in In-App Marketplace
486 *
487 * @param object $block Block data.
488 */
489 public static function output_small_dark_block( $block ) {
490 ?>
491 <div class="addons-small-dark-block">
492 <h1><?php echo esc_html( $block->title ); ?></h1>
493 <p><?php echo esc_html( $block->description ); ?></p>
494 <div class="addons-small-dark-items">
495 <?php foreach ( $block->items as $item ) : ?>
496 <div class="addons-small-dark-item">
497 <?php if ( ! empty( $item->image ) ) : ?>
498 <div class="addons-small-dark-item-icon">
499 <img class="addons-img" src="<?php echo esc_url( $item->image ); ?>" />
500 </div>
501 <?php endif; ?>
502 <?php
503 self::output_button(
504 $item->href,
505 $item->button,
506 'addons-button-outline-white'
507 );
508 ?>
509 </div>
510 <?php endforeach; ?>
511 </div>
512 </div>
513 <?php
514 }
515
516 /**
517 * Handles the outputting of the WooCommerce Services banner block.
518 *
519 * @deprecated 5.9.0 No longer used in In-App Marketplace
520 *
521 * @param object $block Block data.
522 */
523 public static function output_wcs_banner_block( $block = array() ) {
524 $is_active = is_plugin_active( 'woocommerce-services/woocommerce-services.php' );
525 $location = wc_get_base_location();
526
527 if (
528 ! in_array( $location['country'], array( 'US' ), true ) ||
529 $is_active ||
530 ! current_user_can( 'install_plugins' ) ||
531 ! current_user_can( 'activate_plugins' )
532 ) {
533 return;
534 }
535
536 $button_url = wp_nonce_url(
537 add_query_arg(
538 array(
539 'install-addon' => 'woocommerce-services',
540 )
541 ),
542 'install-addon_woocommerce-services'
543 );
544
545 $defaults = array(
546 'image' => WC()->plugin_url() . '/assets/images/wcs-extensions-banner-3x.jpg',
547 'image_alt' => __( 'WooCommerce Shipping', 'woocommerce' ),
548 'title' => __( 'Save time and money with WooCommerce Shipping', 'woocommerce' ),
549 'description' => __( 'Print discounted USPS and DHL labels straight from your WooCommerce dashboard and save on shipping.', 'woocommerce' ),
550 'button' => __( 'Free - Install now', 'woocommerce' ),
551 'href' => $button_url,
552 'logos' => array(),
553 );
554
555 switch ( $location['country'] ) {
556 case 'US':
557 $local_defaults = array(
558 'logos' => array_merge(
559 $defaults['logos'],
560 array(
561 array(
562 'link' => WC()->plugin_url() . '/assets/images/wcs-usps-logo.png',
563 'alt' => 'USPS logo',
564 ),
565 array(
566 'link' => WC()->plugin_url() . '/assets/images/wcs-dhlexpress-logo.png',
567 'alt' => 'DHL Express logo',
568 ),
569 )
570 ),
571 );
572 break;
573 default:
574 $local_defaults = array();
575 }
576
577 $block_data = array_merge( $defaults, $local_defaults, $block );
578 ?>
579 <div class="addons-wcs-banner-block">
580 <div class="addons-wcs-banner-block-image is-full-image">
581 <img
582 class="addons-img"
583 src="<?php echo esc_url( $block_data['image'] ); ?>"
584 alt="<?php echo esc_attr( $block_data['image_alt'] ); ?>"
585 />
586 </div>
587 <div class="addons-wcs-banner-block-content">
588 <h1><?php echo esc_html( $block_data['title'] ); ?></h1>
589 <p><?php echo esc_html( $block_data['description'] ); ?></p>
590 <ul class="wcs-logos-container">
591 <?php foreach ( $block_data['logos'] as $logo ) : ?>
592 <li>
593 <img
594 alt="<?php echo esc_attr( $logo['alt'] ); ?>"
595 class="wcs-service-logo"
596 src="<?php echo esc_url( $logo['link'] ); ?>"
597 >
598 </li>
599 <?php endforeach; ?>
600 </ul>
601 <?php
602 self::output_button(
603 $block_data['href'],
604 $block_data['button'],
605 'addons-button-outline-purple'
606 );
607 ?>
608 </div>
609 </div>
610 <?php
611 }
612
613 /**
614 * Handles the outputting of the WooCommerce Pay banner block.
615 *
616 * @deprecated 5.9.0 No longer used in In-App Marketplace
617 *
618 * @param object $block Block data.
619 */
620 public static function output_wcpay_banner_block( $block = array() ) {
621 $is_active = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' );
622 $location = wc_get_base_location();
623
624 if (
625 ! in_array( $location['country'], array( 'US' ), true ) ||
626 $is_active ||
627 ! current_user_can( 'install_plugins' ) ||
628 ! current_user_can( 'activate_plugins' )
629 ) {
630 return;
631 }
632
633 $button_url = wp_nonce_url(
634 add_query_arg(
635 array(
636 'install-addon' => 'woocommerce-payments',
637 )
638 ),
639 'install-addon_woocommerce-payments'
640 );
641
642 $defaults = array(
643 'image' => WC()->plugin_url() . '/assets/images/wcpayments-icon-secure.png',
644 'image_alt' => __( 'WooCommerce Payments', 'woocommerce' ),
645 'title' => __( 'Payments made simple, with no monthly fees &mdash; exclusively for WooCommerce stores.', 'woocommerce' ),
646 'description' => __( 'Securely accept cards in your store. See payments, track cash flow into your bank account, and stay on top of disputes – right from your dashboard.', 'woocommerce' ),
647 'button' => __( 'Free - Install now', 'woocommerce' ),
648 'href' => $button_url,
649 'logos' => array(),
650 );
651
652 $block_data = array_merge( $defaults, $block );
653 ?>
654 <div class="addons-wcs-banner-block">
655 <div class="addons-wcs-banner-block-image">
656 <img
657 class="addons-img"
658 src="<?php echo esc_url( $block_data['image'] ); ?>"
659 alt="<?php echo esc_attr( $block_data['image_alt'] ); ?>"
660 />
661 </div>
662 <div class="addons-wcs-banner-block-content">
663 <h1><?php echo esc_html( $block_data['title'] ); ?></h1>
664 <p><?php echo esc_html( $block_data['description'] ); ?></p>
665 <?php
666 self::output_button(
667 $block_data['href'],
668 $block_data['button'],
669 'addons-button-outline-purple'
670 );
671 ?>
672 </div>
673 </div>
674 <?php
675 }
676
677
678 /**
679 * Output the HTML for the promotion block.
680 *
681 * @param array $promotion Array of promotion block data.
682 * @return void
683 */
684 public static function output_search_promotion_block( array $promotion ) {
685 ?>
686 <div class="addons-wcs-banner-block">
687 <div class="addons-wcs-banner-block-image">
688 <img
689 class="addons-img"
690 src="<?php echo esc_url( $promotion['image'] ); ?>"
691 alt="<?php echo esc_attr( $promotion['image_alt'] ); ?>"
692 />
693 </div>
694 <div class="addons-wcs-banner-block-content">
695 <h1><?php echo esc_html( $promotion['title'] ); ?></h1>
696 <p><?php echo esc_html( $promotion['description'] ); ?></p>
697 <?php
698 if ( ! empty( $promotion['actions'] ) ) {
699 foreach ( $promotion['actions'] as $action ) {
700 self::output_promotion_action( $action );
701 }
702 }
703 ?>
704 </div>
705 </div>
706 <?php
707 }
708
709
710 /**
711 * Handles the output of a full-width block.
712 *
713 * @deprecated 5.9.0 No longer used in In-App Marketplace
714 *
715 * @param array $section Section data.
716 */
717 public static function output_promotion_block( $section ) {
718 if (
719 ! current_user_can( 'install_plugins' ) ||
720 ! current_user_can( 'activate_plugins' )
721 ) {
722 return;
723 }
724
725 $section_object = (object) $section;
726
727 if ( ! empty( $section_object->geowhitelist ) ) {
728 $section_object->geowhitelist = explode( ',', $section_object->geowhitelist );
729 }
730
731 if ( ! empty( $section_object->geoblacklist ) ) {
732 $section_object->geoblacklist = explode( ',', $section_object->geoblacklist );
733 }
734
735 if ( ! self::show_extension( $section_object ) ) {
736 return;
737 }
738
739 ?>
740 <div class="addons-banner-block addons-promotion-block">
741 <img
742 class="addons-img"
743 src="<?php echo esc_url( $section['image'] ); ?>"
744 alt="<?php echo esc_attr( $section['image_alt'] ); ?>"
745 />
746 <div class="addons-promotion-block-content">
747 <h1 class="addons-promotion-block-title"><?php echo esc_html( $section['title'] ); ?></h1>
748 <div class="addons-promotion-block-description">
749 <?php echo wp_kses_post( $section['description'] ); ?>
750 </div>
751 <div class="addons-promotion-block-buttons">
752 <?php
753 if ( $section['button_1'] ) {
754 self::output_button(
755 $section['button_1_href'],
756 $section['button_1'],
757 'addons-button-expandable addons-button-solid',
758 $section['plugin']
759 );
760 }
761
762 if ( $section['button_2'] ) {
763 self::output_button(
764 $section['button_2_href'],
765 $section['button_2'],
766 'addons-button-expandable addons-button-outline-purple',
767 $section['plugin']
768 );
769 }
770 ?>
771 </div>
772 </div>
773 </div>
774 <?php
775 }
776
777 /**
778 * Handles the outputting of featured sections
779 *
780 * @param array $sections Section data.
781 */
782 public static function output_featured_sections( $sections ) {
783 foreach ( $sections as $section ) {
784 switch ( $section->module ) {
785 case 'banner_block':
786 self::output_banner_block( $section );
787 break;
788 case 'column_start':
789 self::output_column( $section );
790 break;
791 case 'column_end':
792 self::output_column( $section );
793 break;
794 case 'column_block':
795 self::output_column_block( $section );
796 break;
797 case 'small_light_block':
798 self::output_small_light_block( $section );
799 break;
800 case 'small_dark_block':
801 self::output_small_dark_block( $section );
802 break;
803 case 'wcs_banner_block':
804 self::output_wcs_banner_block( (array) $section );
805 break;
806 case 'wcpay_banner_block':
807 self::output_wcpay_banner_block( (array) $section );
808 break;
809 case 'promotion_block':
810 self::output_promotion_block( (array) $section );
811 break;
812 }
813 }
814 }
815
816 /**
817 * Handles the outputting of featured page
818 *
819 * @param array $blocks Featured page's blocks.
820 */
821 private static function output_featured( $blocks ) {
822 foreach ( $blocks as $block ) {
823 $block_type = $block->type ?? null;
824 switch ( $block_type ) {
825 case 'group':
826 self::output_group( $block );
827 break;
828 case 'banner':
829 self::output_banner( $block );
830 break;
831 }
832 }
833 }
834
835 /**
836 * Render a group block including products
837 *
838 * @param mixed $block Block of the page for rendering.
839 *
840 * @return void
841 */
842 private static function output_group( $block ) {
843 $capacity = $block->capacity ?? 3;
844 $product_list_classes = 3 === $capacity ? 'three-column' : 'two-column';
845 $product_list_classes = 'products addons-products-' . $product_list_classes;
846 ?>
847 <section class="addon-product-group">
848 <h2 class="addon-product-group-title"><?php echo esc_html( $block->title ); ?></h2>
849 <div class="addon-product-group-description-container">
850 <?php if ( ! empty( $block->description ) ) : ?>
851 <div class="addon-product-group-description">
852 <?php echo esc_html( $block->description ); ?>
853 </div>
854 <?php endif; ?>
855 <?php if ( null !== $block->url ) : ?>
856 <a class="addon-product-group-see-more" href="<?php echo esc_url( $block->url ); ?>">
857 <?php esc_html_e( 'See more', 'woocommerce' ); ?>
858 </a>
859 <?php endif; ?>
860 </div>
861 <div class="addon-product-group__items">
862 <ul class="<?php echo esc_attr( $product_list_classes ); ?>">
863 <?php
864 $products = array_slice( $block->items, 0, $capacity );
865 foreach ( $products as $item ) {
866 self::render_product_card( $item );
867 }
868 ?>
869 </ul>
870 <div>
871 </section>
872 <?php
873 }
874
875 /**
876 * Render a banner contains a product
877 *
878 * @param mixed $block Block of the page for rendering.
879 *
880 * @return void
881 */
882 private static function output_banner( $block ) {
883 if ( empty( $block->buttons ) ) {
884 // Render a product-like banner.
885 ?>
886 <ul class="products">
887 <?php self::render_product_card( $block, $block->type ); ?>
888 </ul>
889 <?php
890 } else {
891 // Render a banner with buttons.
892 ?>
893 <ul class="products">
894 <li class="product addons-buttons-banner">
895 <div class="addons-buttons-banner-image"
896 style="background-image:url(<?php echo esc_url( $block->image ); ?>)"
897 title="<?php echo esc_attr( $block->image_alt ); ?>"></div>
898 <div class="product-details addons-buttons-banner-details-container">
899 <div class="addons-buttons-banner-details">
900 <h2><?php echo esc_html( $block->title ); ?></h2>
901 <p><?php echo wp_kses( $block->description, array() ); ?></p>
902 </div>
903 <div class="addons-buttons-banner-button-container">
904 <?php
905 foreach ( $block->buttons as $button ) {
906 $button_classes = array( 'button', 'addons-buttons-banner-button' );
907 $type = $button->type ?? null;
908 if ( 'primary' === $type ) {
909 $button_classes[] = 'addons-buttons-banner-button-primary';
910 }
911 ?>
912 <a class="<?php echo esc_attr( implode( ' ', $button_classes ) ); ?>"
913 href="<?php echo esc_url( $button->href ); ?>">
914 <?php echo esc_html( $button->title ); ?>
915 </a>
916 <?php } ?>
917 </div>
918 </div>
919 </li>
920 </ul>
921 <?php
922 }
923 }
924
925 /**
926 * Returns in-app-purchase URL params.
927 */
928 public static function get_in_app_purchase_url_params() {
929 // Get url (from path onward) for the current page,
930 // so WCCOM "back" link returns user to where they were.
931 $back_admin_path = add_query_arg( array() );
932 return array(
933 'wccom-site' => site_url(),
934 'wccom-back' => rawurlencode( $back_admin_path ),
935 'wccom-woo-version' => Constants::get_constant( 'WC_VERSION' ),
936 'wccom-connect-nonce' => wp_create_nonce( 'connect' ),
937 );
938 }
939
940 /**
941 * Add in-app-purchase URL params to link.
942 *
943 * Adds various url parameters to a url to support a streamlined
944 * flow for obtaining and setting up WooCommerce extensons.
945 *
946 * @param string $url Destination URL.
947 */
948 public static function add_in_app_purchase_url_params( $url ) {
949 return add_query_arg(
950 self::get_in_app_purchase_url_params(),
951 $url
952 );
953 }
954
955 /**
956 * Outputs a button.
957 *
958 * @param string $url Destination URL.
959 * @param string $text Button label text.
960 * @param string $style Button style class.
961 * @param string $plugin The plugin the button is promoting.
962 */
963 public static function output_button( $url, $text, $style, $plugin = '' ) {
964 $style = __( 'Free', 'woocommerce' ) === $text ? 'addons-button-outline-purple' : $style;
965 $style = is_plugin_active( $plugin ) ? 'addons-button-installed' : $style;
966 $text = is_plugin_active( $plugin ) ? __( 'Installed', 'woocommerce' ) : $text;
967 $url = self::add_in_app_purchase_url_params( $url );
968 ?>
969 <a
970 class="addons-button <?php echo esc_attr( $style ); ?>"
971 href="<?php echo esc_url( $url ); ?>">
972 <?php echo esc_html( $text ); ?>
973 </a>
974 <?php
975 }
976
977 /**
978 * Output HTML for a promotion action.
979 *
980 * @param array $action Array of action properties.
981 *
982 * @return void
983 */
984 public static function output_promotion_action( array $action ) {
985 if ( empty( $action ) ) {
986 return;
987 }
988 $style = ( ! empty( $action['primary'] ) && $action['primary'] ) ? 'addons-button-solid' : 'addons-button-outline-purple';
989 ?>
990 <a
991 class="addons-button <?php echo esc_attr( $style ); ?>"
992 href="<?php echo esc_url( $action['url'] ); ?>">
993 <?php echo esc_html( $action['label'] ); ?>
994 </a>
995 <?php
996 }
997
998 /**
999 * Output HTML for a promotion action if data couldn't be fetched.
1000 *
1001 * @param string $message Error message.
1002 *
1003 * @return void
1004 */
1005 public static function output_empty( $message = '' ) {
1006 ?>
1007 <div class="wc-addons__empty">
1008 <h2><?php echo wp_kses_post( __( 'Oh no! We\'re having trouble connecting to the extensions catalog right now.', 'woocommerce' ) ); ?></h2>
1009 <?php if ( ! empty( $message ) ) : ?>
1010 <p><?php echo esc_html( $message ); ?></p>
1011 <?php endif; ?>
1012 <p>
1013 <?php
1014 printf(
1015 wp_kses_post(
1016 /* translators: a url */
1017 __(
1018 'To start growing your business, head over to <a href="%s">WooCommerce.com</a>, where you\'ll find the most popular WooCommerce extensions.',
1019 'woocommerce'
1020 )
1021 ),
1022 'https://woocommerce.com/products/?utm_source=extensionsscreen&utm_medium=product&utm_campaign=connectionerror'
1023 );
1024 ?>
1025 </p>
1026 </div>
1027 <?php
1028 }
1029
1030
1031 /**
1032 * Handles output of the addons page in admin.
1033 */
1034 public static function output() {
1035 $section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : '_featured';
1036 $search = isset( $_GET['search'] ) ? sanitize_text_field( wp_unslash( $_GET['search'] ) ) : '';
1037
1038 if ( isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
1039 do_action( 'woocommerce_helper_output' );
1040 return;
1041 }
1042
1043 if ( isset( $_GET['install-addon'] ) ) {
1044 switch ( $_GET['install-addon'] ) {
1045 case 'woocommerce-services':
1046 self::install_woocommerce_services_addon();
1047 break;
1048 case 'woocommerce-payments':
1049 self::install_woocommerce_payments_addon( $section );
1050 break;
1051 default:
1052 // Do nothing.
1053 break;
1054 }
1055 }
1056
1057 $sections = self::get_sections();
1058 $theme = wp_get_theme();
1059 $current_section = isset( $_GET['section'] ) ? $section : '_featured';
1060 $promotions = array();
1061 $addons = array();
1062
1063 if ( '_featured' !== $current_section ) {
1064 $category = $section ? $section : null;
1065 $term = $search ? $search : null;
1066 $country = WC()->countries->get_base_country();
1067 $extension_data = self::get_extension_data( $category, $term, $country );
1068 $addons = is_wp_error( $extension_data ) ? $extension_data : $extension_data->products;
1069 $promotions = ! empty( $extension_data->promotions ) ? $extension_data->promotions : array();
1070 }
1071
1072 // We need Automattic\WooCommerce\Admin\RemoteInboxNotifications for the next part, if not remove all promotions.
1073 if ( ! WC()->is_wc_admin_active() ) {
1074 $promotions = array();
1075 }
1076 // Check for existence of promotions and evaluate out if we should show them.
1077 if ( ! empty( $promotions ) ) {
1078 foreach ( $promotions as $promo_id => $promotion ) {
1079 $evaluator = new PromotionRuleEngine\RuleEvaluator();
1080 $passed = $evaluator->evaluate( $promotion->rules );
1081 if ( ! $passed ) {
1082 unset( $promotions[ $promo_id ] );
1083 }
1084 }
1085 // Transform promotions to the correct format ready for output.
1086 $promotions = self::format_promotions( $promotions );
1087 }
1088
1089 /**
1090 * Addon page view.
1091 *
1092 * @uses $addons
1093 * @uses $search
1094 * @uses $sections
1095 * @uses $theme
1096 * @uses $current_section
1097 */
1098 include_once dirname( __FILE__ ) . '/views/html-admin-page-addons.php';
1099 }
1100
1101 /**
1102 * Install WooCommerce Services from Extensions screens.
1103 */
1104 public static function install_woocommerce_services_addon() {
1105 check_admin_referer( 'install-addon_woocommerce-services' );
1106
1107 $services_plugin_id = 'woocommerce-services';
1108 $services_plugin = array(
1109 'name' => __( 'WooCommerce Services', 'woocommerce' ),
1110 'repo-slug' => 'woocommerce-services',
1111 );
1112
1113 WC_Install::background_installer( $services_plugin_id, $services_plugin );
1114
1115 wp_safe_redirect( remove_query_arg( array( 'install-addon', '_wpnonce' ) ) );
1116 exit;
1117 }
1118
1119 /**
1120 * Install WooCommerce Payments from the Extensions screens.
1121 *
1122 * @param string $section Optional. Extenstions tab.
1123 *
1124 * @return void
1125 */
1126 public static function install_woocommerce_payments_addon( $section = '_featured' ) {
1127 check_admin_referer( 'install-addon_woocommerce-payments' );
1128
1129 $wcpay_plugin_id = 'woocommerce-payments';
1130 $wcpay_plugin = array(
1131 'name' => __( 'WooCommerce Payments', 'woocommerce' ),
1132 'repo-slug' => 'woocommerce-payments',
1133 );
1134
1135 WC_Install::background_installer( $wcpay_plugin_id, $wcpay_plugin );
1136
1137 do_action( 'woocommerce_addon_installed', $wcpay_plugin_id, $section );
1138
1139 wp_safe_redirect( remove_query_arg( array( 'install-addon', '_wpnonce' ) ) );
1140 exit;
1141 }
1142
1143 /**
1144 * We're displaying page=wc-addons and page=wc-addons&section=helper as two separate pages.
1145 * When we're on those pages, add body classes to distinguishe them.
1146 *
1147 * @param string $admin_body_class Unfiltered body class.
1148 *
1149 * @return string Body class with added class for Marketplace or My Subscriptions page.
1150 */
1151 public static function filter_admin_body_classes( string $admin_body_class = '' ): string {
1152 if ( isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
1153 return " $admin_body_class woocommerce-page-wc-subscriptions ";
1154 }
1155
1156 return " $admin_body_class woocommerce-page-wc-marketplace ";
1157 }
1158
1159 /**
1160 * Determine which class should be used for a rating star:
1161 * - golden
1162 * - half-filled (50/50 golden and gray)
1163 * - gray
1164 *
1165 * Consider ratings from 3.0 to 4.0 as an example
1166 * 3.0 will produce 3 stars
1167 * 3.1 to 3.5 will produce 3 stars and a half star
1168 * 3.6 to 4.0 will product 4 stars
1169 *
1170 * @param float $rating Rating of a product.
1171 * @param int $index Index of a star in a row.
1172 *
1173 * @return string CSS class to use.
1174 */
1175 public static function get_star_class( $rating, $index ) {
1176 if ( $rating >= $index ) {
1177 // Rating more that current star to show.
1178 return 'fill';
1179 } elseif (
1180 abs( $index - 1 - floor( $rating ) ) < 0.0000001 &&
1181 0 < ( $rating - floor( $rating ) )
1182 ) {
1183 // For rating more than x.0 and less than x.5 or equal it will show a half star.
1184 return 50 >= floor( ( $rating - floor( $rating ) ) * 100 )
1185 ? 'half-fill'
1186 : 'fill';
1187 }
1188
1189 // Don't show a golden star otherwise.
1190 return 'no-fill';
1191 }
1192
1193 /**
1194 * Take an action object and return the URL based on properties of the action.
1195 *
1196 * @param object $action Action object.
1197 * @return string URL.
1198 */
1199 public static function get_action_url( $action ): string {
1200 if ( ! isset( $action->url ) ) {
1201 return '';
1202 }
1203
1204 if ( isset( $action->url_is_admin_query ) && $action->url_is_admin_query ) {
1205 return wc_admin_url( $action->url );
1206 }
1207
1208 if ( isset( $action->url_is_admin_nonce_query ) && $action->url_is_admin_nonce_query ) {
1209 if ( empty( $action->nonce ) ) {
1210 return '';
1211 }
1212 return wp_nonce_url(
1213 admin_url( $action->url ),
1214 $action->nonce
1215 );
1216 }
1217
1218 return $action->url;
1219 }
1220
1221 /**
1222 * Format the promotion data ready for display, ie fetch locales and actions.
1223 *
1224 * @param array $promotions Array of promotoin objects.
1225 * @return array Array of formatted promotions ready for output.
1226 */
1227 public static function format_promotions( array $promotions ): array {
1228 $formatted_promotions = array();
1229 foreach ( $promotions as $promotion ) {
1230 // Get the matching locale or fall back to en-US.
1231 $locale = PromotionRuleEngine\SpecRunner::get_locale( $promotion->locales );
1232 if ( null === $locale ) {
1233 continue;
1234 }
1235
1236 $promotion_actions = array();
1237 if ( ! empty( $promotion->actions ) ) {
1238 foreach ( $promotion->actions as $action ) {
1239 $action_locale = PromotionRuleEngine\SpecRunner::get_action_locale( $action->locales );
1240 $url = self::get_action_url( $action );
1241
1242 $promotion_actions[] = array(
1243 'name' => $action->name,
1244 'label' => $action_locale->label,
1245 'url' => $url,
1246 'primary' => isset( $action->is_primary ) ? $action->is_primary : false,
1247 );
1248 }
1249 }
1250
1251 $formatted_promotions[] = array(
1252 'title' => $locale->title,
1253 'description' => $locale->description,
1254 'image' => ( 'http' === substr( $locale->image, 0, 4 ) ) ? $locale->image : WC()->plugin_url() . $locale->image,
1255 'image_alt' => $locale->image_alt,
1256 'actions' => $promotion_actions,
1257 );
1258 }
1259 return $formatted_promotions;
1260 }
1261
1262 /**
1263 * Map data from different endpoints to a universal format
1264 *
1265 * Search and featured products has a slightly different products' field names.
1266 * Mapping converts different data structures into a universal one for further processing.
1267 *
1268 * @param mixed $data Product Card Data.
1269 *
1270 * @return object Converted data.
1271 */
1272 public static function map_product_card_data( $data ) {
1273 $mapped = (object) null;
1274
1275 $type = $data->type ?? null;
1276
1277 // Icon.
1278 $mapped->icon = $data->icon ?? null;
1279 if ( null === $mapped->icon && 'banner' === $type ) {
1280 // For product-related banners icon is a product's image.
1281 $mapped->icon = $data->image ?? null;
1282 }
1283
1284 // URL.
1285 $mapped->url = $data->link ?? null;
1286 if ( empty( $mapped->url ) ) {
1287 $mapped->url = $data->url ?? null;
1288 }
1289
1290 // Title.
1291 $mapped->title = $data->title ?? null;
1292
1293 // Vendor Name.
1294 $mapped->vendor_name = $data->vendor_name ?? null;
1295 if ( empty( $mapped->vendor_name ) ) {
1296 $mapped->vendor_name = $data->vendorName ?? null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1297 }
1298
1299 // Vendor URL.
1300 $mapped->vendor_url = $data->vendor_url ?? null;
1301 if ( empty( $mapped->vendor_url ) ) {
1302 $mapped->vendor_url = $data->vendorUrl ?? null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1303 }
1304
1305 // Description.
1306 $mapped->description = $data->excerpt ?? null;
1307 if ( empty( $mapped->description ) ) {
1308 $mapped->description = $data->description ?? null;
1309 }
1310
1311 $has_currency = ! empty( $data->currency ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1312
1313 // Is Free.
1314 if ( $has_currency ) {
1315 $mapped->is_free = 0 === (int) $data->price;
1316 } else {
1317 $mapped->is_free = '&#36;0.00' === $data->price;
1318 }
1319
1320 // Price.
1321 if ( $has_currency ) {
1322 $mapped->price = wc_price( $data->price, array( 'currency' => $data->currency ) );
1323 } else {
1324 $mapped->price = $data->price;
1325 }
1326
1327 // Price suffix, e.g. "per month".
1328 $mapped->price_suffix = $data->price_suffix ?? null;
1329
1330 // Rating.
1331 $mapped->rating = $data->rating ?? null;
1332 if ( null === $mapped->rating ) {
1333 $mapped->rating = $data->averageRating ?? null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1334 }
1335
1336 // Reviews Count.
1337 $mapped->reviews_count = $data->reviews_count ?? null;
1338 if ( null === $mapped->reviews_count ) {
1339 $mapped->reviews_count = $data->reviewsCount ?? null; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
1340 }
1341 // Featured & Promoted product card.
1342 // Label.
1343 $mapped->label = $data->label ?? null;
1344 // Primary color.
1345 $mapped->primary_color = $data->primary_color ?? null;
1346 // Text color.
1347 $mapped->text_color = $data->text_color ?? null;
1348 // Button text.
1349 $mapped->button = $data->button ?? null;
1350
1351 return $mapped;
1352 }
1353
1354 /**
1355 * Render a product card
1356 *
1357 * There's difference in data structure (e.g. field names) between endpoints such as search and
1358 * featured. Inner mapping helps to use universal field names for further work.
1359 *
1360 * @param mixed $data Product data.
1361 * @param string $block_type Block type that's different from the default product card, e.g. a banner.
1362 *
1363 * @return void
1364 */
1365 public static function render_product_card( $data, $block_type = null ) {
1366 $mapped = self::map_product_card_data( $data );
1367 $product_url = self::add_in_app_purchase_url_params( $mapped->url );
1368 $class_names = array( 'product' );
1369 // Specify a class name according to $block_type (if it's specified).
1370 if ( null !== $block_type ) {
1371 $class_names[] = 'addons-product-' . $block_type;
1372 }
1373
1374 $product_details_classes = 'product-details';
1375 if ( 'banner' === $block_type ) {
1376 $product_details_classes .= ' addon-product-banner-details';
1377 }
1378
1379 if ( isset( $mapped->label ) && 'promoted' === $mapped->label ) {
1380 $product_details_classes .= ' promoted';
1381 } elseif ( isset( $mapped->label ) && 'featured' === $mapped->label ) {
1382 $product_details_classes .= ' featured';
1383 }
1384
1385 if ( 'promoted' === $mapped->label
1386 && ! empty( $mapped->primary_color )
1387 && ! empty( $mapped->text_color )
1388 && ! empty( $mapped->button ) ) {
1389 // Promoted product card.
1390 ?>
1391 <li class="product">
1392 <div class="<?php echo esc_attr( $product_details_classes ); ?>" style="border-top: 5px solid <?php echo esc_html( $mapped->primary_color ); ?>;">
1393 <span class="label promoted"><?php esc_attr_e( 'Promoted', 'woocommerce' ); ?></span>
1394 <a href="<?php echo esc_url( $product_url ); ?>">
1395 <h2><?php echo esc_html( $mapped->title ); ?></h2>
1396 </a>
1397 <p><?php echo wp_kses_post( $mapped->description ); ?></p>
1398 </div>
1399 <div class="product-footer-promoted">
1400 <span class="icon"><img src="<?php echo esc_url( $mapped->icon ); ?>" /></span>
1401 <a class="addons-button addons-button-promoted" style="background: <?php echo esc_html( $mapped->primary_color ); ?>; color: <?php echo esc_html( $mapped->text_color ); ?>;" href="<?php echo esc_url( $product_url ); ?>">
1402 <?php echo esc_html( $mapped->button ); ?>
1403 </a>
1404 </div>
1405 </li>
1406 <?php
1407 } else {
1408 // Normal or "featured" product card.
1409 ?>
1410 <li class="<?php echo esc_attr( implode( ' ', $class_names ) ); ?>">
1411 <div class="<?php echo esc_attr( $product_details_classes ); ?>">
1412 <div class="product-text-container">
1413 <?php if ( isset( $mapped->label ) && 'featured' === $mapped->label ) { ?>
1414 <span class="label featured"><?php esc_attr_e( 'Featured', 'woocommerce' ); ?></span>
1415 <?php } ?>
1416 <a href="<?php echo esc_url( $product_url ); ?>">
1417 <h2><?php echo esc_html( $mapped->title ); ?></h2>
1418 </a>
1419 <?php if ( ! empty( $mapped->vendor_name ) && ! empty( $mapped->vendor_url ) ) : ?>
1420 <div class="product-developed-by">
1421 <?php
1422 $vendor_url = add_query_arg(
1423 array(
1424 'utm_source' => 'extensionsscreen',
1425 'utm_medium' => 'product',
1426 'utm_campaign' => 'wcaddons',
1427 'utm_content' => 'devpartner',
1428 ),
1429 $mapped->vendor_url
1430 );
1431
1432 printf(
1433 /* translators: %s vendor link */
1434 esc_html__( 'Developed by %s', 'woocommerce' ),
1435 sprintf(
1436 '<a class="product-vendor-link" href="%1$s" target="_blank">%2$s</a>',
1437 esc_url_raw( $vendor_url ),
1438 esc_html( $mapped->vendor_name )
1439 )
1440 );
1441 ?>
1442 </div>
1443 <?php endif; ?>
1444 <p><?php echo wp_kses_post( $mapped->description ); ?></p>
1445 </div>
1446 <?php if ( ! empty( $mapped->icon ) ) : ?>
1447 <span class="product-img-wrap">
1448 <?php /* Show an icon if it exists */ ?>
1449 <img src="<?php echo esc_url( $mapped->icon ); ?>" />
1450 </span>
1451 <?php endif; ?>
1452 </div>
1453 <div class="product-footer">
1454 <div class="product-price-and-reviews-container">
1455 <div class="product-price-block">
1456 <?php if ( $mapped->is_free ) : ?>
1457 <span class="price"><?php esc_html_e( 'Free', 'woocommerce' ); ?></span>
1458 <?php else : ?>
1459 <span class="price">
1460 <?php
1461 echo wp_kses(
1462 $mapped->price,
1463 array(
1464 'span' => array(
1465 'class' => array(),
1466 ),
1467 'bdi' => array(),
1468 )
1469 );
1470 ?>
1471 </span>
1472 <span class="price-suffix">
1473 <?php
1474 $price_suffix = __( 'per year', 'woocommerce' );
1475 if ( ! empty( $mapped->price_suffix ) ) {
1476 $price_suffix = $mapped->price_suffix;
1477 }
1478 echo esc_html( $price_suffix );
1479 ?>
1480 </span>
1481 <?php endif; ?>
1482 </div>
1483 <?php if ( ! empty( $mapped->reviews_count ) && ! empty( $mapped->rating ) ) : ?>
1484 <?php /* Show rating and the number of reviews */ ?>
1485 <div class="product-reviews-block">
1486 <?php for ( $index = 1; $index <= 5; ++$index ) : ?>
1487 <?php $rating_star_class = 'product-rating-star product-rating-star__' . self::get_star_class( $mapped->rating, $index ); ?>
1488 <div class="<?php echo esc_attr( $rating_star_class ); ?>"></div>
1489 <?php endfor; ?>
1490 <span class="product-reviews-count">(<?php echo (int) $mapped->reviews_count; ?>)</span>
1491 </div>
1492 <?php endif; ?>
1493 </div>
1494 <a class="button" href="<?php echo esc_url( $product_url ); ?>">
1495 <?php esc_html_e( 'View details', 'woocommerce' ); ?>
1496 </a>
1497 </div>
1498 </li>
1499 <?php
1500 }
1501 }
1502
1503 /**
1504 * Retrieves the locale data from a transient.
1505 *
1506 * Transient value is an array of locale data in the following format:
1507 * array(
1508 * 'en_US' => ...,
1509 * 'fr_FR' => ...,
1510 * )
1511 *
1512 * If the transient does not exist, does not have a value, or has expired,
1513 * then the return value will be false.
1514 *
1515 * @param string $transient Transient name. Expected to not be SQL-escaped.
1516 * @param string $locale Locale to retrieve.
1517 * @return mixed Value of transient.
1518 */
1519 private static function get_locale_data_from_transient( $transient, $locale ) {
1520 $transient_value = get_transient( $transient );
1521 $transient_value = is_array( $transient_value ) ? $transient_value : array();
1522 return $transient_value[ $locale ] ?? false;
1523 }
1524
1525 /**
1526 * Sets the locale data in a transient.
1527 *
1528 * Transient value is an array of locale data in the following format:
1529 * array(
1530 * 'en_US' => ...,
1531 * 'fr_FR' => ...,
1532 * )
1533 *
1534 * @param string $transient Transient name. Expected to not be SQL-escaped.
1535 * Must be 172 characters or fewer in length.
1536 * @param mixed $value Transient value. Must be serializable if non-scalar.
1537 * Expected to not be SQL-escaped.
1538 * @param string $locale Locale to set.
1539 * @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration).
1540 * @return bool True if the value was set, false otherwise.
1541 */
1542 private static function set_locale_data_in_transient( $transient, $value, $locale, $expiration = 0 ) {
1543 $transient_value = get_transient( $transient );
1544 $transient_value = is_array( $transient_value ) ? $transient_value : array();
1545 $transient_value[ $locale ] = $value;
1546 return set_transient( $transient, $transient_value, $expiration );
1547 }
1548 }
1549