From 78d575fbb12b37eec05296108050452cd2551948 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 30 Jul 2014 22:31:35 +0200 Subject: [PATCH] Various updates [skip ci] --- app/controllers/BudgetController.php | 1 + app/controllers/LimitController.php | 124 +++---- app/filters.php | 2 +- .../Storage/Limit/EloquentLimitRepository.php | 2 +- .../Trigger/Limits/EloquentLimitTrigger.php | 343 ++++++++++++------ app/models/Limit.php | 55 ++- app/models/LimitRepetition.php | 2 +- app/routes.php | 22 +- app/views/budgets/delete.blade.php | 2 +- app/views/budgets/indexByDate.blade.php | 4 +- app/views/limits/create.blade.php | 38 +- app/views/limits/delete.blade.php | 27 +- app/views/limits/edit.blade.php | 52 ++- 13 files changed, 411 insertions(+), 263 deletions(-) diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index b6ddac3363..97d9124359 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -82,6 +82,7 @@ class BudgetController extends BaseController */ public function indexByDate() { + Event::fire('budgets.change'); // get a list of dates by getting all repetitions: $set = $this->_repository->get(); $budgets = $this->_budgets->organizeByDate($set); diff --git a/app/controllers/LimitController.php b/app/controllers/LimitController.php index 5892c1bd81..40492a4d92 100644 --- a/app/controllers/LimitController.php +++ b/app/controllers/LimitController.php @@ -29,48 +29,75 @@ class LimitController extends BaseController * * @return $this|\Illuminate\View\View */ - public function create($budgetId = null) + public function create(\Budget $budget = null) { $periods = \Config::get('firefly.periods_to_text'); $prefilled = [ 'startdate' => Input::get('startdate') ? : date('Y-m-d'), - 'repeat_freq' => Input::get('repeat_freq') ? : 'monthly' + 'repeat_freq' => Input::get('repeat_freq') ? : 'monthly', + 'budget_id' => $budget ? $budget->id : null ]; $budgets = $this->_budgets->getAsSelectList(); - return View::make('limits.create')->with('budgets', $budgets)->with('budget_id', $budgetId)->with( + return View::make('limits.create')->with('budgets', $budgets)->with( 'periods', $periods )->with('prefilled', $prefilled); } + public function delete(\Limit $limit) + { + return View::make('limits.delete')->with('limit', $limit); + } + + public function destroy($limitId) + { + $limit = $this->_limits->find($limitId); + + + + + + if ($limit) { + $limit->delete(); + + return Redirect::route('budgets.index'); + } else { + return View::make('error')->with('message', 'No such limit!'); + } + } + /** * @param null $limitId * * @return $this|\Illuminate\View\View */ - public function edit($limitId = null) + public function edit(Limit $limit) { - $limit = $this->_limits->find($limitId); $budgets = $this->_budgets->getAsSelectList(); + $periods = \Config::get('firefly.periods_to_text'); - $periods = [ - 'weekly' => 'A week', - 'monthly' => 'A month', - 'quarterly' => 'A quarter', - 'half-year' => 'Six months', - 'yearly' => 'A year', - ]; + return View::make('limits.edit')->with('limit', $limit)->with('budgets', $budgets)->with( + 'periods', $periods + ); + } + public function store(Budget $budget = null) + { - if ($limit) { - return View::make('limits.edit')->with('limit', $limit)->with('budgets', $budgets)->with( - 'periods', $periods - ); + // find a limit with these properties, as we might already have one: + $limit = $this->_limits->store(Input::all()); + if ($limit->id) { + if (Input::get('from') == 'date') { + return Redirect::route('budgets.index'); + } else { + return Redirect::route('budgets.index.budget'); + } + } else { + $budgetId = $budget ? $budget->id : null; + + return Redirect::route('budgets.limits.create', [$budgetId, 'from' => Input::get('from')])->withInput(); } - - return View::make('error')->with('message', 'No such limit.'); - } /** @@ -90,14 +117,17 @@ class LimitController extends BaseController if (!$limit->save()) { Session::flash('error', 'Could not save new limit: ' . $limit->errors()->first()); - return Redirect::route('budgets.limits.edit', $limit->id)->withInput(); + return Redirect::route('budgets.limits.edit', [$limit->id, 'from' => Input::get('from')])->withInput(); } else { Session::flash('success', 'Limit saved!'); foreach ($limit->limitrepetitions()->get() as $rep) { $rep->delete(); } - - return Redirect::route('budgets.index'); + if (Input::get('from') == 'date') { + return Redirect::route('budgets.index'); + } else { + return Redirect::route('budgets.index.budget'); + } } } @@ -105,55 +135,5 @@ class LimitController extends BaseController } - /** - * @return \Illuminate\Http\RedirectResponse - */ - public function store() - { - // find a limit with these properties, as we might already have one: - $limit = $this->_limits->store(Input::all()); - if ($limit->id) { - return Redirect::route('budgets.index'); - } else { - return Redirect::route('budgets.limits.create')->withInput(); - } - } - - /** - * @param $limitId - * - * @return $this|\Illuminate\View\View - */ - public function delete($limitId) - { - $limit = $this->_limits->find($limitId); - - - if ($limit) { - return View::make('limits.delete')->with('limit', $limit); - } else { - return View::make('error')->with('message', 'No such limit!'); - } - } - - /** - * @param $limitId - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\View\View - */ - public function destroy($limitId) - { - $limit = $this->_limits->find($limitId); - - - if ($limit) { - $limit->delete(); - - return Redirect::route('budgets.index'); - } else { - return View::make('error')->with('message', 'No such limit!'); - } - } - } \ No newline at end of file diff --git a/app/filters.php b/app/filters.php index 32f565d3de..ed277adc87 100644 --- a/app/filters.php +++ b/app/filters.php @@ -4,7 +4,7 @@ App::before( function ($request) { - Event::fire('app.before'); + if (Auth::check()) { $toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface'); return $toolkit->getDateRange($request); diff --git a/app/lib/Firefly/Storage/Limit/EloquentLimitRepository.php b/app/lib/Firefly/Storage/Limit/EloquentLimitRepository.php index bb90022b55..77b4fb34ea 100644 --- a/app/lib/Firefly/Storage/Limit/EloquentLimitRepository.php +++ b/app/lib/Firefly/Storage/Limit/EloquentLimitRepository.php @@ -88,7 +88,7 @@ class EloquentLimitRepository implements LimitRepositoryInterface $limit->repeats = isset($data['repeats']) ? intval($data['repeats']) : 0; $limit->repeat_freq = $data['period']; if (!$limit->save()) { - Session::flash('error', 'Could not save: ' . $limit->errors()->first()); + \Session::flash('error', 'Could not save: ' . $limit->errors()->first()); } return $limit; diff --git a/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php b/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php index 7e813fc5a8..b7b05a154d 100644 --- a/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php +++ b/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php @@ -3,7 +3,6 @@ namespace Firefly\Trigger\Limits; use Carbon\Carbon; -use Illuminate\Database\QueryException; use Illuminate\Events\Dispatcher; /** @@ -14,6 +13,15 @@ use Illuminate\Events\Dispatcher; class EloquentLimitTrigger { + /** + * @param Dispatcher $events + */ + public function subscribe(Dispatcher $events) + { + $events->listen('budgets.change', 'Firefly\Trigger\Limits\EloquentLimitTrigger@updateLimitRepetitions'); + + } + public function updateLimitRepetitions() { if (!\Auth::check()) { @@ -22,149 +30,246 @@ class EloquentLimitTrigger // get budgets with limits: $budgets = \Auth::user()->budgets() - ->with(['limits', 'limits.limitrepetitions']) - ->whereNotNull('limits.id') - ->leftJoin('limits', 'components.id', '=', 'limits.component_id')->get(['components.*']); + ->with( + ['limits', 'limits.limitrepetitions'] + ) + ->where('components.class', 'Budget') + ->get(['components.*']); + $start = \Session::get('start'); + $end = new Carbon; - // get todays date. + // double check the non-repeating budgetlimits first. foreach ($budgets as $budget) { - // loop limits: + \Log::debug('Budgetstart: ' . $budget->name); foreach ($budget->limits as $limit) { - // should have a repetition, at the very least - // for the period it starts (startdate and onwards). - if (count($limit->limitrepetitions) == 0) { - // create such a repetition: - $repetition = new \LimitRepetition(); - $start = clone $limit->startdate; - $end = clone $start; - - // go to end: - switch ($limit->repeat_freq) { - case 'daily': - $end->addDay(); - break; - case 'weekly': - $end->addWeek(); - break; - case 'monthly': - $end->addMonth(); - break; - case 'quarterly': - $end->addMonths(3); - break; - case 'half-year': - $end->addMonths(6); - break; - case 'yearly': - $end->addYear(); - break; - } - $end->subDay(); - $repetition->startdate = $start; - $repetition->enddate = $end; - $repetition->amount = $limit->amount; - $repetition->limit()->associate($limit); - - try { - $repetition->save(); - } catch (QueryException $e) { - // do nothing - \Log::error($e->getMessage()); - } - } else { - // there are limits already, do they - // fall into the range surrounding today? - $today = new Carbon; - $today->addMonths(2); - if ($limit->repeats == 1 && $today >= $limit->startdate) { - - /** @var \Carbon\Carbon $flowStart */ - $flowStart = clone $today; - /** @var \Carbon\Carbon $flowEnd */ - $flowEnd = clone $today; + if ($limit->repeats == 0) { + $limit->createRepetition($limit->startdate); + } + if($limit->repeats == 1) { + $start = $limit->startdate; + $end = new Carbon; + // repeat for period: + $current = clone $start; + \Log::debug('Create repeating limit for #'.$limit->id.' starting on ' . $current); + while($current <= $end) { + \Log::debug('Current is now: ' . $current); + $limit->createRepetition(clone $current); + // switch period, add time: switch ($limit->repeat_freq) { case 'daily': - $flowStart->startOfDay(); - $flowEnd->endOfDay(); + $current->addDay(); break; case 'weekly': - $flowStart->startOfWeek(); - $flowEnd->endOfWeek(); + $current->addWeek(); break; case 'monthly': - $flowStart->startOfMonth(); - $flowEnd->endOfMonth(); + $current->addMonth(); break; case 'quarterly': - $flowStart->firstOfQuarter(); - $flowEnd->startOfMonth()->lastOfQuarter()->endOfDay(); + $current->addMonths(3); break; case 'half-year': - - if (intval($flowStart->format('m')) >= 7) { - $flowStart->startOfYear(); - $flowStart->addMonths(6); - } else { - $flowStart->startOfYear(); - } - - $flowEnd->endOfYear(); - if (intval($start->format('m')) <= 6) { - $flowEnd->subMonths(6); - $flowEnd->subDay(); - - } + $current->addMonths(6); break; case 'yearly': - $flowStart->startOfYear(); - $flowEnd->endOfYear(); + $current->addYear(); break; } - $inRange = false; - foreach ($limit->limitrepetitions as $rep) { - if ($rep->startdate->format('dmY') == $flowStart->format('dmY') - && $rep->enddate->format('dmY') == $flowEnd->format('dmY') - ) { - // falls in current range, do nothing? - $inRange = true; - } - } - // if there is none that fall in range, create! - if ($inRange === false) { - // create (but check first)! - $count = \LimitRepetition::where('limit_id', $limit->id)->where('startdate', $flowStart) - ->where('enddate', $flowEnd)->count(); - if ($count == 0) { - $repetition = new \LimitRepetition; - $repetition->startdate = $flowStart; - $repetition->enddate = $flowEnd; - $repetition->amount = $limit->amount; - $repetition->limit()->associate($limit); - try { - $repetition->save(); - } catch (QueryException $e) { - // do nothing - \Log::error($e->getMessage()); - } - } - } } } +// \Log::debug( +// 'Now at budget ' . $budget->name . ', limit #' . $limit->id . ' (' . $limit->repeats . ', ' +// . $limit->repeat_freq . ', ' . $limit->startdate . ').' +// ); +// $count = count($limit->limitrepetitions); +// if ($count == 0) { +// // create such a repetition: +// $repetition = new \LimitRepetition(); +// $start = clone $limit->startdate; +// $end = clone $start; +// +// // go to end: +// switch ($limit->repeat_freq) { +// case 'daily': +// $end->addDay(); +// break; +// case 'weekly': +// $end->addWeek(); +// break; +// case 'monthly': +// $end->addMonth(); +// break; +// case 'quarterly': +// $end->addMonths(3); +// break; +// case 'half-year': +// $end->addMonths(6); +// break; +// case 'yearly': +// $end->addYear(); +// break; +// } +// $end->subDay(); +// $repetition->startdate = $start; +// $repetition->enddate = $end; +// $repetition->amount = $limit->amount; +// $repetition->limit()->associate($limit); +// +// try { +// $repetition->save(); +// \Log::debug('Created new repetition with id #' . $repetition->id); +// } catch (QueryException $e) { +// // do nothing +// \Log::error('Trying to save new Limitrepetition failed!'); +// \Log::error($e->getMessage()); +// } +// +// } + } + } } - } - /** - * @param Dispatcher $events - */ - public function subscribe(Dispatcher $events) - { - $events->listen('app.before', 'Firefly\Trigger\Limits\EloquentLimitTrigger@updateLimitRepetitions'); - - } +// +// +// exit; +// +// +// // get todays date. +// +// foreach ($budgets as $budget) { +// // loop limits: +// foreach ($budget->limits as $limit) { +// // should have a repetition, at the very least +// // for the period it starts (startdate and onwards). +// \Log::debug('Limit #' . $limit->id . ' has ' . count($limit->limitrepetitions) . ' limitreps!'); +// if (count($limit->limitrepetitions) == 0) { +// +// // create such a repetition: +// $repetition = new \LimitRepetition(); +// $start = clone $limit->startdate; +// $end = clone $start; +// +// // go to end: +// switch ($limit->repeat_freq) { +// case 'daily': +// $end->addDay(); +// break; +// case 'weekly': +// $end->addWeek(); +// break; +// case 'monthly': +// $end->addMonth(); +// break; +// case 'quarterly': +// $end->addMonths(3); +// break; +// case 'half-year': +// $end->addMonths(6); +// break; +// case 'yearly': +// $end->addYear(); +// break; +// } +// $end->subDay(); +// $repetition->startdate = $start; +// $repetition->enddate = $end; +// $repetition->amount = $limit->amount; +// $repetition->limit()->associate($limit); +// +// try { +// $repetition->save(); +// } catch (QueryException $e) { +// // do nothing +// \Log::error('Trying to save new Limitrepetition!'); +// \Log::error($e->getMessage()); +// } +// } else { +// // there are limits already, do they +// // fall into the range surrounding today? +// $today = new Carbon; +// $today->addMonths(2); +// if ($limit->repeats == 1 && $today >= $limit->startdate) { +// +// /** @var \Carbon\Carbon $flowStart */ +// $flowStart = clone $today; +// /** @var \Carbon\Carbon $flowEnd */ +// $flowEnd = clone $today; +// +// switch ($limit->repeat_freq) { +// case 'daily': +// $flowStart->startOfDay(); +// $flowEnd->endOfDay(); +// break; +// case 'weekly': +// $flowStart->startOfWeek(); +// $flowEnd->endOfWeek(); +// break; +// case 'monthly': +// $flowStart->startOfMonth(); +// $flowEnd->endOfMonth(); +// break; +// case 'quarterly': +// $flowStart->firstOfQuarter(); +// $flowEnd->startOfMonth()->lastOfQuarter()->endOfDay(); +// break; +// case 'half-year': +// +// if (intval($flowStart->format('m')) >= 7) { +// $flowStart->startOfYear(); +// $flowStart->addMonths(6); +// } else { +// $flowStart->startOfYear(); +// } +// +// $flowEnd->endOfYear(); +// if (intval($start->format('m')) <= 6) { +// $flowEnd->subMonths(6); +// $flowEnd->subDay(); +// +// } +// break; +// case 'yearly': +// $flowStart->startOfYear(); +// $flowEnd->endOfYear(); +// break; +// } +// +// $inRange = false; +// foreach ($limit->limitrepetitions as $rep) { +// if ($rep->startdate->format('dmY') == $flowStart->format('dmY') +// && $rep->enddate->format('dmY') == $flowEnd->format('dmY') +// ) { +// // falls in current range, do nothing? +// $inRange = true; +// } +// } +// // if there is none that fall in range, create! +// if ($inRange === false) { +// // create (but check first)! +// $count = \LimitRepetition::where('limit_id', $limit->id)->where('startdate', $flowStart) +// ->where('enddate', $flowEnd)->count(); +// if ($count == 0) { +// $repetition = new \LimitRepetition; +// $repetition->startdate = $flowStart; +// $repetition->enddate = $flowEnd; +// $repetition->amount = $limit->amount; +// $repetition->limit()->associate($limit); +// try { +// $repetition->save(); +// } catch (QueryException $e) { +// // do nothing +// \Log::error($e->getMessage()); +// } +// } +// } +// } +// } +// } +// } } diff --git a/app/models/Limit.php b/app/models/Limit.php index a83594d5eb..9e4d1b34e1 100644 --- a/app/models/Limit.php +++ b/app/models/Limit.php @@ -1,5 +1,7 @@ belongsTo('Budget', 'component_id'); + } + public function component() { return $this->belongsTo('Component', 'component_id'); } - public function budget() + public function createRepetition(Carbon $start) { - return $this->belongsTo('Budget', 'component_id'); + + $end = clone $start; + // go to end: + switch ($this->repeat_freq) { + case 'daily': + $end->addDay(); + break; + case 'weekly': + $end->addWeek(); + break; + case 'monthly': + $end->addMonth(); + break; + case 'quarterly': + $end->addMonths(3); + break; + case 'half-year': + $end->addMonths(6); + break; + case 'yearly': + $end->addYear(); + break; + } + $end->subDay(); + $count = $this->limitrepetitions()->where('startdate', $start->format('Y-m-d'))->where( + 'enddate', $start->format('Y-m-d') + )->count(); + + if ($count == 0) { + + $repetition = new \LimitRepetition(); + $repetition->startdate = $start; + $repetition->enddate = $end; + $repetition->amount = $this->amount; + $repetition->limit()->associate($this); + + try { + $repetition->save(); + \Log::debug('Created new repetition with id #' . $repetition->id); + } catch (QueryException $e) { + // do nothing + \Log::error('Trying to save new Limitrepetition failed!'); + \Log::error($e->getMessage()); + } + } } public function limitrepetitions() diff --git a/app/models/LimitRepetition.php b/app/models/LimitRepetition.php index 04ce8ac329..558941b91c 100644 --- a/app/models/LimitRepetition.php +++ b/app/models/LimitRepetition.php @@ -101,7 +101,7 @@ class LimitRepetition extends Ardent return $this->startdate->format('Ymd') . '-5'; break; case 'weekly': - return $this->startdate->format('Ymd') . '4'; + return $this->startdate->format('Ymd') . '-4'; break; case 'monthly': return $this->startdate->format('Ymd') . '-3'; diff --git a/app/routes.php b/app/routes.php index edadc1de26..77da0dc4e6 100644 --- a/app/routes.php +++ b/app/routes.php @@ -30,6 +30,18 @@ Route::bind('category', function($value, $route) return null; }); +Route::bind('limit', function($value, $route) + { + if(Auth::check()) { + return Limit:: + where('limits.id', $value)-> + leftJoin('components','components.id','=','limits.component_id')-> + where('components.class','Budget')-> + where('components.user_id',Auth::user()->id)->first(); + } + return null; + }); + // protected routes: Route::group(['before' => 'auth'], function () { @@ -79,9 +91,9 @@ Route::group(['before' => 'auth'], function () { Route::get('/budgets/delete/{budget}',['uses' => 'BudgetController@delete', 'as' => 'budgets.delete']); // limit controller: - Route::get('/budgets/limits/create/{id?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']); - Route::get('/budgets/limits/delete/{id?}',['uses' => 'LimitController@delete','as' => 'budgets.limits.delete']); - Route::get('/budgets/limits/edit/{id?}',['uses' => 'LimitController@edit','as' => 'budgets.limits.edit']); + Route::get('/budgets/limits/create/{budget?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']); + Route::get('/budgets/limits/delete/{limit}',['uses' => 'LimitController@delete','as' => 'budgets.limits.delete']); + Route::get('/budgets/limits/edit/{limit}',['uses' => 'LimitController@edit','as' => 'budgets.limits.edit']); // JSON controller: Route::get('/json/beneficiaries', ['uses' => 'JsonController@beneficiaries', 'as' => 'json.beneficiaries']); @@ -106,7 +118,7 @@ Route::group(['before' => 'csrf|auth'], function () { Route::post('/profile/change-password', ['uses' => 'ProfileController@postChangePassword']); // budget controller: - Route::post('/budgets/store',['uses' => 'BudgetController@store', 'as' => 'budgets.store']); + Route::post('/budgets/store/{budget}',['uses' => 'BudgetController@store', 'as' => 'budgets.store']); Route::post('/budgets/update', ['uses' => 'BudgetController@update', 'as' => 'budgets.update']); Route::post('/budgets/destroy', ['uses' => 'BudgetController@destroy', 'as' => 'budgets.destroy']); @@ -127,7 +139,7 @@ Route::group(['before' => 'csrf|auth'], function () { Route::post('/accounts/destroy', ['uses' => 'AccountController@destroy', 'as' => 'accounts.destroy']); // limit controller: - Route::post('/budgets/limits/store', ['uses' => 'LimitController@store', 'as' => 'budgets.limits.store']); + Route::post('/budgets/limits/store/{budget?}', ['uses' => 'LimitController@store', 'as' => 'budgets.limits.store']); Route::post('/budgets/limits/destroy/{id?}',['uses' => 'LimitController@destroy','as' => 'budgets.limits.destroy']); Route::post('/budgets/limits/update/{id?}',['uses' => 'LimitController@update','as' => 'budgets.limits.update']); diff --git a/app/views/budgets/delete.blade.php b/app/views/budgets/delete.blade.php index b14690b6a6..2042190808 100644 --- a/app/views/budgets/delete.blade.php +++ b/app/views/budgets/delete.blade.php @@ -3,7 +3,7 @@

Firefly - Delete "{{{$budget->name}}}" + Delete budget "{{{$budget->name}}}"

Remember that deleting something is permanent. diff --git a/app/views/budgets/indexByDate.blade.php b/app/views/budgets/indexByDate.blade.php index 7de18c18b1..ab27d67bc1 100644 --- a/app/views/budgets/indexByDate.blade.php +++ b/app/views/budgets/indexByDate.blade.php @@ -74,8 +74,8 @@

- - + +
@if($repetition->limit->repeats == 1) auto repeats diff --git a/app/views/limits/create.blade.php b/app/views/limits/create.blade.php index 26b0221130..a8645b29e3 100644 --- a/app/views/limits/create.blade.php +++ b/app/views/limits/create.blade.php @@ -19,16 +19,18 @@
-{{Form::open(['class' => 'form-horizontal','url' => route('budgets.limits.store')])}} +{{Form::open(['class' => 'form-horizontal','url' => route('budgets.limits.store',$prefilled['budget_id'])])}} +{{Form::hidden('from',e(Input::get('from')))}} +

Mandatory fields

- {{ Form::label('budget_id', 'Budget', ['class' => 'col-sm-3 control-label'])}} -
- {{Form::select('budget_id',$budgets,Input::old('budget_id') ?: $budget_id, ['class' => + {{ Form::label('budget_id', 'Budget', ['class' => 'col-sm-4 control-label'])}} +
+ {{Form::select('budget_id',$budgets,Input::old('budget_id') ?: $prefilled['budget_id'], ['class' => 'form-control'])}} @if($errors->has('budget_id'))

{{$errors->first('name')}}

@@ -39,8 +41,8 @@
- {{ Form::label('startdate', 'Start date', ['class' => 'col-sm-3 control-label'])}} -
+ {{ Form::label('startdate', 'Start date', ['class' => 'col-sm-4 control-label'])}} +
This date indicates when the envelope "starts". The date you select @@ -49,17 +51,17 @@
- + -
+
{{Form::select('period',$periods,Input::old('period') ?: $prefilled['repeat_freq'],['class' => 'form-control'])}} How long will the envelope last? A week, a month, or even longer?
- + -
+