Link Customer instances to a Mailbox instance
This commit is contained in:
parent
e490e4284d
commit
2ac4519d9a
7 changed files with 1295 additions and 1 deletions
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
SPDX-License-Identifier: AGPL
|
||||||
|
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Customer;
|
||||||
|
|
||||||
|
class AddMailboxIdColumnToCustomersTable extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up() {
|
||||||
|
Schema::table('customers', function (Blueprint $table) {
|
||||||
|
// Add a "mailbox_id" field to the customers table, linking each customer entry to a specific mailbox.
|
||||||
|
$table
|
||||||
|
->integer('mailbox_id')
|
||||||
|
->unsigned()
|
||||||
|
// The column is nullable because entries without a linked mailbox might already exist.
|
||||||
|
->nullable();
|
||||||
|
$table
|
||||||
|
->foreign('mailbox_id')
|
||||||
|
->references('id')
|
||||||
|
->on('mailboxes')
|
||||||
|
// On mailbox deletion, delete all customer entries that are linked to it.
|
||||||
|
->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down() {
|
||||||
|
Schema::table('customers', function (Blueprint $table) {
|
||||||
|
// Delete all entries from the customers table that are linked to a specific mailbox.
|
||||||
|
Customer::has('mailbox')->delete();
|
||||||
|
// Delete the extra "mailbox_id" field from the customers table.
|
||||||
|
$table->dropForeign(['mailbox_id']);
|
||||||
|
$table->dropColumn('mailbox_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
31
routes/web.php
Normal file
31
routes/web.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
SPDX-License-Identifier: AGPL
|
||||||
|
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Http\Controllers\CrmController;
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Http\Controllers\CustomersController;
|
||||||
|
|
||||||
|
// FIXME: Routes are not correctly exposed to the main application,
|
||||||
|
// routes/web.php and Modules/Crm/Http/routes.php must be manually edited.
|
||||||
|
|
||||||
|
Route::get('/customers/{id}/edit', CustomersController::class . '@update')->name('customers.update');
|
||||||
|
Route::post('/customers/{id}/edit', CustomersController::class . '@updateSave');
|
||||||
|
Route::get('/customers/{id}/', CustomersController::class . '@conversations')->name('customers.conversations');
|
||||||
|
Route::get('/customers/ajax-search', ['uses' => CustomersController::class . '@ajaxSearch', 'laroute' => true])->name('customers.ajax_search');
|
||||||
|
Route::post('/customers/ajax', ['uses' => CustomersController::class . '@ajax', 'laroute' => true])->name('customers.ajax');
|
||||||
|
|
||||||
|
Route::group([ 'roles' => ['user', 'admin'] ], function() {
|
||||||
|
Route::get('/customers/new', CrmController::class . '@createCustomer')->name('crm.create_customer');
|
||||||
|
Route::post('/customers/new', CrmController::class . '@createCustomerSave');
|
||||||
|
Route::get('/crm/ajax-html/{action}/{param?}', ['uses' => CrmController::class . '@ajaxHtml'])->name('crm.ajax_html');
|
||||||
|
Route::get('/customers/fields/ajax-search', ['uses' => CrmController::class . '@ajaxSearch', 'laroute' => true])->name('crm.ajax_search');
|
||||||
|
Route::post('/crm/ajax', ['uses' => CrmController::class . '@ajax', 'laroute' => true])->name('crm.ajax');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::group([ 'roles' => ['admin'] ], function() {
|
||||||
|
Route::post('/customers/export', ['uses' => CrmController::class . '@export'])->name('crm.export');
|
||||||
|
Route::post('/crm/ajax-admin', ['uses' => CrmController::class . '@ajaxAdmin', 'laroute' => true])->name('crm.ajax_admin');
|
||||||
|
});
|
46
src/Customer.php
Normal file
46
src/Customer.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
SPDX-License-Identifier: AGPL
|
||||||
|
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MMF\FreescoutRestrictedCustomers;
|
||||||
|
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Mailbox;
|
||||||
|
use App\Customer as BaseCustomer;
|
||||||
|
|
||||||
|
class Customer extends BaseCustomer {
|
||||||
|
/**
|
||||||
|
* Attributes fillable using fill() method.
|
||||||
|
*
|
||||||
|
* @var [type]
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
// Default list, imported from BaseCustomer.
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'company',
|
||||||
|
'job_title',
|
||||||
|
'address',
|
||||||
|
'city',
|
||||||
|
'state',
|
||||||
|
'zip',
|
||||||
|
'country',
|
||||||
|
'photo_url',
|
||||||
|
'age',
|
||||||
|
'gender',
|
||||||
|
'notes',
|
||||||
|
'channel',
|
||||||
|
'channel_id',
|
||||||
|
'social_profiles',
|
||||||
|
// Addition specific to this package.
|
||||||
|
'mailbox_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Mailbox that is allowed to access this Customer information.
|
||||||
|
*/
|
||||||
|
public function mailbox() {
|
||||||
|
return $this->belongsTo(Mailbox::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
namespace MMF\FreescoutRestrictedCustomers;
|
namespace MMF\FreescoutRestrictedCustomers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class FreescoutRestrictedCustomersServiceProvider extends ServiceProvider {
|
class FreescoutRestrictedCustomersServiceProvider extends ServiceProvider {
|
||||||
|
@ -14,6 +15,19 @@ class FreescoutRestrictedCustomersServiceProvider extends ServiceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot() {
|
public function boot() {
|
||||||
//
|
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
|
||||||
|
$this->registerRoutes();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function registerRoutes() {
|
||||||
|
Route::group($this->routeConfiguration(), function () {
|
||||||
|
$this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function routeConfiguration() {
|
||||||
|
return [
|
||||||
|
'middleware' => ['web', 'auth', 'roles'],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
749
src/Http/Controllers/CrmController.php
Normal file
749
src/Http/Controllers/CrmController.php
Normal file
|
@ -0,0 +1,749 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
SPDX-License-Identifier: AGPL
|
||||||
|
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MMF\FreescoutRestrictedCustomers\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Conversation;
|
||||||
|
use App\Email;
|
||||||
|
use Modules\Crm\Entities\CustomerField;
|
||||||
|
use Modules\Crm\Entities\CustomerCustomerField;
|
||||||
|
use Validator;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
use Modules\Crm\Http\Controllers\CrmController as BaseCrmController;
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Customer;
|
||||||
|
|
||||||
|
class CrmController extends BaseCrmController {
|
||||||
|
public function createCustomer(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::createCustomer while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$customer = new Customer();
|
||||||
|
|
||||||
|
return view('crm::create_customer', [
|
||||||
|
'customer' => $customer,
|
||||||
|
'emails' => ['']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createCustomerSave(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::createCustomerSave while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'first_name' => 'nullable|string|max:255|required_without:emails.0',
|
||||||
|
'last_name' => 'nullable|string|max:255',
|
||||||
|
'city' => 'nullable|string|max:255',
|
||||||
|
'state' => 'nullable|string|max:255',
|
||||||
|
'zip' => 'nullable|string|max:12',
|
||||||
|
'country' => 'nullable|string|max:2',
|
||||||
|
//'emails' => 'array|required_without:first_name',
|
||||||
|
//'emails.1' => 'nullable|email|required_without:first_name',
|
||||||
|
'emails.*' => 'nullable|email|distinct|required_without:first_name',
|
||||||
|
]);
|
||||||
|
$validator->setAttributeNames([
|
||||||
|
//'emails.1' => __('Email'),
|
||||||
|
'emails.*' => __('Email'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check email uniqueness.
|
||||||
|
$fail = false;
|
||||||
|
foreach ($request->emails as $i => $email) {
|
||||||
|
$sanitized_email = Email::sanitizeEmail($email);
|
||||||
|
if ($sanitized_email) {
|
||||||
|
$email_exists = Email::where('email', $sanitized_email)->first();
|
||||||
|
|
||||||
|
if ($email_exists) {
|
||||||
|
$validator->getMessageBag()->add('emails.'.$i, __('A customer with this email already exists.'));
|
||||||
|
$fail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fail || $validator->fails()) {
|
||||||
|
return redirect()->route('crm.create_customer')
|
||||||
|
->withErrors($validator)
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer = new Customer();
|
||||||
|
|
||||||
|
$customer->setData($request->all());
|
||||||
|
$customer->save();
|
||||||
|
$customer->syncEmails($request->emails);
|
||||||
|
\Eventy::action('customer.created', $customer);
|
||||||
|
|
||||||
|
\Session::flash('flash_success_unescaped', __('Customer saved successfully.'));
|
||||||
|
|
||||||
|
\Session::flash('customer.updated', 1);
|
||||||
|
|
||||||
|
// Create customer.
|
||||||
|
if ($customer->id) {
|
||||||
|
return redirect()->route('customers.update', ['id' => $customer->id]);
|
||||||
|
} else {
|
||||||
|
// Something went wrong.
|
||||||
|
return $this->createCustomer($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajax controller.
|
||||||
|
*/
|
||||||
|
public function ajax(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::ajax while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$response = [
|
||||||
|
'status' => 'error',
|
||||||
|
'msg' => '', // this is error message
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($request->action) {
|
||||||
|
|
||||||
|
// Delete customer.
|
||||||
|
case 'delete_customer':
|
||||||
|
$has_conversations = Conversation::where('customer_id', $request->customer_id)->first();
|
||||||
|
|
||||||
|
if ($has_conversations) {
|
||||||
|
$response['msg'] = __("This customer has conversations. In order to delete the customer you need to completely delete all customer's conversations first.");
|
||||||
|
}
|
||||||
|
if (!$response['msg']) {
|
||||||
|
$customer = Customer::find($request->customer_id);
|
||||||
|
if ($customer) {
|
||||||
|
$customer->deleteCustomer();
|
||||||
|
$response['msg_success'] = __('Customer deleted');
|
||||||
|
$response['status'] = 'success';
|
||||||
|
} else {
|
||||||
|
$response['msg'] = __('Customer not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete_without_conv':
|
||||||
|
// Delete customers by bunches.
|
||||||
|
do {
|
||||||
|
$customers = $this->getCustomersWithoutConvQuery()
|
||||||
|
->limit(100)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($customers as $customer) {
|
||||||
|
$customer->deleteCustomer();
|
||||||
|
}
|
||||||
|
} while(count($customers));
|
||||||
|
|
||||||
|
$response['msg_success'] = __('Customers deleted');
|
||||||
|
$response['status'] = 'success';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$response['msg'] = 'Unknown action';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response['status'] == 'error' && empty($response['msg'])) {
|
||||||
|
$response['msg'] = 'Unknown error occured';
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Response::json($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajax controller.
|
||||||
|
*/
|
||||||
|
public function ajaxAdmin(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::ajaxAdmin while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$response = [
|
||||||
|
'status' => 'error',
|
||||||
|
'msg' => '', // this is error message
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($request->action) {
|
||||||
|
|
||||||
|
// Create/update saved reply
|
||||||
|
case 'customer_field_create':
|
||||||
|
case 'customer_field_update':
|
||||||
|
|
||||||
|
// if (!$user->isAdmin()) {
|
||||||
|
// $response['msg'] = __('Not enough permissions');
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
$name = $request->name;
|
||||||
|
|
||||||
|
if (!$name) {
|
||||||
|
$response['msg'] = __('Name is required');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check unique name.
|
||||||
|
if (!$response['msg']) {
|
||||||
|
$name_exists = CustomerField::where('name', $name);
|
||||||
|
|
||||||
|
if ($request->action == 'customer_field_update') {
|
||||||
|
$name_exists->where('id', '!=', $request->customer_field_id);
|
||||||
|
}
|
||||||
|
$name_exists = $name_exists->first();
|
||||||
|
|
||||||
|
if ($name_exists) {
|
||||||
|
$response['msg'] = __('A Customer Field with this name already exists.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
|
||||||
|
if ($request->action == 'customer_field_update') {
|
||||||
|
$customer_field = CustomerField::find($request->customer_field_id);
|
||||||
|
if (!$customer_field) {
|
||||||
|
$response['msg'] = __('Customer Field not found');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$customer_field = new CustomerField();
|
||||||
|
$customer_field->setSortOrderLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
//$customer_field->mailbox_id = $mailbox->id;
|
||||||
|
$customer_field->name = $name;
|
||||||
|
if ($request->action != 'customer_field_update') {
|
||||||
|
$customer_field->type = $request->type;
|
||||||
|
}
|
||||||
|
if ($customer_field->type == CustomerField::TYPE_DROPDOWN) {
|
||||||
|
|
||||||
|
if ($request->action == 'customer_field_create') {
|
||||||
|
$options = [];
|
||||||
|
$options_tmp = preg_split('/\r\n|[\r\n]/', $request->options ?? '');
|
||||||
|
// Remove empty
|
||||||
|
$option_index = 1;
|
||||||
|
foreach ($options_tmp as $i => $value) {
|
||||||
|
$value = trim($value);
|
||||||
|
if ($value) {
|
||||||
|
$options[$option_index] = $value;
|
||||||
|
$option_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($options)) {
|
||||||
|
$options = [1 => ''];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$options = $request->options;
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer_field->options = $options;
|
||||||
|
|
||||||
|
// Remove values.
|
||||||
|
if ($customer_field->id) {
|
||||||
|
CustomerCustomerField::where('customer_field_id', $customer_field->id)
|
||||||
|
->whereNotIn('value', array_keys($request->options))
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
} elseif (isset($request->options)) {
|
||||||
|
$customer_field->options = $request->options;
|
||||||
|
} else {
|
||||||
|
$customer_field->options = '';
|
||||||
|
}
|
||||||
|
$customer_field->required = $request->filled('required');
|
||||||
|
$customer_field->display = $request->filled('display');
|
||||||
|
$customer_field->conv_list = $request->filled('conv_list');
|
||||||
|
$customer_field->customer_can_view = $request->filled('customer_can_view');
|
||||||
|
$customer_field->customer_can_edit = $request->filled('customer_can_edit');
|
||||||
|
$customer_field->save();
|
||||||
|
|
||||||
|
$response['id'] = $customer_field->id;
|
||||||
|
$response['name'] = $customer_field->name;
|
||||||
|
$response['required'] = (int)$customer_field->required;
|
||||||
|
$response['display'] = (int)$customer_field->display;
|
||||||
|
$response['conv_list'] = (int)$customer_field->conv_list;
|
||||||
|
$response['customer_can_view'] = (int)$customer_field->customer_can_view;
|
||||||
|
$response['customer_can_edit'] = (int)$customer_field->customer_can_edit;
|
||||||
|
$response['status'] = 'success';
|
||||||
|
|
||||||
|
if ($request->action == 'customer_field_update') {
|
||||||
|
$response['msg_success'] = __('Customer field updated');
|
||||||
|
} else {
|
||||||
|
// Flash
|
||||||
|
\Session::flash('flash_success_floating', __('Customer field created'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
case 'customer_field_delete':
|
||||||
|
|
||||||
|
// if (!$user->isAdmin()) {
|
||||||
|
// $response['msg'] = __('Not enough permissions');
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
$customer_field = CustomerField::find($request->customer_field_id);
|
||||||
|
|
||||||
|
if (!$customer_field) {
|
||||||
|
$response['msg'] = __('Customer Field not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
\Eventy::action('customer_field.before_delete', $customer_field);
|
||||||
|
$customer_field->delete();
|
||||||
|
|
||||||
|
// Delete links to customers;
|
||||||
|
CustomerCustomerField::where('customer_field_id', $request->customer_field_id)->delete();
|
||||||
|
|
||||||
|
$response['status'] = 'success';
|
||||||
|
$response['msg_success'] = __('Customer Field deleted');
|
||||||
|
|
||||||
|
\Eventy::action('customer_field.after_delete', $request->customer_field_id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Update saved reply
|
||||||
|
case 'customer_field_update_sort_order':
|
||||||
|
|
||||||
|
// if (!$user->isAdmin()) {
|
||||||
|
// $response['msg'] = __('Not enough permissions');
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
|
||||||
|
$customer_fields = CustomerField::whereIn('id', $request->customer_fields)->select('id', 'sort_order')->get();
|
||||||
|
|
||||||
|
if (count($customer_fields)) {
|
||||||
|
foreach ($request->customer_fields as $i => $request_customer_field_id) {
|
||||||
|
foreach ($customer_fields as $customer_field) {
|
||||||
|
if ($customer_field->id != $request_customer_field_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$customer_field->sort_order = $i+1;
|
||||||
|
$customer_field->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$response['status'] = 'success';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Parse CSV before importing.
|
||||||
|
case 'import_parse':
|
||||||
|
if (!$request->hasFile('file') || !$request->file('file')->isValid() || !$request->file) {
|
||||||
|
$response['msg'] = __('Error occurred uploading file');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
try {
|
||||||
|
$csv = $this->readCsv($request->file('file')->getPathName(), $request->separator, $request->enclosure, $request->encoding);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$response['msg'] = __('Error occurred').': '.$e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg'] && $csv && is_array($csv)) {
|
||||||
|
$response['cols'] = [];
|
||||||
|
foreach ($csv as $r => $row) {
|
||||||
|
if ($request->skip_header && $r == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($row as $c => $value) {
|
||||||
|
if (!empty($response['cols'][$c])
|
||||||
|
&& $response['cols'][$c] != __('Column :number', ['number' => $c+1])
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($request->skip_header) {
|
||||||
|
if ($r == 0) {
|
||||||
|
if (isset($csv[1][$c]) && $csv[1][$c] != '') {
|
||||||
|
$response['cols'][$c] = $value . ' ('.$csv[1][$c].')';
|
||||||
|
} elseif ($value != '') {
|
||||||
|
$response['cols'][$c] = $value;
|
||||||
|
} else {
|
||||||
|
$response['cols'][$c] = __('Column :number', ['number' => $c+1]);
|
||||||
|
}
|
||||||
|
} elseif (isset($csv[0][$c]) && $value) {
|
||||||
|
$response['cols'][$c] = $csv[0][$c] . ' ('.$value.')';
|
||||||
|
} elseif ($value != '') {
|
||||||
|
$response['cols'][$c] = $value;
|
||||||
|
} else {
|
||||||
|
$response['cols'][$c] = __('Column :number', ['number' => $c+1]);
|
||||||
|
}
|
||||||
|
} elseif ($value != '') {
|
||||||
|
$response['cols'][$c] = $value;
|
||||||
|
} else {
|
||||||
|
$response['cols'][$c] = __('Column :number', ['number' => $c+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$response['msg']) {
|
||||||
|
if (!empty($response['cols'])) {
|
||||||
|
$response['status'] = 'success';
|
||||||
|
} else {
|
||||||
|
$response['msg'] = __('Could not parse CSV file.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Import.
|
||||||
|
case 'import_import':
|
||||||
|
if (!$request->hasFile('file') || !$request->file('file')->isValid() || !$request->file) {
|
||||||
|
$response['msg'] = __('Error occurred uploading file');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
try {
|
||||||
|
$csv = $this->readCsv($request->file('file')->getPathName(), $request->separator, $request->enclosure, $request->encoding);
|
||||||
|
$imported = 0;
|
||||||
|
$errors = [];
|
||||||
|
$email_conflicts = [];
|
||||||
|
if ($csv && is_array($csv)) {
|
||||||
|
foreach ($csv as $r => $row) {
|
||||||
|
if ($request->skip_header && $r == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$data = $this->importParseRow($row, json_decode($request->mapping, true));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!empty($data['emails'])) {
|
||||||
|
// Try to find customers with emails.
|
||||||
|
// If found one - update.
|
||||||
|
// If found more than one customer - it's a conflict.
|
||||||
|
$customers_count = 0;
|
||||||
|
$customer_email = '';
|
||||||
|
$customer_customer = null;
|
||||||
|
foreach ($data['emails'] as $email) {
|
||||||
|
$customer = Customer::getByEmail($email);
|
||||||
|
if ($customer) {
|
||||||
|
$customer_email = $email;
|
||||||
|
$customer_customer = $customer;
|
||||||
|
$customers_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($customers_count > 1) {
|
||||||
|
$email_conflicts[] = (int)($r+1);
|
||||||
|
} elseif ($customers_count == 1 && $customer_customer) {
|
||||||
|
// Update existing customer.
|
||||||
|
$imported++;
|
||||||
|
$customer_customer->setData($this->prepareEmails($data), true, true);
|
||||||
|
} else {
|
||||||
|
$customer_customer = Customer::create($data['emails'][0], $this->prepareEmails($data));
|
||||||
|
if ($customer_customer) {
|
||||||
|
$imported++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create without email.
|
||||||
|
if (!empty($data['first_name'])) {
|
||||||
|
$customer_customer = Customer::createWithoutEmail($this->prepareEmails($data));
|
||||||
|
if ($customer_customer) {
|
||||||
|
$imported++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$errors[] = '#'.($r+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set photo.
|
||||||
|
if (!empty($data['photo_url']) && $customer_customer) {
|
||||||
|
try {
|
||||||
|
$customer_customer->setPhotoFromRemoteFile($data['photo_url']);
|
||||||
|
$customer_customer->save();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errors[] = '#'.($r+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if ($imported) {
|
||||||
|
// $flash_type = 'flash_success_floating';
|
||||||
|
// } else {
|
||||||
|
// $flash_type = 'flash_error_floating';
|
||||||
|
// }
|
||||||
|
|
||||||
|
$response['result_html'] = __('Imported or updated: :imported customers.', ['imported' => $imported]);
|
||||||
|
if (count($errors)) {
|
||||||
|
$response['result_html'] .= '<br/>'.__('Could not import the following CSV rows as they contain not enough data: :errors.', ['errors' => implode(', ', $errors)]);
|
||||||
|
}
|
||||||
|
if (count($email_conflicts)) {
|
||||||
|
$response['result_html'] .= '<br/>'.__('Could not import the following CSV rows as emails specified in those rows belong to different existing customers: :email_conflicts.', ['email_conflicts' => implode(', ', $email_conflicts)]);
|
||||||
|
}
|
||||||
|
// \Session::flash($flash_type, __(':imported customer(s) imported, :errors error(s) occurred', ['imported' => $imported, 'errors' => $errors]));
|
||||||
|
// \Session::reflash();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$response['msg'] = __('Error occurred').': '.$e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg'] && $csv) {
|
||||||
|
$response['status'] = 'success';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$response['msg'] = 'Unknown action';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response['status'] == 'error' && empty($response['msg'])) {
|
||||||
|
$response['msg'] = 'Unknown error occured';
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Response::json($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importParseRow($row, $mapping) {
|
||||||
|
// TODO: Find a way to call parent::importParseRow while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$data = [];
|
||||||
|
foreach ($mapping as $field_name => $field_row_i) {
|
||||||
|
if (!isset($row[$field_row_i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$data[$field_name] = $row[$field_row_i];
|
||||||
|
$data_value = $data[$field_name];
|
||||||
|
|
||||||
|
switch ($field_name) {
|
||||||
|
case 'emails':
|
||||||
|
case 'phones':
|
||||||
|
case 'websites':
|
||||||
|
case 'social_profiles':
|
||||||
|
$data[$field_name] = explode(',', $data[$field_name]);
|
||||||
|
if (is_array($data[$field_name])
|
||||||
|
&& count($data[$field_name]) == 1
|
||||||
|
&& isset($data[$field_name][0])
|
||||||
|
&& empty($data[$field_name][0])
|
||||||
|
) {
|
||||||
|
unset($data[$field_name][0]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field_name == 'social_profiles' && is_array($data[$field_name])) {
|
||||||
|
// Social profiles.
|
||||||
|
foreach ($data[$field_name] as $i => $value) {
|
||||||
|
preg_match("/^([^:]+):(.*)/", $value, $m);
|
||||||
|
if (!empty($m[1]) && !empty($m[2])) {
|
||||||
|
$social_name = $m[1];
|
||||||
|
if (array_search($social_name, Customer::$social_type_names)) {
|
||||||
|
$data[$field_name][$i] = [
|
||||||
|
'value' => $m[2],
|
||||||
|
'type' => array_search($social_name, Customer::$social_type_names),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($field_name == 'country') {
|
||||||
|
// Country.
|
||||||
|
if (array_search($data[$field_name], Customer::$countries)) {
|
||||||
|
$data[$field_name] = array_search($data[$field_name], Customer::$countries);
|
||||||
|
}
|
||||||
|
$data[$field_name] = strtoupper(mb_substr($data[$field_name], 0, 2));
|
||||||
|
} elseif (\Str::startsWith($field_name, CustomerField::NAME_PREFIX)) {
|
||||||
|
// Custom field.
|
||||||
|
$value = $data[$field_name];
|
||||||
|
|
||||||
|
$field_id = preg_replace("/^".CustomerField::NAME_PREFIX."/", '', $field_name);
|
||||||
|
$field = CustomerField::find($field_id);
|
||||||
|
|
||||||
|
if ($field) {
|
||||||
|
$data[$field_name] = CustomerField::sanitizeValue($value, $field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomersWithoutConvQuery() {
|
||||||
|
// TODO: Find a way to call parent::getCustomersWithoutConvQuery while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
return Customer::select('customers.*')
|
||||||
|
->leftJoin('conversations', function ($join) {
|
||||||
|
$join->on('conversations.customer_id', '=', 'customers.id');
|
||||||
|
})
|
||||||
|
->leftJoin('threads', function ($join) {
|
||||||
|
$join->on('threads.created_by_customer_id', '=', 'customers.id');
|
||||||
|
})
|
||||||
|
->where('conversations.customer_id', null)
|
||||||
|
->where('threads.created_by_customer_id', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*/
|
||||||
|
public function export(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::export while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$fields = $request->fields ?? [];
|
||||||
|
$export_emails = false;
|
||||||
|
|
||||||
|
$exportable_fields = \Crm::getExportableFields();
|
||||||
|
|
||||||
|
$fields_regular = [];
|
||||||
|
|
||||||
|
foreach ($fields as $i => $field_name) {
|
||||||
|
if (array_key_exists($field_name, $exportable_fields)) {
|
||||||
|
if (!preg_match("/^".CustomerField::NAME_PREFIX."/", $field_name)) {
|
||||||
|
$fields_regular[] = $field_name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unset($fields[$i]);
|
||||||
|
}
|
||||||
|
if ($field_name == 'emails') {
|
||||||
|
$export_emails = true;
|
||||||
|
if (\Helper::isMySql()) {
|
||||||
|
$fields_regular[count($fields_regular)-1] = \DB::raw("GROUP_CONCAT(emails.email SEPARATOR ', ') as emails");
|
||||||
|
} else {
|
||||||
|
$fields_regular[count($fields_regular)-1] = \DB::raw("string_agg(emails.email, ', ') as emails");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
if (count($fields_regular)) {
|
||||||
|
|
||||||
|
if (!in_array('customers.id', $fields_regular)) {
|
||||||
|
$fields_regular[] = 'customers.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($export_emails) {
|
||||||
|
$query = Customer::select($fields_regular)
|
||||||
|
->leftJoin('emails', function ($join) {
|
||||||
|
$join->on('emails.customer_id', '=', 'customers.id');
|
||||||
|
})
|
||||||
|
->groupby('customers.id');
|
||||||
|
} else {
|
||||||
|
$query = Customer::select($fields_regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $query->get()->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add customer fields.
|
||||||
|
$fields_cf = [];
|
||||||
|
foreach ($fields as $field_name) {
|
||||||
|
if (preg_match("/^".CustomerField::NAME_PREFIX."/", $field_name)) {
|
||||||
|
$fields_cf[] = str_replace(CustomerField::NAME_PREFIX, '', $field_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($fields_cf)) {
|
||||||
|
$customer_fields = CustomerCustomerField::whereIn('customer_field_id', $fields_cf)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($results as $i => $row) {
|
||||||
|
foreach ($fields_cf as $cf_id) {
|
||||||
|
$results[$i][CustomerField::NAME_PREFIX.$cf_id] = '';
|
||||||
|
foreach ($customer_fields as $cf_row) {
|
||||||
|
if ($cf_row->customer_id == $row['id'] && $cf_row->customer_field_id == $cf_id) {
|
||||||
|
$results[$i][CustomerField::NAME_PREFIX.$cf_id] = $cf_row->value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($results as $i => $row) {
|
||||||
|
if (!in_array('customers.id', $fields) && isset($row['id'])) {
|
||||||
|
unset($results[$i]['id']);
|
||||||
|
}
|
||||||
|
if (!empty($row['photo_url'])) {
|
||||||
|
$results[$i]['photo_url'] = Customer::getPhotoUrlByFileName($row['photo_url']);
|
||||||
|
}
|
||||||
|
if (!empty($row['phones'])) {
|
||||||
|
$phones = json_decode($row['phones'], true);
|
||||||
|
$row['phones'] = '';
|
||||||
|
$phones_list = [];
|
||||||
|
if (is_array($phones) && !empty($phones)) {
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
$phones_list[] = $phone['value'];
|
||||||
|
}
|
||||||
|
$results[$i]['phones'] = implode(', ', $phones_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($row['websites'])) {
|
||||||
|
$websites = json_decode($row['websites'], true);
|
||||||
|
$results[$i]['websites'] = '';
|
||||||
|
|
||||||
|
if (is_array($websites) && !empty($websites)) {
|
||||||
|
$results[$i]['websites'] = implode(', ', $websites);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($row['social_profiles'])) {
|
||||||
|
$social_profiles = json_decode($row['social_profiles'], true);
|
||||||
|
$row['social_profiles'] = '';
|
||||||
|
$social_profiles_list = [];
|
||||||
|
if (is_array($social_profiles) && !empty($social_profiles)) {
|
||||||
|
foreach ($social_profiles as $social_profile) {
|
||||||
|
$sp_formatted = Customer::formatSocialProfile($social_profile);
|
||||||
|
$social_profiles_list[] = $sp_formatted['type_name'].':'.$social_profile['value'];
|
||||||
|
}
|
||||||
|
$results[$i]['social_profiles'] = implode(', ', $social_profiles_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = 'customers_'.date('Y-m-d').'.csv';
|
||||||
|
|
||||||
|
$encoding = $request->encoding;
|
||||||
|
$separator = $request->separator;
|
||||||
|
|
||||||
|
if ($separator == 'TAB') {
|
||||||
|
$separator = "\t";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename some fields.
|
||||||
|
foreach ($fields as $i => $field_name) {
|
||||||
|
// if (strstr($field_name, 'as emails')) {
|
||||||
|
// $field_name = 'emails';
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!empty($exportable_fields[$field_name])) {
|
||||||
|
$fields[$i] = $exportable_fields[$field_name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema_insert = '"'.implode('"'.$separator.'"', $fields).'"';
|
||||||
|
$out = $schema_insert."\n";
|
||||||
|
|
||||||
|
foreach($results as $row) {
|
||||||
|
$schema_insert = '';
|
||||||
|
|
||||||
|
foreach ($row as $row_value) {
|
||||||
|
$value_prepared = str_replace('"', '""', $row_value ?? '');
|
||||||
|
$value_prepared = str_replace("\t", '', $value_prepared);
|
||||||
|
$schema_insert .= '"'.$value_prepared.'"'.$separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= $schema_insert."\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ob_get_contents()) {
|
||||||
|
ob_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($encoding != 'UTF-8') {
|
||||||
|
try {
|
||||||
|
$out = iconv("UTF-8", $encoding, $out);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// https://github.com/freescout-helpdesk/freescout/issues/2825
|
||||||
|
$out = iconv("UTF-8", $encoding.'//IGNORE', $out);
|
||||||
|
}
|
||||||
|
if ($encoding == 'UCS-2LE') {
|
||||||
|
$out = "\xFF\xFE".$out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// BOM: https://github.com/freescout-helpdesk/freescout/issues/3993
|
||||||
|
$out = "\xEF\xBB\xBF".$out;
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Cache-Control: must-revalidate, no-cache, no-store, private");
|
||||||
|
header("Content-Length: " . strlen($out));
|
||||||
|
header("Content-type: application/csv; charset=UCS-2LE");
|
||||||
|
header("Content-Disposition: attachment; filename=$filename");
|
||||||
|
echo $out;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
386
src/Http/Controllers/CustomersController.php
Normal file
386
src/Http/Controllers/CustomersController.php
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
SPDX-License-Identifier: AGPL
|
||||||
|
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MMF\FreescoutRestrictedCustomers\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Conversation;
|
||||||
|
use App\Email;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Validator;
|
||||||
|
use App\Http\Controllers\CustomersController as BaseCustomersController;
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Customer;
|
||||||
|
|
||||||
|
class CustomersController extends BaseCustomersController {
|
||||||
|
/**
|
||||||
|
* Edit customer.
|
||||||
|
*/
|
||||||
|
public function update($id) {
|
||||||
|
// TODO: Find a way to call parent::update while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$customer = Customer::findOrFail($id);
|
||||||
|
|
||||||
|
$customer_emails = $customer->emails;
|
||||||
|
if (count($customer_emails)) {
|
||||||
|
foreach ($customer_emails as $row) {
|
||||||
|
$emails[] = $row->email;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$emails = [''];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('customers/update', ['customer' => $customer, 'emails' => $emails]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save customer.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function updateSave($id, Request $request) {
|
||||||
|
// TODO: Find a way to call parent::updateSave while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
function mb_ucfirst($string)
|
||||||
|
{
|
||||||
|
return mb_strtoupper(mb_substr($string, 0, 1)).mb_strtolower(mb_substr($string, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer = Customer::findOrFail($id);
|
||||||
|
$flash_message = '';
|
||||||
|
|
||||||
|
// First name or email must be specified
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'first_name' => 'nullable|string|max:255|required_without:emails.0',
|
||||||
|
'last_name' => 'nullable|string|max:255',
|
||||||
|
'city' => 'nullable|string|max:255',
|
||||||
|
'state' => 'nullable|string|max:255',
|
||||||
|
'zip' => 'nullable|string|max:12',
|
||||||
|
'country' => 'nullable|string|max:2',
|
||||||
|
//'emails' => 'array|required_without:first_name',
|
||||||
|
//'emails.1' => 'nullable|email|required_without:first_name',
|
||||||
|
'emails.*' => 'nullable|email|distinct|required_without:first_name',
|
||||||
|
'photo_url' => 'nullable|image|mimes:jpeg,png,jpg,gif',
|
||||||
|
]);
|
||||||
|
$validator->setAttributeNames([
|
||||||
|
'photo_url' => __('Photo'),
|
||||||
|
'emails.*' => __('Email'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Photo
|
||||||
|
$validator->after(function ($validator) use ($customer, $request) {
|
||||||
|
if ($request->hasFile('photo_url')) {
|
||||||
|
$path_url = $customer->savePhoto($request->file('photo_url')->getRealPath(), $request->file('photo_url')->getMimeType());
|
||||||
|
|
||||||
|
if ($path_url) {
|
||||||
|
$customer->photo_url = $path_url;
|
||||||
|
} else {
|
||||||
|
$validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return redirect()->route('customers.update', ['id' => $id])
|
||||||
|
->withErrors($validator)
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_emails = [];
|
||||||
|
$new_emails_change_customer = [];
|
||||||
|
$removed_emails = [];
|
||||||
|
|
||||||
|
// Detect new emails added
|
||||||
|
$customer_emails = $customer->emails()->pluck('email')->toArray();
|
||||||
|
foreach ($request->emails as $email) {
|
||||||
|
if (!in_array($email, $customer_emails)) {
|
||||||
|
$new_emails[] = $email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If new email belongs to another customer, let user know about it in the flash message
|
||||||
|
foreach ($new_emails as $new_email) {
|
||||||
|
$email = Email::where('email', $new_email)->first();
|
||||||
|
if ($email && $email->customer) {
|
||||||
|
// If customer whose email is removed does not have first name and other emails
|
||||||
|
// we have to create first name for this customer
|
||||||
|
if (!$email->customer->first_name && count($email->customer->emails) == 1) {
|
||||||
|
if ($request->first_name) {
|
||||||
|
$email->customer->first_name = $request->first_name;
|
||||||
|
} elseif ($customer->first_name) {
|
||||||
|
$email->customer->first_name = $customer->first_name;
|
||||||
|
} else {
|
||||||
|
$email->customer->first_name = mb_ucfirst($email->getNameFromEmail());
|
||||||
|
}
|
||||||
|
$email->customer->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$flash_message .= __('Email :tag_email_begin:email:tag_email_end has been moved from another customer: :a_begin:customer:a_end.', [
|
||||||
|
'email' => $email->email,
|
||||||
|
'tag_email_begin' => '<strong>',
|
||||||
|
'tag_email_end' => '</strong>',
|
||||||
|
'customer' => $email->customer->getFullName(),
|
||||||
|
'a_begin' => '<strong><a href="'.$email->customer->url().'" target="_blank">',
|
||||||
|
'a_end' => '</a></strong>',
|
||||||
|
]).' ';
|
||||||
|
|
||||||
|
$new_emails_change_customer[] = $email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect removed emails
|
||||||
|
foreach ($customer_emails as $email) {
|
||||||
|
if (!in_array($email, $request->emails)) {
|
||||||
|
$removed_emails[] = $email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$request_data = $request->all();
|
||||||
|
|
||||||
|
if (isset($request_data['photo_url'])) {
|
||||||
|
unset($request_data['photo_url']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer->setData($request_data);
|
||||||
|
// Websites
|
||||||
|
// if (!empty($request->websites)) {
|
||||||
|
// $customer->setWebsites($request->websites);
|
||||||
|
// }
|
||||||
|
$customer->save();
|
||||||
|
|
||||||
|
$customer->syncEmails($request->emails);
|
||||||
|
|
||||||
|
// Update customer_id in all conversations added to the current customer.
|
||||||
|
foreach ($new_emails_change_customer as $new_email) {
|
||||||
|
if ($new_email->customer_id) {
|
||||||
|
$conversations_to_change_customer = Conversation::where('customer_id', $new_email->customer_id)->get();
|
||||||
|
} else {
|
||||||
|
// This does not work for phone conversations.
|
||||||
|
$conversations_to_change_customer = Conversation::where('customer_email', $new_email->email)->get();
|
||||||
|
}
|
||||||
|
foreach ($conversations_to_change_customer as $conversation) {
|
||||||
|
// We have to pass user to create line item and let others know that customer has changed.
|
||||||
|
// Conversation may be even in other mailbox where user does not have an access.
|
||||||
|
$conversation->changeCustomer($new_email->email, $customer, auth()->user());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update customer in conversations for emails removed from current customer.
|
||||||
|
foreach ($removed_emails as $removed_email) {
|
||||||
|
$email = Email::where('email', $removed_email)->first();
|
||||||
|
if ($email) {
|
||||||
|
$conversations = Conversation::where('customer_email', $email->email)->get();
|
||||||
|
foreach ($conversations as $conversation) {
|
||||||
|
$conversation->changeCustomer($email->email, $email->customer, auth()->user());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\Eventy::action('customer.updated', $customer);
|
||||||
|
|
||||||
|
$flash_message = __('Customer saved successfully.').' '.$flash_message;
|
||||||
|
\Session::flash('flash_success_unescaped', $flash_message);
|
||||||
|
|
||||||
|
\Session::flash('customer.updated', 1);
|
||||||
|
|
||||||
|
return redirect()->route('customers.update', ['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View customer conversations.
|
||||||
|
*
|
||||||
|
* @param intg $id
|
||||||
|
*/
|
||||||
|
public function conversations($id) {
|
||||||
|
// TODO: Find a way to call parent::conversations while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$customer = Customer::findOrFail($id);
|
||||||
|
|
||||||
|
$conversations = $customer->conversations()
|
||||||
|
->where('customer_id', $customer->id)
|
||||||
|
->whereIn('mailbox_id', auth()->user()->mailboxesIdsCanView())
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate(Conversation::DEFAULT_LIST_SIZE);
|
||||||
|
|
||||||
|
return view('customers/conversations', [
|
||||||
|
'customer' => $customer,
|
||||||
|
'conversations' => $conversations,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customers ajax search.
|
||||||
|
*/
|
||||||
|
public function ajaxSearch(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::ajaxSearch while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$response = [
|
||||||
|
'results' => [],
|
||||||
|
'pagination' => ['more' => false],
|
||||||
|
];
|
||||||
|
|
||||||
|
$q = $request->q;
|
||||||
|
|
||||||
|
$join_emails = false;
|
||||||
|
if ($request->search_by == 'all' || $request->search_by == 'email' || $request->exclude_email) {
|
||||||
|
$join_emails = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$select_list = ['customers.id', 'first_name', 'last_name'];
|
||||||
|
if ($join_emails) {
|
||||||
|
$select_list[] = 'emails.email';
|
||||||
|
}
|
||||||
|
if ($request->show_fields == 'phone') {
|
||||||
|
$select_list[] = 'phones';
|
||||||
|
}
|
||||||
|
$customers_query = Customer::select($select_list);
|
||||||
|
|
||||||
|
if ($join_emails) {
|
||||||
|
if ($request->allow_non_emails) {
|
||||||
|
$customers_query->leftJoin('emails', 'customers.id', '=', 'emails.customer_id');
|
||||||
|
} else {
|
||||||
|
$customers_query->join('emails', 'customers.id', '=', 'emails.customer_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->search_by == 'all' || $request->search_by == 'email') {
|
||||||
|
$customers_query->where('emails.email', 'like', '%'.$q.'%');
|
||||||
|
}
|
||||||
|
if ($request->exclude_email) {
|
||||||
|
$customers_query->where('emails.email', '<>', $request->exclude_email);
|
||||||
|
}
|
||||||
|
if ($request->search_by == 'all' || $request->search_by == 'name') {
|
||||||
|
$customers_query->orWhere('first_name', 'like', '%'.$q.'%')
|
||||||
|
->orWhere('last_name', 'like', '%'.$q.'%');
|
||||||
|
}
|
||||||
|
if ($request->search_by == 'phone') {
|
||||||
|
$phone_numeric = \Helper::phoneToNumeric($q);
|
||||||
|
if (!$phone_numeric) {
|
||||||
|
$phone_numeric = $q;
|
||||||
|
}
|
||||||
|
$customers_query->where('customers.phones', 'like', '%'.$phone_numeric.'%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$customers = $customers_query->paginate(20);
|
||||||
|
|
||||||
|
foreach ($customers as $customer) {
|
||||||
|
$id = '';
|
||||||
|
$text = '';
|
||||||
|
|
||||||
|
if ($request->show_fields != 'all') {
|
||||||
|
switch ($request->show_fields) {
|
||||||
|
case 'email':
|
||||||
|
$text = $customer->email;
|
||||||
|
break;
|
||||||
|
case 'name':
|
||||||
|
$text = $customer->getFullName();
|
||||||
|
break;
|
||||||
|
case 'phone':
|
||||||
|
// Get phone which matches
|
||||||
|
$phones = $customer->getPhones();
|
||||||
|
foreach ($phones as $phone) {
|
||||||
|
$phone_numeric = \Helper::phoneToNumeric($q);
|
||||||
|
if (strstr($phone['value'], $q) || strstr($phone['n'] ?? '', $phone_numeric)) {
|
||||||
|
$text = $phone['value'];
|
||||||
|
if ($customer->getFullName()) {
|
||||||
|
$text .= ' — '.$customer->getFullName();
|
||||||
|
}
|
||||||
|
$id = $phone['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$text = $customer->getNameAndEmail();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$text = $customer->getNameAndEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$id) {
|
||||||
|
if (!empty($request->use_id)) {
|
||||||
|
$id = $customer->id;
|
||||||
|
} else {
|
||||||
|
$id = $customer->getMainEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$response['results'][] = [
|
||||||
|
'id' => $id,
|
||||||
|
'text' => $text,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response['pagination']['more'] = $customers->hasMorePages();
|
||||||
|
|
||||||
|
return \Response::json($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajax controller.
|
||||||
|
*/
|
||||||
|
public function ajax(Request $request) {
|
||||||
|
// TODO: Find a way to call parent::ajax while only overriding the Customer class,
|
||||||
|
// instead of overriding the whole method here.
|
||||||
|
$response = [
|
||||||
|
'status' => 'error',
|
||||||
|
'msg' => '', // this is error message
|
||||||
|
];
|
||||||
|
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
switch ($request->action) {
|
||||||
|
|
||||||
|
// Change conversation user
|
||||||
|
case 'create':
|
||||||
|
// First name or email must be specified
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'first_name' => 'required|string|max:255',
|
||||||
|
'last_name' => 'nullable|string|max:255',
|
||||||
|
'email' => 'required|email|unique:emails,email',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
foreach ($validator->errors()->getMessages()as $errors) {
|
||||||
|
foreach ($errors as $field => $message) {
|
||||||
|
$response['msg'] .= $message.' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$response['msg']) {
|
||||||
|
$customer = Customer::create($request->email, $request->all());
|
||||||
|
if ($customer) {
|
||||||
|
$response['email'] = $request->email;
|
||||||
|
$response['status'] = 'success';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Conversations navigation
|
||||||
|
case 'customers_pagination':
|
||||||
|
|
||||||
|
$customers = app('App\Http\Controllers\ConversationsController')->searchCustomers($request, $user);
|
||||||
|
|
||||||
|
$response['status'] = 'success';
|
||||||
|
|
||||||
|
$response['html'] = view('customers/partials/customers_table', [
|
||||||
|
'customers' => $customers,
|
||||||
|
])->render();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$response['msg'] = 'Unknown action';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response['status'] == 'error' && empty($response['msg'])) {
|
||||||
|
$response['msg'] = 'Unknown error occurred';
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Response::json($response);
|
||||||
|
}
|
||||||
|
}
|
19
src/Mailbox.php
Normal file
19
src/Mailbox.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
SPDX-License-Identifier: AGPL
|
||||||
|
SPDX-FileCopyrightText: © 2024 Millions Missing FRANCE <info@millionsmissing.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MMF\FreescoutRestrictedCustomers;
|
||||||
|
|
||||||
|
use MMF\FreescoutRestrictedCustomers\Customer;
|
||||||
|
use App\Mailbox as BaseMailbox;
|
||||||
|
|
||||||
|
class Mailbox extends BaseMailbox {
|
||||||
|
/**
|
||||||
|
* Get the Customers that are linked to this Mailbox.
|
||||||
|
*/
|
||||||
|
public function customers() {
|
||||||
|
return $this->hasMany(Customer::class);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue