ldc-crm-client/src/WordPress/WPCrm.php
2025-08-28 16:41:48 +02:00

634 lines
20 KiB
PHP

<?php
/**
* LDCCRM For Wordpress usage
*
* @category API
* @package None
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
* @license MIT https://opensource.org/licenses/MIT
* @link /
*/
namespace LDC\LDCCRM\WordPress;
use LDC\LDCCRM\CrmApiV4;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Enums\LogLevel;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\Customer;
use LDC\LDCCRM\Models\SingleOptin;
use LDC\LDCCRM\Utils;
/* phpcs:ignore Squiz.PHP.CommentedOutCode.Found
TABLE LDC CUSTOMERS
CREATE TABLE `ldc_wp_customers` (
`hashcode` VARCHAR(36) NOT NULL DEFAULT '' COMMENT 'Le hashcode (UUID)',
`email` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Le customer email',
`uuid` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Code unique (inscripton, validation, etc)',
PRIMARY KEY (`hashcode`)
)
COMMENT='Les customers sur un WP liées à la CRM'
COLLATE='utf8mb4_unicode_ci';
*/
/**
* Class de gestion des users en session avec Wordpress
*
* @category API
* @package LDCCrmClient
* @subpackage Wordpress
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
* @license MIT https://opensource.org/licenses/MIT
* @link /
*/
class WPCrm
{
/**
* The session key name
*
* @var string|null
*/
private static ?string $sessionKey = null;
/**
* The session live status
*
* @var boolean
*/
private static bool $sessionDestroyed = false;
/**
* The user object
*
* @var \LDC\LDCCRM\CrmApiV4|null
*/
protected static ?CrmApiV4 $api = null;
/**
* The user object
*
* @var \LDC\LDCCRM\WordPress\WPCrmUser|null
*/
protected static ?WPCrmUser $user = null;
/**
* Get the current user
* return null if the user is not logged in
*
* @return \LDC\LDCCRM\WordPress\WPCrmUser|null
*/
public static function getUser()
{
self::bootSessionIfNeeded();
return self::$user;
}
/**
* Manually login a user
* If self::$user is already set.
*
* @return \LDCCRM\WPCrmUser|null
*/
public static function forceLogin()
{
self::bootSessionIfNeeded();
if ($user = self::getUser()) {
$_SESSION['wpcrm'][self::$sessionKey] = json_encode($user);
}
return $user;
}
/**
* Get the current user
* return api response code if an error occurs
*
* @param string $identification The customer hashcode or email.
*
* @return \LDC\LDCCRM\Wordpress\WPCrmUser|false
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public static function getUserFromApi(string $identification)
{
self::initLdcApi();
try {
if (!($response = self::makeApiV4Call(self::$api, 'getCustomer', [$identification]))) {
return false;
}
} catch (ApiErrorException $e) {
switch ($e->getApiError()) {
case ApiError::modelNotFound->value():
return false;
default:
throw $e;
}
}
/** @var \LDC\LDCCRM\Models\Customer */
$customer = $response;
self::setUser($customer);
return self::$user;
}
/**
* Try to login with CRM API
* A session will be started if credentials are valid.
*
* @param string $email The customer email.
* @param string $password The customer password.
*
* @return boolean
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - authFailed, If credentials are not valid.
* - customerNotActivated, If the account is not activated.
*/
public static function login(string $email, string $password)
{
self::bootSessionIfNeeded();
$validCredentials = self::loginUserWithApi($email, $password);
if ($validCredentials and ($user = self::getUserFromApi($email))) {
self::saveOrUpdateUserToTable();
$_SESSION['wpcrm'][self::$sessionKey] = json_encode($user);
}
return $validCredentials;
}
/**
* Destroy the customer from session
* and therefore logout
*
* @return void
*/
public static function logout()
{
self::bootSessionIfNeeded();
$_SESSION['wpcrm'] = [];
session_destroy();
self::$sessionDestroyed = true;
}
/**
* Check if the user is logged in.
* This means he has an active valid session.
*
* @return boolean
*/
public static function check()
{
self::bootSessionIfNeeded();
return (bool) self::getUser();
}
/**
* Save current customer to
* the local database and on crm
*
* @return boolean true if the operation succeeded false otherwise
* @throws \LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - validationFailed, If provided data is wrong(CRM side).
* - modelNotFound, If this customer hashcode could not be found.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public static function saveUser(): bool
{
if (!self::getUser() or !($customer = self::getCustomer())) {
return false;
}
try {
if (!self::makeApiV4Call($customer, 'save')) {
return false;
}
} catch (ApiErrorException $e) {
switch ($e->getApiError()) {
case ApiError::modelNotFound->value():
return false;
default:
throw $e;
}
}
self::saveOrUpdateUserToTable();
return true;
}
/**
* Delete current customer from
* the local database and the crm
*
* @return boolean true if the operation succeeded
* @throws \LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - modelNotFound, If this customer hashcode could not be found.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public static function deleteUser(): bool
{
if (!self::getUser() or !($customer = self::getCustomer())) {
return false;
}
try {
if (!self::makeApiV4Call($customer, 'delete')) {
return false;
}
} catch (ApiErrorException $e) {
switch ($e->getApiError()) {
case ApiError::authorizationException->value():
case ApiError::modelNotFound->value():
return false;
default:
throw $e;
}
}
self::deleteUserFromTable();
return true;
}
/**
* Toggle the current user optin value
* on the current user and the crm
*
* @return boolean true if the operation succeeded
* @throws \LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - modelNotFound, If this customer hashcode could not be found.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public static function toggleUserOptin(): bool
{
if (!self::getUser() or !($customer = self::getCustomer())) {
return false;
}
try {
$customer->opt_in = !$customer->opt_in;
if (!self::makeApiV4Call($customer, 'save')) {
return false;
}
} catch (ApiErrorException $e) {
switch ($e->getApiError()) {
case ApiError::authorizationException->value():
case ApiError::modelNotFound->value():
return false;
default:
throw $e;
}
}
return true;
}
/**
* Set the single optin status for an email
* on the crm
*
* @param string $email
* @param boolean $opt_in
* @param array|null $answerId
*
* @return boolean true if the operation succeeded false otherwise
* @throws \LDC\LDCCRM\Exceptions\ValidationException If a field email could not be validated.
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - singleOptinCustomerExists, If a customer exists with this email.
*/
public static function setSingleOptin(string $email, bool $opt_in, ?array $answerId = null): bool
{
return self::makeApiV4Call(
SingleOptin::class,
'setStatus',
array_filter([$email, $opt_in, $answerId], fn($v) => !is_null($v))
);
}
/**
* Activate the current user
*
* @return boolean True if the operation succeeded.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - modelNotFound, If this customer hashcode could not be found.
* - customerAlreadyActive, It the customer is already activated.
*/
public static function activateUser(): bool
{
if (!self::getUser() or !($customer = self::getCustomer())) {
return false;
}
if ($customer->status) {
return true;
}
try {
if (!self::makeApiV4Call($customer, 'activate')) {
return false;
}
} catch (ApiErrorException $e) {
switch ($e->getApiError()) {
case ApiError::authorizationException->value():
case ApiError::modelNotFound->value():
return false;
default:
throw $e;
}
}
return true;
}
/**
* Generate a new user
*
* @return WPCrmUser
*/
public static function getNewUser(): WPCrmUser
{
self::$user = new WPCrmUser();
return self::$user;
}
/**
* Remove active user
* to prevent WPCrm::check to return true
*
* @return void
*/
public static function unsetActiveUser(): void
{
self::$user = null;
}
// * --- PRIVATE --- METHODS
/**
* Check customer credentials using the CRM.
*
* @param string $email The customer email.
* @param string $password The customer password.
*
* @return boolean
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* Possibles are : - authFailed, If credentials are not valid.
* - customerNotActivated, If the account is not activated.
*/
private static function loginUserWithApi(string $email, string $password): bool
{
self::initLdcApi();
/** @var boolean $response */
$response = self::makeApiV4Call(self::$api, 'login', [$email, $password]);
return $response;
}
/**
* Get all customer infos
*
* @return \LDC\LDCCRM\Models\Customer|null
*/
private static function getCustomer(): ?Customer
{
if (!self::getUser()) {
return null;
}
if (!Utils::getRawProperty(self::$user, 'info')) {
self::getUserFromApi(self::$user->hashcode);
}
/** @var \LDC\LDCCRM\Models\Customer */
$customer = Utils::getRawProperty(self::$user, 'info');
return $customer;
}
/**
* Create a user
* stores it to the session
* and finally saves it to the database if it does not exists
*
* @param \LDC\LDCCRM\Models\Customer $customer
*
* @return void
*/
private static function setUser(Customer $customer): void
{
self::bootSessionIfNeeded();
if (!self::$user) {
self::$user = new WPCrmUser($customer->hashcode, $customer->email);
}
self::$user->setInfo($customer);
}
/**
* Get the initialized API object
*
* @return \LDC\LDCCRM\CrmApiV4
* @throws \ErrorException If php does not have extension curl.
*/
private static function initLdcApi(): CrmApiV4
{
if (!self::$api) {
if (!extension_loaded('curl')) {
throw new \ErrorException(
'WPCrm requires php extension curl'
);
}
self::$api = new CrmApiV4();
}
return self::$api;
}
/**
* Make a call using apiV4
*
* @param object|string $obj An object or a class name.
* @param string $method The method to call.
* @param array $arguments Arguments to pass.
* @return mixed|false
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any of this exception is thrown.
* @throws \LDC\LDCCRM\Exceptions\CrmException If a critical error occured.
*/
private static function makeApiV4Call($obj, string $method, array $arguments = [])
{
try {
return \forward_static_call_array([$obj, $method], $arguments);
} catch (ApiErrorException $e) {
switch ($e->getApiError()) {
case ApiError::apiAuthFailed:
Utils::log($e->getMessage(), LogLevel::CRIT);
throw $e;
default:
Utils::log($e->getMessage(), LogLevel::DEBUG);
throw $e;
}
} catch (CrmException $e) {
Utils::log($e->getMessage(), LogLevel::CRIT);
throw $e;
}
}
/**
* Boot the php session if needed
*
* @return void
*/
private static function bootSessionIfNeeded(): void
{
self::$sessionKey = ($_SERVER['HTTP_USER_AGENT'] ?? '') . ($_SERVER['REMOTE_ADDR'] ?? '');
/** @var string self::$sessionKey */
self::$sessionKey = \strlen(self::$sessionKey) ? self::$sessionKey : (date('d') ? date('d') : '');
if (!session_id() && !self::$sessionDestroyed) {
session_start();
}
if (!isset($_SESSION['wpcrm'])) {
$_SESSION['wpcrm'] = [];
}
if (
// If session identity is violated.
count($_SESSION['wpcrm']) > 1 or
// If session timed out.
(isset($_SESSION['wpcrm'][self::$sessionKey]) and
isset($_SESSION['lastActivity']) and
// * 15 minutes session.
($_SESSION['lastActivity'] + (15 * 60)) < time())
) {
// Destroy session.
self::$sessionDestroyed = true;
$_SESSION['wpcrm'] = [];
} else {
$_SESSION['lastActivity'] = time();
}
if (!self::$user and isset($_SESSION['wpcrm'][self::$sessionKey])) {
self::$user = WPCrmUser::parseFromJson(
$_SESSION['wpcrm'][self::$sessionKey]
);
}
}
/**
* Get a customer from table
*
* @param string $hashcode The customer hashcode.
*
* @return array|null Database query result in format specified by $output or null on failure.
*/
public static function getUserFromTable(string $hashcode): ?array
{
return self::getWpDb()->get_row(
self::getWpDb()->prepare(
'SELECT * FROM `ldc_wp_customers` WHERE `hashcode` = %s',
[
'hashcode' => $hashcode
]
),
'ARRAY_A'
);
}
/**
* Get a customer from table by an email
*
* @param string $email The customer email.
*
* @return array|null Database query result in format specified by $output or null on failure.
*/
public static function getUserFromTableByEmail(string $email): ?array
{
return self::getWpDb()->get_row(
self::getWpDb()->prepare(
'SELECT * FROM `ldc_wp_customers` WHERE `email` = %s',
[
'email' => $email
]
),
'ARRAY_A'
);
}
/**
* Get a customer from table by an email
*
* @param string $uuid The customer transaction uuid.
*
* @return array|null Database query result in format specified by $output or null on failure.
*/
public static function getUserFromTableByUuid(string $uuid): ?array
{
return self::getWpDb()->get_row(
self::getWpDb()->prepare(
'SELECT * FROM `ldc_wp_customers` WHERE `uuid` = %s',
[
'uuid' => $uuid
]
),
'ARRAY_A'
);
}
/**
* Save or update the user to the table
*
* @return integer The number of row affected on the table.
*/
private static function saveOrUpdateUserToTable(): int
{
if (is_string(self::$user->hashcode) and self::getUserFromTable(self::$user->hashcode)) {
return self::query(
'UPDATE `ldc_wp_customers` SET `email` = %s, `uuid` = %s WHERE `hashcode` = %s',
[
'email' => self::$user->email,
'uuid' => self::$user->uuidToken,
'hashcode' => self::$user->hashcode
]
);
}
return self::query(
'INSERT INTO `ldc_wp_customers`(hashcode, email, uuid) VALUES (%s, %s, %s)',
[
'hashcode' => self::$user->hashcode,
'email' => self::$user->email,
'uuid' => self::$user->uuidToken
]
);
}
/**
* Save or update the user to the table
*
* @return integer The number of row removed from the table.
*/
private static function deleteUserFromTable(): int
{
return self::query(
'DELETE FROM `ldc_wp_customers` WHERE `hashcode` = %s;',
[
'hashcode' => self::$user->hashcode
]
);
}
/**
* Query in DB
*
* @param string $query La request avec les variables en points d'interrogation.
* @param array $params Les paramètres des la requète.
*
* @return integer|boolean Boolean true for CREATE, ALTER, TRUNCATE and DROP queries.
* Number of rows affected/selected for all other queries. Boolean false on error.
*/
private static function query(string $query, array $params = [])
{
return self::getWpDb()->query(
self::getWpDb()->prepare($query, $params)
);
}
/**
* Get WP 'pdo instance'
*
* @return object The $wpdb variable
*/
private static function getWpDb(): object
{
// phpcs:ignore Squiz.PHP.GlobalKeyword.NotAllowed
global $wpdb;
return $wpdb;
}
}