PluginProbe ʕ •ᴥ•ʔ
Hostinger Reach – AI-Powered Email Marketing for WordPress / 1.5.9
Hostinger Reach – AI-Powered Email Marketing for WordPress v1.5.9
1.6.0 1.5.9 1.5.8 1.5.7 1.5.6 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 trunk 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6
hostinger-reach / src / Integrations / WooCommerce / WooCommerceIntegration.php
hostinger-reach / src / Integrations / WooCommerce Last commit date
WooCommerceIntegration.php 4 months ago
WooCommerceIntegration.php
316 lines
1 <?php
2
3 namespace Hostinger\Reach\Integrations\WooCommerce;
4
5 use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
6 use Hostinger\Reach\Api\Webhooks\Handlers\CartAbandoned;
7 use Hostinger\Reach\Api\Webhooks\Handlers\OrderPurchased;
8 use Hostinger\Reach\Integrations\IntegrationInterface;
9 use Hostinger\Reach\Integrations\IntegrationWithForms;
10 use Hostinger\Reach\Dto\PluginData;
11 use Hostinger\Reach\Repositories\FormRepository;
12 use stdClass;
13 use WC_Order;
14 use WP_Post;
15 use WP_REST_Request;
16
17 class WooCommerceIntegration extends IntegrationWithForms implements IntegrationInterface {
18 public FormRepository $form_repository;
19
20 public const INTEGRATION_NAME = 'woocommerce';
21 public const OPTIN_KEY = 'hostinger_reach_optin';
22 public const ORDER_META_OPTIN_KEY = '_wc_other/hostinger-reach/newsletter-optin';
23
24 public static function get_name(): string {
25 return self::INTEGRATION_NAME;
26 }
27
28 public function init(): void {
29 parent::init();
30 add_action( 'hostinger_reach_integration_activated', array( $this, 'set_woocommerce_onboarding_skipped' ) );
31 }
32
33 public function active_integration_hooks(): void {
34 $this->add_form();
35 $this->add_automations();
36 if ( $this->form_repository->is_form_active( self::INTEGRATION_NAME ) ) {
37 $this->init_optin_actions();
38
39 add_action( 'woocommerce_thankyou', array( $this, 'subscribe_customer_to_hostinger_reach' ) );
40 add_action( 'woocommerce_checkout_order_processed', array( $this, 'handle_optin' ) );
41 add_action( 'hostinger_reach_contact_submitted', array( $this, 'handle_submission' ) );
42 }
43 }
44
45 public function init_optin_actions(): void {
46 if ( did_action( 'woocommerce_blocks_loaded' ) ) {
47 $this->register_checkout_blocks_field();
48 } else {
49 add_action( 'woocommerce_blocks_loaded', array( $this, 'register_checkout_blocks_field' ) );
50 }
51
52 add_action(
53 'woocommerce_store_api_checkout_update_order_from_request',
54 array(
55 $this,
56 'handle_checkout_blocks_optin',
57 ),
58 10,
59 2
60 );
61 add_action( 'woocommerce_checkout_after_terms_and_conditions', array( $this, 'add_optin_checkbox' ) );
62 }
63
64
65 public function register_checkout_blocks_field(): void {
66 if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
67 return;
68 }
69
70 woocommerce_register_additional_checkout_field(
71 array(
72 'id' => 'hostinger-reach/newsletter-optin',
73 'label' => __( 'Subscribe to our newsletter', 'hostinger-reach' ),
74 'location' => 'contact',
75 'type' => 'checkbox',
76 'required' => false,
77 'attributes' => array(
78 'data-custom' => 'hostinger-reach-optin',
79 ),
80 'show_in_order_confirmation' => true,
81 )
82 );
83 }
84
85 public function add_optin_checkbox(): void {
86 $this->load_template();
87 }
88
89 public function handle_optin( int $oder_id ): void {
90 $this->set_opted_in( isset( $_POST[ self::OPTIN_KEY ] ), $oder_id );
91 }
92
93 public function handle_checkout_blocks_optin( WC_Order $order, WP_REST_Request $request ): void {
94 $newsletter_optin = false;
95
96 if ( ! $newsletter_optin && isset( $request['additional_fields']['hostinger-reach/newsletter-optin'] ) ) {
97 $newsletter_optin = (bool) $request['additional_fields']['hostinger-reach/newsletter-optin'];
98 }
99
100 $this->set_opted_in( $newsletter_optin, $order->get_id() );
101 }
102
103 public function add_form(): void {
104 $checkout_page_id = null;
105
106 if ( method_exists( $this, 'wc_get_page_id' ) ) {
107 $checkout_page_id = wc_get_page_id( 'checkout' );
108 }
109
110 if ( ! $this->form_repository->exists( self::INTEGRATION_NAME ) ) {
111 $this->form_repository->insert(
112 array(
113 'form_id' => self::INTEGRATION_NAME,
114 'type' => self::INTEGRATION_NAME,
115 'post_id' => $checkout_page_id,
116 'form_title' => __( 'Checkout', 'hostinger-reach' ),
117 )
118 );
119 }
120 }
121
122 public function add_automations(): void {
123 $this->add_automation( OrderPurchased::WEBHOOK_NAME, __( 'Purchases', 'hostinger-reach' ) );
124 $this->add_automation( CartAbandoned::WEBHOOK_NAME, __( 'Abandoned Carts', 'hostinger-reach' ) );
125 }
126
127 public function add_automation( string $automation_name, string $title ): void {
128 if ( ! $this->form_repository->exists( $automation_name ) ) {
129 $this->form_repository->insert(
130 array(
131 'form_id' => $automation_name,
132 'form_title' => $title,
133 'type' => self::INTEGRATION_NAME,
134 'post_id' => null,
135 )
136 );
137 }
138 }
139
140 public function set_woocommerce_onboarding_skipped( string $integration_name ): void {
141 if ( $integration_name === self::INTEGRATION_NAME && class_exists( 'Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile' ) ) {
142 update_option( OnboardingProfile::DATA_OPTION, array( 'skipped' => true ) );
143 }
144 }
145
146 public function handle_submission( array $data ): void {
147 $data['form_id'] = self::INTEGRATION_NAME;
148 $this->form_repository->submit( $data );
149 }
150
151 public function subscribe_customer_to_hostinger_reach( int $order_id ): void {
152 if ( ! $this->is_opted_in( $order_id ) ) {
153 return;
154 }
155
156 $order = wc_get_order( $order_id );
157 if ( ! $order ) {
158 return;
159 }
160
161 $email = $order->get_billing_email();
162 $name = $order->get_billing_first_name();
163 $surname = $order->get_billing_last_name();
164 if ( $email ) {
165 do_action(
166 'hostinger_reach_submit',
167 array(
168 'group' => self::INTEGRATION_NAME,
169 'email' => $email,
170 'name' => $name,
171 'surname' => $surname,
172 'metadata' => array(
173 'plugin' => self::INTEGRATION_NAME,
174 ),
175 )
176 );
177 }
178 }
179
180 public function get_plugin_data(): PluginData {
181 return PluginData::from_array(
182 array(
183 'id' => self::INTEGRATION_NAME,
184 'type' => self::HOSTINGER_INTEGRATION_TYPE_ECOMMERCE,
185 'folder' => 'woocommerce',
186 'file' => 'woocommerce.php',
187 'admin_url' => 'admin.php?page=wc-admin',
188 'add_form_url' => null,
189 'edit_url' => null,
190 'url' => 'https://wordpress.org/plugins/woocommerce/',
191 'download_url' => 'https://downloads.wordpress.org/plugin/woocommerce.zip',
192 'title' => __( 'WooCommerce', 'hostinger-reach' ),
193 'is_edit_form_hidden' => true,
194 'is_active' => class_exists( 'WooCommerce' ),
195 'import_enabled' => true,
196 )
197 );
198 }
199
200 public function get_form_ids( WP_Post $post ): array {
201 return array();
202 }
203
204 public function get_import_summary(): array {
205 if ( ! $this->is_woo_customer_data_available() ) {
206 return array();
207 }
208
209 return array(
210 wc_get_page_id( 'checkout' ) => array(
211 'title' => __( 'WooCommerce Customers', 'hostinger-reach' ),
212 'contacts' => $this->get_contacts_count(),
213 ),
214 );
215 }
216
217 public function get_contacts( mixed $form_id = null, ?int $limit = 100, ?int $offset = 0 ): array {
218 if ( ! $this->is_woo_customer_data_available() ) {
219 return array();
220 }
221
222 global $wpdb;
223 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
224 $table = $wpdb->prefix . 'wc_customer_lookup';
225 $sql = "SELECT email, first_name FROM {$table}";
226
227 if ( $limit > 0 ) {
228 $sql .= $wpdb->prepare( ' LIMIT %d', $limit );
229 }
230
231 if ( $offset > 0 ) {
232 $sql .= $wpdb->prepare( ' OFFSET %d', $offset );
233 }
234
235 return array_map(
236 function ( stdClass $user ) {
237 return array(
238 'email' => $user->email ?? '',
239 'name' => $user->first_name ?? '',
240 'metadata' => array(
241 'form_id' => wc_get_page_id( 'checkout' ),
242 'plugin' => self::INTEGRATION_NAME,
243 'group' => self::INTEGRATION_NAME,
244 ),
245 );
246 },
247 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
248 $wpdb->get_results( $sql )
249 );
250 }
251
252 public function get_contacts_count(): int {
253 global $wpdb;
254 if ( ! $this->is_woo_customer_data_available() ) {
255 return 0;
256 }
257
258 $table = $this->get_woocommerce_customer_table();
259 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
260 return $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" ) ?? 0;
261 }
262
263 private function set_opted_in( bool $opted_in, mixed $oder_id ): void {
264 $customer_id = get_current_user_id();
265 $order = wc_get_order( $oder_id );
266
267 if ( $customer_id > 0 ) {
268 update_user_meta( $customer_id, self::OPTIN_KEY, $opted_in ? 'yes' : 'no' );
269 } elseif ( $order ) {
270 $order->update_meta_data( self::ORDER_META_OPTIN_KEY, $opted_in ? 'yes' : 'no' );
271 $order->save();
272 }
273 }
274
275 private function is_opted_in( mixed $oder_id = false ): bool {
276 $customer_id = get_current_user_id();
277 $order = wc_get_order( $oder_id );
278 $is_opted_in = false;
279
280 if ( $customer_id > 0 ) {
281 $is_opted_in = get_user_meta( get_current_user_id(), self::OPTIN_KEY, true );
282 } elseif ( $order ) {
283 $is_opted_in = $order->get_meta( self::ORDER_META_OPTIN_KEY, true );
284 }
285
286 return $is_opted_in === 'yes';
287 }
288
289 private function get_woocommerce_customer_table(): string {
290 global $wpdb;
291 return "{$wpdb->prefix}wc_customer_lookup";
292 }
293
294 private function is_woo_customer_data_available(): bool {
295 global $wpdb;
296 $table = $this->get_woocommerce_customer_table();
297 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
298 $table_exists = $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) === $table;
299 return function_exists( 'wc_get_page_id' ) && $table_exists;
300 }
301
302 private function load_template(): void {
303 $template = 'optin-checkbox.php';
304 $template_path = 'hostinger-reach/';
305 $default_path = HOSTINGER_REACH_PLUGIN_DIR . 'templates/';
306
307 // phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- Extracting variables for use in template.
308 extract( array( 'is_opted_in' => $this->is_opted_in() ) );
309
310 $located = wc_locate_template( $template, $template_path, $default_path );
311 if ( file_exists( $located ) ) {
312 include $located;
313 }
314 }
315 }
316