Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 155
WPCrm
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 25
6006
0.00% covered (danger)
0.00%
0 / 155
 getUser
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getUserFromApi
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 11
 login
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 logout
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 check
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 saveUser
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 11
 deleteUser
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 12
 toggleUserOptin
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 12
 setSingleOptin
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 activateUser
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 8
 getNewUser
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 unsetActiveUser
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 loginUserWithApi
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 getCustomer
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 setUser
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 5
 initLdcApi
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 6
 makeApiV2Call
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 11
 bootSessionIfNeeded
0.00% covered (danger)
0.00%
0 / 1
156
0.00% covered (danger)
0.00%
0 / 17
 getUserFromTable
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 getUserFromTableByEmail
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 getUserFromTableByUuid
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 5
 saveOrUpdateUserToTable
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 11
 deleteUserFromTable
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 query
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getWpDb
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
1<?php
2
3/**
4 * LDCCRM For Wordpress usage
5 *
6 * @category API
7 * @package  None
8 * @author   Jérémy Munsch <jeremy.munsch@ldc.fr>
9 * @license  MIT https://opensource.org/licenses/MIT
10 * @link     /
11 */
12
13namespace LDC\LDCCRM\WordPress;
14
15use LDC\LDCCRM\CrmApiV2;
16use LDC\LDCCRM\Enums\ApiError;
17use LDC\LDCCRM\Enums\LogLevel;
18use LDC\LDCCRM\Exceptions\ApiErrorException;
19use LDC\LDCCRM\Exceptions\CrmException;
20use LDC\LDCCRM\Models\Customer;
21use LDC\LDCCRM\Models\SingleOptin;
22use LDC\LDCCRM\Utils;
23
24/* phpcs:ignore Squiz.PHP.CommentedOutCode.Found
25    TABLE LDC CUSTOMERS
26    CREATE TABLE `ldc_wp_customers` (
27    `hashcode` VARCHAR(36) NOT NULL DEFAULT '' COMMENT 'Le hashcode (UUID)',
28    `email` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Le customer email',
29    `uuid` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Code unique (inscripton, validation, etc)',
30    PRIMARY KEY (`hashcode`)
31    )
32    COMMENT='Les customers sur un WP liées à la CRM'
33    COLLATE='utf8mb4_unicode_ci';
34*/
35
36/**
37 * Class de gestion des users en session avec Wordpress (ce truc immonde)
38 *
39 * @category API
40 * @package LDCCrmClient
41 * @subpackage Wordpress
42 * @author   Jérémy Munsch <jeremy.munsch@ldc.fr>
43 * @license  MIT https://opensource.org/licenses/MIT
44 * @link     /
45 */
46class WPCrm
47{
48    /**
49     * The session key name
50     *
51     * @var string
52     */
53    private static $sessionKey = null;
54
55    /**
56     * The session live status
57     *
58     * @var boolean
59     */
60    private static $sessionDestroyed = false;
61
62    /**
63     * The user object
64     *
65     * @var \LDC\LDCCRM\CrmApiV2
66     */
67    protected static $api = null;
68
69    /**
70     * The user object
71     *
72     * @var \LDC\LDCCRM\WordPress\WPCrmUser
73     */
74    protected static $user = null;
75
76    /**
77     * Get the current user
78     * return null if the user is not logged in
79     *
80     * @return \LDC\LDCCRM\WordPress\WPCrmUser|null
81     */
82    public static function getUser()
83    {
84        self::bootSessionIfNeeded();
85        return self::$user;
86    }
87
88    /**
89     * Get the current user
90     * return api response code if an error occurs
91     *
92     * @param string $identification The customer hashcode or email.
93     *
94     * @return \LDC\LDCCRM\Wordpress\WPCrmUser|false
95     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
96     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
97     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
98     */
99    public static function getUserFromApi(string $identification)
100    {
101        self::initLdcApi();
102        try {
103            if (!($response = self::makeApiV2Call(self::$api, 'getCustomer', [$identification]))) {
104                return false;
105            }
106        } catch (ApiErrorException $e) {
107            switch ($e->getApiError()) {
108                case ApiError::modelNotFound():
109                    return false;
110                default:
111                    throw $e;
112            }
113        }
114        /** @var \LDC\LDCCRM\Models\Customer */
115        $customer = $response;
116        self::setUser($customer);
117        return self::$user;
118    }
119
120    /**
121     * Try to login with CRM API
122     * A session will be started if credentials are valid.
123     *
124     * @param string $email    The customer email.
125     * @param string $password The customer password.
126     *
127     * @return \LDC\LDCCRM\WordPress\WPCrmUser|false
128     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
129     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
130     * Possibles are : - authFailed, If credentials are not valid.
131     *                 - customerNotActivated, If the account is not activated.
132     */
133    public static function login(string $email, string $password)
134    {
135        self::bootSessionIfNeeded();
136        $validCredentials = self::loginUserWithApi($email, $password);
137
138        if ($validCredentials and ($user = self::getUserFromApi($email))) {
139            self::saveOrUpdateUserToTable();
140            $_SESSION['wpcrm'][self::$sessionKey] = json_encode($user);
141        }
142
143        return $validCredentials;
144    }
145
146    /**
147     * Destroy the customer from session
148     * and therefore logout
149     *
150     * @return void
151     */
152    public static function logout()
153    {
154        self::bootSessionIfNeeded();
155        $_SESSION['wpcrm'] = [];
156        session_destroy();
157        self::$sessionDestroyed = true;
158    }
159
160    /**
161     * Check if the user is logged in.
162     * This means he has an active valid session.
163     *
164     * @return boolean
165     */
166    public static function check()
167    {
168        self::bootSessionIfNeeded();
169        return (bool)self::getUser();
170    }
171
172    /**
173     * Save current customer to
174     * the local database and on crm
175     *
176     * @return boolean true if the operation succeeded false otherwise
177     * @throws LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
178     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
179     * Possibles are : - validationFailed, If provided data is wrong(CRM side).
180     *                 - modelNotFound, If this customer hashcode could not be found.
181     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
182     */
183    public static function saveUser(): bool
184    {
185        if (!self::getUser() or !($customer = self::getCustomer())) {
186            return false;
187        }
188        try {
189            if (!self::makeApiV2Call($customer, 'save')) {
190                return false;
191            }
192        } catch (ApiErrorException $e) {
193            switch ($e->getApiError()) {
194                case ApiError::modelNotFound():
195                    return false;
196                default:
197                    throw $e;
198            }
199        }
200        self::saveOrUpdateUserToTable();
201        return true;
202    }
203
204    /**
205     * Delete current customer from
206     * the local database and the crm
207     *
208     * @return boolean true if the operation succeeded
209     * @throws LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
210     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
211     * Possibles are : - modelNotFound, If this customer hashcode could not be found.
212     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
213     */
214    public static function deleteUser(): bool
215    {
216        if (!self::getUser() or !($customer = self::getCustomer())) {
217            return false;
218        }
219        try {
220            if (!self::makeApiV2Call($customer, 'delete')) {
221                return false;
222            }
223        } catch (ApiErrorException $e) {
224            switch ($e->getApiError()) {
225                case ApiError::authorizationException():
226                case ApiError::modelNotFound():
227                    return false;
228                default:
229                    throw $e;
230            }
231        }
232        self::deleteUserFromTable();
233        return true;
234    }
235
236    /**
237     * Toggle the current user optin value
238     * on the current user and the crm
239     *
240     * @return boolean true if the operation succeeded
241     * @throws LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
242     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
243     * Possibles are : - modelNotFound, If this customer hashcode could not be found.
244     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
245     */
246    public static function toggleUserOptin(): bool
247    {
248        if (!self::getUser() or !($customer = self::getCustomer())) {
249            return false;
250        }
251        try {
252            if (!self::makeApiV2Call($customer, 'save')) {
253                return false;
254            }
255        } catch (ApiErrorException $e) {
256            switch ($e->getApiError()) {
257                case ApiError::authorizationException():
258                case ApiError::modelNotFound():
259                    return false;
260                default:
261                    throw $e;
262            }
263        }
264        $customer->opt_in = !$customer->opt_in;
265        return true;
266    }
267
268    /**
269     * Set the single optin status for an email
270     * on the crm
271     *
272     * @param string  $email
273     * @param boolean $opt_in
274     *
275     * @return boolean true if the operation succeeded false otherwise
276     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field email could not be validated.
277     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
278     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
279     * Possibles are : - singleOptinCustomerExists, If a customer exists with this email.
280     */
281    public static function setSingleOptin(string $email, bool $opt_in): bool
282    {
283        return self::makeApiV2Call(SingleOptin::class, 'setStatus', [
284            $email, $opt_in
285        ]);
286    }
287
288    /**
289     * Toggle the current user optin value
290     * on the current user and the crm
291     *
292     * @return boolean True if the operation succeeded.
293     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a customer's hashcode could not be validated.
294     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
295     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
296     * Possibles are : - modelNotFound, If this customer hashcode could not be found.
297     *                 - customerAlreadyActive, It the customer is already activated.
298     */
299    public static function activateUser(): bool
300    {
301        if (!self::getUser() or !($customer = self::getCustomer())) {
302            return false;
303        }
304        if ($customer->status) {
305            return true;
306        }
307        if (!self::makeApiV2Call($customer, 'activate')) {
308            return false;
309        }
310        $customer->opt_in = !$customer->opt_in;
311        return true;
312    }
313
314    /**
315     * Generate a new user
316     *
317     * @return WPCrmUser
318     */
319    public static function getNewUser(): WPCrmUser
320    {
321        self::$user = new WPCrmUser();
322        return self::$user;
323    }
324
325    /**
326     * Remove active user
327     * to prevent WPCrm::check to return true
328     *
329     * @return void
330     */
331    public static function unsetActiveUser(): void
332    {
333        self::$user = null;
334    }
335
336    // * --- PRIVATE --- METHODS
337
338    /**
339     * Check customer credentials using the CRM.
340     *
341     * @param string $email    The customer email.
342     * @param string $password The customer password.
343     *
344     * @return boolean
345     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
346     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
347     * Possibles are : - authFailed, If credentials are not valid.
348     *                 - customerNotActivated, If the account is not activated.
349     */
350    private static function loginUserWithApi(string $email, string $password): bool
351    {
352        self::initLdcApi();
353
354        /** @var boolean $response */
355        $response = self::makeApiV2Call(self::$api, 'login', [$email, $password]);
356        return $response;
357    }
358
359    /**
360     * Get all customer infos
361     *
362     * @return \LDC\LDCCRM\Models\Customer|null
363     */
364    private static function getCustomer(): ?Customer
365    {
366        if (!self::getUser()) {
367            return null;
368        }
369        if (!Utils::getRawProperty(self::$user, 'info')) {
370            self::getUserFromApi(self::$user->hashcode);
371        }
372        /** @var \LDC\LDCCRM\Models\Customer */
373        $customer = Utils::getRawProperty(self::$user, 'info');
374        return $customer;
375    }
376
377    /**
378     * Create a user
379     * stores it to the session
380     * and finally saves it to the database if it does not exists
381     *
382     * @param \LDC\LDCCRM\Models\Customer $customer
383     *
384     * @return void
385     */
386    private static function setUser(Customer $customer): void
387    {
388        self::bootSessionIfNeeded();
389        if (!self::$user) {
390            self::$user = new WPCrmUser($customer->hashcode, $customer->email);
391        }
392        self::$user->setInfo($customer);
393    }
394
395    /**
396     * Get the initialized API object
397     *
398     * @return \LDC\LDCCRM\CrmApiV2
399     * @throws \ErrorException If php does not have extension curl.
400     */
401    private static function initLdcApi(): CrmApiV2
402    {
403        if (!self::$api) {
404            if (!extension_loaded('curl')) {
405                throw new \ErrorException(
406                    'WPCrm requires php extension curl'
407                );
408            }
409            self::$api = new CrmApiV2();
410        }
411        return self::$api;
412    }
413
414    /**
415     * Make a call using apiV2
416     *
417     * @param object|string $obj       An object or a class name.
418     * @param string        $method    The method to call.
419     * @param array         $arguments Arguments to pass.
420     * @return mixed|false
421     * @throws LDC\LDCCRM\Exceptions\ApiErrorException If any of this exception is thrown.
422     * @throws LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
423     */
424    private static function makeApiV2Call($obj, string $method, array $arguments = [])
425    {
426        try {
427            return \forward_static_call_array([$obj, $method], $arguments);
428        } catch (ApiErrorException $e) {
429            switch ($e->getApiError()) {
430                case ApiError::apiAuthFailed():
431                    Utils::log($e->getMessage(), LogLevel::EMERG());
432                    throw $e;
433                default:
434                    Utils::log($e->getMessage(), LogLevel::DEBUG());
435                    throw $e;
436            }
437        } catch (CrmException $e) {
438            Utils::log($e->getMessage(), LogLevel::EMERG());
439            throw $e;
440        }
441    }
442
443    /**
444     * Boot the php session if needed
445     *
446     * @return void
447     */
448    private static function bootSessionIfNeeded(): void
449    {
450        self::$sessionKey = ($_SERVER['HTTP_USER_AGENT'] ?? '') . ($_SERVER['REMOTE_ADDR'] ?? '');
451        /** @var string self::$sessionKey */
452        self::$sessionKey = \strlen(self::$sessionKey) ? self::$sessionKey : (date('d') ? date('d') : '');
453        if (!isset($_SESSION['wpcrm'])) {
454            $_SESSION['wpcrm'] = [];
455        }
456        if (!session_id() && !self::$sessionDestroyed) {
457            session_start();
458        }
459        if (
460            // If session identity is violated.
461            count($_SESSION['wpcrm']) > 1 or
462            // If session timed out.
463            (isset($_SESSION['wpcrm'][self::$sessionKey]) and
464                isset($_SESSION['lastActivity']) and
465            // * 15 minutes session.
466            ($_SESSION['lastActivity'] + (15 * 60)) < time())
467        ) {
468            // Destroy session.
469            self::$sessionDestroyed = true;
470            $_SESSION['wpcrm']      = [];
471        } else {
472            $_SESSION['lastActivity'] = time();
473        }
474        if (!self::$user and isset($_SESSION['wpcrm'][self::$sessionKey])) {
475            self::$user = WPCrmUser::parseFromJson(
476                $_SESSION['wpcrm'][self::$sessionKey]
477            );
478        }
479    }
480
481    /**
482     * Get a customer from table
483     *
484     * @param string $hashcode The customer hashcode.
485     *
486     * @return array|null Database query result in format specified by $output or null on failure.
487     */
488    public static function getUserFromTable(string $hashcode): ?array
489    {
490        return self::getWpDb()->get_row(
491            self::getWpDb()->prepare(
492                'SELECT * FROM `ldc_wp_customers` WHERE `hashcode` = %s',
493                [
494                    'hashcode' => $hashcode
495                ]
496            ),
497            'ARRAY_A'
498        );
499    }
500
501    /**
502     * Get a customer from table by an email
503     *
504     * @param string $email The customer email.
505     *
506     * @return array|null Database query result in format specified by $output or null on failure.
507     */
508    public static function getUserFromTableByEmail(string $email): ?array
509    {
510        return self::getWpDb()->get_row(
511            self::getWpDb()->prepare(
512                'SELECT * FROM `ldc_wp_customers` WHERE `email` = %s',
513                [
514                    'email' => $email
515                ]
516            ),
517            'ARRAY_A'
518        );
519    }
520
521    /**
522     * Get a customer from table by an email
523     *
524     * @param string $uuid The customer transaction uuid.
525     *
526     * @return array|null Database query result in format specified by $output or null on failure.
527     */
528    public static function getUserFromTableByUuid(string $uuid): ?array
529    {
530        return self::getWpDb()->get_row(
531            self::getWpDb()->prepare(
532                'SELECT * FROM `ldc_wp_customers` WHERE `uuid` = %s',
533                [
534                    'uuid' => $uuid
535                ]
536            ),
537            'ARRAY_A'
538        );
539    }
540
541    /**
542     * Save or update the user to the table
543     *
544     * @return integer The number of row affected on the table.
545     */
546    private static function saveOrUpdateUserToTable(): int
547    {
548        if (is_string(self::$user->hashcode) and self::getUserFromTable(self::$user->hashcode)) {
549            return self::query(
550                'UPDATE `ldc_wp_customers` SET `email` = %s, `uuid` = %s WHERE `hashcode` = %s',
551                [
552                    'email' => self::$user->email,
553                    'uuid' => self::$user->uuidToken,
554                    'hashcode' => self::$user->hashcode
555                ]
556            );
557        }
558        return self::query(
559            'INSERT INTO `ldc_wp_customers`(hashcode, email, uuid) VALUES (%s, %s, %s)',
560            [
561                'hashcode' => self::$user->hashcode,
562                'email' => self::$user->email,
563                'uuid' => self::$user->uuidToken
564            ]
565        );
566    }
567
568    /**
569     * Save or update the user to the table
570     *
571     * @return integer The number of row removed from the table.
572     */
573    private static function deleteUserFromTable(): int
574    {
575        return self::query(
576            'DELETE FROM `ldc_wp_customers` WHERE `hashcode` = %s;',
577            [
578                'hashcode' => self::$user->hashcode
579            ]
580        );
581    }
582
583    /**
584     * Query in DB
585     *
586     * @param string $query  La request avec les variables en points d'interrogation.
587     * @param array  $params Les paramètres des la requète.
588     *
589     * @return integer|boolean Boolean true for CREATE, ALTER, TRUNCATE and DROP queries.
590     * Number of rows affected/selected for all other queries. Boolean false on error.
591     */
592    private static function query(string $query, array $params = [])
593    {
594        return self::getWpDb()->query(
595            self::getWpDb()->prepare($query, $params)
596        );
597    }
598
599    /**
600     * Get WP 'pdo instance'
601     *
602     * @return object The $wpdb variable
603     */
604    private static function getWpDb(): object
605    {
606        // phpcs:ignore Squiz.PHP.GlobalKeyword.NotAllowed
607        global $wpdb;
608        return $wpdb;
609    }
610}