Compare commits

..

No commits in common. "c9276580799b3d087b366c8ca021429ba402c822" and "ee77d2f02d4ac1d71f9bea4de3f6e058f2c68b2b" have entirely different histories.

12 changed files with 908 additions and 47 deletions

View file

@ -1,8 +1,3 @@
0.8.0
* Rely on hooks to filter the Customers search.
* Rely on hooks to link Customers to a Mailbox.
0.7.0 0.7.0
* Convert the PHP package into a Laravel module, * Convert the PHP package into a Laravel module,

View file

@ -8,7 +8,7 @@ use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
class RelaxUnicityOfCustomerEmails extends Migration { class DropUnicityFromCustomerEmails extends Migration {
/** /**
* Run the migrations. * Run the migrations.
* *
@ -16,9 +16,6 @@ class RelaxUnicityOfCustomerEmails extends Migration {
*/ */
public function up() { public function up() {
Schema::table('emails', function (Blueprint $table) { Schema::table('emails', function (Blueprint $table) {
// Ensure a same e-mail can only be set once for a given Customer.
$table->unique(['email', 'customer_id']);
// Allow a same e-mail to be set to multiple Customers.
$table->dropUnique(['email']); $table->dropUnique(['email']);
}); });
} }
@ -31,10 +28,7 @@ class RelaxUnicityOfCustomerEmails extends Migration {
public function down() { public function down() {
Schema::table('emails', function (Blueprint $table) { Schema::table('emails', function (Blueprint $table) {
// TODO: Remove all duplicated e-mails. // TODO: Remove all duplicated e-mails.
// Restore the unicity of e-mails.
$table->unique('email'); $table->unique('email');
// Drop the redundant unicity constraint.
$table->dropUnique(['email', 'customer_id']);
}); });
} }
} }

View file

@ -0,0 +1,21 @@
<?php
namespace Modules\MMFRestrictedCustomers\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class MMFRestrictedCustomersDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
// $this->call("OthersTableSeeder");
}
}

View file

