Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hostinger_Reach_Integration
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 7
3540
0.00% covered (danger)
0.00%
0 / 1
 submit_contact
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 get_api
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 get_group_name
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get_subscriber_data
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 get_subscriber_data_from_fields
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
306
 has_consent
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
132
 handle_hostinger_reach_integration
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
182
1<?php
2/**
3 * Hostinger Reach Integration for Jetpack Contact Forms.
4 *
5 * @package automattic/jetpack
6 */
7
8namespace Automattic\Jetpack\Forms\Service;
9
10use Automattic\Jetpack\Forms\ContactForm\Feedback;
11
12/**
13 * Class Hostinger_Reach_Integration
14 *
15 * Scaffolding for handling integration with Hostinger Reach for Jetpack Contact Forms.
16 * Public API docs are limited; this class mirrors the structure of MailPoet_Integration
17 * and will be implemented once the Hostinger Reach plugin APIs are confirmed.
18 */
19class Hostinger_Reach_Integration {
20    /**
21     * Placeholder for a Hostinger Reach API instance, if needed later.
22     *
23     * @var mixed
24     */
25    protected static $hostinger_api = null;
26
27    /**
28     * Submit contact to Hostinger Reach.
29     *
30     * @param mixed  $api_handler     Reach API handler.
31     * @param array  $subscriber_data Associative array: email, first_name, last_name.
32     * @param string $group_name      Target group name.
33     * @return void
34     */
35    protected static function submit_contact( $api_handler, array $subscriber_data, $group_name ) {
36        if ( ! $api_handler || empty( $subscriber_data['email'] ) ) {
37            return;
38        }
39
40        $subscriber_data['group'] = $group_name;
41        $api_handler->post_contact( $subscriber_data );
42    }
43
44    /**
45     * Get the Hostinger Reach API instance.
46     *
47     * @return mixed
48     */
49    protected static function get_api() {
50        if (
51            null === self::$hostinger_api
52            // @phan-suppress-next-line PhanUndeclaredClassReference
53            && class_exists( \Hostinger\Reach\Api\Handlers\ReachApiHandler::class )
54            // @phan-suppress-next-line PhanUndeclaredClassReference
55            && class_exists( \Hostinger\Reach\Functions::class )
56            // @phan-suppress-next-line PhanUndeclaredClassReference
57            && class_exists( \Hostinger\Reach\Api\ApiKeyManager::class )
58        ) {
59            // @phan-suppress-next-line PhanUndeclaredClassMethod
60            self::$hostinger_api = new \Hostinger\Reach\Api\Handlers\ReachApiHandler(
61                // @phan-suppress-next-line PhanUndeclaredClassMethod
62                new \Hostinger\Reach\Functions(),
63                // @phan-suppress-next-line PhanUndeclaredClassMethod
64                new \Hostinger\Reach\Api\ApiKeyManager()
65            );
66        }
67
68        return self::$hostinger_api;
69    }
70
71    /**
72     * Get group name from form attributes, falling back to 'Jetpack Forms'.
73     *
74     * @param mixed $form Contact form instance.
75     * @return string
76     */
77    protected static function get_group_name( $form ) {
78        $group_name = ! empty( $form->attributes['hostingerReach']['groupName'] ?? '' )
79            ? trim( $form->attributes['hostingerReach']['groupName'] )
80            : 'Jetpack Forms';
81        return $group_name;
82    }
83
84    /**
85     * Extract subscriber data (email, first_name, last_name) using Feedback API (v2 storage).
86     *
87     * @param Feedback $feedback Feedback object for the submission.
88     * @return array Associative array with at least 'email', optionally 'first_name', 'last_name'. Empty array if no email found.
89     */
90    protected static function get_subscriber_data( $feedback ) {
91        if ( ! $feedback->get_author_email() ) {
92            return array();
93        }
94
95        $subscriber_data          = array();
96        $subscriber_data['email'] = $feedback->get_author_email();
97
98        if ( $feedback->get_field_value_by_label( 'First Name' ) ) {
99            $subscriber_data['name'] = $feedback->get_field_value_by_label( 'First Name' );
100        } elseif ( $feedback->get_field_value_by_form_field_id( 'firstname' ) ) {
101            $subscriber_data['name'] = $feedback->get_field_value_by_form_field_id( 'firstname' );
102        } elseif ( $feedback->get_field_value_by_form_field_id( 'first-name' ) ) {
103            $subscriber_data['name'] = $feedback->get_field_value_by_form_field_id( 'first-name' );
104        }
105        if ( $feedback->get_field_value_by_label( 'Last Name' ) ) {
106            $subscriber_data['surname'] = $feedback->get_field_value_by_label( 'Last Name' );
107        } elseif ( $feedback->get_field_value_by_form_field_id( 'lastname' ) ) {
108            $subscriber_data['surname'] = $feedback->get_field_value_by_form_field_id( 'lastname' );
109        } elseif ( $feedback->get_field_value_by_form_field_id( 'last-name' ) ) {
110            $subscriber_data['surname'] = $feedback->get_field_value_by_form_field_id( 'last-name' );
111        }
112
113        return $subscriber_data;
114    }
115
116    /**
117     * Extract subscriber data (email, first_name, last_name) from legacy field data (v1 storage).
118     *
119     * @param array $fields Collection of Contact_Form_Field instances.
120     * @return array Associative array with at least 'email', optionally 'first_name', 'last_name'. Empty array if no email found.
121     */
122    protected static function get_subscriber_data_from_fields( $fields ) {
123        // Try and get the form from any of the fields
124        $form = null;
125        foreach ( $fields as $field ) {
126            if ( ! empty( $field->form ) ) {
127                $form = $field->form;
128                break;
129            }
130        }
131        if ( ! $form || ! is_a( $form, 'Automattic\\Jetpack\\Forms\\ContactForm\\Contact_Form' ) ) {
132            return array();
133        }
134
135        $subscriber_data = array();
136
137        foreach ( $form->fields as $field ) {
138            $id    = strtolower( str_replace( array( ' ', '_' ), '', $field->get_attribute( 'id' ) ) );
139            $label = strtolower( str_replace( array( ' ', '_' ), '', $field->get_attribute( 'label' ) ) );
140
141            if ( ! is_string( $field->value ) ) {
142                continue;
143            }
144
145            $value = trim( $field->value );
146
147            if ( ( $id === 'email' || $label === 'email' ) && ! empty( $value ) ) {
148                $subscriber_data['email'] = $value;
149            } elseif ( ( $id === 'firstname' || $label === 'firstname' ) && ! empty( $value ) ) {
150                $subscriber_data['name'] = $value;
151            } elseif ( ( $id === 'lastname' || $label === 'lastname' ) && ! empty( $value ) ) {
152                $subscriber_data['surname'] = $value;
153            }
154        }
155
156        if ( empty( $subscriber_data['email'] ) ) {
157            return array();
158        }
159
160        return $subscriber_data;
161    }
162
163    /**
164     * Check if submission has consent when required.
165     *
166     * @param Feedback $feedback Feedback object for the submission.
167     * @param mixed    $form     Contact form instance.
168     * @param bool     $is_v2    Whether the data comes from v2 storage.
169     * @return bool True if consent is present or not required; false otherwise.
170     */
171    protected static function has_consent( $feedback, $form, $is_v2 ) {
172        if ( $is_v2 ) {
173            if ( $feedback->has_field_type( 'consent' ) && ! $feedback->has_consent() ) {
174                return false;
175            }
176            return true;
177        }
178
179        $consent_field = null;
180        if ( is_object( $form ) && is_array( $form->fields ) ) {
181            foreach ( $form->fields as $form_field ) {
182                if ( 'consent' === $form_field->get_attribute( 'type' ) ) {
183                    $consent_field = $form_field;
184                    break;
185                }
186            }
187        }
188        if ( $consent_field ) {
189            $consent_type = strtolower( (string) $consent_field->get_attribute( 'consenttype' ) );
190            if ( 'explicit' === $consent_type && ! $consent_field->value ) {
191                return false;
192            }
193        }
194
195        return true;
196    }
197
198    /**
199     * Handle Hostinger Reach integration after feedback post is inserted.
200     *
201     * For now, this only validates preconditions and returns early; real actions will be implemented later.
202     *
203     * @param int   $post_id The post ID for the feedback CPT.
204     * @param array $fields  Collection of Contact_Form_Field instances.
205     * @param bool  $is_spam Whether the submission is spam.
206     */
207    public static function handle_hostinger_reach_integration( $post_id, $fields, $is_spam ) {
208        if ( $is_spam ) {
209            return;
210        }
211
212        // Try and get the form from any of the fields
213        $form = null;
214        foreach ( $fields as $field ) {
215            if ( ! empty( $field->form ) ) {
216                $form = $field->form;
217                break;
218            }
219        }
220        if ( ! $form || ! is_a( $form, 'Automattic\\Jetpack\\Forms\\ContactForm\\Contact_Form' ) ) {
221            return;
222        }
223
224        // Feature is toggled per-form.
225        if ( empty( $form->attributes['hostingerReach']['enabledForForm'] ?? null ) ) {
226            return;
227        }
228
229        $feedback = Feedback::get( $post_id );
230        if ( ! $feedback ) {
231            return;
232        }
233
234        $api_handler = self::get_api();
235        if ( ! $api_handler ) {
236            return;
237        }
238
239        // Respect consent if a consent field exists.
240        $post       = get_post( $post_id );
241        $is_v2_data = ( $post && $post->post_mime_type === 'v2' );
242        if ( ! self::has_consent( $feedback, $form, $is_v2_data ) ) {
243            return;
244        }
245
246        $subscriber_data = $is_v2_data ? self::get_subscriber_data( $feedback ) : self::get_subscriber_data_from_fields( $fields );
247        if ( empty( $subscriber_data ) ) {
248            return;
249        }
250
251        $group_name = self::get_group_name( $form );
252
253        self::submit_contact( $api_handler, $subscriber_data, $group_name );
254    }
255}