From b6527cddc36f8715987c73a6884fd08836bc6ea5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 12 Jan 2017 13:52:37 +0200 Subject: [PATCH] Refactor client portal and email settings requests --- app/Http/Controllers/AccountController.php | 135 +++--------------- .../Requests/SaveClientPortalSettings.php | 58 ++++++++ app/Http/Requests/SaveEmailSettings.php | 27 ++++ app/Http/routes.php | 2 + app/Libraries/HTMLUtils.php | 37 +++++ app/Models/Account.php | 7 + app/Providers/AppServiceProvider.php | 3 + config/app.php | 4 +- resources/lang/en/texts.php | 2 + resources/lang/en/validation.php | 1 + .../views/accounts/client_portal.blade.php | 108 +++++++++++++- .../views/accounts/email_settings.blade.php | 96 +------------ 12 files changed, 264 insertions(+), 216 deletions(-) create mode 100644 app/Http/Requests/SaveClientPortalSettings.php create mode 100644 app/Http/Requests/SaveEmailSettings.php create mode 100644 app/Libraries/HTMLUtils.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index fe0702d9588a..00f47d844266 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -39,6 +39,9 @@ use App\Services\AuthService; use App\Services\PaymentService; use App\Http\Requests\UpdateAccountRequest; +use App\Http\Requests\SaveClientPortalSettings; +use App\Http\Requests\SaveEmailSettings; + /** * Class AccountController */ @@ -714,14 +717,10 @@ class AccountController extends BaseController return AccountController::export(); } elseif ($section === ACCOUNT_INVOICE_SETTINGS) { return AccountController::saveInvoiceSettings(); - } elseif ($section === ACCOUNT_EMAIL_SETTINGS) { - return AccountController::saveEmailSettings(); } elseif ($section === ACCOUNT_INVOICE_DESIGN) { return AccountController::saveInvoiceDesign(); } elseif ($section === ACCOUNT_CUSTOMIZE_DESIGN) { return AccountController::saveCustomizeDesign(); - } elseif ($section === ACCOUNT_CLIENT_PORTAL) { - return AccountController::saveClientPortal(); } elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) { return AccountController::saveEmailTemplates(); } elseif ($section === ACCOUNT_PRODUCTS) { @@ -789,53 +788,27 @@ class AccountController extends BaseController /** * @return \Illuminate\Http\RedirectResponse */ - private function saveClientPortal() + public function saveClientPortalSettings(SaveClientPortalSettings $request) { - $account = Auth::user()->account; - $account->fill(Input::all()); - - // Only allowed for pro Invoice Ninja users or white labeled self-hosted users - if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { - $input_css = Input::get('client_view_css'); - if (Utils::isNinja()) { - // Allow referencing the body element - $input_css = preg_replace('/(? - // - - // Create a new configuration object - $config = \HTMLPurifier_Config::createDefault(); - $config->set('Filter.ExtractStyleBlocks', true); - $config->set('CSS.AllowImportant', true); - $config->set('CSS.AllowTricky', true); - $config->set('CSS.Trusted', true); - - // Create a new purifier instance - $purifier = new \HTMLPurifier($config); - - // Wrap our CSS in style tags and pass to purifier. - // we're not actually interested in the html response though - $html = $purifier->purify(''); - - // The "style" blocks are stored seperately - $output_css = $purifier->context->get('StyleBlocks'); - - // Get the first style block - $sanitized_css = count($output_css) ? $output_css[0] : ''; - } else { - $sanitized_css = $input_css; - } - - $account->client_view_css = $sanitized_css; - } - + $account = $request->user()->account; + $account->fill($request->all()); $account->save(); - Session::flash('message', trans('texts.updated_settings')); + return redirect('settings/' . ACCOUNT_CLIENT_PORTAL) + ->with('message', trans('texts.updated_settings')); + } - return Redirect::to('settings/'.ACCOUNT_CLIENT_PORTAL); + /** + * @return $this|\Illuminate\Http\RedirectResponse + */ + public function saveEmailSettings(SaveEmailSettings $request) + { + $account = $request->user()->account; + $account->fill($request->all()); + $account->save(); + + return redirect('settings/' . ACCOUNT_EMAIL_SETTINGS) + ->with('message', trans('texts.updated_settings')); } /** @@ -905,74 +878,6 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_PRODUCTS); } - /** - * @return $this|\Illuminate\Http\RedirectResponse - */ - private function saveEmailSettings() - { - if (Auth::user()->account->hasFeature(FEATURE_CUSTOM_EMAILS)) { - $user = Auth::user(); - $subdomain = null; - $iframeURL = null; - $rules = []; - - if (Input::get('custom_link') == 'subdomain') { - $subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH)); - if (Utils::isNinja()) { - $exclude = [ - 'www', - 'app', - 'mail', - 'admin', - 'blog', - 'user', - 'contact', - 'payment', - 'payments', - 'billing', - 'invoice', - 'business', - 'owner', - 'info', - 'ninja', - 'docs', - 'doc', - 'documents' - ]; - $rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id|not_in:" . implode(',', $exclude); - } - } else { - $iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH)); - $iframeURL = rtrim($iframeURL, '/'); - } - - $validator = Validator::make(Input::all(), $rules); - - if ($validator->fails()) { - return Redirect::to('settings/'.ACCOUNT_EMAIL_SETTINGS) - ->withErrors($validator) - ->withInput(); - } else { - $account = Auth::user()->account; - $account->subdomain = $subdomain; - $account->iframe_url = $iframeURL; - $account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false; - $account->document_email_attachment = Input::get('document_email_attachment') ? true : false; - $account->email_design_id = Input::get('email_design_id'); - $account->bcc_email = Input::get('bcc_email'); - - if (Utils::isNinja()) { - $account->enable_email_markup = Input::get('enable_email_markup') ? true : false; - } - - $account->save(); - Session::flash('message', trans('texts.updated_settings')); - } - } - - return Redirect::to('settings/'.ACCOUNT_EMAIL_SETTINGS); - } - /** * @return $this|\Illuminate\Http\RedirectResponse */ diff --git a/app/Http/Requests/SaveClientPortalSettings.php b/app/Http/Requests/SaveClientPortalSettings.php new file mode 100644 index 000000000000..3c2485e7d7ef --- /dev/null +++ b/app/Http/Requests/SaveClientPortalSettings.php @@ -0,0 +1,58 @@ +user()->is_admin && $this->user()->isPro(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = []; + + if ($this->custom_link == 'subdomain' && Utils::isNinja()) { + $rules['subdomain'] = "unique:accounts,subdomain,{$this->user()->account_id},id|valid_subdomain"; + } + + return $rules; + } + + public function sanitize() + { + $input = $this->all(); + + if ($this->client_view_css && Utils::isNinja()) { + $input['client_view_css'] = HTMLUtils::sanitize($this->client_view_css); + } + + if ($this->custom_link == 'subdomain') { + $subdomain = substr(strtolower($input['subdomain']), 0, MAX_SUBDOMAIN_LENGTH); + $input['subdomain'] = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $subdomain); + $input['iframe_url'] = null; + } else { + $iframeURL = substr(strtolower($input['iframe_url']), 0, MAX_IFRAME_URL_LENGTH); + $iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', $iframeURL); + $input['iframe_url'] = rtrim($iframeURL, '/'); + $input['subdomain'] = null; + } + + $this->replace($input); + + return $this->all(); + } + +} diff --git a/app/Http/Requests/SaveEmailSettings.php b/app/Http/Requests/SaveEmailSettings.php new file mode 100644 index 000000000000..84f8c5baf8d0 --- /dev/null +++ b/app/Http/Requests/SaveEmailSettings.php @@ -0,0 +1,27 @@ +user()->is_admin && $this->user()->isPro(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'bcc_email' => 'email', + ]; + } + +} diff --git a/app/Http/routes.php b/app/Http/routes.php index ea11794dc264..ae0c55de3b97 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -244,6 +244,8 @@ Route::group([ Route::post('tax_rates/bulk', 'TaxRateController@bulk'); Route::get('settings/email_preview', 'AccountController@previewEmail'); + Route::post('settings/client_portal', 'AccountController@saveClientPortalSettings'); + Route::post('settings/email_settings', 'AccountController@saveEmailSettings'); Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); Route::get('settings/data_visualizations', 'ReportController@d3'); Route::get('settings/reports', 'ReportController@showReports'); diff --git a/app/Libraries/HTMLUtils.php b/app/Libraries/HTMLUtils.php new file mode 100644 index 000000000000..cb0ce92c02ec --- /dev/null +++ b/app/Libraries/HTMLUtils.php @@ -0,0 +1,37 @@ + + // + + // Create a new configuration object + $config = HTMLPurifier_Config::createDefault(); + $config->set('Filter.ExtractStyleBlocks', true); + $config->set('CSS.AllowImportant', true); + $config->set('CSS.AllowTricky', true); + $config->set('CSS.Trusted', true); + + // Create a new purifier instance + $purifier = new HTMLPurifier($config); + + // Wrap our CSS in style tags and pass to purifier. + // we're not actually interested in the html response though + $purifier->purify(''); + + // The "style" blocks are stored seperately + $css = $purifier->context->get('StyleBlocks'); + + // Get the first style block + return count($css) ? $css[0] : ''; + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 790ede8c098c..56c1bd2013f5 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -82,6 +82,13 @@ class Account extends Eloquent 'show_accept_quote_terms', 'require_invoice_signature', 'require_quote_signature', + 'pdf_email_attachment', + 'document_email_attachment', + 'email_design_id', + 'bcc_email', + 'enable_email_markup', + 'subdomain', + 'iframe_url', ]; /** diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6a4514cb53b7..4efe0990f81a 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -198,6 +198,9 @@ class AppServiceProvider extends ServiceProvider return $total <= MAX_INVOICE_AMOUNT; }); + Validator::extend('valid_subdomain', function($attribute, $value, $parameters) { + return ! in_array($value, ['www', 'app','mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner', 'info', 'ninja', 'docs', 'doc', 'documents']); + }); } /** diff --git a/config/app.php b/config/app.php index 84fabfb28d5d..9d9881dfda23 100644 --- a/config/app.php +++ b/config/app.php @@ -222,7 +222,6 @@ return [ 'View' => 'Illuminate\Support\Facades\View', // Added Class Aliases - 'Utils' => 'App\Libraries\Utils', 'Form' => 'Collective\Html\FormFacade', 'HTML' => 'Collective\Html\HtmlFacade', 'SSH' => 'Illuminate\Support\Facades\SSH', @@ -261,6 +260,9 @@ return [ 'Crawler' => 'Jaybizzle\LaravelCrawlerDetect\Facades\LaravelCrawlerDetect', 'Updater' => Codedge\Updater\UpdaterFacade::class, 'Module' => Nwidart\Modules\Facades\Module::class, + + 'Utils' => App\Libraries\Utils::class, + 'HTMLUtils' => App\Libraries\HTMLUtils::class, ], ]; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 57b176ae9872..541ca8fad158 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2312,6 +2312,8 @@ $LANG = array( 'tax_invoice' => 'Tax Invoice', 'emailed_invoices' => 'Successfully emailed invoices', 'emailed_quotes' => 'Successfully emailed quotes', + 'website_url' => 'Website URL', + ); return $LANG; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index d17016b2b53f..b47a12ef4e4c 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -76,6 +76,7 @@ return array( "has_counter" => "The value must contain {\$counter}", "valid_contacts" => "The contact must have either an email or name", "valid_invoice_items" => "The invoice exceeds the maximum amount", + "valid_subdomain" => "The subdomain is restricted", /* |-------------------------------------------------------------------------- diff --git a/resources/views/accounts/client_portal.blade.php b/resources/views/accounts/client_portal.blade.php index c859330a92cc..960aaa98c742 100644 --- a/resources/views/accounts/client_portal.blade.php +++ b/resources/views/accounts/client_portal.blade.php @@ -19,11 +19,14 @@ @parent {!! Former::open_for_files() -->addClass('warn-on-exit') !!} + ->rules([ + 'iframe_url' => 'url', + ]) + ->addClass('warn-on-exit') !!} +{!! Former::populate($account) !!} {!! Former::populateField('enable_client_portal', intval($account->enable_client_portal)) !!} {!! Former::populateField('enable_client_portal_dashboard', intval($account->enable_client_portal_dashboard)) !!} -{!! Former::populateField('client_view_css', $client_view_css) !!} {!! Former::populateField('enable_portal_password', intval($enable_portal_password)) !!} {!! Former::populateField('send_portal_password', intval($send_portal_password)) !!} {!! Former::populateField('enable_buy_now_buttons', intval($account->enable_buy_now_buttons)) !!} @@ -47,16 +50,41 @@
-

{!! trans('texts.navigation') !!}

+

{!! trans('texts.settings') !!}

+ + {!! Former::inline_radios('custom_invoice_link') + ->onchange('onCustomLinkChange()') + ->label(trans('texts.website_url')) + ->radios([ + trans('texts.subdomain') => ['value' => 'subdomain', 'name' => 'custom_link'], + trans('texts.website') => ['value' => 'website', 'name' => 'custom_link'], + ])->check($account->iframe_url ? 'website' : 'subdomain') !!} + {{ Former::setOption('capitalize_translations', false) }} + + {!! Former::text('subdomain') + ->placeholder(trans('texts.www')) + ->onchange('onSubdomainChange()') + ->addGroupClass('subdomain') + ->label(' ') + ->help(trans('texts.subdomain_help')) !!} + + {!! Former::text('iframe_url') + ->placeholder('https://www.example.com/invoice') + ->appendIcon('question-sign') + ->addGroupClass('iframe_url') + ->label(' ') + ->help(trans('texts.subdomain_help')) !!} + + {!! Former::checkbox('enable_client_portal') ->text(trans('texts.enable')) ->help(trans('texts.enable_client_portal_help')) ->value(1) !!} -
-
+ + {!! Former::checkbox('enable_client_portal_dashboard') ->text(trans('texts.enable')) ->help(trans('texts.enable_client_portal_dashboard_help')) @@ -251,6 +279,38 @@ {!! Former::close() !!} + + + + diff --git a/resources/views/accounts/email_settings.blade.php b/resources/views/accounts/email_settings.blade.php index b9d5dbba12bc..001d92226bf1 100644 --- a/resources/views/accounts/email_settings.blade.php +++ b/resources/views/accounts/email_settings.blade.php @@ -15,9 +15,9 @@ @include('accounts.nav', ['selected' => ACCOUNT_EMAIL_SETTINGS, 'advanced' => true]) {!! Former::open()->rules([ - 'iframe_url' => 'url', 'bcc_email' => 'email', ])->addClass('warn-on-exit') !!} + {{ Former::populate($account) }} {{ Former::populateField('pdf_email_attachment', intval($account->pdf_email_attachment)) }} {{ Former::populateField('document_email_attachment', intval($account->document_email_attachment)) }} @@ -49,29 +49,6 @@ {{-- Former::select('recurring_hour')->options($recurringHours) --}} - {!! Former::inline_radios('custom_invoice_link') - ->onchange('onCustomLinkChange()') - ->label(trans('texts.invoice_link')) - ->radios([ - trans('texts.subdomain') => ['value' => 'subdomain', 'name' => 'custom_link'], - trans('texts.website') => ['value' => 'website', 'name' => 'custom_link'], - ])->check($account->iframe_url ? 'website' : 'subdomain') !!} - {{ Former::setOption('capitalize_translations', false) }} - - {!! Former::text('subdomain') - ->placeholder(trans('texts.www')) - ->onchange('onSubdomainChange()') - ->addGroupClass('subdomain') - ->label(' ') - ->help(trans('texts.subdomain_help')) !!} - - {!! Former::text('iframe_url') - ->placeholder('https://www.example.com/invoice') - ->appendIcon('question-sign') - ->addGroupClass('iframe_url') - ->label(' ') - ->help(trans('texts.subdomain_help')) !!} -
@@ -107,36 +84,6 @@ @endif - -