Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
12.22% covered (danger)
12.22%
11 / 90
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hostinger_Reach_Integration
12.22% covered (danger)
12.22%
11 / 90
0.00% covered (danger)
0.00%
0 / 7
1952.79
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
68.75% covered (warning)
68.75%
11 / 16
0.00% covered (danger)
0.00%
0 / 1
18.16
 has_consent
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
110
 handle_hostinger_reach_integration
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
156
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        $subscriber_data = array();
124
125        foreach ( $fields as $field ) {
126            $id    = strtolower( str_replace( array( ' ', '_' ), '', $field->get_attribute( 'id' ) ) );
127            $label = strtolower( str_replace( array( ' ', '_' ), '', $field->get_attribute( 'label' ) ) );
128
129            if ( ! is_string( $field->value ) ) {
130                continue;
131            }
132
133            $value = trim( $field->value );
134
135            if ( ( $id === 'email' || $label === 'email' ) && ! empty( $value ) ) {
136                $subscriber_data['email'] = $value;
137            } elseif ( ( $id === 'firstname' || $label === 'firstname' ) && ! empty( $value ) ) {
138                $subscriber_data['name'] = $value;
139            } elseif ( ( $id === 'lastname' || $label === 'lastname' ) && ! empty( $value ) ) {
140                $subscriber_data['surname'] = $value;
141            }
142        }
143
144        if ( empty( $subscriber_data['email'] ) ) {
145            return array();
146        }
147
148        return $subscriber_data;
149    }
150
151    /**
152     * Check if submission has consent when required.
153     *
154     * @param Feedback $feedback Feedback object for the submission.
155     * @param array    $fields   Visible submitted fields.
156     * @param bool     $uses_feedback_api Whether the data uses structured feedback storage.
157     * @return bool True if consent is present or not required; false otherwise.
158     */
159    protected static function has_consent( $feedback, $fields, $uses_feedback_api ) {
160        if ( $uses_feedback_api ) {
161            if ( $feedback->has_field_type( 'consent' ) && ! $feedback->has_consent() ) {
162                return false;
163            }
164            return true;
165        }
166
167        $consent_field = null;
168        if ( is_array( $fields ) ) {
169            foreach ( $fields as $form_field ) {
170                if ( 'consent' === $form_field->get_attribute( 'type' ) ) {
171                    $consent_field = $form_field;
172                    break;
173                }
174            }
175        }
176        if ( $consent_field ) {
177            $consent_type = strtolower( (string) $consent_field->get_attribute( 'consenttype' ) );
178            if ( 'explicit' === $consent_type && ! $consent_field->value ) {
179                return false;
180            }
181        }
182
183        return true;
184    }
185
186    /**
187     * Handle Hostinger Reach integration after feedback post is inserted.
188     *
189     * For now, this only validates preconditions and returns early; real actions will be implemented later.
190     *
191     * @param int   $post_id The post ID for the feedback CPT.
192     * @param array $fields  Collection of Contact_Form_Field instances.
193     * @param bool  $is_spam Whether the submission is spam.
194     */
195    public static function handle_hostinger_reach_integration( $post_id, $fields, $is_spam ) {
196        if ( $is_spam ) {
197            return;
198        }
199
200        // Try and get the form from any of the fields
201        $form = null;
202        foreach ( $fields as $field ) {
203            if ( ! empty( $field->form ) ) {
204                $form = $field->form;
205                break;
206            }
207        }
208        if ( ! $form || ! is_a( $form, 'Automattic\\Jetpack\\Forms\\ContactForm\\Contact_Form' ) ) {
209            return;
210        }
211
212        // Feature is toggled per-form.
213        if ( empty( $form->attributes['hostingerReach']['enabledForForm'] ?? null ) ) {
214            return;
215        }
216
217        $feedback = Feedback::get( $post_id );
218        if ( ! $feedback ) {
219            return;
220        }
221
222        $api_handler = self::get_api();
223        if ( ! $api_handler ) {
224            return;
225        }
226
227        // Respect consent if a consent field exists.
228        $uses_feedback_api = $feedback->uses_structured_fields();
229        if ( ! self::has_consent( $feedback, $fields, $uses_feedback_api ) ) {
230            return;
231        }
232
233        $subscriber_data = $uses_feedback_api ? self::get_subscriber_data( $feedback ) : self::get_subscriber_data_from_fields( $fields );
234        if ( empty( $subscriber_data ) ) {
235            return;
236        }
237
238        $group_name = self::get_group_name( $form );
239
240        self::submit_contact( $api_handler, $subscriber_data, $group_name );
241    }
242}