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 / Elementor / ElementorIntegration.php
hostinger-reach / src / Integrations / Elementor Last commit date
ElementorIntegration.php 2 months ago SubscriptionFormElementorWidget.php 3 months ago
ElementorIntegration.php
550 lines
1 <?php
2
3 namespace Hostinger\Reach\Integrations\Elementor;
4
5 use Elementor\Core\Documents_Manager;
6 use Elementor\Plugin as ElementorPlugin;
7 use Elementor\Utils;
8 use Elementor\Widget_Base;
9 use ElementorPro\Modules\Forms\Classes\Form_Record;
10 use ElementorPro\Modules\Forms\Submissions\Database\Entities\Form_Snapshot;
11 use ElementorPro\Modules\Forms\Submissions\Database\Query;
12 use ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository;
13 use Exception;
14 use Hostinger\Reach\Dto\PluginData;
15 use Hostinger\Reach\Dto\ReachContact;
16 use Hostinger\Reach\Functions;
17 use Hostinger\Reach\Integrations\IntegrationInterface;
18 use Hostinger\Reach\Integrations\IntegrationWithForms;
19 use WP_Post;
20
21 if ( ! DEFINED( 'ABSPATH' ) ) {
22 exit;
23 }
24
25 class ElementorIntegration extends IntegrationWithForms implements IntegrationInterface {
26
27 public const INTEGRATION_NAME = 'elementor';
28 public const AUTOLOAD_META_KEY = 'hostinger_reach_add_elementor_widget';
29 public const ADD_BLOCK_QUERY_ARG = 'hostinger_reach_add_block';
30 public const ADD_BLOCK_NONCE = 'hostinger_reach_add_block';
31 public const EDITOR_SCROLL_SCRIPT_HANDLE = 'hostinger-reach-elementor-editor-scroll';
32 public const EDITOR_SCROLL_SCRIPT_FILE = 'elementor-editor-scroll.js';
33
34 protected SubscriptionFormElementorWidget $widget;
35
36 public function init(): void {
37 parent::init();
38 add_action( 'hostinger_reach_integration_activated', array( $this, 'on_integration_activated' ) );
39 }
40
41 public function enqueue_editor_scroll_script(): void {
42 $script_path = HOSTINGER_REACH_PLUGIN_DIR . 'frontend/dist/' . self::EDITOR_SCROLL_SCRIPT_FILE;
43 if ( ! file_exists( $script_path ) ) {
44 return;
45 }
46
47 wp_enqueue_script(
48 self::EDITOR_SCROLL_SCRIPT_HANDLE,
49 Functions::get_frontend_url() . self::EDITOR_SCROLL_SCRIPT_FILE,
50 array(),
51 filemtime( $script_path ),
52 true
53 );
54 }
55
56 public function active_integration_hooks(): void {
57 add_action( 'transition_post_status', array( $this, 'handle_transition_post_status' ), 10, 3 );
58 add_filter( 'hostinger_reach_get_group', array( $this, 'filter_hostinger_reach_get_group' ), 10, 2 );
59 add_action( 'elementor_pro/forms/new_record', array( $this, 'handle_elementor_pro_new_record' ) );
60 add_action( 'wp_insert_post', array( $this, 'flag_new_elementor_post' ), 10, 3 );
61 add_action( 'admin_init', array( $this, 'flag_existing_elementor_post' ) );
62 add_action( 'elementor/editor/before_enqueue_scripts', array( $this, 'maybe_insert_reach_widget' ) );
63 add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'enqueue_editor_scroll_script' ) );
64 add_action( 'elementor/widgets/register', array( $this, 'register_new_widgets' ) );
65 }
66
67 public function on_integration_activated( string $integration_name ): void {
68 if ( $integration_name === self::INTEGRATION_NAME ) {
69 $this->set_elementor_onboarding();
70 $this->init_existing_forms();
71 }
72 }
73
74 public function handle_transition_post_status( string $new_status, string $old_status, WP_Post $post ): void {
75 if ( $new_status === 'publish' ) {
76 $this->set_forms( $post );
77 $this->maybe_unset_forms( $post );
78 } elseif ( $old_status === 'publish' ) {
79 $this->unset_all_forms( $post );
80 }
81 }
82
83 public function register_new_widgets(): void {
84 if ( ! class_exists( 'Elementor\Plugin' ) ) {
85 return;
86 }
87 ElementorPlugin::instance()->widgets_manager->register( $this->get_widget() );
88 }
89
90 public function filter_hostinger_reach_get_group( string $group, string $form_id ): string {
91 if ( ! empty( $group ) || ! $this->is_elementor_form_id( $form_id ) ) {
92 return $group;
93 }
94
95 try {
96 $form = $this->form_repository->get( $form_id );
97 $post = $form['post'] ?? null;
98 if ( $post ) {
99 return $post['post_title'] ?? self::INTEGRATION_NAME;
100 }
101 } catch ( Exception $e ) {
102 return self::INTEGRATION_NAME;
103 }
104
105 return self::INTEGRATION_NAME;
106 }
107
108 public static function get_name(): string {
109 return self::INTEGRATION_NAME;
110 }
111
112 public function get_form_ids( WP_Post $post ): array {
113 return array_merge( $this->get_elementor_form_ids_from_content( $post->post_content ), $this->get_elementor_form_ids_from_actions() );
114 }
115
116 public function get_plugin_data(): PluginData {
117 if ( class_exists( 'Elementor\Core\Documents_Manager' ) ) {
118 $add_form_url = add_query_arg(
119 array(
120 self::ADD_BLOCK_QUERY_ARG => '1',
121 ),
122 Documents_Manager::get_create_new_post_url()
123 );
124 } else {
125 $add_form_url = '';
126 }
127
128 return PluginData::from_array(
129 array(
130 'id' => self::INTEGRATION_NAME,
131 'folder' => 'elementor',
132 'file' => 'elementor.php',
133 'admin_url' => 'admin.php?page=elementor',
134 'add_form_url' => $add_form_url,
135 'edit_url' => 'post.php?post={post_id}&action=elementor#elementor-element-{form_id}',
136 'url' => 'https://wordpress.org/plugins/elementor/',
137 'download_url' => 'https://downloads.wordpress.org/plugin/elementor.zip',
138 'title' => __( 'Elementor', 'hostinger-reach' ),
139 'icon' => null,
140 'is_view_form_hidden' => false,
141 'is_edit_form_hidden' => false,
142 'can_toggle_forms' => true,
143 'import_enabled' => $this->is_import_supported(),
144 )
145 );
146 }
147
148 public function handle_elementor_pro_new_record( Form_Record $record ): void {
149 $email = $this->find_email( $record->get_formatted_data() );
150 if ( $email ) {
151 $form = $this->get_form_by_request();
152 $form_repository_item = $this->form_repository->get( $form->id ?? '' );
153 if ( empty( $form_repository_item ) || $form_repository_item['is_active'] === false ) {
154 return;
155 }
156
157 do_action(
158 'hostinger_reach_submit',
159 array(
160 'group' => ! empty( $form ) ? $form->name : self::INTEGRATION_NAME,
161 'email' => $email,
162 'metadata' => array(
163 'plugin' => self::INTEGRATION_NAME . '-pro',
164 'form_id' => ! empty( $form ) ? $form->id : null,
165 ),
166 )
167 );
168 }
169 }
170
171 public function flag_new_elementor_post( int $post_id, WP_Post $post, bool $update ): void {
172 if ( $update ) {
173 return;
174 }
175
176 if ( empty( $_GET[ self::ADD_BLOCK_QUERY_ARG ] ) && empty( $_GET[ self::AUTOLOAD_META_KEY ] ) ) {
177 return;
178 }
179
180 $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
181 if ( ! wp_verify_nonce( $nonce, 'elementor_action_new_post' ) ) {
182 return;
183 }
184
185 if ( ! current_user_can( 'edit_post', $post_id ) ) {
186 return;
187 }
188
189 update_post_meta( $post_id, self::AUTOLOAD_META_KEY, '1' );
190 }
191
192 public function flag_existing_elementor_post(): void {
193 if ( empty( $_GET['action'] ) || $_GET['action'] !== 'elementor' ) {
194 return;
195 }
196
197 if ( empty( $_GET[ self::ADD_BLOCK_QUERY_ARG ] ) ) {
198 return;
199 }
200
201 $post_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : 0;
202 if ( ! $post_id ) {
203 return;
204 }
205
206 $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
207 if ( ! wp_verify_nonce( $nonce, self::ADD_BLOCK_NONCE ) ) {
208 return;
209 }
210
211 if ( ! current_user_can( 'edit_post', $post_id ) ) {
212 return;
213 }
214
215 if ( ! $this->is_elementor_post( $post_id ) ) {
216 return;
217 }
218
219 update_post_meta( $post_id, self::AUTOLOAD_META_KEY, '1' );
220 }
221
222 public function is_elementor_post( int $post_id ): bool {
223 $edit_mode = get_post_meta( $post_id, '_elementor_edit_mode', true );
224
225 return $edit_mode === 'builder';
226 }
227
228 public function maybe_insert_reach_widget(): void {
229 if ( ! $this->is_elementor_installed() ) {
230 return;
231 }
232
233 $post_id = get_the_ID();
234 if ( ! $post_id ) {
235 return;
236 }
237
238 $should_add_widget = get_post_meta( $post_id, self::AUTOLOAD_META_KEY, true ) === '1';
239 if ( ! $should_add_widget ) {
240 return;
241 }
242
243 $document = ElementorPlugin::instance()->documents->get( $post_id );
244 if ( ! $document ) {
245 return;
246 }
247
248 $elements = $document->get_elements_data();
249
250 $widget_data = array(
251 array(
252 'id' => Utils::generate_random_string(),
253 'elType' => 'section',
254 'settings' => array(),
255 'elements' => array(
256 array(
257 'id' => Utils::generate_random_string(),
258 'elType' => 'column',
259 'settings' => array(
260 '_column_size' => 100,
261 ),
262 'elements' => array(
263 array(
264 'id' => Utils::generate_random_string(),
265 'elType' => 'widget',
266 'widgetType' => SubscriptionFormElementorWidget::WIDGET_NAME,
267 'settings' => array(
268 'formId' => SubscriptionFormElementorWidget::FORM_ID_PREFIX . random_int( 1, PHP_INT_MAX ),
269 'showName' => 0,
270 'showSurname' => 0,
271 ),
272 ),
273 ),
274 ),
275 ),
276 ),
277 );
278
279 $document->save( array( 'elements' => array_merge( $elements, $widget_data ) ) );
280 delete_post_meta( $post_id, self::AUTOLOAD_META_KEY );
281 }
282
283 public function is_import_supported(): bool {
284 return class_exists( 'ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository' )
285 && class_exists( 'ElementorPro\Modules\Forms\Submissions\Database\Query' );
286 }
287
288 public function get_import_summary(): array {
289 $summary = array();
290
291 if ( ! $this->is_import_supported() ) {
292 return $summary;
293 }
294
295 $forms = Form_Snapshot_Repository::instance()->all();
296 $query = Query::get_instance();
297
298 foreach ( $forms as $form ) {
299 $form_id = $form->id ?? null;
300 $post_id = $form->post_id ?? null;
301
302 if ( ! $form_id || ! $post_id ) {
303 continue;
304 }
305
306 $counts = $query->count_submissions_by_status(
307 array(
308 'form' => array(
309 'value' => $post_id . '_' . $form_id,
310 ),
311 )
312 );
313
314 $summary[ $form_id ] = array(
315 'title' => $form->name ?? $form_id,
316 'contacts' => (int) $counts->get( 'read', 0 ) + (int) $counts->get( 'unread', 0 ),
317 );
318 }
319
320 return $summary;
321 }
322
323 public function get_contacts( mixed $form_id = null, ?int $limit = 100, ?int $offset = 0 ): array {
324 if ( ! $this->is_import_supported() ) {
325 return array();
326 }
327
328 if ( empty( $form_id ) ) {
329 return array();
330 }
331
332 try {
333 $form = $this->form_repository->get( $form_id );
334 } catch ( Exception $e ) {
335 return array();
336 }
337
338 $post_id = $form['post']['ID'] ?? null;
339 $page = (int) floor( $offset / $limit ) + 1;
340 $result = Query::get_instance()->get_submissions(
341 array(
342 'page' => $page,
343 'per_page' => $limit,
344 'filters' => array(
345 'form' => array(
346 'value' => $post_id . '_' . $form_id,
347 ),
348 'status' => array(
349 'value' => 'all',
350 ),
351 ),
352 'with_meta' => true,
353 'with_form_fields' => false,
354 )
355 );
356
357 $submissions = $result['data'] ?? array();
358 $entries = array();
359
360 foreach ( $submissions as $submission ) {
361 $email = $this->find_email( array( $submission['main']['value'] ?? '' ) );
362
363 if ( ! $email ) {
364 $email = $this->find_email( array_column( $submission['values'], 'value' ) );
365 }
366
367 if ( ! $email ) {
368 continue;
369 }
370
371 $contact = new ReachContact(
372 $email,
373 '',
374 '',
375 array(
376 'plugin' => self::INTEGRATION_NAME,
377 'form_id' => $form_id,
378 'group' => $form->name ?? self::INTEGRATION_NAME,
379 )
380 );
381
382 $entries[] = $contact->to_array();
383 }
384
385 return $entries;
386 }
387
388 public function init_existing_forms(): void {
389 $post_ids = $this->get_elementor_posts();
390 foreach ( $post_ids as $post_id ) {
391 $this->update_forms_from_post( $post_id );
392 }
393 }
394
395 public function set_elementor_onboarding(): void {
396 update_option( 'elementor_onboarded', 1 );
397 }
398
399 private function get_widget(): ?Widget_Base {
400 if ( ! $this->is_elementor_installed() ) {
401 return null;
402 }
403 return new SubscriptionFormElementorWidget();
404 }
405
406 private function update_forms_from_post( int $post_id ): void {
407 $form_ids = $this->get_forms_from_post_id( $post_id );
408 $this->update_form_repository( $form_ids, $post_id );
409 }
410
411 private function set_forms( WP_Post $post ): void {
412 $elementor_reach_forms = $this->get_elementor_form_ids_from_content( $post->post_content );
413 $elementor_pro_forms = $this->get_elementor_form_ids_from_actions();
414 $form_ids = array_merge( $elementor_reach_forms, $elementor_pro_forms );
415
416 $this->update_form_repository( $form_ids, $post->ID );
417 }
418
419 private function update_form_repository( array $form_ids, int $post_id ): void {
420 if ( empty( $form_ids ) ) {
421 update_option( Functions::HOSTINGER_REACH_HAS_FORMS, true );
422 }
423 foreach ( $form_ids as $form_id ) {
424 $form = array(
425 'form_id' => $form_id,
426 'type' => self::INTEGRATION_NAME,
427 );
428
429 if ( $this->form_repository->exists( $form_id ) ) {
430 $this->form_repository->update( $form );
431 } else {
432 $this->form_repository->insert( array_merge( $form, array( 'post_id' => $post_id ) ) );
433 }
434 }
435 }
436
437 private function get_elementor_form_ids_from_content( string $content ): array {
438 if ( ! $this->is_elementor_installed() ) {
439 return array();
440 }
441 $form_ids = array();
442 $pattern = '/<form id="' . SubscriptionFormElementorWidget::FORM_ID_PREFIX . '(\d+)"/';
443 preg_match_all( $pattern, $content, $matches );
444 foreach ( $matches[1] as $form_id ) {
445 $form_ids[] = SubscriptionFormElementorWidget::FORM_ID_PREFIX . $form_id;
446 }
447
448 return $form_ids;
449 }
450
451 private function get_elementor_form_ids_from_actions(): array {
452 $form_ids = array();
453 $actions = json_decode( wp_unslash( $_POST['actions'] ?? '' ), true );
454 $status = $actions['save_builder']['data']['status'] ?? 'draft';
455 $elements = $actions['save_builder']['data']['elements'] ?? array();
456
457 if ( ! empty( $elements ) && $status === 'publish' ) {
458 foreach ( $elements as $element ) {
459 $form_ids = array_merge( $form_ids, $this->find_form_widget( $element ) );
460 }
461 }
462
463 return $form_ids;
464 }
465
466 private function find_form_widget( array $element ): array {
467 $form_ids = array();
468
469 if ( isset( $element['widgetType'] ) && $element['widgetType'] === 'form' && isset( $element['id'] ) ) {
470 $form_ids[] = $element['id'];
471 }
472
473 if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) {
474 foreach ( $element['elements'] as $nested_element ) {
475 $form_ids = array_merge( $form_ids, $this->find_form_widget( $nested_element ) );
476 }
477 }
478
479 return $form_ids;
480 }
481
482 private function is_elementor_form_id( string $form_id ): bool {
483 if ( ! $this->is_elementor_installed() ) {
484 return false;
485 }
486 return str_starts_with( $form_id, SubscriptionFormElementorWidget::FORM_ID_PREFIX );
487 }
488
489 private function find_email( array $data ): string {
490 foreach ( $data as $value ) {
491 $sanitized_value = sanitize_email( $value );
492 if ( filter_var( $sanitized_value, FILTER_VALIDATE_EMAIL ) ) {
493 return $value;
494 }
495 }
496
497 return '';
498 }
499
500 private function get_form_by_request(): ?Form_Snapshot {
501 $post_id = sanitize_text_field( $_POST['post_id'] ?? '' );
502 $form_id = sanitize_text_field( $_POST['form_id'] ?? '' );
503
504 return Form_Snapshot_Repository::instance()->find( $post_id, $form_id );
505 }
506
507 private function get_forms_from_post_id( int $post_id ): array {
508 $form_ids = array();
509 $elementor_metadata = get_post_meta( $post_id, '_elementor_data', true );
510 if ( ! is_string( $elementor_metadata ) || $elementor_metadata === '' ) {
511 return $form_ids;
512 }
513
514 $elementor_metadata = json_decode( $elementor_metadata, true );
515 if ( ! is_array( $elementor_metadata ) ) {
516 return $form_ids;
517 }
518
519 foreach ( $elementor_metadata as $element ) {
520 $form_ids = array_merge( $form_ids, $this->find_form_widget( $element ) );
521 }
522
523 return $form_ids;
524 }
525
526 private function get_elementor_posts(): array {
527 $args = array(
528 'post_type' => array( 'page', 'post', 'product', 'elementor_library' ),
529 'post_status' => 'publish',
530 'posts_per_page' => 100,
531 'fields' => 'ids',
532 'meta_query' => array(
533 array(
534 'key' => '_elementor_data',
535 'value' => '"widgetType":"form"',
536 'compare' => 'LIKE',
537 ),
538 ),
539 );
540
541 $post_ids = get_posts( $args );
542
543 return $post_ids;
544 }
545
546 private function is_elementor_installed(): bool {
547 return class_exists( 'Elementor\Plugin' ) && class_exists( 'Elementor\Widget_Base' ) && class_exists( 'Elementor\Utils' );
548 }
549 }
550