Files
gustavoo-me/wp-content/plugins/fluent-crm/app/Http/Controllers/SettingsController.php
T
2026-07-23 22:56:30 -03:00

1184 lines
47 KiB
PHP

<?php
namespace FluentCrm\App\Http\Controllers;
use FluentCrm\App\Hooks\Handlers\ActivationHandler;
use FluentCrm\App\Models\ActivityLog;
use FluentCrm\App\Models\Campaign;
use FluentCrm\App\Models\CampaignEmail;
use FluentCrm\App\Models\CampaignUrlMetric;
use FluentCrm\App\Models\SystemLog;
use FluentCrm\App\Services\AutoSubscribe;
use FluentCrm\App\Services\DbPerformanceService;
use FluentCrm\App\Services\Helper;
use FluentCrm\App\Services\RoleBasedTagging;
use FluentCrm\Framework\Support\Arr;
use FluentCrm\Framework\Http\Request\Request;
/**
* SettingsController - REST API Handler Class
*
* REST API Handler
*
* @package FluentCrm\App\Http
*
* @version 1.0.0
*/
class SettingsController extends Controller
{
public function get(Request $request)
{
$existingSettings = get_option(FLUENTCRM . '-global-settings');
$keys = $request->get('settings_keys', []);
$returnSettings = [];
foreach ($keys as $key) {
if ($key == 'email_settings') {
$returnSettings[$key] = Helper::getGlobalEmailSettings();
} else if (isset($existingSettings[$key])) {
if ($key == 'business_settings') {
$existingSettings[$key] = fluentcrmGetGlobalSettings('business_settings');
}
$returnSettings[$key] = $existingSettings[$key];
}
}
return $returnSettings;
}
public function save(Request $request)
{
$settings = (array)$request->get('settings', []);
$existingSettings = get_option('fluentcrm-global-settings');
if (!$existingSettings) {
$existingSettings = [];
}
foreach ($settings as $settingsKey => $setting) {
$existingSettings[$settingsKey] = $setting;
if ($settingsKey == 'email_settings') {
$emailFooter = Arr::get($setting, 'email_footer');
if (!Helper::hasComplianceText($emailFooter)) {
return $this->sendError([
'message' => __('##crm.manage_subscription_url## or ##crm.unsubscribe_url## string is required for compliance. Please include unsubscription or manage subscription link', 'fluent-crm')
]);
}
}
}
update_option( 'fluentcrm-global-settings', $existingSettings );
return $this->sendSuccess([
'message' => __('Settings Updated', 'fluent-crm')
]);
}
public function getDoubleOptinSettings(Request $request)
{
//check if list id comes
$listId = $request->get('list_id', null);
$doubleOptinSettings = null;
//if list id sent then it is double optin setup of a list
if ($listId) {
$meta = fluentcrm_get_list_meta($listId, 'double_optin_settings');
$doubleOptinSettings = $meta ? $meta->value : null;
}
//if no double optin setup of list found or this is global
if (!$doubleOptinSettings) {
$doubleOptinSettings = Helper::getDoubleOptinSettings();
if (empty($doubleOptinSettings['tag_based_redirect'])) {
$doubleOptinSettings['tag_based_redirect'] = 'no';
$doubleOptinSettings['tag_redirects'] = [
[
'field_key' => [],
'field_value' => ''
]
];
}
}
$data = [
'settings' => $doubleOptinSettings
];
if ($listId) {
$globalDoubleOptin = fluentcrm_get_list_meta($listId, 'global_double_optin');
$data['global_double_optin'] = $globalDoubleOptin ? $globalDoubleOptin->value : 'yes';
}
if (in_array('settings_fields', $request->get('with', []))) {
$designTemplates = Helper::getEmailDesignTemplates();
$designTemplates = Arr::only($designTemplates, ['simple', 'plain', 'classic', 'raw_classic']);
$data['settings_fields'] = [
'design_template' => [
'type' => 'image-radio',
'label' => __('Design Template', 'fluent-crm'),
'help' => __('Email Design Template for this double-optin email', 'fluent-crm'),
'options' => $designTemplates
],
'email_subject' => [
'type' => 'input-text-popper',
'placeholder' => __('Optin Email Subject', 'fluent-crm'),
'label' => __('Email Subject', 'fluent-crm'),
'help' => __('Your double-optin email subject', 'fluent-crm')
],
'email_pre_header' => [
'type' => 'input-text-popper',
'placeholder' => __('Optin Email Pre Header', 'fluent-crm'),
'label' => __('Email Pre Header', 'fluent-crm'),
'help' => __('Your double-optin email pre header', 'fluent-crm')
],
'email_body' => [
'type' => 'wp-editor',
'placeholder' => __('Double-Optin Email Body', 'fluent-crm'),
'label' => __('Email Body', 'fluent-crm'),
'help' => __('Provide Email Body for the double-optin', 'fluent-crm'),
'inline_help' => __('Use #activate_link# for plain url or {{crm.activate_button|Confirm Subscription}} for default button', 'fluent-crm')
],
'confirmation_html_viewer' => [
'type' => 'html-viewer',
'heading' => __('After Confirmation Actions', 'fluent-crm'),
'info' => __('Please provide details after a contact confirms double opt-in from email', 'fluent-crm')
],
'after_confirmation_type' => [
'type' => 'input-radio',
'label' => __('After Confirmation Type', 'fluent-crm'),
'help' => __('Please select what will happen once a contact confirms double opt-in', 'fluent-crm'),
'options' => [
[
'id' => 'message',
'label' => __('Show Message', 'fluent-crm')
],
[
'id' => 'redirect',
'label' => __('Redirect to an URL', 'fluent-crm')
]
]
],
'after_confirm_message' => [
'type' => 'wp-editor',
'placeholder' => __('After Confirmation Message', 'fluent-crm'),
'label' => __('After Confirmation Message', 'fluent-crm'),
'help' => __('This message will be shown after a subscriber confirm subscription', 'fluent-crm'),
'dependency' => [
'depends_on' => 'after_confirmation_type',
'operator' => '=',
'value' => 'message'
]
],
'after_conf_redirect_url' => [
'type' => 'input-text-popper',
'placeholder' => __('Redirect URL', 'fluent-crm'),
'label' => __('Redirect URL', 'fluent-crm'),
'help' => __('Please provide redirect URL after confirmation', 'fluent-crm'),
'dependency' => [
'depends_on' => 'after_confirmation_type',
'operator' => '=',
'value' => 'redirect'
]
],
'tag_based_redirect' => [
'type' => 'inline-checkbox',
'checkbox_label' => (defined('FLUENTCAMPAIGN')) ? __('Enable Tag based double optin redirect', 'fluent-crm') : __('Enable Tag based double optin redirect (Require FluentCRM Pro)', 'fluent-crm'),
'true_label' => 'yes',
'false_label' => 'no',
'disabled' => !defined('FLUENTCAMPAIGN')
],
'tag_redirects' => [
'label' => __('Configure your redirect URLs based on tags (Will redirect to the provided URL if any selected tag matches the contact)', 'fluent-crm'),
'type' => 'form-many-drop-down-mapper',
'local_label' => __('Targeted Tags', 'fluent-crm'),
'local_placeholder' => __('Select Tags', 'fluent-crm'),
'remote_label' => __('Redirect URL (After Double Optin Confirmation)', 'fluent-crm'),
'field_option_selector' => [
'option_key' => 'tags',
'is_multiple' => true
],
'remote_field_type' => 'input-text-popper',
'remote_field' => [
'placeholder' => __('Redirect URL', 'fluent-crm')
],
'help' => __('User will be redirected to the URL which matches based on the tags at first match', 'fluent-crm'),
'dependency' => [
'depends_on' => 'tag_based_redirect',
'operator' => '=',
'value' => 'yes'
],
'manage_serial' => true
]
];
}
return $this->sendSuccess($data);
}
public function saveDoubleOptinSettings(Request $request)
{
$settings = wp_unslash($request->get('settings'));
$listId = $request->get('list_id');
$globalDoubleOptin = sanitize_text_field($request->get('global_double_optin'));
$this->validate($settings, [
'email_subject' => 'required',
'email_body' => 'required',
'after_confirm_message' => 'required'
], [
'email_subject.required' => __('Email Subject is required', 'fluent-crm'),
'email_body.required' => __('Email Body is required', 'fluent-crm'),
'after_confirm_message.required' => __('After Confirmation Message is required', 'fluent-crm')
]);
// let's check if message contains #activate_link# or {{crm.activate_button
$emailBody = $settings['email_body'];
if (
strpos($emailBody, '#activate_link#') === false &&
strpos($emailBody, '{{crm.activate_button') === false
) {
return $this->sendError([
'message' => __('Email Body need to contains activation link', 'fluent-crm')
]);
}
if ($listId) {
fluentcrm_update_list_meta($listId, 'double_optin_settings', $settings);
if ($globalDoubleOptin) {
fluentcrm_update_list_meta($listId, 'global_double_optin', $globalDoubleOptin);
}
} else {
fluentcrm_update_option('double_optin_settings', $settings);
}
return $this->sendSuccess([
'message' => __('Double Opt-in settings has been updated', 'fluent-crm')
]
);
}
public function TestRequestResolver(Request $request)
{
return [
'message' => __('Valid', 'fluent-crm'),
'params' => $request->all()
];
}
public function resetDB(Request $request)
{
if (!current_user_can('manage_options')) {
return $this->sendError([
'message' => __('Sorry, You do not have admin permission to reset database', 'fluent-crm')
]);
}
if (!defined('FLUENTCRM_IS_DEV_FEATURES') || !FLUENTCRM_IS_DEV_FEATURES) {
return $this->sendError([
'message' => __('Development mode is not activated. So you cannot use this feature. You can define "FLUENTCRM_IS_DEV_FEATURES" in your wp-config to enable this feature', 'fluent-crm')
]);
}
$tables = [
'fc_campaign_emails',
'fc_campaigns',
'fc_campaign_url_metrics',
'fc_funnel_metrics',
'fc_funnels',
'fc_funnel_sequences',
'fc_funnel_subscribers',
'fc_lists',
'fc_meta',
'fc_subscriber_meta',
'fc_subscriber_notes',
'fc_subscriber_pivot',
'fc_subscribers',
'fc_tags',
'fc_url_stores'
];
if (defined('FLUENTCAMPAIGN_PLUGIN_URL')) {
$tables[] = 'fc_sequence_tracker';
}
global $wpdb;
foreach ($tables as $table) {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query("DROP TABLE IF EXISTS " . $wpdb->prefix . $table);
}
// All tables are delete now let's run the migration
(new ActivationHandler)->handle(false);
if (defined('FLUENTCAMPAIGN_PLUGIN_URL')) {
\FluentCampaign\App\Migration\Migrate::run(false);
}
$options = [
'_fluentcrm_commerce_modules'
];
foreach ($options as $option) {
delete_option($option);
}
return [
'message' => __('All FluentCRM Database Tables have been reset', 'fluent-crm'),
'tables' => $tables
];
}
public function getBounceConfigs()
{
$securityCode = fluentcrm_get_option('_fc_bounce_key');
if (!$securityCode) {
$securityCode = 'fcrm_' . substr(md5(wp_generate_uuid4()), 0, 14); // first 14 digit
fluentcrm_update_option('_fc_bounce_key', $securityCode);
}
$bounceSettings = [
'ses' => [
'label' => __('Amazon SES', 'fluent-crm'),
'webhook_url' => site_url('index.php?fluentcrm=1&route=bounce_handler&provider=ses&verify_key=' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handler-with-amazon-ses/',
'input_title' => __('Amazon SES Bounce Handler URL', 'fluent-crm'),
'input_info' => __('Please use this bounce handler url in your Amazon SES + SNS settings', 'fluent-crm')
],
'tosend' => [
'label' => __('ToSend', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/tosend/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-tosend/',
'input_title' => __('ToSend Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your ToSend\'s Webhook settings to enable Bounce Handling with FluentCRM. Select both Bounce and Complaint events.', 'fluent-crm')
],
'mailgun' => [
'label' => __('Mailgun', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/mailgun/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-mailgun/',
'input_title' => __('Mailgun Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your Mailgun\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'pepipost' => [
'label' => __('PepiPost', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/pepipost/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-pepipost/',
'input_title' => __('PepiPost Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your PepiPost\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'postmark' => [
'label' => __('PostMark', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/postmark/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-postmark/',
'input_title' => __('PostMark Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your PostMark\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'sendgrid' => [
'label' => __('SendGrid', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/sendgrid/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-sendgrid/',
'input_title' => __('SendGrid Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your SendGrid\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'sparkpost' => [
'label' => __('SparkPost', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/sparkpost/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-sparkpost/',
'input_title' => __('SparkPost Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your SparkPost\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'elasticemail' => [
'label' => __('Elastic Email', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/elasticemail/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-elastic-email/',
'input_title' => __('Elastic Email Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your Elastic Email\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'postalserver' => [
'label' => __('Postal Server', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/postalserver/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-postal-server/',
'input_title' => __('Postal Server Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your Postal Server\'s Webhook settings to enable Bounce Handling with FluentCRM. Please select only MessageBounced & MessageDeliveryFailed event', 'fluent-crm')
],
'smtp2go' => [
'label' => __('SMTP2Go', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/smtp2go/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-smtp2go/',
'input_title' => __('SMTP2Go Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your SMTP2Go\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
'brevo' => [
'label' => __('Brevo (ex Sendinblue)', 'fluent-crm'),
'webhook_url' => get_rest_url(null, 'fluent-crm/v2/public/bounce_handler/brevo/handle/' . $securityCode),
'doc_url' => 'https://fluentcrm.com/docs/bounce-handling-with-brevo/',
'input_title' => __('Brevo Bounce Handler Webhook URL', 'fluent-crm'),
'input_info' => __('Please paste this URL into your Brevo\'s Webhook settings to enable Bounce Handling with FluentCRM', 'fluent-crm')
],
];
$data = [
/**
* Determine FluentCRM Bounce Handler settings.
*
* This filter allows modification of the bounce handler settings.
*
* @param array $bounceSettings The current bounce settings.
* @param string $securityCode The security code for the bounce handler.
* @since 2.5.95
*
*/
'bounce_settings' => apply_filters('fluent_crm/bounce_handlers', $bounceSettings, $securityCode)
];
if (defined('FLUENTMAIL')) {
$smtpSettings = get_option('fluentmail-settings', []);
if (!$smtpSettings || !count($smtpSettings['connections'])) {
$data['fluentsmtp_info'] = [
'configured' => false
];
} else {
$data['fluentsmtp_info'] = [
'configured' => true,
'verified_senders' => array_keys($smtpSettings['mappings'])
];
}
$data['fluentsmtp_info']['config_url'] = admin_url('options-general.php?page=fluent-mail#/connections');
} else {
$data['fluentsmtp_info'] = false;
}
return $data;
}
public function getAutoSubscribeSettings(Request $request)
{
$autoSubscribeService = new AutoSubscribe();
$roleBasedTaggingClass = new RoleBasedTagging();
$data = [
'registration_setting' => $autoSubscribeService->getRegistrationSettings(),
'comment_settings' => $autoSubscribeService->getCommentSettings(),
'user_syncing_settings' => $autoSubscribeService->getUserSyncSettings(),
'role_based_tagging_settings' => $roleBasedTaggingClass->getSettings(true),
'date_time_settings' => [
'classic_date_time' => Arr::get(Helper::getExperimentalSettings(), 'classic_date_time', 'no')
]
];
$with = $request->get('with', []);
if (in_array('fields', $with)) {
$data['registration_fields'] = $autoSubscribeService->getRegistrationFields();
$data['comment_fields'] = $autoSubscribeService->getCommentFields();
$data['user_syncing_fields'] = $autoSubscribeService->getUserSyncFields();
$data['role_based_tagging_settings_fields'] = $roleBasedTaggingClass->getFields();
}
if (defined('WC_PLUGIN_FILE')) {
$data['woo_checkout_fields'] = $autoSubscribeService->getWooCheckoutFields();
$data['woo_checkout_settings'] = $autoSubscribeService->getWooCheckoutSettings();
}
if (defined('FLUENTCART_VERSION')) {
$data['fluent_cart_checkout_fields'] = $autoSubscribeService->getFluentCartCheckoutFields();
$data['fluent_cart_checkout_settings'] = $autoSubscribeService->getFluentCartCheckoutSettings();
}
return $data;
}
public function saveAutoSubscribeSettings(Request $request)
{
$registrationSettings = $request->get('registration_setting', []);
$commentSettings = $request->get('comment_settings', []);
$userSyncSettings = $request->get('user_syncing_settings', []);
$dateTimeSettings = $request->get('date_time_settings', []);
fluentcrm_update_option('user_registration_subscribe_settings', $registrationSettings);
fluentcrm_update_option('comment_form_subscribe_settings', $commentSettings);
fluentcrm_update_option('user_syncing_settings', $userSyncSettings);
if (is_array($dateTimeSettings) && array_key_exists('classic_date_time', $dateTimeSettings)) {
$experimentalSettings = Helper::getExperimentalSettings();
$experimentalSettings['classic_date_time'] = sanitize_text_field($dateTimeSettings['classic_date_time']) === 'yes' ? 'yes' : 'no';
update_option('_fluentcrm_experimental_settings', $experimentalSettings, 'yes');
}
if (defined('FLUENTCAMPAIGN_PLUGIN_VERSION')) {
$roleBasedSettings = $request->get('role_based_tagging_settings', []);
fluentcrm_update_option('role_based_tagging_settings', $roleBasedSettings);
}
if (defined('WC_PLUGIN_FILE') && defined('FLUENTCAMPAIGN_DIR_FILE')) {
$wooCheckoutSettings = $request->get('woo_checkout_settings');
fluentcrm_update_option('woo_checkout_form_subscribe_settings', $wooCheckoutSettings);
fluentCrmSetCache('woo_checkout_form_subscribe_settings', $wooCheckoutSettings, 86400);
}
if (defined('FLUENTCART_VERSION')) {
$fluentCartCheckoutSettings = (new AutoSubscribe())->sanitizeFluentCartCheckoutSettings(
$request->get('fluent_cart_checkout_settings', [])
);
fluentcrm_update_option('fluent_cart_checkout_form_subscribe_settings', $fluentCartCheckoutSettings);
}
return [
'message' => __('Settings has been updated', 'fluent-crm')
];
}
public function getCronStatus()
{
$events = [];
$nextRun = Helper::getNextMinuteTaskTimeStamp();
$events[] = (object)array(
'hook' => 'fluentcrm_scheduled_every_minute_tasks',
'is_overdue' => (time() - $nextRun) > 30,
'human_name' => __('Scheduled Email Sending Tasks', 'fluent-crm'),
'next_run' => human_time_diff($nextRun, time()),
'interval' => 60
);
$nextFiverMinutesRun = wp_next_scheduled('fluentcrm_scheduled_five_minute_tasks');
$events[] = (object)array(
'hook' => 'fluentcrm_scheduled_hourly_tasks',
'is_overdue' => ($nextFiverMinutesRun - time()) < -60,
'human_name' => __('Scheduled Email Processing', 'fluent-crm'),
'next_run' => human_time_diff($nextFiverMinutesRun, time()),
'interval' => 300
);
$nextHourlyRun = wp_next_scheduled('fluentcrm_scheduled_hourly_tasks');
$events[] = (object)array(
'hook' => 'fluentcrm_scheduled_hourly_tasks',
'is_overdue' => ($nextHourlyRun - time()) < -120,
'human_name' => __('Scheduled Automation Tasks', 'fluent-crm'),
'next_run' => human_time_diff($nextHourlyRun, time()),
'interval' => 3600
);
return [
'cron_events' => $events,
'server' => [
'memory_limit' => intval(fluentCrmGetMemoryLimit() / 1048576) . 'MB',
'usage_percent' => fluentCrmGetMemoryUsagePercentage(),
'max_execution_time' => fluentCrmMaxRunTime() . ' seconds',
'has_server_cron' => defined('DISABLE_WP_CRON') && DISABLE_WP_CRON
]
];
}
public function runCron(Request $request)
{
$hookName = $request->get('hook');
$hookNames = [
'fluentcrm_scheduled_every_minute_tasks' => __('Scheduled Email Sending', 'fluent-crm'),
'fluentcrm_scheduled_hourly_tasks' => __('Scheduled Automation Tasks', 'fluent-crm'),
'fluentcrm_scheduled_five_minute_tasks' => __('Scheduled Email Processing', 'fluent-crm')
];
if (!isset($hookNames[$hookName])) {
return $this->sendError([
'message' => __('The provided hook name is not valid', 'fluent-crm')
]);
}
do_action($hookName);
return [
'message' => __('Selected CRON Event successfully ran', 'fluent-crm')
];
}
/**
* Return the health of the critical DB indexes for the settings UI.
*
* Cheap by default: serves the cached snapshot unless ?fresh=1 is passed
* (e.g. to re-verify right after a repair).
*
* @param Request $request
* @return array
*/
public function getDbIndexHealth(Request $request)
{
$fromDb = $request->getSafe('fresh', 'sanitize_text_field') === '1';
return $this->sendSuccess([
'indexes' => array_values(DbPerformanceService::getIndexHealth($fromDb))
]);
}
/**
* Repair any missing/broken critical DB indexes.
*
* Runs inline (the ALTERs are idempotent and prefer a non-blocking online
* build) behind a short transient lock so concurrent admin tabs/users can't
* trigger overlapping ALTERs on the same tables. If a repair is already in
* flight we report that rather than stacking a second one.
*
* @param Request $request
* @return array
*/
public function repairDbIndexes(Request $request)
{
// Re-check from the live DB so a stale "ok" cache can't make us skip a
// genuinely needed repair (and a stale "broken" cache can't force a
// pointless ALTER pass).
if (!DbPerformanceService::hasBrokenIndex(true)) {
return $this->sendSuccess([
'message' => __('All database indexes are healthy.', 'fluent-crm'),
'indexes' => array_values(DbPerformanceService::getIndexHealth(false))
]);
}
$lockKey = 'fc_db_index_repairing';
if (get_transient($lockKey)) {
// Not an error — another tab/request is already repairing. Report it
// as a benign "pending" success so the client doesn't surface a
// false failure notice while the other request finishes the work.
return $this->sendSuccess([
'pending' => true,
'message' => __('A database index repair is already running.', 'fluent-crm'),
'indexes' => array_values(DbPerformanceService::getIndexHealth(false))
]);
}
set_transient($lockKey, 1, 5 * MINUTE_IN_SECONDS);
try {
$result = DbPerformanceService::repairBrokenIndexes();
} finally {
delete_transient($lockKey);
}
if ($result['failed']) {
return $this->sendError([
'message' => __('Some database indexes could not be repaired. Please check your database user privileges or contact your host.', 'fluent-crm'),
'failed' => $result['failed'],
'indexes' => array_values($result['health'])
], 422);
}
return $this->sendSuccess([
'message' => __('Database indexes repaired successfully.', 'fluent-crm'),
'repaired' => $result['repaired'],
'indexes' => array_values($result['health'])
]);
}
public function getOldLogDetails(Request $request)
{
$data = $request->all();
$this->validate($data, [
'days_before' => 'required|numeric|min:7',
'selected_logs' => 'array|required'
]);
$selectedLogs = $data['selected_logs'];
$daysBefore = $data['days_before'];
$refDate = gmdate('Y-m-d 00:00:01', time() - $daysBefore * 86400);
$dataCounters = [];
if (in_array('emails', $selectedLogs)) {
$dataCounters[] = [
'title' => __('Email History Logs', 'fluent-crm'),
'count' => CampaignEmail::where('created_at', '<', $refDate)
->where('status', 'sent')
->count()
];
}
if (in_array('email_clicks', $selectedLogs)) {
$dataCounters[] = [
'title' => __('Email clicks', 'fluent-crm'),
'count' => CampaignUrlMetric::where('type', 'click')
->where('created_at', '<', $refDate)
->count()
];
}
if (in_array('email_open', $selectedLogs)) {
$dataCounters[] = [
'title' => __('Email Opens', 'fluent-crm'),
'count' => CampaignUrlMetric::where('type', 'open')
->where('created_at', '<', $refDate)
->count()
];
}
if (in_array('system_logs', $selectedLogs)) {
$dataCounters[] = [
'title' => __('System Logs', 'fluent-crm'),
'count' => SystemLog::where('created_at', '<', $refDate)
->count()
];
}
if (in_array('activity_logs', $selectedLogs)) {
$dataCounters[] = [
'title' => __('Activity Logs', 'fluent-crm'),
'count' => ActivityLog::where('created_at', '<', $refDate)
->count()
];
}
return [
'log_counts' => $dataCounters
];
}
public function removeOldLogs(Request $request)
{
$data = $request->all();
$this->validate($data, [
'days_before' => 'required|numeric|min:7',
'selected_logs' => 'array|required'
]);
$selectedLogs = $data['selected_logs'];
$daysBefore = $data['days_before'];
$perChunk = 10000; // Deleting 10,000 per chunk
$hasMore = false;
$refDate = gmdate('Y-m-d 00:00:01', time() - $daysBefore * 86400);
if (in_array('emails', $selectedLogs)) {
$campaignIds = CampaignEmail::where('created_at', '<', $refDate)
->where('status', 'sent')
->groupBy('campaign_id')
->pluck('campaign_id');
foreach ($campaignIds->toArray() as $campaignId) {
fluentcrm_update_campaign_meta($campaignId, '_data_trunked', 'yes');
}
CampaignEmail::where('created_at', '<', $refDate)
->where('status', 'sent')
->limit($perChunk)
->delete();
$hasMore = CampaignEmail::where('created_at', '<', $refDate)
->where('status', 'sent')
->exists();
}
$urlMetricsTypes = [];
if (in_array('email_clicks', $selectedLogs)) {
$urlMetricsTypes[] = 'click';
}
if (in_array('email_open', $selectedLogs)) {
$urlMetricsTypes[] = 'open';
}
if ($urlMetricsTypes) {
CampaignUrlMetric::whereIn('type', $urlMetricsTypes)
->where('created_at', '<', $refDate)
->limit($perChunk)
->delete();
if (!$hasMore) {
$hasMore = CampaignUrlMetric::whereIn('type', $urlMetricsTypes)
->where('created_at', '<', $refDate)
->exists();
}
}
if (in_array('system_logs', $selectedLogs)) {
SystemLog::where('created_at', '<', $refDate)
->limit($perChunk)
->delete();
if (!$hasMore) {
$hasMore = SystemLog::where('created_at', '<', $refDate)
->exists();
}
}
if (in_array('activity_logs', $selectedLogs)) {
ActivityLog::where('created_at', '<', $refDate)
->limit($perChunk)
->delete();
if (!$hasMore) {
$hasMore = ActivityLog::where('created_at', '<', $refDate)
->exists();
}
}
return [
/* translators: %d is the number of days; used to indicate how old the deleted logs were. */
'message' => sprintf(__('Logs older than %d days have been deleted successfully', 'fluent-crm'), $daysBefore),
'has_more' => $hasMore
];
}
public function deleteRestKey(Request $request)
{
$data = $request->all();
$this->validate($data, [
'user_id' => 'required',
'uuid' => 'required'
]);
$data['user_id'] = intval($data['user_id']);
$data['uuid'] = sanitize_text_field($data['uuid']);
if (!get_user_meta($data['user_id'], '_fcrm_has_role', true)) {
return $this->sendError([
'message' => __('Sorry, the provided user does not have FluentCRM access', 'fluent-crm')
]);
}
if (!current_user_can('manage_options')) {
return $this->sendError([
'message' => __('Sorry, You do not have permission to delete REST API', 'fluent-crm')
]);
}
$deleted = \WP_Application_Passwords::delete_application_password($data['user_id'], $data['uuid']);
if ($deleted) {
$applicationUsers = fluentcrm_get_option('_rest_api_users', []);
foreach ($applicationUsers[$data['user_id']] as $index => $uuid) {
if ($uuid == $data['uuid']) {
array_splice($applicationUsers[$data['user_id']], $index, 1);
}
}
fluentcrm_update_option('_rest_api_users', $applicationUsers);
}
if (!$deleted) {
return $this->sendError([
'message' => __('Something is wrong', 'fluent-crm')
]);
}
return [
'message' => __('API Key has been successfully deleted', 'fluent-crm')
];
}
public function getRestKeys(Request $request)
{
$query = new \WP_User_Query(array(
'meta_key' => '_fcrm_has_role',
'meta_value' => 1,
'meta_compare' => '=',
'number' => 200
));
$managers = [];
foreach ($query->get_results() as $user) {
if (user_can($user, 'manage_options')) {
continue;
}
$managers[] = [
'id' => $user->ID,
'full_name' => $user->first_name . ' - ' . $user->last_name,
'email' => $user->user_email
];
}
$applicationUsers = fluentcrm_get_option('_rest_api_users', []);
$restApps = [];
if ($applicationUsers) {
$userIds = array_keys($applicationUsers);
$restUsers = get_users([
'include' => $userIds,
'number' => 20
]);
foreach ($restUsers as $restUser) {
$applicationUUIDs = $applicationUsers[$restUser->ID];
$passwords = get_user_meta($restUser->ID, '_application_passwords', true);
$crmApps = [];
foreach ($passwords as $password) {
if (in_array($password['uuid'], $applicationUUIDs)) {
$crmApps[] = [
'uuid' => $password['uuid'],
'name' => $password['name'],
'created' => gmdate('Y-m-d H:i:s', $password['created'])
];
}
}
if (!$crmApps) {
continue;
}
$restApps[] = [
'id' => $restUser->ID,
'first_name' => $restUser->first_name,
'last_name' => $restUser->last_name,
'email' => $restUser->user_email,
'api_keys' => $crmApps,
'manage_url' => admin_url('user-edit.php?user_id=' . $restUser->ID . '#application-passwords-section')
];
}
}
return [
'managers' => $managers,
'rest_keys' => $restApps
];
}
public function createRestKey(Request $request)
{
$data = $request->all();
$this->validate($data, [
'api_name' => 'required',
'api_user_id' => 'required'
]);
$data['api_user_id'] = intval($data['api_user_id']);
$data['api_name'] = sanitize_text_field($data['api_name']);
// check if the provided user has FluentCRM Access
if (!get_user_meta($data['api_user_id'], '_fcrm_has_role', true)) {
return $this->sendError([
'message' => __('Sorry, the provided user does not have FluentCRM access', 'fluent-crm')
]);
}
if (!current_user_can('manage_options')) {
return $this->sendError([
'message' => __('Sorry, You do not have permission to create REST API', 'fluent-crm')
]);
}
$user = get_user_by('ID', $data['api_user_id']);
if (is_wp_error($user)) {
return $this->sendError([
'message' => __('Sorry, the provided user does not have FluentCRM access', 'fluent-crm')
]);
}
$prepared = (object)[
'name' => $data['api_name']
];
$created = \WP_Application_Passwords::create_new_application_password($user->ID, wp_slash((array)$prepared));
if (is_wp_error($created)) {
return $this->sendError([
'message' => $created->get_error_message()
]);
}
$password = $created[0];
$item = \WP_Application_Passwords::get_user_application_password($user->ID, $created[1]['uuid']);
$item['info'] = [
'api_password' => \WP_Application_Passwords::chunk_password($password),
'api_username' => $user->user_login,
];
$uuid = $item['uuid'];
$applicationUsers = fluentcrm_get_option('_rest_api_users', []);
if (!isset($applicationUsers[$user->ID])) {
$applicationUsers[$user->ID] = [];
}
$applicationUsers[$user->ID][] = $uuid;
fluentcrm_update_option('_rest_api_users', $applicationUsers);
return [
'item' => $item,
'message' => __('API Key has been successfully created', 'fluent-crm')
];
}
public function getIntegrations(Request $request)
{
$withFields = in_array('fields', $request->get('with', []));
/**
* Determine the deep integration providers for FluentCRM.
*
* This filter allows modification of the deep integration providers used in FluentCRM such as Woocommerce, Easy Digital Downloads, etc.
*
* @param array An array of deep integration providers.
* @param bool $withFields Whether to include fields in the integration providers.
* @since 2.5.1
*
*/
$deepIntegrationProviders = apply_filters('fluentcrm_deep_integration_providers', [], $withFields);
return [
'integrations' => $deepIntegrationProviders
];
}
public function saveIntegration(Request $request)
{
$provider = $request->get('provider');
$action = $request->get('action');
$data = $request->all();
if ($action == 'sync') {
/**
* Determine whether to allow deep integration sync for a specific provider.
*
* This filter allows you to modify the result of the deep integration sync for a given provider.
*
* @param mixed The result of the integration sync. Default false. Expected to be a boolean.
* @param array $data The data to be synced.
* @since 2.5.1
*
*/
$result = apply_filters('fluentcrm_deep_integration_sync_' . $provider, false, $data);
} else {
/**
* Determine the result of saving deep integration settings for a specific provider.
*
* The dynamic portion of the hook name, `$provider`, refers to the specific integration provider.
*
* @param mixed The result of the save operation. Default false. Expected to be a boolean.
* @param array $data The data being saved.
* @since 2.5.1
*
*/
$result = apply_filters('fluentcrm_deep_integration_save_' . $provider, false, $data);
}
if ($result) {
if (is_wp_error($result)) {
return $this->sendError([
'message' => $result->get_error_message()
]);
}
return $result;
}
return $this->sendError([
'message' => __('Sorry, the provided provider does not exist', 'fluent-crm')
]);
}
public function getComplianceSettings(Request $request)
{
return [
'settings' => Helper::getComplianceSettings()
];
}
public function updateComplianceSettings(Request $request)
{
$data = Arr::only($request->all(), array_keys(Helper::getComplianceSettings()));
$validValues = [
'yes',
'no',
'anonymous'
];
foreach ($data as $key => $datum) {
if (!in_array($datum, $validValues)) {
$data[$key] = '';
} else {
$data[$key] = $datum;
}
}
update_option('_fluentcrm_compliance_settings', $data, 'no');
do_action('fluent_crm/sync_subscriber_delete_setting', 'compliance_settings', $data['delete_contact_on_user']);
return [
'message' => __('Settings has been successfully updated', 'fluent-crm'),
'settings' => $data
];
}
public function getExperimentalSettings(Request $request)
{
return [
'settings' => Helper::getExperimentalSettings()
];
}
public function updateExperimentalSettings(Request $request)
{
$data = Arr::only($request->all(), array_keys(Helper::getExperimentalSettings()));
foreach ($data as $key => $datum) {
if ($key === 'campaign_ids' && is_array($datum)) {
$data[$key] = array_map('intval', $datum);
} elseif ($key === 'frontend_portal_page_id') {
$data[$key] = absint($datum);
} elseif ($key === 'frontend_portal_slug') {
$data[$key] = sanitize_title($datum);
} elseif ($key === 'frontend_portal_render_type') {
$renderType = sanitize_text_field($datum);
$data[$key] = in_array($renderType, ['standalone', 'shortcode'], true) ? $renderType : 'standalone';
} else {
$data[$key] = sanitize_text_field($datum);
}
}
if (Arr::get($data, 'frontend_portal') === 'yes' && empty($data['frontend_portal_slug'])) {
$data['frontend_portal_slug'] = 'fluentcrm';
}
if (Arr::get($data, 'company_module') == 'yes') {
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/CompaniesMigrator.php');
\FluentCrmMigrations\CompaniesMigrator::migrate();
}
if (Arr::get($data, 'event_tracking') == 'yes') {
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/SubscriberEventTracking.php');
\FluentCrmMigrations\SubscriberEventTracking::migrate();
}
if (Arr::get($data, 'activity_log') == 'yes') {
require_once(FLUENTCRM_PLUGIN_PATH . 'database/migrations/ActivityLogsMigrator.php');
\FluentCrmMigrations\ActivityLogsMigrator::migrate();
}
update_option('_fluentcrm_experimental_settings', $data, 'yes');
return [
'message' => __('Settings has been updated', 'fluent-crm')
];
}
public function getCampaigns(Request $request)
{
$campaigns = Campaign::orderBy('id', 'DESC')->get();
return [
'campaigns' => $campaigns
];
}
}