495 lines
16 KiB
PHP
495 lines
16 KiB
PHP
<?php
|
|
|
|
namespace FluentCrm\App\Http\Controllers;
|
|
|
|
use FluentCrm\App\Models\Company;
|
|
use FluentCrm\App\Services\Helper;
|
|
use FluentCrm\App\Services\Libs\FileSystem;
|
|
use FluentCrm\App\Services\Sanitize;
|
|
use FluentCrm\Framework\Support\Arr;
|
|
use FluentCrm\Framework\Http\Request\Request;
|
|
use FluentCrm\App\Models\Subscriber;
|
|
|
|
/**
|
|
* CsvController - REST API Handler Class
|
|
*
|
|
* REST API Handler
|
|
*
|
|
* @package FluentCrm\App\Http
|
|
*
|
|
* @version 1.0.0
|
|
*/
|
|
class CsvController extends Controller
|
|
{
|
|
|
|
/**
|
|
* @param \FluentCrm\Framework\Http\Request\Request $request
|
|
* @return \WP_REST_Response
|
|
* @throws \FluentCrm\Framework\Validator\ValidationException
|
|
*/
|
|
public function upload(Request $request)
|
|
{
|
|
if (is_multisite()) {
|
|
add_filter('upload_mimes', function ($types) {
|
|
if (empty($types['csv'])) {
|
|
$types['csv'] = 'text/csv';
|
|
}
|
|
return $types;
|
|
});
|
|
}
|
|
|
|
$files = $this->validate($this->request->files(), [
|
|
'file' => 'mimetypes:' . implode(',', fluentcrmCsvMimes())
|
|
], [
|
|
'file.mimetypes' => __('The file must be a valid CSV.', 'fluent-crm')
|
|
]);
|
|
|
|
$delimeter = $request->get('delimiter', 'comma');
|
|
|
|
if ($delimeter == 'comma') {
|
|
$delimeter = ',';
|
|
} else {
|
|
$delimeter = ';';
|
|
}
|
|
|
|
$uploadedFiles = FileSystem::put($files);
|
|
|
|
try {
|
|
$csv = $this->getCsvReader(FileSystem::get($uploadedFiles[0]['file']));
|
|
$csv->setDelimiter($delimeter);
|
|
$headers = $csv->fetchOne();
|
|
} catch (\Exception $exception) {
|
|
return $this->sendError([
|
|
'message' => $exception->getMessage()
|
|
]);
|
|
}
|
|
|
|
if (count($headers) != count(array_unique($headers))) {
|
|
return $this->sendError([
|
|
'message' => __('Looks like your csv has same name header multiple times. Please fix your csv first and remove any duplicate header column', 'fluent-crm')
|
|
]);
|
|
}
|
|
|
|
|
|
if ($request->get('type') == 'company') {
|
|
$mappables = Company::mappables();
|
|
} else {
|
|
$mappables = Subscriber::mappables();
|
|
}
|
|
|
|
$headerItems = array_values(array_filter($headers));
|
|
$subscriberColumns = array_keys($mappables);
|
|
|
|
$maps = [];
|
|
|
|
$customFields = fluentcrm_get_custom_contact_fields();
|
|
|
|
$fieldsMap = [];
|
|
if ($customFields) {
|
|
foreach ($customFields as $field) {
|
|
$fieldsMap[$field['slug']] = $field['label'];
|
|
}
|
|
}
|
|
|
|
foreach ($headerItems as $headerItem) {
|
|
$tableMap = (in_array($headerItem, $subscriberColumns)) ? $headerItem : null;
|
|
|
|
if (!$tableMap) {
|
|
$santizedItem = str_replace(' ', '_', strtolower($headerItem));
|
|
if (in_array($santizedItem, $subscriberColumns)) {
|
|
$tableMap = $santizedItem;
|
|
}
|
|
}
|
|
|
|
if (!empty($fieldsMap) && in_array($headerItem, $fieldsMap)) {
|
|
$tableMap = array_search($headerItem, $fieldsMap);
|
|
}
|
|
|
|
$maps[] = [
|
|
'csv' => $headerItem,
|
|
'table' => $tableMap
|
|
];
|
|
}
|
|
|
|
if ($request->get('type') == 'company') {
|
|
/**
|
|
* Determine the columns of the company table in FluentCRM.
|
|
*
|
|
* This filter allows you to modify the columns of the company table in the CSV export.
|
|
*
|
|
* @since 2.8.0
|
|
*
|
|
* @param array $subscriberColumns An array of default subscriber columns.
|
|
*/
|
|
$columns = apply_filters(
|
|
'fluent_crm/company_table_columns', $subscriberColumns
|
|
);
|
|
} else {
|
|
/**
|
|
* Determine the columns of the subscriber table in FluentCRM.
|
|
*
|
|
* This filter allows you to modify the columns displayed in the subscriber table.
|
|
*
|
|
* @since 2.8.0
|
|
*
|
|
* @param array $subscriberColumns An array of default subscriber table columns.
|
|
*/
|
|
$columns = apply_filters(
|
|
'fluent_crm/subscriber_table_columns', $subscriberColumns
|
|
);
|
|
}
|
|
|
|
return $this->send([
|
|
'file' => $uploadedFiles[0]['file'],
|
|
'headers' => $headerItems,
|
|
'fields' => $mappables,
|
|
'columns' => $columns,
|
|
'map' => $maps
|
|
]);
|
|
}
|
|
|
|
public function import()
|
|
{
|
|
$inputs = $this->request->only([
|
|
'map', 'tags', 'lists', 'file', 'update', 'new_status', 'double_optin_email', 'import_silently', 'force_update_status'
|
|
]);
|
|
|
|
if (Arr::get($inputs, 'import_silently') == 'yes') {
|
|
if (!defined('FLUENTCRM_DISABLE_TAG_LIST_EVENTS')) {
|
|
define('FLUENTCRM_DISABLE_TAG_LIST_EVENTS', true);
|
|
}
|
|
}
|
|
|
|
$forceStatusChange = Arr::get($inputs, 'force_update_status') == 'yes';
|
|
|
|
$delimeter = $this->request->get('delimiter', 'comma');
|
|
|
|
if ($delimeter == 'comma') {
|
|
$delimeter = ',';
|
|
} else {
|
|
$delimeter = ';';
|
|
}
|
|
|
|
$status = $inputs['new_status'];
|
|
|
|
try {
|
|
$reader = $this->getCsvReader(FileSystem::get($inputs['file']));
|
|
$reader->setDelimiter($delimeter);
|
|
|
|
if (method_exists($reader, 'getRecords')) {
|
|
$aHeaders = $reader->fetchOne(0);
|
|
|
|
$allRecords = $reader->getRecords($aHeaders);
|
|
|
|
if (!is_array($allRecords)) {
|
|
$allRecords = iterator_to_array($allRecords, true);
|
|
}
|
|
|
|
unset($allRecords[0]);
|
|
$allRecords = array_values($allRecords);
|
|
} else {
|
|
$aHeaders = $reader->fetchOne(0);
|
|
$allRecords = $reader->fetchAssoc($aHeaders);
|
|
if (!is_array($allRecords)) {
|
|
$allRecords = iterator_to_array($allRecords, true);
|
|
}
|
|
|
|
unset($allRecords[0]);
|
|
|
|
$allRecords = array_values($allRecords);
|
|
}
|
|
} catch (\Exception $exception) {
|
|
return $this->sendError([
|
|
'message' => $exception->getMessage()
|
|
]);
|
|
}
|
|
|
|
|
|
$page = $this->request->get('importing_page', 1);
|
|
|
|
$processPerRequest = apply_filters('fluent_crm/csv_import_contact_limit_per_request', 100);
|
|
|
|
$offset = ($page - 1) * $processPerRequest;
|
|
$records = array_slice($allRecords, $offset, $processPerRequest);
|
|
|
|
|
|
$customFieldKeys = $this->customFieldKeys();
|
|
$subscribers = [];
|
|
$skipped = [];
|
|
|
|
$isCompanyEnabled = Helper::isCompanyEnabled();
|
|
|
|
foreach ($records as $record) {
|
|
if (!array_filter($record)) {
|
|
continue;
|
|
}
|
|
|
|
$subscriber = [
|
|
'custom_values' => []
|
|
];
|
|
foreach ($inputs['map'] as $map) {
|
|
if (!$map['table']) {
|
|
continue;
|
|
}
|
|
if (isset($map['csv'], $map['table'])) {
|
|
if (in_array($map['table'], ['tags', 'lists'])) {
|
|
//if tags or lists are mapped to be imported
|
|
if ($map['table'] == 'tags') {
|
|
$subscriber['tags'] = !empty($record[$map['csv']]) ? explode(',', $record[$map['csv']]) : [];
|
|
} else {
|
|
$subscriber['lists'] = !empty($record[$map['csv']]) ? explode(',', $record[$map['csv']]) : [];
|
|
}
|
|
}
|
|
else if (in_array($map['table'], $customFieldKeys)) {
|
|
$subscriber['custom_values'][$map['table']] = $record[$map['csv']];
|
|
} else {
|
|
$subscriber[$map['table']] = $record[$map['csv']];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!array_key_exists('email', $subscriber)) {
|
|
return $this->sendError(['email' => __('The email field is required.', 'fluent-crm')], 422);
|
|
}
|
|
|
|
$subscriber['email'] = is_string($subscriber['email']) ? trim($subscriber['email']) : $subscriber['email'];
|
|
|
|
if ($subscriber['email'] && is_email($subscriber['email'])) {
|
|
|
|
if (isset($subscriber['company_id']) && $subscriber['company_id'] && $isCompanyEnabled) {
|
|
$companyNameOrId = $subscriber['company_id'];
|
|
if (is_string($companyNameOrId)) {
|
|
$company = Company::query()->firstOrCreate([
|
|
'name' => $subscriber['company_id']
|
|
], [
|
|
'name' => $subscriber['company_id']
|
|
]);
|
|
|
|
if ($company) {
|
|
$subscriber['company_id'] = $company->id;
|
|
} else {
|
|
unset($subscriber['company_id']);
|
|
}
|
|
} else {
|
|
$company = Company::find($subscriber['company_id']);
|
|
if (!$company) {
|
|
unset($subscriber['company_id']);
|
|
}
|
|
}
|
|
}
|
|
|
|
$subscribers[] = Sanitize::contact($subscriber);
|
|
} else {
|
|
$skipped[] = $subscriber;
|
|
}
|
|
}
|
|
|
|
if (!isset($inputs['tags'])) {
|
|
$inputs['tags'] = [];
|
|
}
|
|
|
|
if (!isset($inputs['lists'])) {
|
|
$inputs['lists'] = [];
|
|
}
|
|
|
|
$sendDoubleOptin = Arr::get($inputs, 'double_optin_email') == 'yes';
|
|
|
|
$result = Subscriber::import(
|
|
$subscribers, $inputs['tags'], $inputs['lists'], $inputs['update'], $status, $sendDoubleOptin, $forceStatusChange, 'csv'
|
|
);
|
|
|
|
$totalSkipped = count($result['skips']) + count($skipped);
|
|
|
|
$completed = $offset + count($records);
|
|
$totalCount = count($allRecords);
|
|
$hasMore = $completed < $totalCount;
|
|
if (!$hasMore) {
|
|
FileSystem::delete($inputs['file']);
|
|
}
|
|
|
|
return $this->sendSuccess([
|
|
'total' => $totalCount,
|
|
'completed' => $completed,
|
|
'total_page' => ceil($totalCount / $processPerRequest),
|
|
'skipped' => $totalSkipped,
|
|
'invalid_contacts' => $skipped,
|
|
'skipped_contacts' => $result['skips'],
|
|
'invalid_email_counts' => count($skipped),
|
|
'inserted' => count($result['inserted']),
|
|
'updated' => count($result['updated']),
|
|
'has_more' => $hasMore,
|
|
'last_page' => $page,
|
|
'tags' => $inputs['tags'],
|
|
'lists' => $inputs['lists'],
|
|
'offset' => $offset,
|
|
'result' => $result
|
|
]);
|
|
}
|
|
|
|
public function importCompanies()
|
|
{
|
|
$inputs = $this->request->only([
|
|
'map', 'file', 'update', 'create_owner'
|
|
]);
|
|
|
|
$delimeter = $this->request->get('delimiter', 'comma');
|
|
|
|
if ($delimeter == 'comma') {
|
|
$delimeter = ',';
|
|
} else {
|
|
$delimeter = ';';
|
|
}
|
|
|
|
try {
|
|
$reader = $this->getCsvReader(FileSystem::get($inputs['file']));
|
|
$reader->setDelimiter($delimeter);
|
|
|
|
if (method_exists($reader, 'getRecords')) {
|
|
$aHeaders = $reader->fetchOne(0);
|
|
|
|
$allRecords = $reader->getRecords($aHeaders);
|
|
|
|
if (!is_array($allRecords)) {
|
|
$allRecords = iterator_to_array($allRecords, true);
|
|
}
|
|
|
|
unset($allRecords[0]);
|
|
$allRecords = array_values($allRecords);
|
|
} else {
|
|
$aHeaders = $reader->fetchOne(0);
|
|
$allRecords = $reader->fetchAssoc($aHeaders);
|
|
if (!is_array($allRecords)) {
|
|
$allRecords = iterator_to_array($allRecords, true);
|
|
}
|
|
|
|
unset($allRecords[0]);
|
|
|
|
$allRecords = array_values($allRecords);
|
|
}
|
|
} catch (\Exception $exception) {
|
|
return $this->sendError([
|
|
'message' => $exception->getMessage()
|
|
]);
|
|
}
|
|
|
|
$page = $this->request->get('importing_page', 1);
|
|
$processPerRequest = 100;
|
|
$offset = ($page - 1) * $processPerRequest;
|
|
$records = array_slice($allRecords, $offset, $processPerRequest);
|
|
|
|
$willCreateOwner = $this->request->get('create_owner') == 'yes';
|
|
$willUpdate = $this->request->get('update') == 'yes';
|
|
|
|
$customFields = fluentcrm_get_custom_company_fields();
|
|
|
|
$companies = [];
|
|
$skipped = [];
|
|
foreach ($records as $record) {
|
|
if (!array_filter($record)) {
|
|
continue;
|
|
}
|
|
|
|
$company = [];
|
|
foreach ($inputs['map'] as $map) {
|
|
if (!$map['table']) {
|
|
continue;
|
|
}
|
|
if (isset($map['csv'], $map['table'])) {
|
|
$company[$map['table']] = trim($record[$map['csv']]);
|
|
}
|
|
}
|
|
|
|
if (empty($company['name'])) {
|
|
return $this->sendError(['email' => __('The company name field is required.', 'fluent-crm')], 422);
|
|
}
|
|
|
|
if (!$willUpdate) {
|
|
// check if exists
|
|
if (Company::where('name', $company['name'])->first()) {
|
|
$skipped[] = $company;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
if ($customFields) {
|
|
$customValues = [];
|
|
|
|
foreach ($company as $dataKey => $dataValue) {
|
|
if (strpos($dataKey, '_custom_') === 0) {
|
|
$customKey = str_replace('_custom_', '', $dataKey);
|
|
$customValues[$customKey] = $dataValue;
|
|
unset($company[$dataKey]);
|
|
}
|
|
}
|
|
|
|
$company['custom_values'] = $customValues;
|
|
}
|
|
|
|
$company = Sanitize::company($company);
|
|
|
|
if (!empty($company['owner_email']) && is_email($company['owner_email'])) {
|
|
$ownerEmail = sanitize_email($company['owner_email']);
|
|
} else {
|
|
$ownerEmail = null;
|
|
}
|
|
|
|
if ($ownerEmail) {
|
|
$owner = FluentCrmApi('contacts')->getContact($ownerEmail);
|
|
if ($owner) {
|
|
$company['owner_id'] = $owner->id;
|
|
} else if ($willCreateOwner) {
|
|
$owner = FluentCrmApi('contacts')->createOrUpdate([
|
|
'full_name' => sanitize_text_field(Arr::get($company, 'owner_name')),
|
|
'email' => $ownerEmail,
|
|
'status' => 'subscribed'
|
|
]);
|
|
|
|
if ($owner) {
|
|
$company['owner_id'] = $owner->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
$createdCompany = FluentCrmApi('companies')->createOrUpdate($company);
|
|
$companies[] = $createdCompany;
|
|
}
|
|
|
|
$completed = $offset + count($companies);
|
|
$totalCount = count($allRecords);
|
|
$hasMore = $completed < $totalCount;
|
|
if (!$hasMore) {
|
|
FileSystem::delete($inputs['file']);
|
|
}
|
|
|
|
return $this->sendSuccess([
|
|
'total' => $totalCount,
|
|
'completed' => count($companies),
|
|
'total_page' => ceil($totalCount / $processPerRequest),
|
|
'skipped' => count($skipped),
|
|
'has_more' => $hasMore,
|
|
'last_page' => $page,
|
|
'offset' => $offset
|
|
]);
|
|
}
|
|
|
|
protected function customFieldKeys()
|
|
{
|
|
$fields = fluentcrm_get_option('contact_custom_fields', []);
|
|
$keys = [];
|
|
foreach ($fields as $field) {
|
|
$keys[] = $field['slug'];
|
|
}
|
|
return $keys;
|
|
}
|
|
|
|
private function getCsvReader($file)
|
|
{
|
|
if (!class_exists(' \League\Csv\Reader')) {
|
|
include FLUENTCRM_PLUGIN_PATH . 'app/Services/Libs/csv/autoload.php';
|
|
}
|
|
|
|
return \League\Csv\Reader::createFromString($file);
|
|
}
|
|
}
|