Email template defaults (#3090)

* Update User and Company User

* Email Template Defaults for Settings

* Separate methods for saving client and group settings
This commit is contained in:
David Bomba 2019-11-24 17:37:53 +11:00 committed by GitHub
parent 5c9ada0f77
commit 52c031e290
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 414 additions and 21 deletions

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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",

View File

@ -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');

View File

@ -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');

View File

@ -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;
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -68,7 +68,7 @@ class StoreUserRequest extends Request
}
$this->replace($input);
\Log::error($input);
return $this->all();
}

View File

@ -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();
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\ValidationRules;
use App\Libraries\MultiDB;
use App\Models\User;
use App\Utils\Traits\ClientGroupSettingsSaver;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ValidClientGroupSettingsRule
* @package App\Http\ValidationRules
*/
class ValidClientGroupSettingsRule implements Rule
{
use ClientGroupSettingsSaver;
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public $return_data;
public function passes($attribute, $value)
{
$data = $this->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];
}
}

View File

@ -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();

View File

@ -0,0 +1,220 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Utils\Traits;
use App\DataMapper\CompanySettings;
/**
* Class ClientGroupSettingsSaver
* @package App\Utils\Traits
*/
trait ClientGroupSettingsSaver
{
/**
* Saves a setting object
*
* Works for groups|clients|companies
* @param array $settings The request input settings array
* @param object $entity The entity which the settings belongs to
* @return void
*/
public function saveSettings($settings, $entity)
{
if(!$settings)
return;
$entity_settings = $this->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;
}
}
}

View File

@ -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));
}
}