From 52c031e2905d4bc24ddff6f7b536be454f64f672 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 24 Nov 2019 17:37:53 +1100 Subject: [PATCH] Email template defaults (#3090) * Update User and Company User * Email Template Defaults for Settings * Separate methods for saving client and group settings --- app/DataMapper/CompanySettings.php | 14 ++ app/DataMapper/EmailTemplateDefaults.php | 73 ++++++ app/Http/Controllers/UserController.php | 10 + .../Requests/Client/StoreClientRequest.php | 4 +- .../Requests/Client/UpdateClientRequest.php | 4 +- .../GroupSetting/StoreGroupSettingRequest.php | 4 +- .../UpdateGroupSettingRequest.php | 4 +- app/Http/Requests/User/ShowUserRequest.php | 3 +- app/Http/Requests/User/StoreUserRequest.php | 2 +- app/Http/Requests/User/UpdateUserRequest.php | 20 +- .../ValidClientGroupSettingsRule.php | 58 +++++ app/Repositories/UserRepository.php | 2 +- app/Utils/Traits/ClientGroupSettingsSaver.php | 220 ++++++++++++++++++ tests/Unit/GroupSettingsTest.php | 17 ++ 14 files changed, 414 insertions(+), 21 deletions(-) create mode 100644 app/Http/ValidationRules/ValidClientGroupSettingsRule.php create mode 100644 app/Utils/Traits/ClientGroupSettingsSaver.php diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 440fb07eb2de..a3c912244bf1 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -403,6 +403,20 @@ class CompanySettings extends BaseSettings $data->email_subject_invoice = EmailTemplateDefaults::emailInvoiceSubject(); $data->email_template_invoice = EmailTemplateDefaults:: emailInvoiceTemplate(); + $data->email_subject_quote = EmailTemplateDefaults::emailQuoteSubject(); + $data->email_subject_payment = EmailTemplateDefaults::emailPaymentSubject(); + $data->email_subject_statement = EmailTemplateDefaults::emailStatementSubject(); + $data->email_template_quote = EmailTemplateDefaults::emailQuoteTemplate(); + $data->email_template_payment = EmailTemplateDefaults::emailPaymentTemplate(); + $data->email_template_statement = EmailTemplateDefaults::emailStatementTemplate(); + $data->email_subject_reminder1 = EmailTemplateDefaults::emailReminder1Subject(); + $data->email_subject_reminder2 = EmailTemplateDefaults::emailReminder2Subject(); + $data->email_subject_reminder3 = EmailTemplateDefaults::emailReminder3Subject(); + $data->email_subject_reminder_endless = EmailTemplateDefaults::emailReminderEndlessSubject(); + $data->email_template_reminder1 = EmailTemplateDefaults::emailReminder1Template(); + $data->email_template_reminder2 = EmailTemplateDefaults::emailReminder2Template(); + $data->email_template_reminder3 = EmailTemplateDefaults::emailReminder3Template(); + $data->email_template_reminder_endless = EmailTemplateDefaults::emailReminderEndlessTemplate(); return self::setCasts($data, self::$casts); diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php index 2a1db3e6c084..a9ce7dac54aa 100644 --- a/app/DataMapper/EmailTemplateDefaults.php +++ b/app/DataMapper/EmailTemplateDefaults.php @@ -25,8 +25,81 @@ class EmailTemplateDefaults return Parsedown::instance()->line(self::transformText('invoice_message')); } + public static function emailQuoteSubject() + { + return Parsedown::instance()->line(self::transformText('quote_subject')); + } + + public static function emailQuoteTemplate() + { + return Parsedown::instance()->line(self::transformText('quote_message')); + } + + public static function emailPaymentSubject() + { + return Parsedown::instance()->line(self::transformText('payment_subject')); + } + + public static function emailPaymentTemplate() + { + return Parsedown::instance()->line(self::transformText('payment_message')); + } + + public static function emailReminder1Subject() + { + return Parsedown::instance()->line(self::transformText('reminder_subject')); + } + + public static function emailReminder1Template() + { + return Parsedown::instance()->line('First Email Reminder Text'); + } + + public static function emailReminder2Subject() + { + return Parsedown::instance()->line(self::transformText('reminder_subject')); + } + + public static function emailReminder2Template() + { + return Parsedown::instance()->line('Second Email Reminder Text'); + } + + public static function emailReminder3Subject() + { + return Parsedown::instance()->line(self::transformText('reminder_subject')); + } + + public static function emailReminder3Template() + { + return Parsedown::instance()->line('Third Email Reminder Text'); + } + + public static function emailReminderEndlessSubject() + { + return Parsedown::instance()->line(self::transformText('reminder_subject')); + } + + public static function emailReminderEndlessTemplate() + { + return Parsedown::instance()->line('Endless Email Reminder Text'); + } + + public static function emailStatementSubject() + { + return Parsedown::instance()->line('Statement Subject needs texts record!'); + } + + public static function emailStatementTemplate() + { + return Parsedown::instance()->line('Statement Templates needs texts record!'); + } + + private static function transformText($string) { return str_replace(":", "$", ctrans('texts.'.$string)); } } + + diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index e1440e04b089..27202baf9dba 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -404,6 +404,16 @@ class UserController extends BaseController * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), * @OA\Parameter(ref="#/components/parameters/include"), * @OA\Parameter( + * name="token_name", + * in="path", + * description="Customized name for the Users API Token", + * example="iOS Device 11 iPad", + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Parameter( * name="id", * in="path", * description="The User Hashed ID", diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index 133f0a017f60..db81e79400ad 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\Client; use App\DataMapper\ClientSettings; use App\Http\Requests\Request; -use App\Http\ValidationRules\ValidSettingsRule; +use App\Http\ValidationRules\ValidClientGroupSettingsRule; use App\Models\Client; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Log; @@ -43,7 +43,7 @@ class StoreClientRequest extends Request /* Ensure we have a client name, and that all emails are unique*/ //$rules['name'] = 'required|min:1'; $rules['id_number'] = 'unique:clients,id_number,' . $this->id . ',id,company_id,' . $this->company_id; - $rules['settings'] = new ValidSettingsRule(); + $rules['settings'] = new ValidClientGroupSettingsRule(); $rules['contacts.*.email'] = 'distinct'; $contacts = request('contacts'); diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index 882e907dc1f1..ac73732b656c 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -12,7 +12,7 @@ namespace App\Http\Requests\Client; use App\Http\Requests\Request; -use App\Http\ValidationRules\ValidSettingsRule; +use App\Http\ValidationRules\ValidClientGroupSettingsRule; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Log; use Illuminate\Validation\Rule; @@ -44,7 +44,7 @@ class UpdateClientRequest extends Request $rules['shipping_country_id'] = 'integer|nullable'; //$rules['id_number'] = 'unique:clients,id_number,,id,company_id,' . auth()->user()->company()->id; $rules['id_number'] = 'unique:clients,id_number,' . $this->id . ',id,company_id,' . $this->company_id; - $rules['settings'] = new ValidSettingsRule(); + $rules['settings'] = new ValidClientGroupSettingsRule(); $rules['contacts.*.email'] = 'distinct'; $contacts = request('contacts'); diff --git a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php index e512ae60a93f..9e78078ba634 100644 --- a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\GroupSetting; use App\DataMapper\ClientSettings; use App\Http\Requests\Request; -use App\Http\ValidationRules\ValidSettingsRule; +use App\Http\ValidationRules\ValidClientGroupSettingsRule; use App\Models\GroupSetting; use Illuminate\Support\Facades\Log; @@ -37,7 +37,7 @@ class StoreGroupSettingRequest extends Request $this->sanitize(); $rules['name'] = 'required'; - $rules['settings'] = new ValidSettingsRule(); + $rules['settings'] = new ValidClientGroupSettingsRule(); return $rules; } diff --git a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php index 31eb04ca53d2..dd3008219df8 100644 --- a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php @@ -12,7 +12,7 @@ namespace App\Http\Requests\GroupSetting; use App\Http\Requests\Request; -use App\Http\ValidationRules\ValidSettingsRule; +use App\Http\ValidationRules\ValidClientGroupSettingsRule; use Illuminate\Support\Facades\Log; use Illuminate\Validation\Rule; @@ -33,7 +33,7 @@ class UpdateGroupSettingRequest extends Request { $this->sanitize(); - $rules['settings'] = new ValidSettingsRule(); + $rules['settings'] = new ValidClientGroupSettingsRule(); return $rules; diff --git a/app/Http/Requests/User/ShowUserRequest.php b/app/Http/Requests/User/ShowUserRequest.php index 4bf42e32647e..62d8b0202a57 100644 --- a/app/Http/Requests/User/ShowUserRequest.php +++ b/app/Http/Requests/User/ShowUserRequest.php @@ -24,7 +24,8 @@ class ShowUserRequest extends Request public function authorize() : bool { - return auth()->user()->can('view', $this->user); + //return auth()->user()->can('view', $this->user); + return auth()->user()->isAdmin(); } } \ No newline at end of file diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php index 5a3a3b5e638e..6cf6c3d751c9 100644 --- a/app/Http/Requests/User/StoreUserRequest.php +++ b/app/Http/Requests/User/StoreUserRequest.php @@ -68,7 +68,7 @@ class StoreUserRequest extends Request } $this->replace($input); -\Log::error($input); + return $this->all(); } diff --git a/app/Http/Requests/User/UpdateUserRequest.php b/app/Http/Requests/User/UpdateUserRequest.php index c296c9e4bc7b..d9b9e4974324 100644 --- a/app/Http/Requests/User/UpdateUserRequest.php +++ b/app/Http/Requests/User/UpdateUserRequest.php @@ -24,7 +24,7 @@ class UpdateUserRequest extends Request public function authorize() : bool { - return auth()->user()->can('edit', $this->user); + return auth()->user()->id === $this->id || auth()->user()->isAdmin(); } @@ -34,24 +34,24 @@ class UpdateUserRequest extends Request $this->sanitize(); $input = $this->all(); + $rules = []; - return [ - 'first_name' => 'required|string|max:100', - 'last_name' => 'required|string:max:100', - 'email' => ['required', new UniqueUserRule($this->user, $input['email'])], - ]; + if(isset($input['email'])) + $rules['email'] = ['sometimes', new UniqueUserRule($this->user, $input['email'])]; + + return $rules; } public function sanitize() { $input = $this->all(); - if(!isset($input['email'])) - { - $input['email'] = null; - } + if(isset($input['company_user']) && !auth()->user()->isAdmin()) + unset($input['company_user']); $this->replace($input); + + return $this->all(); } diff --git a/app/Http/ValidationRules/ValidClientGroupSettingsRule.php b/app/Http/ValidationRules/ValidClientGroupSettingsRule.php new file mode 100644 index 000000000000..cfe8cfb9a0fe --- /dev/null +++ b/app/Http/ValidationRules/ValidClientGroupSettingsRule.php @@ -0,0 +1,58 @@ +validateSettings($value); + + if (is_array($data)) + { + $this->return_data = $data; + return false; + } + else + return true; + } + + /** + * @return string + */ + public function message() + { + return $this->return_data[0]." is not a valid ".$this->return_data[1]; + + } + + + +} diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 82a4db4a9d21..52bc35336bb0 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -54,7 +54,7 @@ class UserRepository extends BaseRepository $user->fill($data); $user->save(); - if($data['company_user']) + if(isset($data['company_user'])) { $company = auth()->user()->company(); diff --git a/app/Utils/Traits/ClientGroupSettingsSaver.php b/app/Utils/Traits/ClientGroupSettingsSaver.php new file mode 100644 index 000000000000..f0412b21d250 --- /dev/null +++ b/app/Utils/Traits/ClientGroupSettingsSaver.php @@ -0,0 +1,220 @@ +settings; + + //unset protected properties. + foreach(CompanySettings::$protected_fields as $field) + unset($settings[$field]); + + + /** + * for clients and group settings, if a field is not set or is set to a blank value, + * we unset it from the settings object + */ + foreach($settings as $key => $value) + { + + if(!isset($settings->{$key}) || empty($settings->{$key}) || (!is_object($settings->{$key}) && strlen($settings->{$key}) == 0)) + unset($settings->{$key}); + + } + + + $settings = $this->checkSettingType($settings); + + //iterate through set properties with new values; + foreach($settings as $key => $value) + $entity_settings->{$key} = $value; + + $entity->settings = $entity_settings; + $entity->save(); + + return $entity_settings; + } + + /** + * Used for custom validation of inbound + * settings request. + * + * Returns an array of errors, or boolean TRUE + * on successful validation + * @param array $settings The request() settings array + * @return array|bool Array on failure, boolean TRUE on success + */ + public function validateSettings($settings) + { + $settings = (object)$settings; + $casts = CompanySettings::$casts; + + ksort($casts); + + foreach($settings as $key => $value) + { + + if(!isset($settings->{$key}) || empty($settings->{$key}) || (!is_object($settings->{$key}) && strlen($settings->{$key}) == 0)) + unset($settings->{$key}); + + } + + foreach ($casts as $key => $value){ + + /*Separate loop if it is a _id field which is an integer cast as a string*/ + if(substr($key, -3) == '_id' || substr($key, -14) == 'number_counter'){ + $value = "integer"; + + if(!property_exists($settings, $key)){ + continue; + } + else if(!$this->checkAttribute($value, $settings->{$key})){ + return [$key, $value]; + } + + continue; + } + + /* Handles unset settings or blank strings */ + if(!property_exists($settings, $key) || is_null($settings->{$key}) || !isset($settings->{$key}) || $settings->{$key} == '') + continue; + + + /*Catch all filter */ + if(!$this->checkAttribute($value, $settings->{$key})) + return [$key, $value]; + + + } + + return true; + } + + /** + * Checks the settings object for + * correct property types. + * + * The method will drop invalid types from + * the object and will also settype() the property + * so that it can be saved cleanly + * + * @param array $settings The settings request() array + * @return object stdClass object + */ + private function checkSettingType($settings) : \stdClass + { + $settings = (object)$settings; + $casts = CompanySettings::$casts; + + foreach ($casts as $key => $value){ + + /*Separate loop if it is a _id field which is an integer cast as a string*/ + if(substr($key, -3) == '_id' || substr($key, -14) == 'number_counter'){ + $value = "integer"; + + if(!property_exists($settings, $key)){ + continue; + } + elseif($this->checkAttribute($value, $settings->{$key})){ + + if(substr($key, -3) == '_id') + settype($settings->{$key}, 'string'); + else + settype($settings->{$key}, $value); + + } + else { + unset($settings->{$key}); + } + + continue; + } + + /* Handles unset settings or blank strings */ + if(!property_exists($settings, $key) || is_null($settings->{$key}) || !isset($settings->{$key}) || $settings->{$key} == ''){ + continue; + } + + /*Catch all filter */ + if($this->checkAttribute($value, $settings->{$key})){ + + if($value == 'string' && is_null($settings->{$key})) + $settings->{$key} = ''; + + settype($settings->{$key}, $value); + } + else { + unset($settings->{$key}); + } + + } + return $settings; + } + + + /** + * Type checks a object property. + * @param string $key The type + * @param string $value The object property + * @return bool TRUE if the property is the expected type + */ + private function checkAttribute($key, $value) :bool + { + switch ($key) + { + case 'int': + case 'integer': + return ctype_digit(strval($value)); + case 'real': + case 'float': + case 'double': + return is_float($value) || is_numeric(strval($value)); + case 'string': + return method_exists($value, '__toString' ) || is_null($value) || is_string($value); + case 'bool': + case 'boolean': + return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); + case 'object': + return is_object($value); + case 'array': + return is_array($value); + case 'json': + json_decode($string); + return (json_last_error() == JSON_ERROR_NONE); + default: + return false; + } + } + +} \ No newline at end of file diff --git a/tests/Unit/GroupSettingsTest.php b/tests/Unit/GroupSettingsTest.php index b51a5c510743..326c85a53839 100644 --- a/tests/Unit/GroupSettingsTest.php +++ b/tests/Unit/GroupSettingsTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit; use App\DataMapper\ClientSettings; use App\DataMapper\CompanySettings; use App\Models\GroupSetting; +use App\Utils\Traits\ClientGroupSettingsSaver; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\MockAccountData; use Tests\TestCase; @@ -17,6 +18,7 @@ class GroupSettingsTest extends TestCase { use MockAccountData; use DatabaseTransactions; + use ClientGroupSettingsSaver; public function setUp() :void { @@ -202,4 +204,19 @@ class GroupSettingsTest extends TestCase $this->assertEquals($this->client->getSetting('timezone_id'), 'COMPANY'); $this->assertEquals($this->client->getMergedSettings()->timezone_id, 'COMPANY'); } + + public function testDiscardingUnsetProperties() + { + + $this->settings = $this->company->settings; + + \Log::error(print_r($this->settings,1)); + + $this->assertTrue($this->validateSettings($this->settings)); + + $new_settings = $this->saveSettings($this->settings, $this->client); + + \Log::error(print_r($new_settings,1)); + } + } \ No newline at end of file