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 / OptInMonster / OptInMonsterIntegration.php
hostinger-reach / src / Integrations / OptInMonster Last commit date
OptInMonsterIntegration.php 5 months ago
OptInMonsterIntegration.php
282 lines
1 <?php
2
3 namespace Hostinger\Reach\Integrations\OptInMonster;
4
5 use Exception;
6 use Hostinger\Reach\Dto\PluginData;
7 use Hostinger\Reach\Dto\ReachContact;
8 use Hostinger\Reach\Functions;
9 use Hostinger\Reach\Integrations\Integration;
10 use Hostinger\Reach\Integrations\IntegrationInterface;
11 use OMAPI;
12 use OMAPI_Api;
13 use stdClass;
14 use WP_Post;
15 use WP_REST_Request;
16 use WP_REST_Response;
17
18 if ( ! DEFINED( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 class OptInMonsterIntegration extends Integration implements IntegrationInterface {
23
24 public const INTEGRATION_NAME = 'optinmonster';
25 protected Functions $functions;
26
27 public function __construct( Functions $functions ) {
28 $this->functions = $functions;
29 }
30
31 public function active_integration_hooks(): void {
32 $this->enqueue_assets();
33 $this->register_routes();
34 }
35
36 public function get_post_type(): array {
37 return array( 'omapi' );
38 }
39
40 public static function get_name(): string {
41 return self::INTEGRATION_NAME;
42 }
43
44 public function get_plugin_data(): PluginData {
45 return PluginData::from_array(
46 array(
47 'id' => self::INTEGRATION_NAME,
48 'title' => __( 'OptinMonster', 'hostinger-reach' ),
49 'folder' => 'optinmonster',
50 'file' => 'optin-monster-wp-api.php',
51 'admin_url' => 'admin.php?page=optin-monster-dashboard',
52 'add_form_url' => 'admin.php?page=optin-monster-templates&type=popup',
53 'edit_url' => 'admin.php?page=optin-monster-campaigns&campaignId={post_name}',
54 'url' => 'https://optinmonster.com/',
55 'download_url' => 'https://wordpress.org/plugins/optinmonster',
56 'icon' => 'https://ps.w.org/optinmonster/assets/icon-256x256.png',
57 'import_enabled' => true,
58 )
59 );
60 }
61
62 public function submit_optinmonster( WP_REST_Request $request ): WP_REST_Response {
63 if ( ! $this->is_authorized( $request ) ) {
64 return $this->handle_response( 403, array( 'errors' => 'You cannot perform this action' ) );
65 }
66
67 try {
68 $data = json_decode( $request->get_body() ?? '', true );
69 $form_id = $this->get_post_id_by_form_id( $data['formId'] ?? '' );
70 if ( $form_id && ! $this->is_form_enabled( $form_id ) ) {
71 return $this->handle_response( 403, array( 'errors' => 'Form is not active' ) );
72 }
73 $email = $this->find_email( $data['fields'] ?? array() );
74 if ( $email ) {
75 $campaign_name = $data['tags']['campaign_name'] ?? '';
76 $form_name = $data['tags']['form_name'] ?? '';
77 $group = $campaign_name . ' - ' . $form_name;
78 do_action(
79 'hostinger_reach_submit',
80 array(
81 'email' => $email,
82 'group' => empty( $group ) ? self::INTEGRATION_NAME : $group,
83 'metadata' => array(
84 'plugin' => self::INTEGRATION_NAME,
85 ),
86 )
87 );
88 }
89 } catch ( Exception $e ) {
90 return $this->handle_response( 400, array( 'errors' => 'Invalid JSON data' ) );
91 }
92
93 return $this->handle_response( 200, array( 'message' => 'success' ) );
94 }
95
96 public function get_import_summary(): array {
97 $leads = $this->do_request( 'v2', 'leads' );
98 $campaigns = $leads?->campaigns ?? array();
99 $summary = array();
100 foreach ( $campaigns as $campaign ) {
101 $form_id = $this->get_post_id_by_form_id( $campaign->slug );
102 $summary[ $form_id ?: $campaign->slug ] = array(
103 'title' => $campaign->name,
104 'contacts' => $campaign->leads_count_all_time,
105 );
106 }
107
108 return $summary;
109 }
110
111 public function get_contacts( mixed $form_id = null, ?int $limit = 100, ?int $offset = 0 ): array {
112 $campaign_slug = $form_id;
113 $post = get_post( $form_id );
114 if ( $post ) {
115 $campaign_slug = $post->post_name;
116 }
117
118 $campaign_id = $this->get_campaign_by_slug( $campaign_slug );
119 $page = intdiv( $offset, $limit ) + 1;
120
121 $leads = $this->do_request(
122 'v2',
123 'leads',
124 array(
125 'campaigns[]' => $campaign_id,
126 'perPage' => $limit,
127 'page' => $page,
128 )
129 );
130
131 if ( ! $leads?->pagination || $this->is_overflowing_pagination( $leads->pagination, $page ) ) {
132 return array();
133 }
134
135 $entries = array_map(
136 function ( mixed $lead ) use ( $form_id ) {
137 $contact = new ReachContact(
138 $lead->email,
139 $lead?->fist_name ?? '',
140 $lead?->last_name ?? '',
141 array(
142 'plugin' => self::INTEGRATION_NAME,
143 'form_id' => $form_id,
144 )
145 );
146
147 return $contact->to_array();
148 },
149 $leads->leads ?? array()
150 );
151
152 return $entries;
153 }
154
155 /**
156 * OptinMonster API returns the last page when the currentPage overflows the number of items.
157 * So we need to check that edge case manually here.
158 */
159 private function is_overflowing_pagination( stdClass $pagination, int $current_page ): bool {
160 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
161 return $current_page !== $pagination->currentPage ?? 0;
162 }
163
164 private function get_campaign_by_slug( string $campaign_slug ): string {
165 $leads = $this->do_request( 'v2', 'leads' );
166 $campaigns = $leads?->campaigns ?? array();
167
168 $campaign = null;
169 foreach ( $campaigns as $item ) {
170 $slug = $item->slug ?? '';
171 if ( $slug === $campaign_slug ) {
172 $campaign = $item;
173 break;
174 }
175 }
176
177 if ( $campaign ) {
178 return $campaign->id ?? '';
179 }
180
181 return '';
182 }
183
184 private function do_request( string $version, string $route, array $args = array() ): mixed {
185 if ( ! $this->is_optinmonster_api_available() ) {
186 return null;
187 }
188 $api = OMAPI_Api::build( $version, $route, 'GET', OMAPI::get_instance()->get_api_credentials() );
189 $request = $api->request( $args );
190 if ( is_wp_error( $request ) || empty( $request ) ) {
191 return null;
192 }
193
194 return $request;
195 }
196
197 private function is_optinmonster_api_available(): bool {
198 return class_exists( '\OMAPI_Api' ) && class_exists( '\OMAPI' );
199 }
200
201 private function is_authorized( WP_REST_Request $request ): bool {
202 $nonce = $request->get_header( 'X-WP-Nonce' );
203 if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
204 return false;
205 }
206
207 return true;
208 }
209
210 private function register_routes(): void {
211 add_action(
212 'rest_api_init',
213 function (): void {
214 register_rest_route(
215 'hostinger-reach/v1',
216 '/optinmonster',
217 array(
218 'methods' => 'POST',
219 'callback' => array( $this, 'submit_optinmonster' ),
220 'permission_callback' => '__return_true',
221 )
222 );
223 }
224 );
225 }
226
227 private function enqueue_assets(): void {
228 $handler_name = 'hostinger_reach_' . self::INTEGRATION_NAME;
229 $js_file = $this->functions->get_frontend_dir() . self::INTEGRATION_NAME . '.js';
230
231 if ( $js_file ) {
232 wp_enqueue_script(
233 $handler_name,
234 Functions::get_frontend_url() . self::INTEGRATION_NAME . '.js',
235 array(),
236 filemtime( $this->functions->get_frontend_dir() . self::INTEGRATION_NAME . '.js' ),
237 true
238 );
239
240 wp_localize_script(
241 $handler_name,
242 $handler_name . '_data',
243 array(
244 'nonce' => wp_create_nonce( 'wp_rest' ),
245 'restUrl' => esc_url_raw( rest_url( 'hostinger-reach/v1/optinmonster' ) ),
246 )
247 );
248 }
249 }
250
251 private function find_email( array $data ): string {
252 foreach ( $data as $value ) {
253 $sanitized_value = sanitize_email( $value );
254 if ( filter_var( $sanitized_value, FILTER_VALIDATE_EMAIL ) ) {
255 return $value;
256 }
257 }
258
259 return '';
260 }
261
262 private function get_post_id_by_form_id( string $form_id ): int {
263 if ( ! $form_id ) {
264 return 0;
265 }
266 $post = get_page_by_path( $form_id, OBJECT, 'omapi' );
267 if ( $post instanceof WP_Post ) {
268 return $post->ID;
269 }
270
271 return 0;
272 }
273
274 private function handle_response( int $code, array $data ): WP_REST_Response {
275 $response = new WP_REST_Response();
276 $response->set_status( $code );
277 $response->set_data( $data );
278
279 return $response;
280 }
281 }
282