@ -43,6 +43,100 @@ class Customer extends BaseCustomer {
return $this->belongsTo(Mailbox::class); return $this->belongsTo(Mailbox::class);
} }
/**
* Create customer or get existing and fill empty fields.
*
* @param string $email
* @param array $data [description]
*
* @return [type] [description]
*/
public static function create($email, $data = []) {
$new = false;
$email = Email::sanitizeEmail($email);
if (!$email) {
return null;
}
$email_obj = Email::where('email', $email)->first();
if ($email_obj) {
$customer = $email_obj->customer;
// In case somehow the email has no customer.
if (!$customer) {
// Customer will be saved and connected to the email later.
$customer = new self();
}
// Update name if empty.
/*if (empty($customer->first_name) && !empty($data['first_name'])) {
$customer->first_name = $data['first_name'];
if (empty($customer->last_name) && !empty($data['last_name'])) {
$customer->last_name = $data['last_name'];
}
$customer->save();
}*/
} else {
$customer = new self();
$email_obj = new Email();
$email_obj->email = $email;
$new = true;
}
// Ensure that the Mailbox relationship is set, if provided.
if ( in_array('mailbox_id', $data) )
$customer->mailbox_id = $data['mailbox_id'];
// Set empty fields
if ($customer->setData($data, false) || !$customer->id) {
$customer->save();
}
if (empty($email_obj->id) || !$email_obj->customer_id || $email_obj->customer_id != $customer->id) {
// Email may have been set in setData().
$save_email = true;
if (!empty($data['emails']) && is_array($data['emails'])) {
foreach ($data['emails'] as $data_email) {
if (is_string($data_email) && $data_email == $email) {
$save_email = false;
break;
}
if (is_array($data_email) && !empty($data_email['value']) && $data_email['value'] == $email) {
$save_email = false;
break;
}
}
}
if ($save_email) {
$email_obj->customer()->associate($customer);
$email_obj->save();
}
}
// Todo: check phone uniqueness.
if ($new) {
\Eventy::action('customer.created', $customer);
}
return $customer;
}
/**
* Set empty fields.
*/
public function setData($data, $replace_data = true, $save = false) {
// Set the Mailbox this Customer should be linked to.
// TODO: Throw an error if the Mailbox is not set.
if ( isset($data['mailbox']) ) {
// TODO: Check that the current user is allowed to access this Mailbox.
$data['mailbox_id'] = $data['mailbox'];
unset($data['mailbox']);
}
return parent::setData($data, $replace_data, $save);
}
/** /**
* Only return a Customer instance if it is available to the current User. * Only return a Customer instance if it is available to the current User.
* *

View file

@ -6,14 +6,14 @@
namespace Modules\MMFRestrictedCustomers\Http\Controllers; namespace Modules\MMFRestrictedCustomers\Http\Controllers;
use Validator;
use Illuminate\Http\Request;
use App\Conversation; use App\Conversation;
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 Modules\Crm\Http\Controllers\CrmController as BaseCrmController;
use Modules\MMFRestrictedCustomers\Entities\Customer; use Modules\MMFRestrictedCustomers\Entities\Customer;
use Modules\MMFRestrictedCustomers\Entities\Email; use Modules\MMFRestrictedCustomers\Entities\Email;
use Modules\MMFRestrictedCustomers\Entities\Mailbox; use Modules\MMFRestrictedCustomers\Entities\Mailbox;
@ -34,7 +34,7 @@ class CrmController extends BaseCrmController {
} }
public function createCustomerSave(Request $request) { public function createCustomerSave(Request $request) {
// TODO: Find a way to call parent::createCustomerSave while only overriding the Email class, // TODO: Find a way to call parent::createCustomerSave while only overriding the Customer class,
// instead of overriding the whole method here. // instead of overriding the whole method here.
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'first_name' => 'nullable|string|max:255|required_without:emails.0', 'first_name' => 'nullable|string|max:255|required_without:emails.0',
@ -152,4 +152,603 @@ class CrmController extends BaseCrmController {
return \Response::json($response); 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;
}
} }

View file

@ -6,13 +6,11 @@
namespace Modules\MMFRestrictedCustomers\Http\Controllers; namespace Modules\MMFRestrictedCustomers\Http\Controllers;
use Validator;
use Illuminate\Http\Request;
use App\Conversation; use App\Conversation;
use App\Email;
use Illuminate\Http\Request;
use Validator;
use App\Http\Controllers\CustomersController as BaseCustomersController; use App\Http\Controllers\CustomersController as BaseCustomersController;
use Modules\MMFRestrictedCustomers\Entities\Customer; use Modules\MMFRestrictedCustomers\Entities\Customer;
class CustomersController extends BaseCustomersController { class CustomersController extends BaseCustomersController {
@ -20,6 +18,8 @@ class CustomersController extends BaseCustomersController {
* Edit customer. * Edit customer.
*/ */
public function update($id) { 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 = Customer::findOrFail($id);
// Get the list of Mailboxes the current User has access to. // Get the list of Mailboxes the current User has access to.
@ -43,6 +43,161 @@ class CustomersController extends BaseCustomersController {
]); ]);
} }
/**
* 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. * View customer conversations.
* *

View file

@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Route;
Route::group(['middleware' => 'web', 'prefix' => \Helper::getSubdirectory(), 'namespace' => 'Modules\MMFRestrictedCustomers\Http\Controllers'], function() { Route::group(['middleware' => 'web', 'prefix' => \Helper::getSubdirectory(), 'namespace' => 'Modules\MMFRestrictedCustomers\Http\Controllers'], function() {
// Customers // Customers
Route::get('/customers/{id}/edit', CustomersController::class . '@update')->name('customers.update'); 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/{id}/', CustomersController::class . '@conversations')->name('customers.conversations');
Route::get('/customers/ajax-search', ['uses' => CustomersController::class . '@ajaxSearch', 'laroute' => true])->name('customers.ajax_search'); 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::post('/customers/ajax', ['uses' => CustomersController::class . '@ajax', 'laroute' => true])->name('customers.ajax');
@ -22,4 +23,8 @@ Route::group(['middleware' => 'web', 'prefix' => \Helper::getSubdirectory(), 'na
Route::get('/customers/fields/ajax-search', ['uses' => CrmController::class . '@ajaxSearch', 'laroute' => true])->name('crm.ajax_search'); 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::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');
});
}); });

View file

@ -36,7 +36,7 @@ class MMFRestrictedCustomersServiceProvider extends ServiceProvider {
* Module hooks. * Module hooks.
*/ */
public function hooks() { public function hooks() {
// When searching for customers, restrict the list to the ones the current user is allowed to see. // When searching for customer, restrict the list to the ones the current user is allowed to see.
Eventy::addFilter('search.customers.apply_filters', function($query_customers) { Eventy::addFilter('search.customers.apply_filters', function($query_customers) {
$user = auth()->user(); $user = auth()->user();
// Do not restrict the query if the current user is an admin. // Do not restrict the query if the current user is an admin.
@ -48,22 +48,6 @@ class MMFRestrictedCustomersServiceProvider extends ServiceProvider {
} }
return $query_customers; return $query_customers;
}); });
// When creating or updating a customer, ensure its mailbox id is set.
Eventy::addAction(
'customer.set_data',
function($customer, $data, $replace_data) {
if ( isset($data['mailbox']) ) {
// TODO: Check that the current user is allowed to access this Mailbox.
$data['mailbox_id'] = $data['mailbox'];
unset($data['mailbox']);
}
// TODO: Throw an error if the Mailbox is not set.
$customer->mailbox_id = $data['mailbox_id'];
},
$priority = 20,
$arguments = 3
);
} }
/** /**

View file

@ -16,9 +16,11 @@ You have been warned.
## Installation instructions ## Installation instructions
### Install the module ### Install the package with composer
**TODO** ```
composer require "millions-missing-france/mmfrestrictedcustomers" "0.7.0"
```
### Edit the application routes ### Edit the application routes
@ -43,7 +45,7 @@ should be replaced with:
```php ```php
// Customers // Customers
Route::get('/customers/{id}/edit', '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@update')->name('customers.update'); Route::get('/customers/{id}/edit', '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@update')->name('customers.update');
Route::post('/customers/{id}/edit', 'CustomersController@updateSave'); Route::post('/customers/{id}/edit', '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@updateSave');
Route::get('/customers/{id}/', '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@conversations')->name('customers.conversations'); Route::get('/customers/{id}/', '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@conversations')->name('customers.conversations');
Route::get('/customers/ajax-search', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@ajaxSearch', 'laroute' => true])->name('customers.ajax_search'); Route::get('/customers/ajax-search', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@ajaxSearch', 'laroute' => true])->name('customers.ajax_search');
Route::post('/customers/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@ajax', 'laroute' => true])->name('customers.ajax'); Route::post('/customers/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CustomersController@ajax', 'laroute' => true])->name('customers.ajax');
@ -62,6 +64,12 @@ Route::group(['middleware' => ['web', 'auth', 'roles'], 'roles' => ['user', 'adm
Route::get('/customers/fields/ajax-search', ['uses' => 'CrmController@ajaxSearch', 'laroute' => true])->name('crm.ajax_search'); Route::get('/customers/fields/ajax-search', ['uses' => 'CrmController@ajaxSearch', 'laroute' => true])->name('crm.ajax_search');
Route::post('/crm/ajax', ['uses' => 'CrmController@ajax', 'laroute' => true])->name('crm.ajax'); Route::post('/crm/ajax', ['uses' => 'CrmController@ajax', 'laroute' => true])->name('crm.ajax');
}); });
Route::group(['middleware' => ['web', 'auth', 'roles'], 'roles' => ['admin'], 'prefix' => \Helper::getSubdirectory(), 'namespace' => 'Modules\Crm\Http\Controllers'], function()
{
Route::post('/customers/export', ['uses' => 'CrmController@export'])->name('crm.export');
Route::post('/crm/ajax-admin', ['uses' => 'CrmController@ajaxAdmin', 'laroute' => true])->name('crm.ajax_admin');
});
``` ```
should be replaced with: should be replaced with:
@ -75,6 +83,12 @@ Route::group(['middleware' => ['web', 'auth', 'roles'], 'roles' => ['user', 'adm
Route::get('/customers/fields/ajax-search', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CrmController@ajaxSearch', 'laroute' => true])->name('crm.ajax_search'); Route::get('/customers/fields/ajax-search', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CrmController@ajaxSearch', 'laroute' => true])->name('crm.ajax_search');
Route::post('/crm/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CrmController@ajax', 'laroute' => true])->name('crm.ajax'); Route::post('/crm/ajax', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CrmController@ajax', 'laroute' => true])->name('crm.ajax');
}); });
Route::group(['middleware' => ['web', 'auth', 'roles'], 'roles' => ['admin'], 'prefix' => \Helper::getSubdirectory(), 'namespace' => '\Modules\MMFRestrictedCustomers\Http\Controllers'], function()
{
Route::post('/customers/export', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CrmController@export'])->name('crm.export');
Route::post('/crm/ajax-admin', ['uses' => '\Modules\MMFRestrictedCustomers\Http\Controllers\CrmController@ajaxAdmin', 'laroute' => true])->name('crm.ajax_admin');
});
``` ```
#### Modules/Crm/Providers/CrmServiceProvider.php #### Modules/Crm/Providers/CrmServiceProvider.php

View file

@ -5,7 +5,7 @@
@foreach ($customers as $customer) @foreach ($customers as $customer)
<a href="{{ Eventy::filter('customer.card.url', route('customers.update', ['id' => $customer->id]), $customer) }}" class="card hover-shade" @action('customer.card.link', $customer) > <a href="{{ Eventy::filter('customer.card.url', route('customers.update', ['id' => $customer->id]), $customer) }}" class="card hover-shade" @action('customer.card.link', $customer) >
<img src="{{ $customer->getPhotoUrl() }}" /> <img src="{{ $customer->getPhotoUrl() }}" />
@if ($customer->mailbox_id == null) @if ($customer->mailbox == null)
<p style="font-size:12px; color: red;">{{ __('Warning: no mailbox') }}</p> <p style="font-size:12px; color: red;">{{ __('Warning: no mailbox') }}</p>
@endif @endif
<h4 title="{{ $customer->first_name }} {{ $customer->last_name }}">{{ $customer->first_name }} {{ $customer->last_name }}</h4> <h4 title="{{ $customer->first_name }} {{ $customer->last_name }}">{{ $customer->first_name }} {{ $customer->last_name }}</h4>

View file

@ -1,7 +1,7 @@
{ {
"name": "millions-missing-france/freescout-restricted-customers", "name": "millions-missing-france/freescout-restricted-customers",
"description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes", "description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes",
"version": "0.8.0", "version": "0.7.0",
"type": "library", "type": "library",
"license": ["AGPL-3.0-only"], "license": ["AGPL-3.0-only"],
"authors": [ "authors": [

View file

@ -2,7 +2,7 @@
"name": "MMFRestrictedCustomers", "name": "MMFRestrictedCustomers",
"alias": "mmfrestrictedcustomers", "alias": "mmfrestrictedcustomers",
"description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes", "description": "Freescout restricted customers - Restrict access to Freescout customers to specific mailboxes",
"version": "0.8.0", "version": "0.7.0",
"detailsUrl": "", "detailsUrl": "",
"author": "Millions Missing FRANCE", "author": "Millions Missing FRANCE",
"authorUrl": "info@millionsmissing.fr", "authorUrl": "info@millionsmissing.fr",