Initial commit

This commit is contained in:
gustavooth
2026-07-23 22:56:30 -03:00
commit 22a1006598
1816 changed files with 410742 additions and 0 deletions
@@ -0,0 +1,203 @@
<?php
namespace FluentCrm\App\Api\Classes;
defined('ABSPATH') || exit;
use FluentCrm\App\Models\Company;
use FluentCrm\App\Models\CustomCompanyField;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\Framework\Support\Arr;
/**
* Company Class - PHP APi Wrapper
*
* Company API Wrapper Class that can be used as <code>FluentCrmApi('companies')</code> to get the class instance
*
* @package FluentCrm\App\Api\Classes
* @namespace FluentCrm\App\Api\Classes
*
* @version 2.8.0
*/
class Companies
{
private $instance = null;
private $allowedInstanceMethods = [
'all',
'get',
'find',
'first',
'paginate'
];
public function __construct(Company $instance)
{
$this->instance = $instance;
}
public function getCompany($idOrName, $with = [])
{
if (is_numeric($idOrName)) {
return Company::where('id', $idOrName)->with($with)->first();
}
if (is_string($idOrName)) {
return Company::where('email', $idOrName)->with($with)->first();
}
return false;
}
public function createOrUpdate($data)
{
$exist = null;
if (!empty($data['id'])) {
$exist = Company::where('id', $data['id'])->first();
} else {
$exist = Company::where('name', $data['name'])->first();
}
if ($exist) {
if (!empty($data['owner_id']) && $data['owner_id'] != $exist->owner_id) {
$contact = Subscriber::find($data['owner_id']);
if ($contact) {
$contact->attachCompanies([$exist->id]);
if (empty($contact->company_id)) {
$contact->company_id = $exist->id;
$contact->save();
}
}
}
if (isset($data['custom_values'])) {
$existingMeta = $exist->meta;
$values = Arr::get($data, 'custom_values', []);
$values = (new CustomCompanyField())->formatCustomFieldValues($values);
$temp = $existingMeta['custom_values'];
foreach ($values as $key => $value)
{
$temp[$key] = $value;
}
$existingMeta['custom_values'] = $temp;
$exist->meta = $existingMeta;
unset($data['custom_values']);
}
$exist->fill($data);
$exist->save();
do_action('fluent_crm/company_updated', $exist, $data);
return $exist;
} else if (empty($data['name'])) {
return false;
}
$fillables = (new Company())->getFillable();
$fillables[] = 'custom_values';
$data = Arr::only($data, $fillables);
$values = Arr::get($data, 'custom_values', []);
$values = (new CustomCompanyField())->formatCustomFieldValues($values);
$data['meta'] = [
'custom_values' => $values
];
$company = Company::create($data);
do_action('fluent_crm/company_created', $company, $data);
if ($company->owner_id) {
$owner = Subscriber::find($company->owner_id);
if ($owner) {
$owner->attachCompanies([$company->id]);
if (empty($owner->company_id)) {
$owner->company_id = $company->id;
$owner->save();
}
}
}
return $company;
}
public function attachContactsByIds($contactIds, $companyIds)
{
$companyIds = array_map('intval', $companyIds);
$subscriberIds = array_map('intval', $contactIds);
$subscribers = Subscriber::whereIn('id', $subscriberIds)->get();
$companies = Company::whereIn('id', $companyIds)->get();
if ((count($companyIds) != count($companies)) || $subscribers->isEmpty() || $companies->isEmpty()) {
return false;
}
$firstCompanyId = $companyIds[0];
$validIds = [];
foreach ($companies as $company) {
$validIds[] = $company->id;
}
foreach ($subscribers as $subscriber) {
$subscriber->attachCompanies($validIds);
if (!$subscriber->company_id) {
$subscriber->company_id = $firstCompanyId;
$subscriber->save();
}
}
return [
'companies' => $companies,
'subscribers' => $subscribers
];
}
public function detachContactsByIds($contactIds, $companyIds)
{
$companyIds = array_map('intval', $companyIds);
$subscriberIds = array_map('intval', $contactIds);
$subscribers = Subscriber::whereIn('id', $subscriberIds)->get();
$companies = Company::whereIn('id', $companyIds)->get();
if ((count($companyIds) != count($companies)) || $subscribers->isEmpty() || $companies->isEmpty()) {
return false;
}
$validIds = [];
foreach ($companies as $company) {
if ($company->owner_id && in_array($company->owner_id, $subscriberIds)) {
$company->owner_id = NULL;
$company->save();
}
$validIds[] = $company->id;
}
$lastPrimaryId = false;
foreach ($subscribers as $subscriber) {
$subscriber = $subscriber->detachCompanies($validIds);
if (in_array($subscriber->company_id, $validIds)) {
$companies = $subscriber->companies;
if (count($companies)) {
$lastPrimaryId = $companies[0]->id;
$subscriber->company_id = $lastPrimaryId;
$subscriber->save();
continue;
}
$subscriber->company_id = NULL;
$subscriber->save();
}
}
return [
'companies' => $companies,
'last_primary_company_id' => $lastPrimaryId
];
}
}
@@ -0,0 +1,269 @@
<?php
namespace FluentCrm\App\Api\Classes;
defined('ABSPATH') || exit;
use FluentCrm\App\Models\CustomContactField;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\App\Models\SubscriberMeta;
use FluentCrm\App\Services\ContactsQuery;
use FluentCrm\Framework\Support\Arr;
/**
* Contacts Class - PHP APi Wrapper
*
* Contacts API Wrapper Class that can be used as <code>FluentCrmApi('contacts')</code> to get the class instance
*
* @package FluentCrm\App\Api\Classes
* @namespace FluentCrm\App\Api\Classes
*
* @version 1.0.0
*/
class Contacts
{
private $instance = null;
private $allowedInstanceMethods = [
'all',
'get',
'find',
'first',
'paginate'
];
public function __construct(Subscriber $instance)
{
$this->instance = $instance;
}
/**
* Get Contact by contact id or email
*
* Use:
* <code>FluentCrmApi('contacts')->getContact($idOrEmail);</code>
*
* @param int|string $idOrEmail Contact ID or Email
* @return false|Subscriber Model of the subscriber
*/
public function getContact($idOrEmail)
{
if (is_numeric($idOrEmail)) {
return Subscriber::where('id', $idOrEmail)->first();
} else if (is_string($idOrEmail)) {
return Subscriber::where('email', $idOrEmail)->first();
}
return false;
}
/**
* Get Contact by user id or Email
*
* Use:
* <code>FluentCrmApi('contacts')->getContactByUserRef($userIdOrEmail);</code>
*
* @param int|string $userIdOrEmail User ID or Email
* @return false|Subscriber Model of the subscriber
*/
public function getContactByUserRef($userIdOrEmail)
{
$userIdFallback = false;
if (is_numeric($userIdOrEmail)) {
$subscriber = Subscriber::where('user_id', $userIdOrEmail)->first();
if ($subscriber) {
return $subscriber;
}
$user = get_user_by('ID', $userIdOrEmail);
if (!$user) {
return false;
}
$userIdFallback = $user->ID;
$userIdOrEmail = $user->user_email;
}
$contact = false;
if (is_string($userIdOrEmail)) {
$contact = Subscriber::where('email', $userIdOrEmail)->first();
if ($contact && $userIdFallback) {
$contact->user_id = $userIdFallback;
$contact->save();
}
}
return $contact;
}
/**
* Get Contact by contact id
*
* @param int $userId User ID
* @return false|Subscriber Model of the subscriber
*/
public function getContactByUserId($userId)
{
return Subscriber::where('user_id', $userId)->first();
}
/**
* Create or Update Contact
*
* Usage:
*
* <code>FluentCrmApi('contacts')->createOrUpdateContact($data, $forceUpdate, $deleteOtherValues, $sync)</code>;
*
* @param array $data contact data to add or update
* @param bool $forceUpdate if true, will update the contact status forcefully
* @param bool $deleteOtherValues if true, will delete all custom fields data and add the new one
* @param bool $sync no use case yet
* @return false|Subscriber
*/
public function createOrUpdate($data, $forceUpdate = false, $deleteOtherValues = false, $sync = false)
{
if (empty($data['email']) || !is_email($data['email'])) {
return false;
}
if (!$forceUpdate) {
$exist = Subscriber::where('email', $data['email'])->first();
if ($exist && $exist->status != 'subscribed' && !empty($data['status'])) {
$forceUpdate = true;
}
}
if (!isset($data['custom_values'])) {
$customFieldKeys = [];
$customFields = (new CustomContactField)->getGlobalFields()['fields'];
foreach ($customFields as $field) {
$customFieldKeys[] = $field['slug'];
}
if ($customFieldKeys) {
$customFieldsData = Arr::only($data, $customFieldKeys);
$customFieldsData = array_filter($customFieldsData);
if ($customFields) {
$data['custom_values'] = (new CustomContactField)->formatCustomFieldValues($customFieldsData);
}
}
}
return $this->instance->updateOrCreate($data, $forceUpdate, $deleteOtherValues, $sync);
}
/**
* Get The current logged in contact
*
* Use
* <pre>FluentCrmApi('contacts')->getCurrentContact()</pre>
*
* @return false|Subscriber
*/
public function getCurrentContact($cached = true, $useSecureCookie = false)
{
static $currentContact;
if ($cached && $currentContact) {
return $currentContact;
}
$userId = get_current_user_id();
if ($userId) {
$user = get_user_by('ID', $userId);
$currentContact = $this->instance->where('user_id', $user->ID)->orWhere('email', $user->user_email)->first();
}
if (!$currentContact && $useSecureCookie) {
$currentContact = $this->getContactBySecureHash(\FluentCrm\Framework\Support\Arr::get($_COOKIE, 'fc_hash_secure'));
}
return $currentContact;
}
public function getContactBySecureHash($hash)
{
if (!$hash) {
return null;
}
$secureMeta = SubscriberMeta::where('value', $hash)->where('key', '_secure_hash')
->first();
if ($secureMeta) {
return $this->instance->where('id', $secureMeta->subscriber_id)->first();
}
return null;
}
public function getContactByManagedSecureHash($hash)
{
if (!$hash) {
return null;
}
$secureMeta = SubscriberMeta::where('value', $hash)->where('key', '_secure_managed_hash')
->first();
if ($secureMeta) {
return $this->instance->where('id', $secureMeta->subscriber_id)->first();
}
return null;
}
/**
* To Contact's Advanced Query Class
* Use
* <pre>FluentCrmApi('contacts')->query($args)</pre>
* @param $args array
* @return \FluentCrm\App\Services\ContactsQuery
*/
public function query($args)
{
return new ContactsQuery($args);
}
/**
* @return \FluentCrm\App\Models\Subscriber
*/
public function getInstance()
{
return $this->instance;
}
public function getCustomFields($types = [], $byOptions = false)
{
$customFields = fluentcrm_get_custom_contact_fields();
if ($types) {
$customFields = array_filter($customFields, function ($field) use ($types) {
return in_array($field['type'], $types);
});
}
if ($byOptions) {
$formatted = [];
foreach ($customFields as $field) {
$formatted[] = [
'id' => $field['slug'],
'title' => $field['label']
];
}
return $formatted;
}
return $customFields;
}
public function __call($method, $params)
{
if (in_array($method, $this->allowedInstanceMethods)) {
return call_user_func_array([$this->instance, $method], $params);
}
/* translators: %s: method name */
throw new \Exception(sprintf('Method %s does not exist.', esc_html($method)));
}
}
@@ -0,0 +1,166 @@
<?php
namespace FluentCrm\App\Api\Classes;
use FluentCrm\App\Models\Subscriber;
defined('ABSPATH') || exit;
/**
* Extend API Wrapper for FluentCRM FluentCrmApi('extend')
*
* Contacts API Wrapper Class that can be used as <code>FluentCrmApi('extend')</code> to get the class instance
*
* @package FluentCrm\App\Api\Classes
* @namespace FluentCrm\App\Api\Classes
*
* @version 1.0.0
*/
final class Extender
{
public function addProfileSection($key, $sectionTitle, $callback, $saveCallback = null)
{
add_filter('fluentcrm_profile_sections', function ($sections) use ($key, $sectionTitle) {
$sections[$key] = [
'name' => 'fluentcrm_profile_extended',
'title' => $sectionTitle,
'handler' => 'route',
'query' => [
'handler' => $key
]
];
return $sections;
});
add_filter('fluencrm_profile_section_' . $key, function ($content, $subscriber) use ($callback) {
if (is_callable($callback)) {
return $callback($content, $subscriber);
}
return $content;
}, 10, 2);
if ($saveCallback) {
add_filter('fluencrm_profile_section_save_' . $key, function ($response, $data, $subscriber) use ($saveCallback) {
if (is_callable($saveCallback)) {
return $saveCallback($response, $data, $subscriber);
}
return $response;
}, 10, 3);
}
}
public function addCompanyProfileSection($key, $sectionTitle, $callback, $saveCallback = null)
{
add_filter('fluent_crm/company_profile_sections', function ($sections) use ($key, $sectionTitle) {
$sections[$key] = [
'name' => 'fluent_crm_company_section_extended',
'title' => $sectionTitle,
'handler' => 'route',
'query' => [
'handler' => $key
]
];
return $sections;
});
add_filter('fluent_crm/company_profile_section_' . $key, function ($content, $subscriber) use ($callback) {
if (is_callable($callback)) {
return $callback($content, $subscriber);
}
return $content;
}, 10, 2);
if ($saveCallback) {
add_filter('fluent_crm/company_profile_section_save_' . $key, function ($response, $data, $company) use ($saveCallback) {
if (is_callable($saveCallback)) {
return $saveCallback($response, $data, $company);
}
return $response;
}, 10, 3);
}
}
public function addSmartCode($key, $title, $shortcodes, $callback)
{
$reservedKeys = [
'crm',
'other',
'contact',
'wp',
'fluentcrm',
'user',
'learndash',
'tutorlms',
'aff_wp',
'edd_customer',
'lifterlms',
'woo_customer'
];
if (in_array($key, $reservedKeys)) {
return;
}
/*
* this is shortcode processor function
*/
add_filter('fluent_crm/extended_smart_codes', function ($groups) use ($key, $title, $shortcodes) {
$groups[] = [
'key' => $key,
'title' => $title,
'shortcodes' => $this->formatShortcodes($key, $shortcodes)
];
return $groups;
}, 100);
/*
* This is the callback function for the shortcode parser
*/
add_filter('fluent_crm/smartcode_group_callback_' . $key, function ($code, $valueKey, $defaultValue, $subscriber) use ($callback) {
if (is_callable($callback)) {
return $callback($code, $valueKey, $defaultValue, $subscriber);
}
return $code; // return the code if no parser function is provided
}, 10, 4);
}
/**
* @param $groupKey string
* @param $shortcodes array
* @return array
*/
private function formatShortcodes($groupKey, $shortcodes)
{
$processed = [];
foreach ($shortcodes as $key => $title) {
$processed['{{' . $groupKey . '.' . $key . '}}'] = $title;
}
return $processed;
}
public function addContactWidget($callback, $priority = 20)
{
add_filter('fluent_crm/subscriber_info_widgets', function ($widgets, $subscriber) use ($callback) {
if (is_callable($callback)) {
$data = $callback($subscriber);
if (is_array($data) && isset($data['title']) && isset($data['content'])) {
$widgets[] = [
'title' => $data['title'],
'content' => $data['content']
];
}
}
return $widgets;
}, $priority, 2);
}
public function getCompaniesByContactEmail($email)
{
$subscriber = Subscriber::where('email', $email)->with('companies')->first();
if (!$subscriber) {
return [];
}
return $subscriber->companies;
}
}
@@ -0,0 +1,96 @@
<?php
namespace FluentCrm\App\Api\Classes;
use FluentCrm\App\Models\Lists as CrmLists;
use FluentCrm\Framework\Support\Arr;
/**
* Contacts List Class - PHP APi Wrapper
*
* Contacts API Wrapper Class that can be used as <code>FluentCrmApi('lists')</code> to get the class instance.
* This will contain all the methods of \FluentCrm\App\Models\Lists model.
*
* @package FluentCrm\App\Api\Classes
*
* @version 1.0.0
*/
class Lists
{
private $instance = null;
private $allowedInstanceMethods = [
'all',
'get',
'find',
'first',
'paginate'
];
public function __construct(CrmLists $instance)
{
$this->instance = $instance;
}
public function getInstance()
{
return $this->instance;
}
/**
* Add Lists as Bulk
*
* Use As: <code>FluentCrmApi('lists')->addBulk($lists)</code>
*
* @param array $lists Array of Lists with title, slug etc
* @return array of List Objects
*/
public function importBulk($lists)
{
$newLists = [];
foreach ($lists as $list) {
if (!$list['title']) {
continue;
}
if(empty($list['slug'])) {
$list['slug'] = sanitize_title($list['title'], 'display');
} else {
$list['slug'] = sanitize_title($list['slug'], 'display');
}
$list['slug'] = sanitize_text_field($list['slug']);
$list = \FluentCrm\App\Models\Lists::updateOrCreate(
array_filter([
'slug' => $list['slug'],
'title' => sanitize_text_field($list['title']),
'description' => sanitize_textarea_field(Arr::get($list, 'description'))
]),
['slug' => $list['slug']]
);
if($list->wasRecentlyCreated) {
do_action('fluentcrm_list_created', $list->id);
do_action('fluent_crm/list_created', $list);
} else {
do_action('fluentcrm_list_updated', $list->id);
do_action('fluent_crm/list_updated', $list);
}
$newLists[] = $list;
}
return $newLists;
}
public function __call($method, $params)
{
if (in_array($method, $this->allowedInstanceMethods)) {
return call_user_func_array([$this->instance, $method], $params);
}
/* translators: %s: method name */
throw new \Exception(sprintf('Method %s does not exist.', esc_html($method)));
}
}
@@ -0,0 +1,87 @@
<?php
/**
* Contact Tags Class - PHP APi Wrapper
*
* Contacts Tags API Wrapper Class that can be used as fluentCrmApi('tags') to get the class instance
*
* @package FluentCrm\App\Api\Classes
*
* @version 1.0.0
*/
namespace FluentCrm\App\Api\Classes;
use FluentCrm\App\Models\Tag;
use FluentCrm\Framework\Support\Arr;
class Tags
{
private $instance = null;
private $allowedInstanceMethods = [
'all',
'get',
'find',
'first',
'paginate'
];
public function importBulk($tags)
{
$newTags = [];
foreach ($tags as $tag) {
if (!$tag['title']) {
continue;
}
if (empty($tag['slug'])) {
$tag['slug'] = sanitize_title($tag['title'], 'display');
} else {
$tag['slug'] = sanitize_title($tag['slug'], 'display');
}
$tag['slug'] = sanitize_text_field($tag['slug']);
$tag = \FluentCrm\App\Models\Tag::updateOrCreate(
array_filter([
'slug' => $tag['slug'],
'title' => sanitize_text_field($tag['title']),
'description' => sanitize_textarea_field(Arr::get($tag, 'description'))
]),
['slug' => $tag['slug']]
);
if ($tag->wasRecentlyCreated) {
do_action('fluentcrm_tag_created', $tag->id);
do_action('fluent_crm/tag_created', $tag);
} else {
do_action('fluentcrm_tag_updated', $tag->id);
do_action('fluent_crm/tag_updated', $tag);
}
$newTags[] = $tag;
}
return $newTags;
}
public function __construct(Tag $instance)
{
$this->instance = $instance;
}
public function getInstance()
{
return $this->instance;
}
public function __call($method, $params)
{
if (in_array($method, $this->allowedInstanceMethods)) {
return call_user_func_array([$this->instance, $method], $params);
}
/* translators: %s: method name */
throw new \Exception(sprintf('Method %s does not exist.', esc_html($method)));
}
}
@@ -0,0 +1,128 @@
<?php
namespace FluentCrm\App\Api\Classes;
use FluentCrm\App\Models\Subscriber;
use FluentCrm\App\Services\Helper;
use FluentCrm\Framework\Support\Arr;
defined('ABSPATH') || exit;
/**
* Extend API Wrapper for FluentCRM FluentCrmApi('tracker')
*
* Contacts API Wrapper Class that can be used as <code>FluentCrmApi('tracker')</code> to get the class instance
*
* @package FluentCrm\App\Api\Classes
* @namespace FluentCrm\App\Api\Classes
*
* @version 2.8.4
*/
final class Tracker
{
/*
* Create event for a subscriber
* Example Data:
* [
* 'subscriber_id' => 1, // optional
* 'email' => '', // optional
* 'user_id' => 1, // optional
* 'provider' => 'woocommerce|custom|or_anything', // optional
* 'event_key' => 'checkout',
* 'title' => 'Purchase Done',
* 'value' => 'STRING|Number'
* ];
* @param array $data | \WP_Error
*
* @return \WP_Error|\FluentCrm\App\Models\EventTracker
*/
public function track($data, $repeatable = true)
{
if (!Helper::isExperimentalEnabled('event_tracking')) {
return new \WP_Error('not_enabled', 'Event Tracker is not enabled');
}
// find the subscriber
$subscriber = $this->getSubscriber($data);
if (is_wp_error($subscriber)) {
return $subscriber;
}
// validate the data
if (empty($data['event_key']) || empty($data['title'])) {
return new \WP_Error('invalid_data', 'Invalid data provided. key and event are required');
}
// take only first 200 characters
$data['event_key'] = substr($data['event_key'], 0, 192);
$data['title'] = substr($data['title'], 0, 192);
$eventData = [
'provider' => sanitize_text_field(Arr::get($data, 'provider', 'custom')),
'subscriber_id' => $subscriber->id,
'event_key' => sanitize_text_field($data['event_key']),
'title' => sanitize_text_field($data['title']),
'value' => sanitize_textarea_field(Arr::get($data, 'value', '')),
'counter' => 1 // This is actually the count of the event
];
if ($repeatable) {
// check if exist
$event = \FluentCrm\App\Models\EventTracker::where('subscriber_id', $subscriber->id)
->where('event_key', $eventData['event_key'])
->where('title', $eventData['title'])
->first();
if ($event) {
$event->value = $eventData['value'];
$event->counter++;
$event->save();
do_action('fluent_crm/event_tracked', $event, $subscriber);
return $event;
}
}
$createdEvent = \FluentCrm\App\Models\EventTracker::create($eventData);
do_action('fluent_crm/event_tracked', $createdEvent, $subscriber);
return $createdEvent;
}
private function getSubscriber($data)
{
if (!empty($data['subscriber'])) {
return $data['subscriber'];
}
// check for subscriber
if (empty($data['subscriber_id']) && empty($data['email']) && empty($data['user_id'])) {
$subscriber = fluentcrm_get_current_contact();
if ($subscriber) {
return $subscriber;
}
return new \WP_Error('subscriber_not_found', 'Current Subscriber could not be found');
}
$subscriber = null;
if (!empty($data['subscriber_id'])) {
$subscriber = Subscriber::where('id', $data['subscriber_id'])->first();
} else if (!empty($data['email'])) {
$subscriber = Subscriber::where('email', $data['email'])->first();
} else if (!empty($data['user_id'])) {
$user = get_user_by('ID', $data['user_id']);
if ($user) {
$subscriber = Subscriber::where('email', $user->user_email)->first();
}
}
if (!$subscriber) {
return new \WP_Error('subscriber_not_found', 'Subscriber not found');
}
return $subscriber;
}
}