__('All Contacts', 'fluent-crm'), 'count' => Subscriber::count(), ], [ 'title' => __('Subscribers', 'fluent-crm'), 'count' => Subscriber::where('status', 'subscribed')->count(), ], [ 'title' => __('Campaigns', 'fluent-crm'), 'count' => Campaign::count(), ], [ 'title' => __('Automations', 'fluent-crm'), 'count' => Funnel::count(), ], [ 'title' => __('All Emails', 'fluent-crm'), 'count' => CampaignEmail::count(), ], [ 'title' => __('Send Emails', 'fluent-crm'), 'count' => CampaignEmail::where('status', 'sent')->count(), ], [ 'title' => __('Scheduled Emails', 'fluent-crm'), 'count' => CampaignEmail::where('status', 'scheduled')->count(), ], [ 'title' => __('Max Rune Time', 'fluent-crm'), 'count' => fluentCrmMaxRunTime() . ' Seconds' ] ]; $format = \WP_CLI\Utils\get_flag_value($assoc_args, 'format', 'table'); \WP_CLI\Utils\format_items( $format, $overallStats, ['title', 'count'] ); } /** * Sync EDD 3 customers into FluentCRM commerce tables from EDD order data. */ public function sync_edd_customers($args, $assoc_args) { $this->requireEdd3Cli(); $tags = \WP_CLI\Utils\get_flag_value($assoc_args, 'tags', ''); $lists = \WP_CLI\Utils\get_flag_value($assoc_args, 'lists', ''); $contactStatus = \WP_CLI\Utils\get_flag_value($assoc_args, 'contact_status', 'subscribed'); $fire_event = \WP_CLI\Utils\get_flag_value($assoc_args, 'event', 'no'); if (!in_array($contactStatus, ['pending', 'subscribed'])) { \WP_CLI::error('Possible contact_status value: pending|subscribed'); } $formattedTags = []; $formattedTagNames = []; if ($tags) { $tags = array_unique(array_filter(array_filter(explode(',', $tags), 'trim'), 'absint')); if ($tags) { $allTags = Tag::whereIn('id', $tags)->get(); foreach ($allTags as $tag) { $formattedTagNames[] = $tag->title . ' (' . $tag->id . ')'; $formattedTags[] = $tag->id; } } } $formattedListNames = []; $formattedLists = []; if (trim($lists)) { $lists = array_unique(array_filter(array_filter(explode(',', $lists), 'trim'), 'absint')); if (count($lists)) { $allLists = Lists::whereIn('id', $lists)->get(); foreach ($allLists as $list) { $formattedListNames[] = $list->title . ' (' . $list->id . ')'; $formattedLists[] = $list->id; } } } if (!defined('FLUENTCAMPAIGN')) { \WP_CLI::error('FluentCRM Pro is required'); } $isMigrated = Commerce::isMigrated(true); if (!$isMigrated) { \WP_CLI::line('Migrating Initial Database'); Commerce::migrate(); \WP_CLI::line('Initial Database Migration done. Going to next step...'); } else { \WP_CLI::line('Initial Database exist. Going to next step...'); Commerce::resetModuleData('edd'); } $customersTotal = fluentCrmDb()->table('edd_customers')->count(); \WP_CLI\Utils\format_items( 'table', [ [ 'status' => __('Completed', 'fluent-crm'), 'count' => fluentCrmDb()->table('edd_orders') ->whereIn('status', ['complete', 'completed']) ->count() ], [ 'status' => __('Processing', 'fluent-crm'), 'count' => fluentCrmDb()->table('edd_orders')->where('status', 'processing')->count() ], [ 'status' => __('Subscription Payments', 'fluent-crm'), 'count' => fluentCrmDb()->table('edd_orders')->where('status', 'edd_subscription')->count() ], [ 'status' => __('Customer Counts', 'fluent-crm'), 'count' => $customersTotal ] ], ['status', 'count'] ); \WP_CLI::line('The following Tags, Lists & Status will be applied to the customers:'); \WP_CLI\Utils\format_items('yaml', [ [ 'type' => __('Tags', 'fluent-crm'), 'Value' => $formattedTagNames ], [ 'type' => __('Lists', 'fluent-crm'), 'Value' => $formattedListNames ], [ 'type' => __('Status', 'fluent-crm'), 'Value' => $contactStatus ] ], ['type', 'Value']); if ($contactStatus == 'pending') { \WP_CLI::line('---'); \WP_CLI::line('A Double optin email will be sent to contacts who does not have subscribed status'); } \WP_CLI::confirm('Do you want to continue?'); \WP_CLI::line('Nice! Starting data syncing'); $limit = 30; $offset = 0; $processingStatus = true; if ($fire_event == 'no') { if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) { define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true); } } $skippedContacts = []; $resultItems = []; // EDD 3 sync reads canonical edd_orders statuses only. $paymentStatuses = ['edd_subscription', 'processing', 'complete', 'completed', 'partially_refunded']; $progress = \WP_CLI\Utils\make_progress_bar('Synced Customers', $customersTotal); while ($processingStatus) { $customers = fluentCrmDb()->table('edd_customers') ->limit($limit) ->offset($offset) ->get(); $offset += $limit; if ($customers->isEmpty()) { $processingStatus = false; } else { foreach ($customers as $customer) { $result = \FluentCampaign\App\Services\Integrations\Edd\EddCommerceHelper::syncCommerceCustomer( $customer, $contactStatus, $paymentStatuses, $formattedTags, $formattedLists ); if ($result) { $progress->tick(); $resultItems[] = [ 'contact_id' => $result['relation']->subscriber_id, 'email' => $result['subscriber']->email, 'status' => $result['subscriber']->status, 'lifetime_value' => $result['relation']->total_order_value, 'order_count' => $result['relation']->total_order_count, ]; } else { $skippedContacts[] = [ 'name' => $customer->name, 'email' => $customer->email, 'value' => number_format($customer->purchase_value, 2, '.', '') ]; } } } } Commerce::enableModule('edd'); Commerce::cacheStoreAverage('edd'); $relationCount = ContactRelationModel::provider('edd')->count(); \WP_CLI::line(sprintf('Awesome! %d customers has been synced', $relationCount)); if ($skippedContacts) { \WP_CLI::line(sprintf('%d contacts has been skipped', count($skippedContacts))); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fwrite(STDOUT, 'Show Skipped contacts? yes/no' . ' '); $value = strtolower(trim(fgets(STDIN))); if ($value == 'yes') { \WP_CLI\Utils\format_items( 'table', $skippedContacts, ['name', 'email', 'value'] ); } } \WP_CLI::line('Nice. All Done'); } /** * Remove synced EDD commerce data only when EDD 3 is available. */ public function disable_edd_sync() { $this->requireEdd3Cli(); $module = 'edd'; Commerce::disableModule($module); ContactRelationModel::provider($module)->delete(); ContactRelationItemsModel::provider($module)->delete(); if (!ContactRelationModel::first()) { global $wpdb; $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}fc_contact_relations"); $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}fc_contact_relation_items"); } fluentcrm_update_option('_' . $module . '_customer_sync_count', 0); fluentcrm_delete_option('_' . $module . '_store_average'); \WP_CLI::line('EDD Data with FluentCRM has been removed'); } /* * Usage: wp fluent_crm sync_woo_customers --tags=TAG_IDS --lists=LISTS_IDS * tags and lists needs to be comma separated values * If you just want to sync just run: * wp fluent_crm sync_woo_customers */ public function sync_woo_customers($args, $assoc_args) { if (!defined('WC_PLUGIN_FILE')) { \WP_CLI::error('WooCommerce is not installed'); } $tags = \WP_CLI\Utils\get_flag_value($assoc_args, 'tags', ''); $lists = \WP_CLI\Utils\get_flag_value($assoc_args, 'lists', ''); $contactStatus = \WP_CLI\Utils\get_flag_value($assoc_args, 'contact_status', 'subscribed'); $fire_event = \WP_CLI\Utils\get_flag_value($assoc_args, 'event', 'no'); if (!in_array($contactStatus, ['pending', 'subscribed'])) { \WP_CLI::error('Possible contact_status value: pending|subscribed'); } $formattedTags = []; $formattedLists = []; $formattedTagNames = []; $formattedListNames = []; if ($tags) { $tags = array_unique(array_filter(array_filter(explode(',', $tags), 'trim'), 'absint')); if ($tags) { $allTags = Tag::whereIn('id', $tags)->get(); foreach ($allTags as $tag) { $formattedTagNames[] = $tag->title . ' (' . $tag->id . ')'; $formattedTags[] = $tag->id; } } } if ($lists) { $lists = array_unique(array_filter(array_filter(explode(',', $lists), 'trim'), 'absint')); if ($lists) { $allLists = Lists::whereIn('id', $lists)->get(); foreach ($allLists as $list) { $formattedListNames[] = $list->title . ' (' . $list->id . ')'; $formattedLists[] = $list->id; } } } if (!defined('FLUENTCAMPAIGN')) { \WP_CLI::error('FluentCRM Pro is required for this command'); } $isMigrated = Commerce::isMigrated(true); $offset = 0; if (!$isMigrated) { \WP_CLI::line('Migrating Initial Database'); Commerce::migrate(); \WP_CLI::line('Initial Database Migration done. Going to next step...'); } else { \WP_CLI::line('Initial Database exist. Going to next step...'); $completedCount = ContactRelationModel::where('provider', 'woo')->count(); if ($completedCount) { // ask if they want to resume \WP_CLI::line(sprintf('Looks like you have migrated %d customers already.', $completedCount)); // Adding space to question and showing it. // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fwrite(STDOUT, 'Do you want to resume? Type YES to resume or type NO start from scratch: ' . ' '); $result = strtolower(trim(fgets(STDIN))); if ($result == 'yes') { $offset = $completedCount; \WP_CLI::line('Nice! Resuming your data syncing'); } else { Commerce::resetModuleData('woo'); } } else { Commerce::resetModuleData('woo'); } } $customersTotal = fluentCrmDb()->table('wc_customer_lookup')->count(); $orderStats = []; $statuses = wc_get_is_paid_statuses(); foreach ($statuses as $status) { $orderStats[] = [ 'status' => ucfirst($status), 'count' => fluentCrmDb()->table('posts')->where('post_type', 'shop_order ')->where('post_status', 'wc-' . $status)->count() ]; } \WP_CLI\Utils\format_items('table', $orderStats, ['status', 'count']); \WP_CLI::line('The following Tags, Lists & Status will be applied to the customers:'); \WP_CLI\Utils\format_items('yaml', [ [ 'type' => __('Tags', 'fluent-crm'), 'Value' => $formattedTagNames ], [ 'type' => __('Lists', 'fluent-crm'), 'Value' => $formattedListNames ], [ 'type' => __('Status', 'fluent-crm'), 'Value' => $contactStatus ] ], ['type', 'Value']); if ($contactStatus == 'pending') { \WP_CLI::line('---'); \WP_CLI::line('A Double optin email will be sent to contacts who does not have subscribed status'); } \WP_CLI::confirm('Do you want to continue?'); \WP_CLI::line('Nice! Starting data syncing'); $limit = 10; $processingStatus = true; if ($fire_event == 'no') { if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) { define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true); } } Commerce::resetModuleData('woo'); $skippedContacts = []; $resultItems = []; $progress = \WP_CLI\Utils\make_progress_bar('Synced Customers', $customersTotal); $processedOrdersCount = 0; while ($processingStatus) { $customers = fluentCrmDb()->table('wc_customer_lookup') ->orderBy('customer_id', 'ASC') ->limit($limit) ->offset($offset) ->get(); $offset += 10; if ($customers->isEmpty()) { $processingStatus = false; } else { foreach ($customers as $customer) { $result = \FluentCampaign\App\Services\Integrations\WooCommerce\WooSyncHelper::syncCommerceCustomer($customer, $contactStatus, $statuses, $formattedTags, $formattedLists); if ($result) { $processedOrdersCount += $result['orders_count']; $progress->tick(); $resultItems[] = [ 'contact_id' => $result['relation']->subscriber_id, 'email' => $result['subscriber']->email, 'status' => $result['subscriber']->status, 'lifetime_value' => $result['relation']->total_order_value, 'order_count' => $result['relation']->total_order_count, ]; } else { $skippedContacts[] = [ 'name' => $customer->first_name . ' ' . $customer->last_name, 'email' => $customer->email ]; } } } } Commerce::enableModule('woo'); Commerce::cacheStoreAverage('woo'); $relationCount = ContactRelationModel::provider('woo')->count(); \WP_CLI::line(sprintf('Awesome! %d customers has been synced', $relationCount)); \WP_CLI::line(sprintf('%d orders has been synced', $processedOrdersCount)); if ($skippedContacts) { \WP_CLI::line(sprintf('%d contacts has been skipped', count($skippedContacts))); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fwrite(STDOUT, 'Show Skipped contacts? yes/no' . ': '); $value = strtolower(trim(fgets(STDIN))); if ($value == 'yes') { \WP_CLI\Utils\format_items( 'table', $skippedContacts, ['name', 'email'] ); } } \WP_CLI::line('Nice. All Done'); } public function disable_woo_sync() { $module = 'woo'; Commerce::disableModule($module); ContactRelationModel::provider($module)->delete(); ContactRelationItemsModel::provider($module)->delete(); if (!ContactRelationModel::first()) { global $wpdb; $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}fc_contact_relations"); $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}fc_contact_relation_items"); } fluentcrm_update_option('_' . $module . '_customer_sync_count', 0); fluentcrm_delete_option('_' . $module . '_store_average'); \WP_CLI::line('WooCommerce Data with FluentCRM has been removed'); } /* * Usage: wp fluent_crm sync_learndash_students --tags=TAG_IDS --lists=LISTS_IDS * tags and lists needs to be comma separated values * If you just want to sync just run: * wp fluent_crm sync_learndash_students */ public function sync_learndash_students($args, $assoc_args) { if (!defined('LEARNDASH_VERSION')) { \WP_CLI::error('LearnDash is not installed'); } $tags = \WP_CLI\Utils\get_flag_value($assoc_args, 'tags', ''); $lists = \WP_CLI\Utils\get_flag_value($assoc_args, 'lists', ''); $contactStatus = \WP_CLI\Utils\get_flag_value($assoc_args, 'contact_status', 'subscribed'); $fire_event = \WP_CLI\Utils\get_flag_value($assoc_args, 'event', 'no'); if (!in_array($contactStatus, ['pending', 'subscribed'])) { \WP_CLI::error('Possible contact_status value: pending|subscribed'); } $formattedTags = []; $formattedLists = []; $formattedTagNames = []; $formattedListNames = []; if ($tags) { $tags = array_unique(array_filter(array_filter(explode(',', $tags), 'trim'), 'absint')); if ($tags) { $allTags = Tag::whereIn('id', $tags)->get(); foreach ($allTags as $tag) { $formattedTagNames[] = $tag->title . ' (' . $tag->id . ')'; $formattedTags[] = $tag->id; } } } if ($lists) { $lists = array_unique(array_filter(array_filter(explode(',', $lists), 'trim'), 'absint')); if ($lists) { $allLists = Lists::whereIn('id', $lists)->get(); foreach ($allLists as $list) { $formattedListNames[] = $list->title . ' (' . $list->id . ')'; $formattedLists[] = $list->id; } } } if (!defined('FLUENTCAMPAIGN')) { \WP_CLI::error('FluentCRM Pro is required for this command'); } $isMigrated = Commerce::isMigrated(true); if (!$isMigrated) { \WP_CLI::line('Migrating Initial Database'); Commerce::migrate(); \WP_CLI::line('Initial Database Migration done. Going to next step...'); } else { \WP_CLI::line('Initial Database exist. Going to next step...'); Commerce::resetModuleData('learndash'); } $settings = [ 'tags' => $formattedTags, 'lists' => $formattedLists, 'contact_status' => $contactStatus ]; fluentcrm_update_option('_learndash_sync_settings', $settings); $studentsCount = fluentCrmDb()->table('usermeta') ->select([ fluentCrmDb()->raw('DISTINCT(user_id) as student_user_id'), ]) ->where(function ($query) { $query->where('meta_key', 'LIKE', 'learndash_group_users_%') ->orWhere('meta_key', 'LIKE', 'course_%_access_from'); }) ->groupBy('user_id') ->count(); \WP_CLI::line('The following Tags, Lists & Status will be applied to the students:'); \WP_CLI\Utils\format_items('yaml', [ [ 'type' => __('Total Students', 'fluent-crm'), 'Value' => $studentsCount ], [ 'type' => __('Tags', 'fluent-crm'), 'Value' => $formattedTagNames ], [ 'type' => __('Lists', 'fluent-crm'), 'Value' => $formattedListNames ], [ 'type' => __('Status', 'fluent-crm'), 'Value' => $contactStatus ] ], ['type', 'Value']); $sendDoubleOptin = false; if ($contactStatus == 'pending') { $sendDoubleOptin = true; \WP_CLI::line('---'); \WP_CLI::line('A Double optin email will be sent to contacts who does not have subscribed status'); } \WP_CLI::confirm('Do you want to continue?'); \WP_CLI::line('Nice! Starting data syncing'); $processingStatus = true; if ($fire_event == 'no') { if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) { define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true); } } Commerce::resetModuleData('learndash'); $skippedContacts = []; $progress = \WP_CLI\Utils\make_progress_bar('Synced Students', $studentsCount); $deepIntegrationClass = new DeepIntegration(); $offset = 0; while ($processingStatus) { $students = fluentCrmDb()->table('usermeta') ->select([ fluentCrmDb()->raw('DISTINCT(user_id) as student_user_id'), ]) ->where(function ($query) { $query->where('meta_key', 'LIKE', 'learndash_group_users_%') ->orWhere('meta_key', 'LIKE', 'course_%_access_from'); }) ->groupBy('user_id') ->offset($offset) ->limit(10) ->orderBy('user_id', 'ASC') ->get(); $offset += 10; if ($students->isEmpty()) { $processingStatus = false; } else { foreach ($students as $student) { $result = $deepIntegrationClass->syncStudent($student->student_user_id, $contactStatus, $formattedTags, $formattedLists, $sendDoubleOptin, true); $progress->tick(); if (!$result) { $skippedContacts[] = [ 'user_id' => $student->student_user_id ]; } } } } Commerce::enableModule('learndash'); $relationCount = ContactRelationModel::provider('learndash')->count(); \WP_CLI::line(sprintf('Awesome! %d students has been synced', $relationCount)); if ($skippedContacts) { \WP_CLI::line(sprintf('%d contacts has been skipped', count($skippedContacts))); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fwrite(STDOUT, 'Show Skipped contacts? yes/no' . ': '); $value = strtolower(trim(fgets(STDIN))); if ($value == 'yes') { \WP_CLI\Utils\format_items( 'table', $skippedContacts, ['student_user_id'] ); } } \WP_CLI::line('Nice. All Done'); } /** * Display EDD 3 commerce statistics from synced FluentCRM commerce data. */ public function edd_stats($args, $assoc_args) { $isHelp = $type = \WP_CLI\Utils\get_flag_value($assoc_args, 'commands', ''); $this->requireEdd3Cli(); if (!defined('FLUENTCAMPAIGN')) { \WP_CLI::error('FluentCRM Pro is required'); } if (!Commerce::isMigrated()) { \WP_CLI::error('Data is not migrated yet. Please sync the data first'); } $type = \WP_CLI\Utils\get_flag_value($assoc_args, 'type', ''); $productId = \WP_CLI\Utils\get_flag_value($assoc_args, 'product_id', ''); $period = \WP_CLI\Utils\get_flag_value($assoc_args, 'period', ''); if ($type == 'overall') { $items = EddCommerceHelper::stats(); \WP_CLI\Utils\format_items( 'table', $items, ['type', 'amount'] ); } else if ($type == 'products' && !$productId) { $productItems = EddCommerceHelper::productsStats($period); \WP_CLI\Utils\format_items( 'table', $productItems, ['id', 'name', 'formatted_sales', 'percent'] ); } else if ($type == 'products' && $productId) { if ($productId == 'all') { $uniqueProducts = ContactRelationItemsModel::provider('edd')->groupBy('item_id') ->select('item_id') ->get(); foreach ($uniqueProducts as $uniqueProduct) { $this->showEddProductReport($uniqueProduct->item_id, $period); } } else { $this->showEddProductReport($productId, $period); } } else if ($type == 'license_stats') { $items = EddCommerceHelper::getLicenseStats(); \WP_CLI\Utils\format_items( 'table', $items, ['label', 'count'] ); } else if ($type == 'license_sites') { $items = EddCommerceHelper::getLicenseActivations(); \WP_CLI\Utils\format_items( 'table', $items, ['label', 'activated_sites'] ); } else { \WP_CLI::line('Possible Commands:'); \WP_CLI::line('--type=overall'); \WP_CLI::line('--type=products'); \WP_CLI::line('--type=products --product_id=PRODUCT_ID|all'); \WP_CLI::line('--type=license_stats'); \WP_CLI::line('--type=license_sites'); } } private function showEddProductReport($productId, $period = '') { if (!$productId || !is_numeric($productId)) { \WP_CLI::error('--product_id=PRODUCT_ID (int) parameter is required'); } $stats = EddCommerceHelper::productStat($productId, $period); $post = get_post($productId); \WP_CLI::line('-----'); \WP_CLI::line(sprintf('Stats for %s', $post->post_title)); \WP_CLI\Utils\format_items( 'table', $stats, ['name', 'formatted_sales', 'percent', 'count', 'type'] ); } public function reset_db() { if (!defined('WP_DEBUG') || !WP_DEBUG) { \WP_CLI::error('WP_DEBUG must be enabled'); return; } \WP_CLI::confirm('Do you really want to remove all the contacts and related data?'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fwrite(STDOUT, 'Please Type "yes" if you really want to do this? yes/no' . ': '); $value = strtolower(trim(fgets(STDIN))); if ($value != 'yes') { return; } $tables = [ 'fc_campaign_emails', 'fc_campaigns', 'fc_campaign_url_metrics', 'fc_funnel_metrics', 'fc_funnels', 'fc_funnel_sequences', 'fc_funnel_subscribers', 'fc_meta', 'fc_subscriber_meta', 'fc_subscriber_notes', 'fc_subscriber_pivot', 'fc_subscribers', 'fc_url_stores', ]; if (defined('FLUENTCAMPAIGN')) { $tables[] = 'fc_sequence_tracker'; $tables[] = 'fc_contact_relations'; $tables[] = 'fc_contact_relation_items'; } global $wpdb; foreach ($tables as $table) { \WP_CLI::line('Droping Table: ' . $table); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared WordPress.DB.PreparedSQL.NotPrepared $wpdb->query("DROP TABLE IF EXISTS " . $wpdb->prefix . $table); } $options = [ '_fluentcrm_commerce_modules' ]; foreach ($options as $option) { delete_option($option); } // 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); } \WP_CLI::line('All FluentCRM Database Tables have been truncated'); } /** * Apply a FluentCRM tag to users with active lifetime EDD 3 licenses. */ public function edd_add_ltd_tag($args, $assoc_args) { $this->requireEdd3Cli(); if (empty($assoc_args) || count($assoc_args) != 2) { \WP_CLI::line('use --product=productId --tag=tagID'); return; } if (empty($assoc_args['product']) || empty($assoc_args['tag'])) { \WP_CLI::line('use --product=productId --tag=tagID'); return; } $productId = $assoc_args['product']; $tagId = $assoc_args['tag']; if (empty($productId) || empty($tagId)) { \WP_CLI::line('use --product=productId --tag=tagID'); return; } if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) { define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true); } $licenses = fluentCrmDb()->table('edd_licenses') ->select(['user_id']) ->where('download_id', $productId) ->where('expiration', 0) ->where('status', '!=', 'disabled') ->get(); $tag = Tag::find($tagId); if (!$tag) { \WP_CLI::line('Provided tag could not be found'); return; } if ($licenses->isEmpty()) { \WP_CLI::line('No users found'); return; } \WP_CLI::confirm(count($licenses) . " users found. Are you sure to add tag to those users?", $assoc_args); $completedCount = 0; foreach ($licenses as $license) { if (!$license->user_id) { continue; } $userId = $license->user_id; $subscriber = FluentCrmApi('contacts')->getContactByUserRef($userId); if (!$subscriber) { \WP_CLI::line('No user found ' . $userId); continue; } $subscriber->attachTags([$tagId]); $completedCount++; } \WP_CLI::line('Total Done: ' . $completedCount); } /** * Apply a FluentCRM tag to users with active EDD 3 licenses for a price option. */ public function edd_add_price_tag($args, $assoc_args) { $this->requireEdd3Cli(); if (empty($assoc_args) || count($assoc_args) != 3) { \WP_CLI::line('use --product=productId --price_id=PRICEID --tag=tagID'); return; } if (empty($assoc_args['product']) || empty($assoc_args['tag']) || empty($assoc_args['price_id'])) { \WP_CLI::line('use --product=productId --tag=tagID'); return; } $productId = $assoc_args['product']; $tagId = $assoc_args['tag']; $priceId = $assoc_args['price_id']; if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) { define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true); } $licenses = fluentCrmDb()->table('edd_licenses') ->select(['user_id']) ->where('download_id', $productId) ->where('price_id', $priceId) ->where('status', '!=', 'disabled') ->get(); $tag = Tag::find($tagId); if (!$tag) { \WP_CLI::line('Provided tag could not be found'); return; } if ($licenses->isEmpty()) { \WP_CLI::line('No users found'); return; } \WP_CLI::confirm(count($licenses) . " users found. Are you sure to add tag to those users?", $assoc_args); $completedCount = 0; foreach ($licenses as $license) { if (!$license->user_id) { continue; } $userId = $license->user_id; $subscriber = FluentCrmApi('contacts')->getContactByUserRef($userId); if (!$subscriber) { \WP_CLI::line('No user found ' . $userId); continue; } $subscriber->attachTags([$tagId]); $completedCount++; } \WP_CLI::line('Total Done: ' . $completedCount); } /** * Stop EDD CLI flows before they touch legacy EDD 2 data or tables. */ private function requireEdd3Cli() { if (!Helper::isEdd3()) { \WP_CLI::error('Easy Digital Downloads 3 is required'); } } /* * wp fluent_crm activate_license --key=YOUR_LICENSE_KEY */ public function activate_license($args, $assoc_args) { if (empty($assoc_args['key'])) { \WP_CLI::line('use --key=LICENSE_KEY to activate the license'); return; } $licenseKey = trim(sanitize_text_field($assoc_args['key'])); if (!class_exists('\FluentCampaign\App\Services\PluginManager\LicenseManager')) { \WP_CLI::line('FluentCRM Pro is required'); return; } \WP_CLI::line('Validating License, Please wait'); $licenseManager = new \FluentCampaign\App\Services\PluginManager\LicenseManager(); $response = $licenseManager->activateLicense($licenseKey); if (is_wp_error($response)) { \WP_CLI::error($response->get_error_message()); return; } \WP_CLI::line('Your license key has been successfully updated'); \WP_CLI::line('Your License Status: ' . $response['status']); \WP_CLI::line('Expire Date: ' . $response['expires']); return; } public function license_status() { if (!class_exists('\FluentCampaign\App\Services\PluginManager\LicenseManager')) { \WP_CLI::line('FluentCRM Pro is required'); return; } \WP_CLI::line('Fetching License details, Please wait'); $licenseManager = new \FluentCampaign\App\Services\PluginManager\LicenseManager(); $licenseManager->verifyRemoteLicense(true); $response = $licenseManager->getLicenseDetails(); \WP_CLI::line('Your License Status: ' . $response['status']); \WP_CLI::line('Expires: ' . $response['expires']); return; } /* * Send Pending Emails parallelly via CLI * use it with caution * Requires the "Multi-threaded email sending" experimental feature to be * enabled — it is a parallel sender and shares the cross-process rate budget * that only exists in multi-thread mode. No-ops when the flag is off. * basic usage: wp fluent_crm cli_send * advanced usage: wp fluent_crm cli_send --force=yes --option_key=fluentcrm_is_sending_cli_emails --run_time=50 --offset=200 --min_pending=300 --silent=yes */ public function cli_send($args, $assoc_args) { $interactive = true; $showLogs = \WP_CLI\Utils\get_flag_value($assoc_args, 'silent') != 'yes'; if (\WP_CLI\Utils\get_flag_value($assoc_args, 'force') == 'yes') { $interactive = false; } // The CLI sender is a PARALLEL sender — it works from an offset so it can // run alongside the cron Handler, and shares the install's per-second rate // budget with it. That budget is only coordinated across processes (via // GlobalRateLimiter's DB path) when multi-threading is enabled; with the // flag off the cron Handler paces in memory and would have no idea this // process is also sending, so the combined rate could exceed the cap and // trip the provider's throttle. Gate the command on the same flag so the // flag stays the single source of truth for "is parallel sending active". if (!Helper::isExperimentalEnabled('multi_threading_emails')) { // Exit NON-ZERO in both modes. A scripted `--silent` cron wrapper that // checks $? must see failure, not a silent success that sends nothing // and leaves the queue stranded without tripping monitoring. if ($showLogs) { \WP_CLI::error('Multi-threaded email sending is disabled, so the CLI sender is a no-op. It runs in parallel with the cron sender and needs the "Multi-threaded email sending" experimental feature enabled (Settings → Experimental Features) so the per-second rate stays coordinated across both.'); } \WP_CLI::halt(1); } $pendingEmails = Helper::getUpcomingEmailCount(); if ($pendingEmails < 500) { if ($showLogs) { \WP_CLI::error('Pending Emails must need be atleast 500. Currently you have ' . $pendingEmails); } return; } if ($interactive) { \WP_CLI::confirm('Do you really want to start sending emails via cron? Please make sure you have high-volume email sending limit (per second). Also you should have atleast 8GM RAM on your server. Please confirm.'); } if ($showLogs) { \WP_CLI::line('Starting Email Sending Process. Please wait...'); } $optionKey = \WP_CLI\Utils\get_flag_value($assoc_args, 'option_key', 'fluentcrm_is_sending_cli_emails'); $runTime = \WP_CLI\Utils\get_flag_value($assoc_args, 'run_time', 50); $offset = \WP_CLI\Utils\get_flag_value($assoc_args, 'offset', 200); $minPending = \WP_CLI\Utils\get_flag_value($assoc_args, 'min_pending', 300); $handler = new CliSendingHandler($optionKey, $runTime, $offset, $minPending); $result = $handler->handle(); if (is_wp_error($result)) { \WP_CLI::error($result->get_error_message()); return; } \WP_CLI::line('Email Sending Process Completed - ' . $optionKey); } public function reindex_wp_user_ids() { $subscribers = Subscriber::all(); $progress = \WP_CLI\Utils\make_progress_bar('Synced Contacts', $subscribers->count()); foreach ($subscribers as $subscriber) { $progress->tick(); $user = get_user_by('email', $subscriber->email); if (!$subscriber->user_id && !$user) { continue; } if ($user) { if ($subscriber->user_id == $user->ID) { continue; } } if ($user) { $subscriber->user_id = $user->ID; $subscriber->save(); \WP_CLI::line("Updated user_id for contact ID: {$subscriber->id}"); } else if ($subscriber->user_id) { $subscriber->user_id = NULL; $subscriber->save(); \WP_CLI::line("Removed user_id for contact ID: {$subscriber->id}"); } } \WP_CLI::line('User ids for contacts has been synced'); } public function simulate_funnel($args, $assoc_args) { (new SimulateFunnelCommand())->handle($args, $assoc_args); } }