diff --git a/.env.example b/.env.example index de63021933422..2d1db72861cca 100644 --- a/.env.example +++ b/.env.example @@ -81,3 +81,6 @@ WEPAY_FEE_PAYER=payee WEPAY_APP_FEE_MULTIPLIER=0.002 WEPAY_APP_FEE_FIXED=0 WEPAY_THEME='{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}' # See https://www.wepay.com/developer/reference/structures#theme + +BLUEVINE_PARTNER_UNIQUE_ID= +BLUEVINE_PARTNER_TOKEN= diff --git a/.gitignore b/.gitignore index eba40da49d7d9..0b0536378eb08 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ Thumbs.db /error_log /auth.json /public/error_log +/Modules /ninja.sublime-project /ninja.sublime-workspace diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 17484c8f6aefe..9b5bbad58eefd 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -73,12 +73,16 @@ class CheckData extends Command { $this->logMessage('Done'); $errorEmail = env('ERROR_EMAIL'); - if ( ! $this->isValid && $errorEmail) { - Mail::raw($this->log, function ($message) use ($errorEmail) { - $message->to($errorEmail) - ->from(CONTACT_EMAIL) - ->subject('Check-Data'); - }); + if ( ! $this->isValid) { + if ($errorEmail) { + Mail::raw($this->log, function ($message) use ($errorEmail) { + $message->to($errorEmail) + ->from(CONTACT_EMAIL) + ->subject('Check-Data'); + }); + } else { + $this->info($this->log); + } } } @@ -209,6 +213,7 @@ class CheckData extends Command { ->where('accounts.id', '!=', 20432) ->where('clients.is_deleted', '=', 0) ->where('invoices.is_deleted', '=', 0) + ->where('invoices.is_public', '=', 1) ->where('invoices.invoice_type_id', '=', INVOICE_TYPE_STANDARD) ->where('invoices.is_recurring', '=', 0) ->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999'); diff --git a/app/Console/Commands/MakeClass.php b/app/Console/Commands/MakeClass.php new file mode 100644 index 0000000000000..83b99fa62b300 --- /dev/null +++ b/app/Console/Commands/MakeClass.php @@ -0,0 +1,174 @@ +laravel['modules']->findOrFail($this->getModuleName()); + $path = str_replace('/', '\\', config('modules.paths.generator.' . $this->argument('class'))); + + return (new Stub('/' . $this->argument('prefix') . $this->argument('class') . '.stub', [ + 'NAMESPACE' => $this->getClassNamespace($module) . "\\" . $path, + 'LOWER_NAME' => $module->getLowerName(), + 'CLASS' => $this->getClass(), + 'STUDLY_NAME' => Str::studly($module->getLowerName()), + 'DATATABLE_COLUMNS' => $this->getColumns(), + 'FORM_FIELDS' => $this->getFormFields(), + 'DATABASE_FIELDS' => $this->getDatabaseFields($module), + 'TRANSFORMER_FIELDS' => $this->getTransformerFields($module), + ]))->render(); + } + + public function getDestinationFilePath() + { + $path = $this->laravel['modules']->getModulePath($this->getModuleName()); + $seederPath = $this->laravel['modules']->config('paths.generator.' . $this->argument('class')); + + return $path . $seederPath . '/' . $this->getFileName() . '.php'; + } + + /** + * @return string + */ + protected function getFileName() + { + if ($this->option('filename')) { + return $this->option('filename'); + } + return studly_case($this->argument('prefix')) . studly_case($this->argument('name')) . Str::studly($this->argument('class')); + } + + protected function getColumns() + { + $fields = $this->option('fields'); + $fields = explode(',', $fields); + $str = ''; + + foreach ($fields as $field) { + if ( ! $field) { + continue; + } + $field = explode(':', $field)[0]; + $str .= '[ + \''. $field . '\', + function ($model) { + return $model->' . $field . '; + } + ],'; + } + + return $str; + } + + protected function getFormFields() + { + $fields = $this->option('fields'); + $fields = explode(',', $fields); + $str = ''; + + foreach ($fields as $field) { + if ( ! $field) { + continue; + } + $parts = explode(':', $field); + $field = $parts[0]; + $type = $parts[1]; + + if ($type == 'text') { + $str .= "{!! Former::textarea('" . $field . "') !!}\n"; + } else { + $str .= "{!! Former::text('" . $field . "') !!}\n"; + } + } + + return $str; + } + + protected function getDatabaseFields($module) + { + $fields = $this->option('fields'); + $fields = explode(',', $fields); + $str = ''; + + foreach ($fields as $field) { + if ( ! $field) { + continue; + } + $field = explode(':', $field)[0]; + $str .= "'" . $module->getLowerName() . ".{$field}', "; + } + + return $str; + } + + protected function getTransformerFields($module) + { + $fields = $this->option('fields'); + $fields = explode(',', $fields); + $str = ''; + + foreach ($fields as $field) { + if ( ! $field) { + continue; + } + $field = explode(':', $field)[0]; + $str .= "'{$field}' => $" . $module->getLowerName() . "->$field,\n "; + } + + return rtrim($str); + + } +} diff --git a/app/Console/Commands/MakeModule.php b/app/Console/Commands/MakeModule.php new file mode 100644 index 0000000000000..b96cb9ea76745 --- /dev/null +++ b/app/Console/Commands/MakeModule.php @@ -0,0 +1,98 @@ +argument('name'); + $fields = $this->argument('fields'); + $migrate = $this->option('migrate'); + $lower = strtolower($name); + + // convert 'name:string,description:text' to 'name,description' + $fillable = explode(',', $fields); + $fillable = array_map(function($item) { + return explode(':', $item)[0]; + }, $fillable); + $fillable = join(',', $fillable); + + $this->info("Creating module: {$name}..."); + + Artisan::call('module:make', ['name' => [$name]]); + Artisan::call('module:make-migration', ['name' => "create_{$lower}_table", '--fields' => $fields, 'module' => $name]); + Artisan::call('module:make-model', ['model' => $name, 'module' => $name, '--fillable' => $fillable]); + + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'views', '--fields' => $fields, '--filename' => 'edit.blade']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'datatable', '--fields' => $fields]); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'repository', '--fields' => $fields]); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'policy']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'auth-provider']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'presenter']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'request']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'request', 'prefix' => 'create']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'request', 'prefix' => 'update']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'api-controller']); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'transformer', '--fields' => $fields]); + Artisan::call('ninja:make-class', ['name' => $name, 'module' => $name, 'class' => 'lang', '--filename' => 'texts']); + + if ($migrate == 'false') { + $this->info("Use the following command to run the migrations:\nphp artisan module:migrate $name"); + } else { + Artisan::call('module:migrate', ['module' => $name]); + } + + Artisan::call('module:dump'); + + $this->info("Done"); + } + + protected function getArguments() + { + return [ + ['name', InputArgument::REQUIRED, 'The name of the module.'], + ['fields', InputArgument::OPTIONAL, 'The fields of the module.'] + ]; + } + + protected function getOptions() + { + return array( + array('migrate', null, InputOption::VALUE_OPTIONAL, 'The model attributes.', null), + ); + } + +} diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 89e08bf582bae..3a546f187edf8 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -58,7 +58,7 @@ class SendRecurringInvoices extends Command $today = new DateTime(); $invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user') - ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) + ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) ->orderBy('id', 'asc') ->get(); $this->info(count($invoices).' recurring invoice(s) found'); @@ -81,7 +81,7 @@ class SendRecurringInvoices extends Command } $delayedAutoBillInvoices = Invoice::with('account.timezone', 'recurring_invoice', 'invoice_items', 'client', 'user') - ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS FALSE + ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS FALSE AND is_public IS TRUE AND balance > 0 AND due_date = ? AND recurring_invoice_id IS NOT NULL', [$today->format('Y-m-d')]) ->orderBy('invoices.id', 'asc') diff --git a/app/Console/Commands/stubs/api-controller.stub b/app/Console/Commands/stubs/api-controller.stub new file mode 100644 index 0000000000000..8a047352ca8d3 --- /dev/null +++ b/app/Console/Commands/stubs/api-controller.stub @@ -0,0 +1,164 @@ +$LOWER_NAME$Repo = $$LOWER_NAME$Repo; + } + + /** + * @SWG\Get( + * path="/$LOWER_NAME$", + * summary="List of $LOWER_NAME$", + * tags={"$LOWER_NAME$"}, + * @SWG\Response( + * response=200, + * description="A list with $LOWER_NAME$", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function index() + { + $data = $this->$LOWER_NAME$Repo->all(); + + return $this->listResponse($data); + } + + /** + * @SWG\Get( + * path="/$LOWER_NAME$/{$LOWER_NAME$_id}", + * summary="Individual $STUDLY_NAME$", + * tags={"$LOWER_NAME$"}, + * @SWG\Response( + * response=200, + * description="A single $LOWER_NAME$", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + + public function show($STUDLY_NAME$Request $request) + { + return $this->itemResponse($request->entity()); + } + + + + + /** + * @SWG\Post( + * path="/$LOWER_NAME$", + * tags={"$LOWER_NAME$"}, + * summary="Create a $LOWER_NAME$", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$") + * ), + * @SWG\Response( + * response=200, + * description="New $LOWER_NAME$", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function store(Create$STUDLY_NAME$Request $request) + { + $$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input()); + + return $this->itemResponse($$LOWER_NAME$); + } + + /** + * @SWG\Put( + * path="/$LOWER_NAME$/{$LOWER_NAME$_id}", + * tags={"$LOWER_NAME$"}, + * summary="Update a $LOWER_NAME$", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$") + * ), + * @SWG\Response( + * response=200, + * description="Update $LOWER_NAME$", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + + public function update(Update$STUDLY_NAME$Request $request, $publicId) + { + if ($request->action) { + return $this->handleAction($request); + } + + $$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input(), $request->entity()); + + return $this->itemResponse($$LOWER_NAME$); + } + + + /** + * @SWG\Delete( + * path="/$LOWER_NAME$/{$LOWER_NAME$_id}", + * tags={"$LOWER_NAME$"}, + * summary="Delete a $LOWER_NAME$", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$") + * ), + * @SWG\Response( + * response=200, + * description="Delete $LOWER_NAME$", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + + public function destroy(Update$STUDLY_NAME$Request $request) + { + $$LOWER_NAME$ = $request->entity(); + + $this->$LOWER_NAME$Repo->delete($$LOWER_NAME$); + + return $this->itemResponse($$LOWER_NAME$); + } + +} diff --git a/app/Console/Commands/stubs/auth-provider.stub b/app/Console/Commands/stubs/auth-provider.stub new file mode 100644 index 0000000000000..465c2dc96ef52 --- /dev/null +++ b/app/Console/Commands/stubs/auth-provider.stub @@ -0,0 +1,17 @@ + \Modules\$STUDLY_NAME$\Policies\$STUDLY_NAME$Policy::class, + ]; +} diff --git a/app/Console/Commands/stubs/command.stub b/app/Console/Commands/stubs/command.stub new file mode 100755 index 0000000000000..1537601329820 --- /dev/null +++ b/app/Console/Commands/stubs/command.stub @@ -0,0 +1,68 @@ +$LOWER_NAME$Repo = $$LOWER_NAME$Repo; + } + + /** + * Display a listing of the resource. + * @return Response + */ + public function index() + { + return view('list_wrapper', [ + 'entityType' => '$LOWER_NAME$', + 'datatable' => new $STUDLY_NAME$Datatable(), + 'title' => mtrans('$LOWER_NAME$', '$LOWER_NAME$_list'), + ]); + } + + public function datatable(DatatableService $datatableService) + { + $search = request()->input('test'); + $userId = Auth::user()->filterId(); + + $datatable = new $STUDLY_NAME$Datatable(); + $query = $this->$LOWER_NAME$Repo->find($search, $userId); + + return $datatableService->createDatatable($datatable, $query); + } + + /** + * Show the form for creating a new resource. + * @return Response + */ + public function create($STUDLY_NAME$Request $request) + { + $data = [ + '$LOWER_NAME$' => null, + 'method' => 'POST', + 'url' => '$LOWER_NAME$', + 'title' => mtrans('$LOWER_NAME$', 'new_$LOWER_NAME$'), + ]; + + return view('$LOWER_NAME$::edit', $data); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Response + */ + public function store(Create$STUDLY_NAME$Request $request) + { + $$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input()); + + return redirect()->to($$LOWER_NAME$->present()->editUrl) + ->with('message', mtrans('$LOWER_NAME$', 'created_$LOWER_NAME$')); + } + + /** + * Show the form for editing the specified resource. + * @return Response + */ + public function edit($STUDLY_NAME$Request $request) + { + $$LOWER_NAME$ = $request->entity(); + + $data = [ + '$LOWER_NAME$' => $$LOWER_NAME$, + 'method' => 'PUT', + 'url' => '$LOWER_NAME$/' . $$LOWER_NAME$->public_id, + 'title' => mtrans('$LOWER_NAME$', 'edit_$LOWER_NAME$'), + ]; + + return view('$LOWER_NAME$::edit', $data); + } + + /** + * Show the form for editing a resource. + * @return Response + */ + public function show($STUDLY_NAME$Request $request) + { + return redirect()->to("$LOWER_NAME$/{$request->$LOWER_NAME$}/edit"); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @return Response + */ + public function update(Update$STUDLY_NAME$Request $request) + { + $$LOWER_NAME$ = $this->$LOWER_NAME$Repo->save($request->input(), $request->entity()); + + return redirect()->to($$LOWER_NAME$->present()->editUrl) + ->with('message', mtrans('$LOWER_NAME$', 'updated_$LOWER_NAME$')); + } + + /** + * Update multiple resources + */ + public function bulk() + { + $action = request()->input('action'); + $ids = request()->input('public_id') ?: request()->input('ids'); + $count = $this->$LOWER_NAME$Repo->bulk($ids, $action); + + return redirect()->to('$LOWER_NAME$') + ->with('message', mtrans('$LOWER_NAME$', $action . '_$LOWER_NAME$_complete')); + } +} diff --git a/app/Console/Commands/stubs/createrequest.stub b/app/Console/Commands/stubs/createrequest.stub new file mode 100644 index 0000000000000..d8ad07cfb0a80 --- /dev/null +++ b/app/Console/Commands/stubs/createrequest.stub @@ -0,0 +1,28 @@ +user()->can('create', '$LOWER_NAME$'); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + + ]; + } +} diff --git a/app/Console/Commands/stubs/datatable.stub b/app/Console/Commands/stubs/datatable.stub new file mode 100644 index 0000000000000..f9983b806e14d --- /dev/null +++ b/app/Console/Commands/stubs/datatable.stub @@ -0,0 +1,43 @@ +created_at); + } + ], + ]; + } + + public function actions() + { + return [ + [ + mtrans('$LOWER_NAME$', 'edit_$LOWER_NAME$'), + function ($model) { + return URL::to("$LOWER_NAME$/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', ['$LOWER_NAME$', $model->user_id]); + } + ], + ]; + } + +} diff --git a/app/Console/Commands/stubs/event.stub b/app/Console/Commands/stubs/event.stub new file mode 100755 index 0000000000000..ad1cb696699c2 --- /dev/null +++ b/app/Console/Commands/stubs/event.stub @@ -0,0 +1,30 @@ + '$STUDLY_NAME$', + '$LOWER_NAME$_list' => '$STUDLY_NAME$ List', + 'archive_$LOWER_NAME$' => 'Archive $STUDLY_NAME$', + 'delete_$LOWER_NAME$' => 'Delete $STUDLY_NAME$', + 'edit_$LOWER_NAME$' => 'Edit $STUDLY_NAME$', + 'restore_$LOWER_NAME$' => 'Restore $STUDLY_NAME$', + 'new_$LOWER_NAME$' => 'New $STUDLY_NAME$', + 'created_$LOWER_NAME$' => 'Successfully created $LOWER_NAME$', + 'updated_$LOWER_NAME$' => 'Successfully updated $LOWER_NAME$', + +); + +return $LANG; + +?> diff --git a/app/Console/Commands/stubs/listener.stub b/app/Console/Commands/stubs/listener.stub new file mode 100755 index 0000000000000..5637af194df83 --- /dev/null +++ b/app/Console/Commands/stubs/listener.stub @@ -0,0 +1,31 @@ +view('view.name'); + } +} diff --git a/app/Console/Commands/stubs/middleware.stub b/app/Console/Commands/stubs/middleware.stub new file mode 100755 index 0000000000000..954583ed4cf59 --- /dev/null +++ b/app/Console/Commands/stubs/middleware.stub @@ -0,0 +1,21 @@ +increments('id'); + $table->unsignedInteger('user_id')->index(); + $table->unsignedInteger('account_id')->index(); + $table->unsignedInteger('client_id')->index()->nullable(); + +$FIELDS$ + $table->timestamps(); + $table->softDeletes(); + $table->boolean('is_deleted')->default(false); + + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); + + $table->unsignedInteger('public_id')->index(); + $table->unique( ['account_id', 'public_id'] ); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists(strtolower('$TABLE$')); + } +} diff --git a/app/Console/Commands/stubs/migration/delete.stub b/app/Console/Commands/stubs/migration/delete.stub new file mode 100755 index 0000000000000..c145a2f5466b3 --- /dev/null +++ b/app/Console/Commands/stubs/migration/delete.stub @@ -0,0 +1,31 @@ +increments('id'); +$FIELDS$ + $table->timestamps(); + }); + } +} diff --git a/app/Console/Commands/stubs/migration/plain.stub b/app/Console/Commands/stubs/migration/plain.stub new file mode 100755 index 0000000000000..73565c28e5d9a --- /dev/null +++ b/app/Console/Commands/stubs/migration/plain.stub @@ -0,0 +1,27 @@ +line('The introduction to the notification.') + ->action('Notification Action', 'https://laravel.com') + ->line('Thank you for using our application!'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Console/Commands/stubs/policy.stub b/app/Console/Commands/stubs/policy.stub new file mode 100644 index 0000000000000..fdb0f5a884a6e --- /dev/null +++ b/app/Console/Commands/stubs/policy.stub @@ -0,0 +1,10 @@ + \Modules\$STUDLY_NAME$\Policies\$STUDLY_NAME$Policy::class, + ]; + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } +} diff --git a/app/Console/Commands/stubs/repository.stub b/app/Console/Commands/stubs/repository.stub new file mode 100644 index 0000000000000..208172251ecff --- /dev/null +++ b/app/Console/Commands/stubs/repository.stub @@ -0,0 +1,71 @@ +orderBy('created_at', 'desc') + ->withTrashed(); + } + + public function find($filter = null, $userId = false) + { + $query = DB::table('$LOWER_NAME$') + ->where('$LOWER_NAME$.account_id', '=', \Auth::user()->account_id) + ->select( + $DATABASE_FIELDS$ + '$LOWER_NAME$.public_id', + '$LOWER_NAME$.deleted_at', + '$LOWER_NAME$.created_at', + '$LOWER_NAME$.is_deleted', + '$LOWER_NAME$.user_id' + ); + + $this->applyFilters($query, '$LOWER_NAME$'); + + if ($userId) { + $query->where('clients.user_id', '=', $userId); + } + + /* + if ($filter) { + $query->where(); + } + */ + + return $query; + } + + public function save($data, $$LOWER_NAME$ = null) + { + $entity = $$LOWER_NAME$ ?: $STUDLY_NAME$::createNew(); + + $entity->fill($data); + $entity->save(); + + /* + if (!$publicId || $publicId == '-1') { + event(new ClientWasCreated($client)); + } else { + event(new ClientWasUpdated($client)); + } + */ + + return $entity; + } + +} diff --git a/app/Console/Commands/stubs/request.stub b/app/Console/Commands/stubs/request.stub new file mode 100755 index 0000000000000..4bf64c64eb896 --- /dev/null +++ b/app/Console/Commands/stubs/request.stub @@ -0,0 +1,10 @@ + 'auth', 'namespace' => '$MODULE_NAMESPACE$\$STUDLY_NAME$\Http\Controllers'], function() +{ + Route::resource('$LOWER_NAME$', '$STUDLY_NAME$Controller'); + Route::post('$LOWER_NAME$/bulk', '$STUDLY_NAME$Controller@bulk'); + Route::get('api/$LOWER_NAME$', '$STUDLY_NAME$Controller@datatable'); +}); + +Route::group(['middleware' => 'api', 'namespace' => '$MODULE_NAMESPACE$\$STUDLY_NAME$\Http\ApiControllers', 'prefix' => 'api/v1'], function() +{ + Route::resource('$LOWER_NAME$', '$STUDLY_NAME$ApiController'); +}); diff --git a/app/Console/Commands/stubs/scaffold/config.stub b/app/Console/Commands/stubs/scaffold/config.stub new file mode 100755 index 0000000000000..74c3001c9c1e9 --- /dev/null +++ b/app/Console/Commands/stubs/scaffold/config.stub @@ -0,0 +1,5 @@ + '$STUDLY_NAME$' +]; diff --git a/app/Console/Commands/stubs/scaffold/provider.stub b/app/Console/Commands/stubs/scaffold/provider.stub new file mode 100755 index 0000000000000..048eaddf70a63 --- /dev/null +++ b/app/Console/Commands/stubs/scaffold/provider.stub @@ -0,0 +1,110 @@ + \Modules\$STUDLY_NAME$\Policies\$STUDLY_NAME$Policy::class, + ]; + + /** + * Boot the application events. + * + * @return void + */ + public function boot(GateContract $gate) + { + parent::boot($gate); + + $this->registerTranslations(); + $this->registerConfig(); + $this->registerViews(); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + __DIR__.'/../$PATH_CONFIG$/config.php' => config_path('$LOWER_NAME$.php'), + ]); + $this->mergeConfigFrom( + __DIR__.'/../$PATH_CONFIG$/config.php', '$LOWER_NAME$' + ); + } + + /** + * Register views. + * + * @return void + */ + public function registerViews() + { + $viewPath = base_path('resources/views/modules/$LOWER_NAME$'); + + $sourcePath = __DIR__.'/../$PATH_VIEWS$'; + + $this->publishes([ + $sourcePath => $viewPath + ]); + + $this->loadViewsFrom(array_merge(array_map(function ($path) { + return $path . '/modules/$LOWER_NAME$'; + }, \Config::get('view.paths')), [$sourcePath]), '$LOWER_NAME$'); + } + + /** + * Register translations. + * + * @return void + */ + public function registerTranslations() + { + $langPath = base_path('resources/lang/modules/$LOWER_NAME$'); + + if (is_dir($langPath)) { + $this->loadTranslationsFrom($langPath, '$LOWER_NAME$'); + } else { + $this->loadTranslationsFrom(__DIR__ .'/../$PATH_LANG$', '$LOWER_NAME$'); + } + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } +} diff --git a/app/Console/Commands/stubs/seeder.stub b/app/Console/Commands/stubs/seeder.stub new file mode 100755 index 0000000000000..dd4349080a871 --- /dev/null +++ b/app/Console/Commands/stubs/seeder.stub @@ -0,0 +1,21 @@ +call("OthersTableSeeder"); + } +} diff --git a/app/Console/Commands/stubs/start.stub b/app/Console/Commands/stubs/start.stub new file mode 100755 index 0000000000000..7be329cbe4652 --- /dev/null +++ b/app/Console/Commands/stubs/start.stub @@ -0,0 +1,15 @@ +getDefaults($$LOWER_NAME$), [ + $TRANSFORMER_FIELDS$ + 'id' => (int) $$LOWER_NAME$->public_id, + 'updated_at' => $this->getTimestamp($$LOWER_NAME$->updated_at), + 'archived_at' => $this->getTimestamp($$LOWER_NAME$->deleted_at), + ]); + } +} diff --git a/app/Console/Commands/stubs/updaterequest.stub b/app/Console/Commands/stubs/updaterequest.stub new file mode 100644 index 0000000000000..99bf461f00a6b --- /dev/null +++ b/app/Console/Commands/stubs/updaterequest.stub @@ -0,0 +1,28 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + + ]; + } +} diff --git a/app/Console/Commands/stubs/views.stub b/app/Console/Commands/stubs/views.stub new file mode 100755 index 0000000000000..01c29a682920f --- /dev/null +++ b/app/Console/Commands/stubs/views.stub @@ -0,0 +1,57 @@ +@extends('header') + +@section('content') + + {!! Former::open($url) + ->addClass('col-md-10 col-md-offset-1 warn-on-exit') + ->method($method) + ->rules([]) !!} + + @if ($$LOWER_NAME$) + {!! Former::populate($$LOWER_NAME$) !!} +
+ {!! Former::text('public_id') !!} +
+ @endif + +
+
+ +
+
+ + $FORM_FIELDS$ + +
+
+ +
+
+ +
+ + {!! Button::normal(trans('texts.cancel')) + ->large() + ->asLinkTo(URL::to('/$LOWER_NAME$')) + ->appendIcon(Icon::create('remove-circle')) !!} + + {!! Button::success(trans('texts.save')) + ->submit() + ->large() + ->appendIcon(Icon::create('floppy-disk')) !!} + +
+ + {!! Former::close() !!} + + + + + +@stop diff --git a/app/Console/Commands/stubs/views/master.stub b/app/Console/Commands/stubs/views/master.stub new file mode 100755 index 0000000000000..14fd322d37b6a --- /dev/null +++ b/app/Console/Commands/stubs/views/master.stub @@ -0,0 +1,12 @@ + + + + + + + Module $STUDLY_NAME$ + + + @yield('content') + + diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 665d5925329ee..deab7b163c533 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -23,6 +23,8 @@ class Kernel extends ConsoleKernel 'App\Console\Commands\SendReminders', 'App\Console\Commands\GenerateResources', 'App\Console\Commands\TestOFX', + 'App\Console\Commands\MakeModule', + 'App\Console\Commands\MakeClass', ]; /** diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index 05039ca99a3a6..1920978c0e77c 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -75,7 +75,7 @@ class AccountApiController extends BaseAPIController $updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false; $transformer = new AccountTransformer(null, $request->serializer); - $account->load($transformer->getDefaultIncludes()); + $account->load(array_merge($transformer->getDefaultIncludes(), ['projects.client'])); $account = $this->createItem($account, $transformer, 'account'); return $this->response($account); @@ -192,7 +192,7 @@ class AccountApiController extends BaseAPIController $provider = $request->input('provider'); try { - $user = Socialite::driver($provider)->userFromToken($token); + $user = Socialite::driver($provider)->stateless()->userFromToken($token); } catch (Exception $exception) { return $this->errorResponse(['message' => $exception->getMessage()], 401); } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 3782b1c7ba7aa..6b0dd86aa2164 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -212,7 +212,7 @@ class AccountController extends BaseController $days_total = $planDetails['paid']->diff($planDetails['expires'])->days; $percent_used = $days_used / $days_total; - $credit = $planDetails['plan_price'] * (1 - $percent_used); + $credit = floatval($company->payment->amount) * (1 - $percent_used); } if ($newPlan['price'] > $credit) { @@ -224,7 +224,9 @@ class AccountController extends BaseController } } else { - if ($plan != PLAN_FREE) { + if ($plan == PLAN_FREE) { + $company->discount = 0; + } else { $company->plan_term = $term; $company->plan_price = $newPlan['price']; $company->num_users = $numUsers; @@ -244,9 +246,26 @@ class AccountController extends BaseController * @param $visible * @return mixed */ - public function setTrashVisible($entityType, $visible) + public function setEntityFilter($entityType, $filter = '') { - Session::put("show_trash:{$entityType}", $visible == 'true'); + if ($filter == 'true') { + $filter = ''; + } + + // separate state and status filters + $filters = explode(',', $filter); + $stateFilter = []; + $statusFilter = []; + foreach ($filters as $filter) { + if (in_array($filter, \App\Models\EntityModel::$statuses)) { + $stateFilter[] = $filter; + } else { + $statusFilter[] = $filter; + } + } + + Session::put("entity_state_filter:{$entityType}", join(',', $stateFilter)); + Session::put("entity_status_filter:{$entityType}", join(',', $statusFilter)); return RESULT_SUCCESS; } @@ -720,9 +739,27 @@ class AccountController extends BaseController */ private function saveAccountManagement() { - $account = Auth::user()->account; + $user = Auth::user(); + $account = $user->account; $modules = Input::get('modules'); + $user->force_pdfjs = Input::get('force_pdfjs') ? true : false; + $user->save(); + + $account->live_preview = Input::get('live_preview') ? true : false; + + // Automatically disable live preview when using a large font + $fonts = Cache::get('fonts')->filter(function($font) use ($account) { + if ($font->google_font) { + return false; + } + return $font->id == $account->header_font_id || $font->id == $account->body_font_id; + }); + if ($account->live_preview && count($fonts)) { + $account->live_preview = false; + Session::flash('warning', trans('texts.live_preview_disabled')); + } + $account->enabled_modules = $modules ? array_sum($modules) : 0; $account->save(); @@ -880,27 +917,29 @@ class AccountController extends BaseController if (Input::get('custom_link') == 'subdomain') { $subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH)); - $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); + 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, '/'); @@ -1035,19 +1074,6 @@ class AccountController extends BaseController $account->invoice_design_id = Input::get('invoice_design_id'); $account->font_size = intval(Input::get('font_size')); $account->page_size = Input::get('page_size'); - $account->live_preview = Input::get('live_preview') ? true : false; - - // Automatically disable live preview when using a large font - $fonts = Cache::get('fonts')->filter(function($font) use ($account) { - if ($font->google_font) { - return false; - } - return $font->id == $account->header_font_id || $font->id == $account->body_font_id; - }); - if ($account->live_preview && count($fonts)) { - $account->live_preview = false; - Session::flash('warning', trans('texts.live_preview_disabled')); - } $labels = []; foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms', 'balance_due', 'partial_due', 'subtotal', 'paid_to_date', 'discount', 'tax'] as $field) { diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 844cd21754626..004418d44528e 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -411,6 +411,7 @@ class AccountGatewayController extends BaseController 'description' => trans('texts.wepay_account_description'), 'theme_object' => json_decode(WEPAY_THEME), 'callback_uri' => $accountGateway->getWebhookUrl(), + 'rbits' => $account->present()->rBits, ]; if (WEPAY_ENABLE_CANADA) { diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 8b33c71e93efd..647ea7dd2eecc 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -60,10 +60,7 @@ class AppController extends BaseController $database = Input::get('database'); $dbType = 'mysql'; // $database['default']; $database['connections'] = [$dbType => $database['type']]; - $mail = Input::get('mail'); - $email = $mail['username']; - $mail['from']['address'] = $email; if ($test == 'mail') { return self::testMail($mail); @@ -97,6 +94,7 @@ class AppController extends BaseController $_ENV['MAIL_HOST'] = $mail['host']; $_ENV['MAIL_USERNAME'] = $mail['username']; $_ENV['MAIL_FROM_NAME'] = $mail['from']['name']; + $_ENV['MAIL_FROM_ADDRESS'] = $mail['from']['address']; $_ENV['MAIL_PASSWORD'] = $mail['password']; $_ENV['PHANTOMJS_CLOUD_KEY'] = 'a-demo-key-with-low-quota-per-ip-address'; $_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain']; @@ -173,8 +171,8 @@ class AppController extends BaseController $_ENV['MAIL_HOST'] = $mail['host']; $_ENV['MAIL_USERNAME'] = $mail['username']; $_ENV['MAIL_FROM_NAME'] = $mail['from']['name']; + $_ENV['MAIL_FROM_ADDRESS'] = $mail['from']['address']; $_ENV['MAIL_PASSWORD'] = $mail['password']; - $_ENV['MAIL_FROM_ADDRESS'] = $mail['username']; $_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain']; $_ENV['MAILGUN_SECRET'] = $mail['mailgun_secret']; } @@ -218,7 +216,7 @@ class AppController extends BaseController private function testMail($mail) { - $email = $mail['username']; + $email = $mail['from']['address']; $fromName = $mail['from']['name']; foreach ($mail as $key => $val) { diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index c583f919b86f9..11e78f6b66c08 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -1,6 +1,7 @@ datatable, FILTER_VALIDATE_BOOLEAN); + $referer = Request::server('HTTP_REFERER'); $entityTypes = Utils::pluralizeEntityType($entityType); + // when restoring redirect to entity if ($action == 'restore' && count($ids) == 1) { return redirect("{$entityTypes}/" . $ids[0]); + // when viewing from a datatable list + } elseif (strpos($referer, '/clients/')) { + return redirect($referer); } elseif ($isDatatable || ($action == 'archive' || $action == 'delete')) { return redirect("{$entityTypes}"); + // when viewing individual entity } elseif (count($ids)) { return redirect("{$entityTypes}/" . $ids[0]); } else { diff --git a/app/Http/Controllers/BlueVineController.php b/app/Http/Controllers/BlueVineController.php new file mode 100644 index 0000000000000..188054d858c37 --- /dev/null +++ b/app/Http/Controllers/BlueVineController.php @@ -0,0 +1,88 @@ + Input::get( 'name' ), + 'business_phone_number' => Input::get( 'phone' ), + 'email' => Input::get( 'email' ), + 'personal_fico_score' => intval( Input::get( 'fico_score' ) ), + 'business_annual_revenue' => intval( Input::get( 'annual_revenue' ) ), + 'business_monthly_average_bank_balance' => intval( Input::get( 'average_bank_balance' ) ), + 'business_inception_date' => date( 'Y-m-d', strtotime( Input::get( 'business_inception' ) ) ), + 'partner_internal_business_id' => 'ninja_account_' . $user->account_id, + ); + + if ( ! empty( Input::get( 'quote_type_factoring' ) ) ) { + $data['invoice_factoring_offer'] = true; + $data['desired_credit_line'] = intval( Input::get( 'desired_credit_limit' )['invoice_factoring'] ); + } + + if ( ! empty( Input::get( 'quote_type_loc' ) ) ) { + $data['line_of_credit_offer'] = true; + $data['desired_credit_line_for_loc'] = intval( Input::get( 'desired_credit_limit' )['line_of_credit'] ); + } + + + $api_client = new \GuzzleHttp\Client(); + try { + $response = $api_client->request( 'POST', + 'https://app.bluevine.com/api/v1/user/register_external?' . http_build_query( array( + 'external_register_token' => env( 'BLUEVINE_PARTNER_TOKEN' ), + 'c' => env( 'BLUEVINE_PARTNER_UNIQUE_ID' ), + 'signup_parent_url' => URL::to( '/bluevine/completed' ), + ) ), array( + 'json' => $data + ) + ); + } catch ( \GuzzleHttp\Exception\RequestException $ex ) { + if ( $ex->getCode() == 403 ) { + $response_body = $ex->getResponse()->getBody( true ); + $response_data = json_decode( $response_body ); + + return response()->json( [ + 'error' => true, + 'message' => $response_data->reason + ] ); + } else { + return response()->json( [ + 'error' => true + ] ); + } + } + + $user->account->bluevine_status = 'signed_up'; + $user->account->save(); + + $quote_data = json_decode( $response->getBody() ); + + return response()->json( $quote_data ); + } + + public function hideMessage() { + $user = Auth::user(); + + if ( $user ) { + $user->account->bluevine_status = 'ignored'; + $user->account->save(); + } + + return 'success'; + } + + public function handleCompleted() { + Session::flash( 'message', trans( 'texts.bluevine_completed' ) ); + + return Redirect::to( '/dashboard' ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index e7bcd6e82e919..321692f7e3d41 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -19,6 +19,7 @@ use App\Services\ClientService; use App\Http\Requests\ClientRequest; use App\Http\Requests\CreateClientRequest; use App\Http\Requests\UpdateClientRequest; +use App\Ninja\Datatables\ClientDatatable; class ClientController extends BaseController { @@ -41,20 +42,11 @@ class ClientController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_CLIENT, + 'datatable' => new ClientDatatable(), 'title' => trans('texts.clients'), - 'sortCol' => '4', - 'columns' => Utils::trans([ - 'checkbox', - 'client', - 'contact', - 'email', - 'date_created', - 'last_login', - 'balance', - '' - ]), + 'statuses' => Client::getStatuses(), ]); } @@ -123,9 +115,9 @@ class ClientController extends BaseController 'client' => $client, 'credit' => $client->getTotalCredit(), 'title' => trans('texts.view_client'), - 'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0, - 'hasQuotes' => Invoice::scope()->invoiceType(INVOICE_TYPE_QUOTE)->whereClientId($client->id)->count() > 0, - 'hasTasks' => Task::scope()->whereClientId($client->id)->count() > 0, + 'hasRecurringInvoices' => Invoice::scope()->recurring()->withArchived()->whereClientId($client->id)->count() > 0, + 'hasQuotes' => Invoice::scope()->quotes()->withArchived()->whereClientId($client->id)->count() > 0, + 'hasTasks' => Task::scope()->withArchived()->whereClientId($client->id)->count() > 0, 'gatewayLink' => $token ? $token->gatewayLink() : false, 'gatewayName' => $token ? $token->gatewayName() : false, ]; diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index c2d850f0a1c57..9ee46c7b9ec4e 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -203,7 +203,7 @@ class ClientPortalController extends BaseController if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { return RESULT_FAILURE; } - + $invitation->signature_base64 = Input::get('signature'); $invitation->signature_date = date_create(); $invitation->save(); diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 1a3db4b8d7cf0..e7fca9d922cb6 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -7,10 +7,13 @@ use URL; use Utils; use View; use App\Models\Client; +use App\Models\Credit; use App\Services\CreditService; use App\Ninja\Repositories\CreditRepository; +use App\Http\Requests\UpdateCreditRequest; use App\Http\Requests\CreateCreditRequest; use App\Http\Requests\CreditRequest; +use App\Ninja\Datatables\CreditDatatable; class CreditController extends BaseController { @@ -33,19 +36,10 @@ class CreditController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_CREDIT, + 'datatable' => new CreditDatatable(), 'title' => trans('texts.credits'), - 'sortCol' => '4', - 'columns' => Utils::trans([ - 'checkbox', - 'client', - 'credit_amount', - 'credit_balance', - 'credit_date', - 'private_notes', - '' - ]), ]); } @@ -62,40 +56,53 @@ class CreditController extends BaseController 'method' => 'POST', 'url' => 'credits', 'title' => trans('texts.new_credit'), - 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ]; return View::make('credits.edit', $data); } - /* public function edit($publicId) { - $credit = Credit::scope($publicId)->firstOrFail(); + $credit = Credit::withTrashed()->scope($publicId)->firstOrFail(); $this->authorize('edit', $credit); $credit->credit_date = Utils::fromSqlDate($credit->credit_date); $data = array( - 'client' => null, + 'client' => $credit->client, + 'clientPublicId' => $credit->client->public_id, 'credit' => $credit, 'method' => 'PUT', 'url' => 'credits/'.$publicId, 'title' => 'Edit Credit', - 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ); + 'clients' => null, + ); - return View::make('credit.edit', $data); + return View::make('credits.edit', $data); + } + + public function update(UpdateCreditRequest $request) + { + $credit = $request->entity(); + + return $this->save($credit); } - */ public function store(CreateCreditRequest $request) { - $credit = $this->creditRepo->save($request->input()); + return $this->save(); + } - Session::flash('message', trans('texts.created_credit')); + private function save($credit = null) + { + $credit = $this->creditService->save(Input::all(), $credit); - return redirect()->to($credit->client->getRoute()); + $message = $credit->wasRecentlyCreated ? trans('texts.created_credit') : trans('texts.updated_credit'); + Session::flash('message', $message); + + return redirect()->to("clients/{$credit->client->public_id}#credits"); } public function bulk() @@ -109,6 +116,6 @@ class CreditController extends BaseController Session::flash('message', $message); } - return Redirect::to('credits'); + return $this->returnBulk(ENTITY_CREDIT, $action, $ids); } } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 14cb4c638bf1d..37c4be4201814 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -35,7 +35,7 @@ class DashboardController extends BaseController $metrics = $dashboardRepo->totals($accountId, $userId, $viewAll); $paidToDate = $dashboardRepo->paidToDate($account, $userId, $viewAll); $averageInvoice = $dashboardRepo->averages($account, $userId, $viewAll); - $balances = $dashboardRepo->balances($accountId, $userId, $viewAll); + $balances = $dashboardRepo->balances($accountId, $userId, $viewAll); $activities = $dashboardRepo->activities($accountId, $userId, $viewAll); $pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll); $upcoming = $dashboardRepo->upcoming($accountId, $userId, $viewAll); @@ -43,6 +43,10 @@ class DashboardController extends BaseController $expenses = $dashboardRepo->expenses($accountId, $userId, $viewAll); $tasks = $dashboardRepo->tasks($accountId, $userId, $viewAll); + $showBlueVinePromo = ! $account->bluevine_status + && env('BLUEVINE_PARTNER_UNIQUE_ID') + && $account->created_at <= date( 'Y-m-d', strtotime( '-1 month' )); + // check if the account has quotes $hasQuotes = false; foreach ([$upcoming, $pastDue] as $data) { @@ -75,6 +79,7 @@ class DashboardController extends BaseController $data = [ 'account' => $user->account, + 'user' => $user, 'paidToDate' => $paidToDate, 'balances' => $balances, 'averageInvoice' => $averageInvoice, @@ -90,8 +95,29 @@ class DashboardController extends BaseController 'currencies' => $currencies, 'expenses' => $expenses, 'tasks' => $tasks, + 'showBlueVinePromo' => $showBlueVinePromo, ]; + if ($showBlueVinePromo) { + $usdLast12Months = 0; + $pastYear = date( 'Y-m-d', strtotime( '-1 year' )); + $paidLast12Months = $dashboardRepo->paidToDate( $account, $userId, $viewAll, $pastYear ); + + foreach ( $paidLast12Months as $item ) { + if ( $item->currency_id == null ) { + $currency = $user->account->currency_id ?: DEFAULT_CURRENCY; + } else { + $currency = $item->currency_id; + } + + if ( $currency == CURRENCY_DOLLAR ) { + $usdLast12Months += $item->value; + } + } + + $data['usdLast12Months'] = $usdLast12Months; + } + return View::make('dashboard', $data); } diff --git a/app/Http/Controllers/ExpenseCategoryController.php b/app/Http/Controllers/ExpenseCategoryController.php index 283ab0346206d..fdea57cbe8e04 100644 --- a/app/Http/Controllers/ExpenseCategoryController.php +++ b/app/Http/Controllers/ExpenseCategoryController.php @@ -6,6 +6,7 @@ use Input; use Session; use App\Services\ExpenseCategoryService; use App\Ninja\Repositories\ExpenseCategoryRepository; +use App\Ninja\Datatables\ExpenseCategoryDatatable; use App\Http\Requests\ExpenseCategoryRequest; use App\Http\Requests\CreateExpenseCategoryRequest; use App\Http\Requests\UpdateExpenseCategoryRequest; @@ -29,15 +30,10 @@ class ExpenseCategoryController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_EXPENSE_CATEGORY, + 'datatable' => new ExpenseCategoryDatatable(), 'title' => trans('texts.expense_categories'), - 'sortCol' => '1', - 'columns' => Utils::trans([ - 'checkbox', - 'name', - '' - ]), ]); } @@ -78,7 +74,7 @@ class ExpenseCategoryController extends BaseController Session::flash('message', trans('texts.created_expense_category')); - return redirect()->to($category->getRoute()); + return redirect()->to('/expense_categories'); } public function update(UpdateExpenseCategoryRequest $request) diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 1130b6b10385d..cc850cbf32722 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -19,6 +19,7 @@ use App\Ninja\Repositories\ExpenseRepository; use App\Http\Requests\ExpenseRequest; use App\Http\Requests\CreateExpenseRequest; use App\Http\Requests\UpdateExpenseRequest; +use App\Ninja\Datatables\ExpenseDatatable; class ExpenseController extends BaseController { @@ -48,21 +49,10 @@ class ExpenseController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_EXPENSE, + 'datatable' => new ExpenseDatatable(), 'title' => trans('texts.expenses'), - 'sortCol' => '3', - 'columns' => Utils::trans([ - 'checkbox', - 'vendor', - 'client', - 'expense_date', - 'amount', - 'category', - 'public_notes', - 'status', - '' - ]), ]); } @@ -262,7 +252,7 @@ class ExpenseController extends BaseController 'countries' => Cache::get('countries'), 'customLabel1' => Auth::user()->account->custom_vendor_label1, 'customLabel2' => Auth::user()->account->custom_vendor_label2, - 'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->orderBy('name')->get(), + 'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(), ]; } diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 3f840bb5bb625..f1e74bd2a9244 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -12,7 +12,9 @@ use App\Models\Contact; use App\Models\Credit; use App\Models\Task; use App\Models\Invoice; +use App\Models\Product; use App\Models\Payment; +use App\Models\Expense; use App\Models\Vendor; use App\Models\VendorContact; @@ -212,6 +214,19 @@ class ExportController extends BaseController ->get(); } + if ($request->input('include') === 'all' || $request->input('expenses')) { + $data['expenses'] = Expense::scope() + ->with('user', 'vendor.vendor_contacts', 'client.contacts', 'expense_category') + ->withArchived() + ->get(); + } + + if ($request->input('include') === 'all' || $request->input('products')) { + $data['products'] = Product::scope() + ->withArchived() + ->get(); + } + if ($request->input('include') === 'all' || $request->input('vendors')) { $data['vendors'] = Vendor::scope() ->with('user', 'vendor_contacts', 'country') diff --git a/app/Http/Controllers/IntegrationController.php b/app/Http/Controllers/IntegrationController.php index a55498db8f439..cb1a905f3775e 100644 --- a/app/Http/Controllers/IntegrationController.php +++ b/app/Http/Controllers/IntegrationController.php @@ -38,6 +38,6 @@ class IntegrationController extends Controller return Response::json('Failed to create subscription', 500); } - return Response::json('{"id":'.$subscription->id.'}', 201); + return Response::json(['id' => $subscription->id], 201); } } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 843a83777d9cf..9e131bed5d1f7 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -22,6 +22,7 @@ use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\DocumentRepository; +use App\Ninja\Datatables\InvoiceDatatable; use App\Services\InvoiceService; use App\Services\PaymentService; use App\Services\RecurringInvoiceService; @@ -57,21 +58,11 @@ class InvoiceController extends BaseController $data = [ 'title' => trans('texts.invoices'), 'entityType' => ENTITY_INVOICE, - 'sortCol' => '3', - 'columns' => Utils::trans([ - 'checkbox', - 'invoice_number', - 'client', - 'invoice_date', - 'invoice_total', - 'balance_due', - 'due_date', - 'status', - '' - ]), + 'statuses' => Invoice::getStatuses(), + 'datatable' => new InvoiceDatatable(), ]; - return response()->view('list', $data); + return response()->view('list_wrapper', $data); } public function getDatatable($clientPublicId = null) @@ -108,6 +99,7 @@ class InvoiceController extends BaseController if ($clone) { $invoice->id = $invoice->public_id = null; + $invoice->is_public = false; $invoice->invoice_number = $account->getNextInvoiceNumber($invoice); $invoice->balance = $invoice->amount; $invoice->invoice_status_id = 0; @@ -138,10 +130,6 @@ class InvoiceController extends BaseController DropdownButton::DIVIDER ]; - if ($invoice->invoice_status_id < INVOICE_STATUS_SENT && !$invoice->is_recurring) { - $actions[] = ['url' => 'javascript:onMarkClick()', 'label' => trans('texts.mark_sent')]; - } - if ($entityType == ENTITY_QUOTE) { if ($invoice->quote_invoice_id) { $actions[] = ['url' => URL::to("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans('texts.view_invoice')]; @@ -153,7 +141,8 @@ class InvoiceController extends BaseController $actions[] = ['url' => URL::to("quotes/{$invoice->quote_id}/edit"), 'label' => trans('texts.view_quote')]; } - if (!$invoice->is_recurring && $invoice->balance > 0) { + if (!$invoice->is_recurring && $invoice->balance > 0 && $invoice->is_public) { + $actions[] = ['url' => 'javascript:submitBulkAction("markPaid")', 'label' => trans('texts.mark_paid')]; $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; } @@ -203,7 +192,7 @@ class InvoiceController extends BaseController } // Set the invitation data on the client's contacts - if (!$clone) { + if ($invoice->is_public && ! $clone) { $clients = $data['clients']; foreach ($clients as $client) { if ($client->id != $invoice->client->id) { @@ -523,7 +512,13 @@ class InvoiceController extends BaseController $count = $this->invoiceService->bulk($ids, $action); if ($count > 0) { - $key = $action == 'markSent' ? "updated_{$entityType}" : "{$action}d_{$entityType}"; + if ($action == 'markSent') { + $key = 'marked_sent_invoice'; + } elseif ($action == 'markPaid') { + $key = 'created_payment'; + } else { + $key = "{$action}d_{$entityType}"; + } $message = Utils::pluralize($key, $count); Session::flash('message', $message); } diff --git a/app/Http/Controllers/OnlinePaymentController.php b/app/Http/Controllers/OnlinePaymentController.php index e7d709096e37b..b6c9a280cecfa 100644 --- a/app/Http/Controllers/OnlinePaymentController.php +++ b/app/Http/Controllers/OnlinePaymentController.php @@ -117,7 +117,8 @@ class OnlinePaymentController extends BaseController } else { Session::flash('message', trans('texts.applied_payment')); } - return redirect()->to('view/' . $invitation->invitation_key); + + return $this->completePurchase($invitation); } catch (Exception $exception) { return $this->error($paymentDriver, $exception, true); } @@ -152,12 +153,27 @@ class OnlinePaymentController extends BaseController if ($paymentDriver->completeOffsitePurchase(Input::all())) { Session::flash('message', trans('texts.applied_payment')); } - return redirect()->to($invitation->getLink()); + return $this->completePurchase($invitation, true); } catch (Exception $exception) { return $this->error($paymentDriver, $exception); } } + private function completePurchase($invitation, $isOffsite = false) + { + if ($redirectUrl = session('redirect_url:' . $invitation->invitation_key)) { + $separator = strpos($redirectUrl, '?') === false ? '?' : '&'; + return redirect()->to($redirectUrl . $separator . 'invoice_id=' . $invitation->invoice->public_id); + } else { + // Allow redirecting to iFrame for offsite payments + if ($isOffsite) { + return redirect()->to($invitation->getLink()); + } else { + return redirect()->to('view/' . $invitation->invitation_key); + } + } + } + /** * @param $paymentDriver * @param $exception @@ -253,17 +269,18 @@ class OnlinePaymentController extends BaseController } $account = Account::whereAccountKey(Input::get('account_key'))->first(); - $redirectUrl = Input::get('redirect_url', URL::previous()); + $redirectUrl = Input::get('redirect_url'); + $failureUrl = URL::previous(); if ( ! $account || ! $account->enable_buy_now_buttons || ! $account->hasFeature(FEATURE_BUY_NOW_BUTTONS)) { - return redirect()->to("{$redirectUrl}/?error=invalid account"); + return redirect()->to("{$failureUrl}/?error=invalid account"); } Auth::onceUsingId($account->users[0]->id); $product = Product::scope(Input::get('product_id'))->first(); if ( ! $product) { - return redirect()->to("{$redirectUrl}/?error=invalid product"); + return redirect()->to("{$failureUrl}/?error=invalid product"); } $rules = [ @@ -274,7 +291,7 @@ class OnlinePaymentController extends BaseController $validator = Validator::make(Input::all(), $rules); if ($validator->fails()) { - return redirect()->to("{$redirectUrl}/?error=" . $validator->errors()->first()); + return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first()); } $data = [ @@ -300,6 +317,10 @@ class OnlinePaymentController extends BaseController $invitation = $invoice->invitations[0]; $link = $invitation->getLink(); + if ($redirectUrl) { + session(['redirect_url:' . $invitation->invitation_key => $redirectUrl]); + } + if ($gatewayTypeAlias) { return redirect()->to($invitation->getLink('payment') . "/{$gatewayTypeAlias}"); } else { diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 62a1183278ab1..2b38ba25d76b9 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -13,6 +13,7 @@ use App\Services\PaymentService; use App\Http\Requests\PaymentRequest; use App\Http\Requests\CreatePaymentRequest; use App\Http\Requests\UpdatePaymentRequest; +use App\Ninja\Datatables\PaymentDatatable; class PaymentController extends BaseController { @@ -59,22 +60,10 @@ class PaymentController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_PAYMENT, + 'datatable' => new PaymentDatatable(), 'title' => trans('texts.payments'), - 'sortCol' => '7', - 'columns' => Utils::trans([ - 'checkbox', - 'invoice', - 'client', - 'transaction_reference', - 'method', - 'source', - 'payment_amount', - 'payment_date', - 'status', - '' - ]), ]); } @@ -94,8 +83,8 @@ class PaymentController extends BaseController public function create(PaymentRequest $request) { $invoices = Invoice::scope() - ->invoiceType(INVOICE_TYPE_STANDARD) - ->where('is_recurring', '=', false) + ->invoices() + ->whereIsPublic(true) ->where('invoices.balance', '>', 0) ->with('client', 'invoice_status') ->orderBy('invoice_number')->get(); @@ -139,8 +128,11 @@ class PaymentController extends BaseController $data = [ 'client' => null, 'invoice' => null, - 'invoices' => Invoice::scope()->invoiceType(INVOICE_TYPE_STANDARD)->where('is_recurring', '=', false) - ->with('client', 'invoice_status')->orderBy('invoice_number')->get(), + 'invoices' => Invoice::scope() + ->invoices() + ->whereIsPublic(true) + ->with('client', 'invoice_status') + ->orderBy('invoice_number')->get(), 'payment' => $payment, 'entity' => $payment, 'method' => 'PUT', @@ -172,7 +164,7 @@ class PaymentController extends BaseController Session::flash('message', trans('texts.created_payment')); } - return redirect()->to($payment->client->getRoute()); + return redirect()->to($payment->client->getRoute() . '#payments'); } /** @@ -203,6 +195,6 @@ class PaymentController extends BaseController Session::flash('message', $message); } - return redirect()->to('payments'); + return $this->returnBulk(ENTITY_PAYMENT, $action, $ids); } } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 9c7269494907f..e9ab8dd005a80 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -10,6 +10,7 @@ use Redirect; use App\Models\Product; use App\Models\TaxRate; use App\Services\ProductService; +use App\Ninja\Datatables\ProductDatatable; /** * Class ProductController @@ -38,23 +39,11 @@ class ProductController extends BaseController */ public function index() { - $columns = [ - 'checkbox', - 'product', - 'description', - 'unit_cost' - ]; - - if (Auth::user()->account->invoice_item_taxes) { - $columns[] = 'tax_rate'; - } - $columns[] = 'action'; - - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_PRODUCT, + 'datatable' => new ProductDatatable(), 'title' => trans('texts.products'), - 'sortCol' => '4', - 'columns' => Utils::trans($columns), + 'statuses' => Product::getStatuses(), ]); } @@ -166,7 +155,8 @@ class ProductController extends BaseController $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); $count = $this->productService->bulk($ids, $action); - Session::flash('message', trans('texts.archived_product')); + $message = Utils::pluralize($action.'d_product', $count); + Session::flash('message', $message); return $this->returnBulk(ENTITY_PRODUCT, $action, $ids); } diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php new file mode 100644 index 0000000000000..9e43e2c63918e --- /dev/null +++ b/app/Http/Controllers/ProjectController.php @@ -0,0 +1,113 @@ +projectRepo = $projectRepo; + $this->projectService = $projectService; + } + + /** + * Display a listing of the resource. + * + * @return Response + */ + public function index() + { + return View::make('list_wrapper', [ + 'entityType' => ENTITY_PROJECT, + 'datatable' => new ProjectDatatable(), + 'title' => trans('texts.projects'), + ]); + } + + public function getDatatable($expensePublicId = null) + { + $search = Input::get('sSearch'); + $userId = Auth::user()->filterId(); + + return $this->projectService->getDatatable($search, $userId); + } + + public function create(ProjectRequest $request) + { + $data = [ + 'project' => null, + 'method' => 'POST', + 'url' => 'projects', + 'title' => trans('texts.new_project'), + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => $request->client_id, + ]; + + return View::make('projects.edit', $data); + } + + public function edit(ProjectRequest $request) + { + $project = $request->entity(); + + $data = [ + 'project' => $project, + 'method' => 'PUT', + 'url' => 'projects/' . $project->public_id, + 'title' => trans('texts.edit_project'), + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => $project->client ? $project->client->public_id : null, + ]; + + return View::make('projects.edit', $data); + } + + public function store(CreateProjectRequest $request) + { + $project = $this->projectService->save($request->input()); + + Session::flash('message', trans('texts.created_project')); + + return redirect()->to($project->getRoute()); + } + + public function update(UpdateProjectRequest $request) + { + $project = $this->projectService->save($request->input(), $request->entity()); + + Session::flash('message', trans('texts.updated_project')); + + return redirect()->to($project->getRoute()); + } + + public function bulk() + { + $action = Input::get('action'); + $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); + $count = $this->projectService->bulk($ids, $action); + + if ($count > 0) { + $field = $count == 1 ? "{$action}d_project" : "{$action}d_projects"; + $message = trans("texts.$field", ['count' => $count]); + Session::flash('message', $message); + } + + return redirect()->to('/projects'); + } + +} diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index c8c952debe46c..005da1b11d5bb 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -20,6 +20,7 @@ use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Services\InvoiceService; use App\Http\Requests\InvoiceRequest; +use App\Ninja\Datatables\InvoiceDatatable; class QuoteController extends BaseController { @@ -41,23 +42,16 @@ class QuoteController extends BaseController public function index() { + $datatable = new InvoiceDatatable(); + $datatable->entityType = ENTITY_QUOTE; + $data = [ 'title' => trans('texts.quotes'), 'entityType' => ENTITY_QUOTE, - 'sortCol' => '3', - 'columns' => Utils::trans([ - 'checkbox', - 'quote_number', - 'client', - 'quote_date', - 'quote_total', - 'valid_until', - 'status', - 'action' - ]), + 'datatable' => $datatable, ]; - return response()->view('list', $data); + return response()->view('list_wrapper', $data); } public function getDatatable($clientPublicId = null) diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index 4a1d6431ed5a3..db1b83ba39499 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -2,6 +2,7 @@ use Utils; use App\Ninja\Repositories\InvoiceRepository; +use App\Ninja\Datatables\RecurringInvoiceDatatable; /** * Class RecurringInvoiceController @@ -32,18 +33,10 @@ class RecurringInvoiceController extends BaseController $data = [ 'title' => trans('texts.recurring_invoices'), 'entityType' => ENTITY_RECURRING_INVOICE, - 'columns' => Utils::trans([ - 'checkbox', - 'frequency', - 'client', - 'start_date', - 'end_date', - 'invoice_total', - 'action' - ]) + 'datatable' => new RecurringInvoiceDatatable(), ]; - return response()->view('list', $data); + return response()->view('list_wrapper', $data); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/TaskApiController.php b/app/Http/Controllers/TaskApiController.php index 02ac417cf0896..632a8b034d0b9 100644 --- a/app/Http/Controllers/TaskApiController.php +++ b/app/Http/Controllers/TaskApiController.php @@ -1,5 +1,6 @@ withTrashed() + ->with('client', 'invoice', 'project') ->orderBy('created_at', 'desc'); return $this->listResponse($tasks); @@ -84,4 +86,38 @@ class TaskApiController extends BaseAPIController return $this->response($data); } + + + /** + * @SWG\Put( + * path="/task/{task_id}", + * tags={"task"}, + * summary="Update a task", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/Task") + * ), + * @SWG\Response( + * response=200, + * description="Update task", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Task")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + + public function update(UpdateTaskRequest $request) + { + $task = $request->entity(); + + $task = $this->taskRepo->save($task->public_id, \Illuminate\Support\Facades\Input::all()); + + return $this->itemResponse($task); + + } + } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index bcc803243f992..9de256c989764 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -10,12 +10,14 @@ use Session; use DropdownButton; use App\Models\Client; use App\Models\Task; +use App\Models\Project; use App\Ninja\Repositories\TaskRepository; use App\Ninja\Repositories\InvoiceRepository; use App\Services\TaskService; use App\Http\Requests\TaskRequest; use App\Http\Requests\CreateTaskRequest; use App\Http\Requests\UpdateTaskRequest; +use App\Ninja\Datatables\TaskDatatable; /** * Class TaskController @@ -66,19 +68,10 @@ class TaskController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => ENTITY_TASK, + 'datatable' => new TaskDatatable(), 'title' => trans('texts.tasks'), - 'sortCol' => '2', - 'columns' => Utils::trans([ - 'checkbox', - 'client', - 'date', - 'duration', - 'description', - 'status', - '' - ]), ]); } @@ -128,6 +121,7 @@ class TaskController extends BaseController $data = [ 'task' => null, 'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0), + 'projectPublicId' => Input::old('project_id') ? Input::old('project_id') : ($request->project_id ?: 0), 'method' => 'POST', 'url' => 'tasks', 'title' => trans('texts.new_task'), @@ -179,6 +173,7 @@ class TaskController extends BaseController 'task' => $task, 'entity' => $task, 'clientPublicId' => $task->client ? $task->client->public_id : 0, + 'projectPublicId' => $task->project ? $task->project->public_id : 0, 'method' => 'PUT', 'url' => 'tasks/'.$task->public_id, 'title' => trans('texts.edit_task'), @@ -214,6 +209,7 @@ class TaskController extends BaseController return [ 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), 'account' => Auth::user()->account, + 'projects' => Project::scope()->with('client.contacts')->orderBy('name')->get(), ]; } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index b2d587c1a8b41..e18c8148bb66a 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -215,7 +215,7 @@ class UserController extends BaseController Session::flash('message', $message); } - return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT); + return Redirect::to('users/' . $user->public_id . '/edit'); } public function sendConfirmation($userPublicId) diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index f1bd21e98452f..effda8c552836 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -15,6 +15,7 @@ use App\Services\VendorService; use App\Http\Requests\VendorRequest; use App\Http\Requests\CreateVendorRequest; use App\Http\Requests\UpdateVendorRequest; +use App\Ninja\Datatables\VendorDatatable; class VendorController extends BaseController { @@ -37,19 +38,10 @@ class VendorController extends BaseController */ public function index() { - return View::make('list', [ + return View::make('list_wrapper', [ 'entityType' => 'vendor', + 'datatable' => new VendorDatatable(), 'title' => trans('texts.vendors'), - 'sortCol' => '4', - 'columns' => Utils::trans([ - 'checkbox', - 'vendor', - 'city', - 'phone', - 'email', - 'date_created', - '' - ]), ]); } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 44785f06c522f..3fbef68126589 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -16,18 +16,6 @@ class VerifyCsrfToken extends BaseVerifier 'complete/*', 'signup/register', 'api/v1/*', - 'api/v1/login', - 'api/v1/clients/*', - 'api/v1/clients', - 'api/v1/invoices/*', - 'api/v1/invoices', - 'api/v1/quotes', - 'api/v1/payments', - 'api/v1/tasks', - 'api/v1/email_invoice', - 'api/v1/hooks', - 'api/v1/users', - 'api/v1/users/*', 'hook/email_opened', 'hook/email_bounced', 'reseller_stats', diff --git a/app/Http/Requests/CreatePaymentAPIRequest.php b/app/Http/Requests/CreatePaymentAPIRequest.php index 2151db745a284..4a43bf368b2bf 100644 --- a/app/Http/Requests/CreatePaymentAPIRequest.php +++ b/app/Http/Requests/CreatePaymentAPIRequest.php @@ -2,6 +2,8 @@ use App\Models\Invoice; + + class CreatePaymentAPIRequest extends PaymentRequest { /** @@ -9,6 +11,9 @@ class CreatePaymentAPIRequest extends PaymentRequest * * @return bool */ + + + public function authorize() { return $this->user()->can('create', ENTITY_PAYMENT); @@ -30,6 +35,7 @@ class CreatePaymentAPIRequest extends PaymentRequest $invoice = Invoice::scope($this->invoice_id) ->invoices() + ->whereIsPublic(true) ->firstOrFail(); $this->merge([ @@ -47,4 +53,7 @@ class CreatePaymentAPIRequest extends PaymentRequest return $rules; } + + + } diff --git a/app/Http/Requests/CreatePaymentRequest.php b/app/Http/Requests/CreatePaymentRequest.php index 745f0afa3ba40..ff30f0da9d8e8 100644 --- a/app/Http/Requests/CreatePaymentRequest.php +++ b/app/Http/Requests/CreatePaymentRequest.php @@ -24,6 +24,7 @@ class CreatePaymentRequest extends PaymentRequest $input = $this->input(); $invoice = Invoice::scope($input['invoice']) ->invoices() + ->whereIsPublic(true) ->firstOrFail(); $rules = [ diff --git a/app/Http/Requests/CreateProjectRequest.php b/app/Http/Requests/CreateProjectRequest.php new file mode 100644 index 0000000000000..000286e927d04 --- /dev/null +++ b/app/Http/Requests/CreateProjectRequest.php @@ -0,0 +1,27 @@ +user()->can('create', ENTITY_PROJECT); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'name' => sprintf('required|unique:projects,name,,id,account_id,%s', $this->user()->account_id), + 'client_id' => 'required', + ]; + } +} diff --git a/app/Http/Requests/EntityRequest.php b/app/Http/Requests/EntityRequest.php index 88ba623d03ead..f9add11ea5a05 100644 --- a/app/Http/Requests/EntityRequest.php +++ b/app/Http/Requests/EntityRequest.php @@ -3,6 +3,7 @@ use Input; use Utils; use App\Libraries\HistoryUtils; +use App\Models\EntityModel; class EntityRequest extends Request { @@ -34,13 +35,13 @@ class EntityRequest extends Request { return null; } - $class = Utils::getEntityClass($this->entityType); + $class = EntityModel::getClassName($this->entityType); - if (method_exists($class, 'trashed')) { - $this->entity = $class::scope($publicId)->withTrashed()->firstOrFail(); - } else { - $this->entity = $class::scope($publicId)->firstOrFail(); - } + if (method_exists($class, 'trashed')) { + $this->entity = $class::scope($publicId)->withTrashed()->firstOrFail(); + } else { + $this->entity = $class::scope($publicId)->firstOrFail(); + } return $this->entity; } diff --git a/app/Http/Requests/ProjectRequest.php b/app/Http/Requests/ProjectRequest.php new file mode 100644 index 0000000000000..42820c5e6be52 --- /dev/null +++ b/app/Http/Requests/ProjectRequest.php @@ -0,0 +1,7 @@ +req = $req; + } + /** * Validate the input. * @@ -48,4 +56,23 @@ abstract class Request extends FormRequest { return $this->all(); } + + public function response(array $errors) + { + /* If the user is not validating from a mobile app - pass through parent::response */ + if(!isset($this->req->api_secret)) + return parent::response($errors); + + /* If the user is validating from a mobile app - pass through first error string and return error */ + foreach($errors as $error) { + foreach ($error as $key => $value) { + + $message['error'] = ['message'=>$value]; + $message = json_encode($message, JSON_PRETTY_PRINT); + $headers = Utils::getApiHeaders(); + + return Response::make($message, 400, $headers); + } + } + } } diff --git a/app/Http/Requests/UpdateCreditRequest.php b/app/Http/Requests/UpdateCreditRequest.php new file mode 100644 index 0000000000000..0bc9d64066eac --- /dev/null +++ b/app/Http/Requests/UpdateCreditRequest.php @@ -0,0 +1,26 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'amount' => 'required|positive', + ]; + } +} diff --git a/app/Http/Requests/UpdateProjectRequest.php b/app/Http/Requests/UpdateProjectRequest.php new file mode 100644 index 0000000000000..cbc4a5b321ed9 --- /dev/null +++ b/app/Http/Requests/UpdateProjectRequest.php @@ -0,0 +1,26 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'name' => sprintf('required|unique:projects,name,%s,id,account_id,%s', $this->entity()->id, $this->user()->account_id), + ]; + } +} diff --git a/app/Http/ViewComposers/ClientPortalHeaderComposer.php b/app/Http/ViewComposers/ClientPortalHeaderComposer.php index ac0d6ca686185..7525b80bf699b 100644 --- a/app/Http/ViewComposers/ClientPortalHeaderComposer.php +++ b/app/Http/ViewComposers/ClientPortalHeaderComposer.php @@ -45,7 +45,7 @@ class ClientPortalHeaderComposer ->join('documents', 'documents.invoice_id', '=', 'invoices.id') ->count(); - $view->with('hasQuotes', $client->quotes->count()); + $view->with('hasQuotes', $client->publicQuotes->count()); $view->with('hasCredits', $client->creditsWithBalance->count()); $view->with('hasDocuments', $hasDocuments); } diff --git a/app/Http/routes.php b/app/Http/routes.php index c052a2a28679b..1e1ca6be8aa56 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -125,7 +125,7 @@ if (Utils::isReseller()) { Route::group(['middleware' => 'auth:user'], function() { Route::get('dashboard', 'DashboardController@index'); Route::get('dashboard_chart_data/{group_by}/{start_date}/{end_date}/{currency_id}/{include_expenses}', 'DashboardController@chartData'); - Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); + Route::get('set_entity_filter/{entity_type}/{filter?}', 'AccountController@setEntityFilter'); Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']); @@ -138,22 +138,29 @@ Route::group(['middleware' => 'auth:user'], function() { Route::post('users/change_password', 'UserController@changePassword'); Route::resource('clients', 'ClientController'); - Route::get('api/clients', ['as'=>'api.clients', 'uses'=>'ClientController@getDatatable']); - Route::get('api/activities/{client_id?}', ['as'=>'api.activities', 'uses'=>'ActivityController@getDatatable']); + Route::get('api/clients', 'ClientController@getDatatable'); + Route::get('api/activities/{client_id?}', 'ActivityController@getDatatable'); Route::post('clients/bulk', 'ClientController@bulk'); Route::resource('tasks', 'TaskController'); - Route::get('api/tasks/{client_id?}', ['as'=>'api.tasks', 'uses'=>'TaskController@getDatatable']); + Route::get('api/tasks/{client_id?}', 'TaskController@getDatatable'); Route::get('tasks/create/{client_id?}', 'TaskController@create'); Route::post('tasks/bulk', 'TaskController@bulk'); + Route::get('projects', 'ProjectController@index'); + Route::get('api/projects', 'ProjectController@getDatatable'); + Route::get('projects/create/{client_id?}', 'ProjectController@create'); + Route::post('projects', 'ProjectController@store'); + Route::put('projects/{projects}', 'ProjectController@update'); + Route::get('projects/{projects}/edit', 'ProjectController@edit'); + Route::post('projects/bulk', 'ProjectController@bulk'); - Route::get('api/recurring_invoices/{client_id?}', ['as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable']); + Route::get('api/recurring_invoices/{client_id?}', 'InvoiceController@getRecurringDatatable'); Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory'); Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory'); Route::resource('invoices', 'InvoiceController'); - Route::get('api/invoices/{client_id?}', ['as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable']); + Route::get('api/invoices/{client_id?}', 'InvoiceController@getDatatable'); Route::get('invoices/create/{client_id?}', 'InvoiceController@create'); Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring'); Route::get('recurring_invoices', 'RecurringInvoiceController@index'); @@ -175,20 +182,20 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('quotes/{invoices}', 'InvoiceController@edit'); Route::post('quotes', 'InvoiceController@store'); Route::get('quotes', 'QuoteController@index'); - Route::get('api/quotes/{client_id?}', ['as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable']); + Route::get('api/quotes/{client_id?}', 'QuoteController@getDatatable'); Route::post('quotes/bulk', 'QuoteController@bulk'); Route::resource('payments', 'PaymentController'); Route::get('payments/create/{client_id?}/{invoice_id?}', 'PaymentController@create'); - Route::get('api/payments/{client_id?}', ['as'=>'api.payments', 'uses'=>'PaymentController@getDatatable']); + Route::get('api/payments/{client_id?}', 'PaymentController@getDatatable'); Route::post('payments/bulk', 'PaymentController@bulk'); Route::resource('credits', 'CreditController'); Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create'); - Route::get('api/credits/{client_id?}', ['as'=>'api.credits', 'uses'=>'CreditController@getDatatable']); + Route::get('api/credits/{client_id?}', 'CreditController@getDatatable'); Route::post('credits/bulk', 'CreditController@bulk'); - Route::get('api/products', ['as'=>'api.products', 'uses'=>'ProductController@getDatatable']); + Route::get('api/products', 'ProductController@getDatatable'); Route::resource('products', 'ProductController'); Route::post('products/bulk', 'ProductController@bulk'); @@ -199,29 +206,34 @@ Route::group(['middleware' => 'auth:user'], function() { // vendor Route::resource('vendors', 'VendorController'); - Route::get('api/vendor', ['as'=>'api.vendors', 'uses'=>'VendorController@getDatatable']); + Route::get('api/vendors', 'VendorController@getDatatable'); Route::post('vendors/bulk', 'VendorController@bulk'); // Expense Route::resource('expenses', 'ExpenseController'); Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create'); - Route::get('api/expense', ['as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable']); - Route::get('api/vendor_expense/{id}', ['as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor']); + Route::get('api/expenses', 'ExpenseController@getDatatable'); + Route::get('api/expenses/{id}', 'ExpenseController@getDatatableVendor'); Route::post('expenses/bulk', 'ExpenseController@bulk'); Route::get('expense_categories', 'ExpenseCategoryController@index'); - Route::get('api/expense_categories', ['as'=>'api.expense_categories', 'uses'=>'ExpenseCategoryController@getDatatable']); + Route::get('api/expense_categories', 'ExpenseCategoryController@getDatatable'); Route::get('expense_categories/create', 'ExpenseCategoryController@create'); Route::post('expense_categories', 'ExpenseCategoryController@store'); Route::put('expense_categories/{expense_categories}', 'ExpenseCategoryController@update'); Route::get('expense_categories/{expense_categories}/edit', 'ExpenseCategoryController@edit'); Route::post('expense_categories/bulk', 'ExpenseCategoryController@bulk'); + + // BlueVine + Route::post('bluevine/signup', 'BlueVineController@signup'); + Route::get('bluevine/hide_message', 'BlueVineController@hideMessage'); + Route::get('bluevine/completed', 'BlueVineController@handleCompleted'); }); Route::group([ 'middleware' => ['auth:user', 'permissions.required'], 'permissions' => 'admin', ], function() { - Route::get('api/users', ['as'=>'api.users', 'uses'=>'UserController@getDatatable']); + Route::get('api/users', 'UserController@getDatatable'); Route::resource('users', 'UserController'); Route::post('users/bulk', 'UserController@bulk'); Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); @@ -231,11 +243,11 @@ Route::group([ Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); Route::get('/manage_companies', 'UserController@manageCompanies'); - Route::get('api/tokens', ['as'=>'api.tokens', 'uses'=>'TokenController@getDatatable']); + Route::get('api/tokens', 'TokenController@getDatatable'); Route::resource('tokens', 'TokenController'); Route::post('tokens/bulk', 'TokenController@bulk'); - Route::get('api/tax_rates', ['as'=>'api.tax_rates', 'uses'=>'TaxRateController@getDatatable']); + Route::get('api/tax_rates', 'TaxRateController@getDatatable'); Route::resource('tax_rates', 'TaxRateController'); Route::post('tax_rates/bulk', 'TaxRateController@bulk'); @@ -260,13 +272,13 @@ Route::group([ Route::get('gateways/create/{show_wepay?}', 'AccountGatewayController@create'); Route::resource('gateways', 'AccountGatewayController'); Route::get('gateways/{public_id}/resend_confirmation', 'AccountGatewayController@resendConfirmation'); - Route::get('api/gateways', ['as'=>'api.gateways', 'uses'=>'AccountGatewayController@getDatatable']); + Route::get('api/gateways', 'AccountGatewayController@getDatatable'); Route::post('account_gateways/bulk', 'AccountGatewayController@bulk'); Route::get('bank_accounts/import_ofx', 'BankAccountController@showImportOFX'); Route::post('bank_accounts/import_ofx', 'BankAccountController@doImportOFX'); Route::resource('bank_accounts', 'BankAccountController'); - Route::get('api/bank_accounts', ['as'=>'api.bank_accounts', 'uses'=>'BankAccountController@getDatatable']); + Route::get('api/bank_accounts', 'BankAccountController@getDatatable'); Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); @@ -294,7 +306,6 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::get('invoices', 'InvoiceApiController@index'); Route::get('download/{invoice_id}', 'InvoiceApiController@download'); Route::resource('invoices', 'InvoiceApiController'); - Route::get('payments', 'PaymentApiController@index'); Route::resource('payments', 'PaymentApiController'); Route::get('tasks', 'TaskApiController@index'); Route::resource('tasks', 'TaskApiController'); @@ -349,9 +360,9 @@ Route::get('/comments/feed', function() { }); if (!defined('CONTACT_EMAIL')) { - define('CONTACT_EMAIL', Config::get('mail.from.address')); - define('CONTACT_NAME', Config::get('mail.from.name')); - define('SITE_URL', Config::get('app.url')); + define('CONTACT_EMAIL', config('mail.from.address')); + define('CONTACT_NAME', config('mail.from.name')); + define('SITE_URL', config('app.url')); define('ENV_DEVELOPMENT', 'local'); define('ENV_STAGING', 'staging'); @@ -383,6 +394,7 @@ if (!defined('CONTACT_EMAIL')) { define('ENTITY_BANK_ACCOUNT', 'bank_account'); define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount'); define('ENTITY_EXPENSE_CATEGORY', 'expense_category'); + define('ENTITY_PROJECT', 'project'); define('INVOICE_TYPE_STANDARD', 1); define('INVOICE_TYPE_QUOTE', 2); @@ -516,12 +528,17 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_NUM_VENDORS', 100); define('MAX_NUM_VENDORS_PRO', 20000); + define('STATUS_ACTIVE', 'active'); + define('STATUS_ARCHIVED', 'archived'); + define('STATUS_DELETED', 'deleted'); + define('INVOICE_STATUS_DRAFT', 1); define('INVOICE_STATUS_SENT', 2); define('INVOICE_STATUS_VIEWED', 3); define('INVOICE_STATUS_APPROVED', 4); define('INVOICE_STATUS_PARTIAL', 5); define('INVOICE_STATUS_PAID', 6); + define('INVOICE_STATUS_OVERDUE', 7); define('PAYMENT_STATUS_PENDING', 1); define('PAYMENT_STATUS_VOIDED', 2); @@ -530,6 +547,15 @@ if (!defined('CONTACT_EMAIL')) { define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5); define('PAYMENT_STATUS_REFUNDED', 6); + define('TASK_STATUS_LOGGED', 1); + define('TASK_STATUS_RUNNING', 2); + define('TASK_STATUS_INVOICED', 3); + define('TASK_STATUS_PAID', 4); + + define('EXPENSE_STATUS_LOGGED', 1); + define('EXPENSE_STATUS_INVOICED', 2); + define('EXPENSE_STATUS_PAID', 3); + define('CUSTOM_DESIGN', 11); define('FREQUENCY_WEEKLY', 1); @@ -619,12 +645,14 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); define('NINJA_DATE', '2000-01-01'); - define('NINJA_VERSION', '2.8.2' . env('NINJA_VERSION_SUFFIX')); + define('NINJA_VERSION', '2.9.0' . env('NINJA_VERSION_SUFFIX')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/')); + define('NINJA_FORUM_URL', env('NINJA_FORUM_URL', 'https://www.invoiceninja.com/forums/forum/support/')); + define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/')); define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com')); define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja')); define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja')); @@ -637,6 +665,8 @@ if (!defined('CONTACT_EMAIL')) { define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all')); define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect')); define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja')); + define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en'); + define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox'); define('MSBOT_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token'); define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application'); @@ -879,8 +909,23 @@ if (!defined('CONTACT_EMAIL')) { return $string != $english ? $string : ''; } } + + // include modules in translations + function mtrans($entityType, $text = false) + { + if ( ! $text) { + $text = $entityType; + } + + if ( ! Utils::isNinjaProd() && $module = Module::find($entityType)) { + return trans("{$module->getLowerName()}::texts.{$text}"); + } else { + return trans("texts.{$text}"); + } + } } + /* if (Utils::isNinjaDev()) { diff --git a/app/Libraries/CurlUtils.php b/app/Libraries/CurlUtils.php index 8a74b3e053daf..d6ce9d0e37c0a 100644 --- a/app/Libraries/CurlUtils.php +++ b/app/Libraries/CurlUtils.php @@ -29,6 +29,11 @@ class CurlUtils curl_setopt_array($curl, $opts); $response = curl_exec($curl); + + if ($error = curl_error($curl)) { + Utils::logError('CURL Error #' . curl_errno($curl) . ': ' . $error); + } + curl_close($curl); return $response; diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 2dede0e95dab0..c869cce10d3f4 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -431,6 +431,12 @@ class Utils public static function pluralizeEntityType($type) { + if ( ! Utils::isNinjaProd()) { + if ($module = \Module::find($type)) { + return $module->get('plural', $type); + } + } + if ($type === ENTITY_EXPENSE_CATEGORY) { return 'expense_categories'; } else { @@ -708,11 +714,6 @@ class Utils return $year + $offset; } - public static function getEntityClass($entityType) - { - return 'App\\Models\\' . static::getEntityName($entityType); - } - public static function getEntityName($entityType) { return ucwords(Utils::toCamelCase($entityType)); @@ -725,7 +726,7 @@ class Utils } elseif ($model->first_name || $model->last_name) { return $model->first_name.' '.$model->last_name; } else { - return $model->email; + return $model->email ?: ''; } } @@ -1066,6 +1067,22 @@ class Utils }); } + public static function getReadableUrl($path) + { + $url = static::getDocsUrl($path); + + $parts = explode('/', $url); + $part = $parts[count($parts) - 1]; + $part = str_replace('#', '> ', $part); + $part = str_replace(['.html', '-', '_'], ' ', $part); + + if ($part) { + return trans('texts.user_guide') . ': ' . ucwords($part); + } else { + return trans('texts.user_guide'); + } + } + public static function getDocsUrl($path) { $page = ''; diff --git a/app/Listeners/CreditListener.php b/app/Listeners/CreditListener.php index c8d16a4776c25..2ad809cd24d75 100644 --- a/app/Listeners/CreditListener.php +++ b/app/Listeners/CreditListener.php @@ -41,27 +41,7 @@ class CreditListener $credit->client_id = $payment->client_id; $credit->credit_date = Carbon::now()->toDateTimeString(); $credit->balance = $credit->amount = $payment->getCompletedAmount(); - $credit->private_notes = $payment->transaction_reference; - $credit->save(); - } - - /** - * @param PaymentWasRefunded $event - */ - public function refundedPayment(PaymentWasRefunded $event) - { - $payment = $event->payment; - - // if the payment was from a credit we need to refund the credit - if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) { - return; - } - - $credit = Credit::createNew(); - $credit->client_id = $payment->client_id; - $credit->credit_date = Carbon::now()->toDateTimeString(); - $credit->balance = $credit->amount = $event->refundAmount; - $credit->private_notes = $payment->transaction_reference; + $credit->private_notes = trans('texts.refunded_credit_payment'); $credit->save(); } } diff --git a/app/Listeners/NotificationListener.php b/app/Listeners/NotificationListener.php index d18b0a32671b1..ad94e5767a610 100644 --- a/app/Listeners/NotificationListener.php +++ b/app/Listeners/NotificationListener.php @@ -80,6 +80,10 @@ class NotificationListener */ public function viewedInvoice(InvoiceInvitationWasViewed $event) { + if ( ! floatval($event->invoice->balance)) { + return; + } + $this->sendEmails($event->invoice, 'viewed'); $this->pushService->sendNotification($event->invoice, 'viewed'); } @@ -89,6 +93,10 @@ class NotificationListener */ public function viewedQuote(QuoteInvitationWasViewed $event) { + if ($event->quote->quote_invoice_id) { + return; + } + $this->sendEmails($event->quote, 'viewed'); $this->pushService->sendNotification($event->quote, 'viewed'); } @@ -118,4 +126,4 @@ class NotificationListener $this->pushService->sendNotification($event->payment->invoice, 'paid'); } -} \ No newline at end of file +} diff --git a/app/Models/Account.php b/app/Models/Account.php index bf7e2e3968c09..68f84ccf69da9 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -7,6 +7,7 @@ use DateTime; use Event; use Cache; use App; +use Carbon; use App\Events\UserSettingsChanged; use Illuminate\Support\Facades\Storage; use Illuminate\Database\Eloquent\SoftDeletes; @@ -303,6 +304,14 @@ class Account extends Eloquent return $this->hasMany('App\Models\ExpenseCategory','account_id','id')->withTrashed(); } + /** + * @return mixed + */ + public function projects() + { + return $this->hasMany('App\Models\Project','account_id','id')->withTrashed(); + } + /** * @param $value */ @@ -1387,7 +1396,7 @@ class Account extends Eloquent $date = date_create(); } - return $date->format('Y-m-d'); + return Carbon::instance($date); } /** diff --git a/app/Models/Client.php b/app/Models/Client.php index a1db398ef58bc..02da4dc912a57 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -173,6 +173,14 @@ class Client extends EntityModel return $this->hasMany('App\Models\Invoice')->where('invoice_type_id', '=', INVOICE_TYPE_QUOTE); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function publicQuotes() + { + return $this->hasMany('App\Models\Invoice')->where('invoice_type_id', '=', INVOICE_TYPE_QUOTE)->whereIsPublic(true); + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Models/Company.php b/app/Models/Company.php index 847289a88eb2b..94111e4465568 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -1,7 +1,10 @@ belongsTo('App\Models\Payment'); } + + public function hasActivePromo() + { + if ($this->discount_expires) { + return false; + } + + return $this->promo_expires && $this->promo_expires->gte(Carbon::today()); + } + + // handle promos and discounts + public function hasActiveDiscount(Carbon $date = null) + { + if ( ! $this->discount) { + return false; + } + + $date = $date ?: Carbon::today(); + + return $this->discount_expires && $this->discount_expires->gt($date); + } + + public function discountedPrice($price) + { + if ( ! $this->hasActivePromo() && ! $this->hasActiveDiscount()) { + return $price; + } + + return $price - ($price * $this->discount); + } + + public function hasEarnedPromo() + { + if ( ! Utils::isNinjaProd() || Utils::isPro()) { + return false; + } + + // if they've already had a discount or a promotion is active return false + if ($this->discount_expires || $this->hasActivePromo()) { + return false; + } + + // after 52 weeks, offer a 50% discount for 3 days + $discounts = [ + 52 => [.5, 3], + 16 => [.5, 3], + 10 => [.25, 5], + ]; + + foreach ($discounts as $weeks => $promo) { + list($discount, $validFor) = $promo; + $difference = $this->created_at->diffInWeeks(); + if ($difference >= $weeks) { + $this->discount = $discount; + $this->promo_expires = date_create()->modify($validFor . ' days')->format('Y-m-d'); + $this->save(); + return true; + } + } + + return false; + } } diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 02a38777509f6..b7d041b148b9d 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -61,6 +61,14 @@ class Credit extends EntityModel return ''; } + /** + * @return string + */ + public function getRoute() + { + return "/credits/{$this->public_id}"; + } + /** * @return mixed */ diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 51ec29da1a002..e272b43508579 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -31,6 +31,15 @@ class EntityModel extends Eloquent */ public static $notifySubscriptions = true; + /** + * @var array + */ + public static $statuses = [ + STATUS_ACTIVE, + STATUS_ARCHIVED, + STATUS_DELETED, + ]; + /** * @param null $context * @return mixed @@ -63,7 +72,6 @@ class EntityModel extends Eloquent $lastEntity = $className::whereAccountId($entity->account_id); } - if (static::$hasPublicId) { $lastEntity = $lastEntity->orderBy('public_id', 'DESC') ->first(); @@ -184,6 +192,16 @@ class EntityModel extends Eloquent */ public static function getClassName($entityType) { + if ( ! Utils::isNinjaProd()) { + if ($module = \Module::find($entityType)) { + return "Modules\\{$module->getName()}\\Models\\{$module->getName()}"; + } + } + + if ($entityType == ENTITY_QUOTE || $entityType == ENTITY_RECURRING_INVOICE) { + $entityType = ENTITY_INVOICE; + } + return 'App\\Models\\' . ucwords(Utils::toCamelCase($entityType)); } @@ -193,6 +211,12 @@ class EntityModel extends Eloquent */ public static function getTransformerName($entityType) { + if ( ! Utils::isNinjaProd()) { + if ($module = \Module::find($entityType)) { + return "Modules\\{$module->getName()}\\Transformers\\{$module->getName()}Transformer"; + } + } + return 'App\\Ninja\\Transformers\\' . ucwords(Utils::toCamelCase($entityType)) . 'Transformer'; } @@ -281,4 +305,34 @@ class EntityModel extends Eloquent return false; } + + public static function getStates($entityType = false) + { + $data = []; + + foreach (static::$statuses as $status) { + $data[$status] = trans("texts.{$status}"); + } + + return $data; + } + + public static function getStatuses($entityType = false) + { + return []; + } + + public static function getStatesFor($entityType = false) + { + $class = static::getClassName($entityType); + + return $class::getStates($entityType); + } + + public static function getStatusesFor($entityType = false) + { + $class = static::getClassName($entityType); + + return $class::getStatuses($entityType); + } } diff --git a/app/Models/Expense.php b/app/Models/Expense.php index b19b7e2637f95..1829fed8d1862 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -211,6 +211,16 @@ class Expense extends EntityModel { return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2); } + + public static function getStatuses($entityType = false) + { + $statuses = []; + $statuses[EXPENSE_STATUS_LOGGED] = trans('texts.logged'); + $statuses[EXPENSE_STATUS_INVOICED] = trans('texts.invoiced'); + $statuses[EXPENSE_STATUS_PAID] = trans('texts.paid'); + + return $statuses; + } } Expense::creating(function ($expense) { diff --git a/app/Models/ExpenseCategory.php b/app/Models/ExpenseCategory.php index 94f1fb1fbf6b0..77782e5b85efd 100644 --- a/app/Models/ExpenseCategory.php +++ b/app/Models/ExpenseCategory.php @@ -47,5 +47,4 @@ class ExpenseCategory extends EntityModel { return "/expense_categories/{$this->public_id}/edit"; } - } diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index fd0fc7d720da6..370c521b78301 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -97,8 +97,8 @@ class Invitation extends EntityModel if ($this->$field && $this->field != '0000-00-00 00:00:00') { $date = Utils::dateToString($this->$field); $hasValue = true; + $parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date; } - $parts[] = trans('texts.invitation_status_' . $status) . ': ' . $date; } return $hasValue ? implode($parts, '
') : false; @@ -123,6 +123,11 @@ class Invitation extends EntityModel $this->save(); } + public function isSent() + { + return $this->sent_date && $this->sent_date != '0000-00-00 00:00:00'; + } + public function markViewed() { $invoice = $this->invoice; diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index ad20fc1830e27..76b21947b29af 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -153,7 +153,7 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function affectsBalance() { - return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring; + return $this->isType(INVOICE_TYPE_STANDARD) && !$this->is_recurring && $this->is_public; } /** @@ -161,7 +161,7 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function getAdjustment() { - if (!$this->affectsBalance()) { + if ( ! $this->affectsBalance()) { return 0; } @@ -173,6 +173,11 @@ class Invoice extends EntityModel implements BalanceAffecting */ private function getRawAdjustment() { + // if we've just made the invoice public then apply the full amount + if ($this->is_public && ! $this->getOriginal('is_public')) { + return $this->amount; + } + return floatval($this->amount) - floatval($this->getOriginal('amount')); } @@ -353,6 +358,16 @@ class Invoice extends EntityModel implements BalanceAffecting ->where('is_recurring', '=', false); } + /** + * @param $query + * @return mixed + */ + public function scopeRecurring($query) + { + return $query->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) + ->where('is_recurring', '=', true); + } + /** * @param $query * @return mixed @@ -400,11 +415,30 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function markInvitationsSent($notify = false) { + if ( ! $this->relationLoaded('invitations')) { + $this->load('invitations'); + } + foreach ($this->invitations as $invitation) { $this->markInvitationSent($invitation, false, $notify); } } + public function areInvitationsSent() + { + if ( ! $this->relationLoaded('invitations')) { + $this->load('invitations'); + } + + foreach ($this->invitations as $invitation) { + if ( ! $invitation->isSent()) { + return false; + } + } + + return true; + } + /** * @param $invitation * @param bool $messageId @@ -1249,6 +1283,35 @@ class Invoice extends EntityModel implements BalanceAffecting return $recurInvoice->auto_bill == AUTO_BILL_ALWAYS || ($recurInvoice->auto_bill != AUTO_BILL_OFF && $recurInvoice->client_enable_auto_bill); } + + public static function getStatuses($entityType = false) + { + $statuses = []; + + if ($entityType == ENTITY_RECURRING_INVOICE) { + return $statuses; + } + + foreach (\Cache::get('invoiceStatus') as $status) { + if ($entityType == ENTITY_QUOTE) { + if (in_array($status->id, [INVOICE_STATUS_PAID, INVOICE_STATUS_PARTIAL])) { + continue; + } + } elseif ($entityType == ENTITY_INVOICE) { + if (in_array($status->id, [INVOICE_STATUS_APPROVED])) { + continue; + } + } + + $statuses[$status->id] = trans('texts.status_' . strtolower($status->name)); + } + + if ($entityType == ENTITY_INVOICE) { + $statuses[INVOICE_STATUS_OVERDUE] = trans('texts.overdue'); + } + + return $statuses; + } } Invoice::creating(function ($invoice) { diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index 303c437f91678..ef9a43f4eb254 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -1,5 +1,6 @@ public_id}/edit"; + } + + /** + * @return mixed + */ + public function client() + { + return $this->belongsTo('App\Models\Client')->withTrashed(); + } + +} + +Project::creating(function ($project) { + $project->setNullValues(); +}); + +Project::updating(function ($project) { + $project->setNullValues(); +}); diff --git a/app/Models/Task.php b/app/Models/Task.php index 5b4ca6b6eb584..5a114a245ad0a 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -69,6 +69,14 @@ class Task extends EntityModel return $this->belongsTo('App\Models\Client')->withTrashed(); } + /** + * @return mixed + */ + public function project() + { + return $this->belongsTo('App\Models\Project')->withTrashed(); + } + /** * @param $task * @return string @@ -195,6 +203,18 @@ class Task extends EntityModel return $query; } + + public static function getStatuses($entityType = false) + { + $statuses = []; + $statuses[TASK_STATUS_LOGGED] = trans('texts.logged'); + $statuses[TASK_STATUS_RUNNING] = trans('texts.running'); + $statuses[TASK_STATUS_INVOICED] = trans('texts.invoiced'); + $statuses[TASK_STATUS_PAID] = trans('texts.paid'); + + return $statuses; + } + } diff --git a/app/Ninja/Datatables/ClientDatatable.php b/app/Ninja/Datatables/ClientDatatable.php index 400e51f08ce74..cb4850f73e7dd 100644 --- a/app/Ninja/Datatables/ClientDatatable.php +++ b/app/Ninja/Datatables/ClientDatatable.php @@ -7,6 +7,7 @@ use Auth; class ClientDatatable extends EntityDatatable { public $entityType = ENTITY_CLIENT; + public $sortCol = 4; public function columns() { @@ -18,9 +19,9 @@ class ClientDatatable extends EntityDatatable } ], [ - 'first_name', + 'contact', function ($model) { - return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name)->toHtml(); + return link_to("clients/{$model->public_id}", $model->contact ?: '')->toHtml(); } ], [ @@ -30,7 +31,7 @@ class ClientDatatable extends EntityDatatable } ], [ - 'clients.created_at', + 'client_created_at', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); } diff --git a/app/Ninja/Datatables/CreditDatatable.php b/app/Ninja/Datatables/CreditDatatable.php index 7f258e377d1d6..6e2dd491b120d 100644 --- a/app/Ninja/Datatables/CreditDatatable.php +++ b/app/Ninja/Datatables/CreditDatatable.php @@ -7,6 +7,7 @@ use Auth; class CreditDatatable extends EntityDatatable { public $entityType = ENTITY_CREDIT; + public $sortCol = 4; public function columns() { @@ -37,7 +38,11 @@ class CreditDatatable extends EntityDatatable [ 'credit_date', function ($model) { - return Utils::fromSqlDate($model->credit_date); + if ( ! Auth::user()->can('viewByOwner', [ENTITY_CREDIT, $model->user_id])){ + return Utils::fromSqlDate($model->credit_date); + } + + return link_to("credits/{$model->public_id}/edit", Utils::fromSqlDate($model->credit_date))->toHtml(); } ], [ @@ -52,6 +57,15 @@ class CreditDatatable extends EntityDatatable public function actions() { return [ + [ + trans('texts.edit_credit'), + function ($model) { + return URL::to("credits/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_CREDIT, $model->user_id]); + } + ], [ trans('texts.apply_credit'), function ($model) { diff --git a/app/Ninja/Datatables/EntityDatatable.php b/app/Ninja/Datatables/EntityDatatable.php index 085645ff2069c..99e2a414ad892 100644 --- a/app/Ninja/Datatables/EntityDatatable.php +++ b/app/Ninja/Datatables/EntityDatatable.php @@ -5,11 +5,16 @@ class EntityDatatable public $entityType; public $isBulkEdit; public $hideClient; + public $sortCol = 1; - public function __construct($isBulkEdit = true, $hideClient = false) + public function __construct($isBulkEdit = true, $hideClient = false, $entityType = false) { $this->isBulkEdit = $isBulkEdit; $this->hideClient = $hideClient; + + if ($entityType) { + $this->entityType = $entityType; + } } public function columns() @@ -21,4 +26,42 @@ class EntityDatatable { return []; } + + public function bulkActions() + { + return [ + [ + 'label' => mtrans($this->entityType, 'archive_'.$this->entityType), + 'url' => 'javascript:submitForm_'.$this->entityType.'("archive")', + ], + [ + 'label' => mtrans($this->entityType, 'delete_'.$this->entityType), + 'url' => 'javascript:submitForm_'.$this->entityType.'("delete")', + ] + ]; + } + + public function columnFields() + { + $data = []; + $columns = $this->columns(); + + if ($this->isBulkEdit) { + $data[] = 'checkbox'; + } + + foreach ($columns as $column) { + if (count($column) == 3) { + // third column is optionally used to determine visibility + if (!$column[2]) { + continue; + } + } + $data[] = $column[0]; + } + + $data[] = ''; + + return $data; + } } diff --git a/app/Ninja/Datatables/ExpenseCategoryDatatable.php b/app/Ninja/Datatables/ExpenseCategoryDatatable.php index 7ef850b0a49cf..82206b8f8cf6b 100644 --- a/app/Ninja/Datatables/ExpenseCategoryDatatable.php +++ b/app/Ninja/Datatables/ExpenseCategoryDatatable.php @@ -7,6 +7,7 @@ use Auth; class ExpenseCategoryDatatable extends EntityDatatable { public $entityType = ENTITY_EXPENSE_CATEGORY; + public $sortCol = 1; public function columns() { diff --git a/app/Ninja/Datatables/ExpenseDatatable.php b/app/Ninja/Datatables/ExpenseDatatable.php index 5832fd8e63af8..6bf432e6d02b2 100644 --- a/app/Ninja/Datatables/ExpenseDatatable.php +++ b/app/Ninja/Datatables/ExpenseDatatable.php @@ -7,6 +7,7 @@ use Auth; class ExpenseDatatable extends EntityDatatable { public $entityType = ENTITY_EXPENSE; + public $sortCol = 3; public function columns() { @@ -81,7 +82,7 @@ class ExpenseDatatable extends EntityDatatable } ], [ - 'expense_status_id', + 'status', function ($model) { return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced, $model->balance); } @@ -113,7 +114,7 @@ class ExpenseDatatable extends EntityDatatable [ trans('texts.invoice_expense'), function ($model) { - return "javascript:invoiceEntity({$model->public_id})"; + return "javascript:submitForm_expense('invoice', {$model->public_id})"; }, function ($model) { return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE); diff --git a/app/Ninja/Datatables/InvoiceDatatable.php b/app/Ninja/Datatables/InvoiceDatatable.php index 646ebff05a2fb..2bdb6e1c3b8d3 100644 --- a/app/Ninja/Datatables/InvoiceDatatable.php +++ b/app/Ninja/Datatables/InvoiceDatatable.php @@ -7,6 +7,7 @@ use Auth; class InvoiceDatatable extends EntityDatatable { public $entityType = ENTITY_INVOICE; + public $sortCol = 3; public function columns() { @@ -14,7 +15,7 @@ class InvoiceDatatable extends EntityDatatable return [ [ - 'invoice_number', + $entityType == ENTITY_INVOICE ? 'invoice_number' : 'quote_number', function ($model) use ($entityType) { if(!Auth::user()->can('viewByOwner', [ENTITY_INVOICE, $model->user_id])){ return $model->invoice_number; @@ -34,9 +35,9 @@ class InvoiceDatatable extends EntityDatatable ! $this->hideClient ], [ - 'invoice_date', + 'date', function ($model) { - return Utils::fromSqlDate($model->invoice_date); + return Utils::fromSqlDate($model->date); } ], [ @@ -58,13 +59,13 @@ class InvoiceDatatable extends EntityDatatable $entityType == ENTITY_INVOICE ], [ - 'due_date', + $entityType == ENTITY_INVOICE ? 'due_date' : 'valid_until', function ($model) { return Utils::fromSqlDate($model->due_date); }, ], [ - 'invoice_status_name', + 'status', function ($model) use ($entityType) { return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($model); } @@ -109,13 +110,22 @@ class InvoiceDatatable extends EntityDatatable ], [ trans('texts.mark_sent'), - function ($model) { - return "javascript:markEntity({$model->public_id})"; + function ($model) use ($entityType) { + return "javascript:submitForm_{$entityType}('markSent', {$model->public_id})"; }, function ($model) { return $model->invoice_status_id < INVOICE_STATUS_SENT && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); } ], + [ + trans('texts.mark_paid'), + function ($model) use ($entityType) { + return "javascript:submitForm_{$entityType}('markPaid', {$model->public_id})"; + }, + function ($model) { + return $model->balance > 0 && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + } + ], [ trans('texts.enter_payment'), function ($model) { @@ -146,7 +156,7 @@ class InvoiceDatatable extends EntityDatatable [ trans('texts.convert_to_invoice'), function ($model) { - return "javascript:convertEntity({$model->public_id})"; + return "javascript:submitForm_quote('convert', {$model->public_id})"; }, function ($model) use ($entityType) { return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); @@ -190,4 +200,25 @@ class InvoiceDatatable extends EntityDatatable return "

$label

"; } + public function bulkActions() + { + $actions = parent::bulkActions(); + + if ($this->entityType == ENTITY_INVOICE || $this->entityType == ENTITY_QUOTE) { + $actions[] = \DropdownButton::DIVIDER; + $actions[] = [ + 'label' => mtrans($this->entityType, 'mark_sent'), + 'url' => 'javascript:submitForm_'.$this->entityType.'("markSent")', + ]; + } + + if ($this->entityType == ENTITY_INVOICE) { + $actions[] = [ + 'label' => mtrans($this->entityType, 'mark_paid'), + 'url' => 'javascript:submitForm_'.$this->entityType.'("markPaid")', + ]; + } + + return $actions; + } } diff --git a/app/Ninja/Datatables/PaymentDatatable.php b/app/Ninja/Datatables/PaymentDatatable.php index faa9733ca9da8..fb7dc175d5c7c 100644 --- a/app/Ninja/Datatables/PaymentDatatable.php +++ b/app/Ninja/Datatables/PaymentDatatable.php @@ -8,6 +8,7 @@ use App\Models\PaymentMethod; class PaymentDatatable extends EntityDatatable { public $entityType = ENTITY_PAYMENT; + public $sortCol = 7; protected static $refundableGateways = [ GATEWAY_STRIPE, @@ -19,7 +20,7 @@ class PaymentDatatable extends EntityDatatable { return [ [ - 'invoice_number', + 'invoice_name', function ($model) { if(!Auth::user()->can('viewByOwner', [ENTITY_INVOICE, $model->invoice_user_id])){ return $model->invoice_number; @@ -46,13 +47,13 @@ class PaymentDatatable extends EntityDatatable } ], [ - 'payment_type', + 'method', function ($model) { return ($model->payment_type && !$model->last4) ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : ''); } ], [ - 'payment_type_id', + 'source', function ($model) { $code = str_replace(' ', '', strtolower($model->payment_type)); $card_type = trans('texts.card_' . $code); diff --git a/app/Ninja/Datatables/ProductDatatable.php b/app/Ninja/Datatables/ProductDatatable.php index a5b3cbfc218d3..bf57064f1ad44 100644 --- a/app/Ninja/Datatables/ProductDatatable.php +++ b/app/Ninja/Datatables/ProductDatatable.php @@ -8,6 +8,7 @@ use Str; class ProductDatatable extends EntityDatatable { public $entityType = ENTITY_PRODUCT; + public $sortCol = 4; public function columns() { diff --git a/app/Ninja/Datatables/ProjectDatatable.php b/app/Ninja/Datatables/ProjectDatatable.php new file mode 100644 index 0000000000000..15b8fc617875d --- /dev/null +++ b/app/Ninja/Datatables/ProjectDatatable.php @@ -0,0 +1,59 @@ +can('editByOwner', [ENTITY_PROJECT, $model->user_id])) { + return $model->project; + } + + return link_to("projects/{$model->public_id}/edit", $model->project)->toHtml(); + } + ], + [ + 'client_name', + function ($model) + { + if ($model->client_public_id) { + if(!Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])){ + return Utils::getClientDisplayName($model); + } + + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); + } else { + return ''; + } + } + ] + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_project'), + function ($model) { + return URL::to("projects/{$model->public_id}/edit") ; + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->user_id]); + } + ], + ]; + } + +} diff --git a/app/Ninja/Datatables/TaskDatatable.php b/app/Ninja/Datatables/TaskDatatable.php index 0f0a1cc09c79f..4d3956f2601ec 100644 --- a/app/Ninja/Datatables/TaskDatatable.php +++ b/app/Ninja/Datatables/TaskDatatable.php @@ -8,6 +8,7 @@ use App\Models\Task; class TaskDatatable extends EntityDatatable { public $entityType = ENTITY_TASK; + public $sortCol = 3; public function columns() { @@ -24,7 +25,17 @@ class TaskDatatable extends EntityDatatable ! $this->hideClient ], [ - 'created_at', + 'project', + function ($model) { + if(!Auth::user()->can('editByOwner', [ENTITY_PROJECT, $model->project_user_id])){ + return $model->project; + } + + return $model->project_public_id ? link_to("projects/{$model->project_public_id}/edit", $model->project)->toHtml() : ''; + } + ], + [ + 'date', function ($model) { if(!Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])){ return Task::calcStartTime($model); @@ -33,7 +44,7 @@ class TaskDatatable extends EntityDatatable } ], [ - 'time_log', + 'duration', function($model) { return Utils::formatTime(Task::calcDuration($model)); } @@ -45,7 +56,7 @@ class TaskDatatable extends EntityDatatable } ], [ - 'invoice_number', + 'status', function ($model) { return self::getStatusLabel($model); } @@ -77,7 +88,7 @@ class TaskDatatable extends EntityDatatable [ trans('texts.stop_task'), function ($model) { - return "javascript:stopTask({$model->public_id})"; + return "javascript:submitForm_task('stop', {$model->public_id})"; }, function ($model) { return $model->is_running && Auth::user()->can('editByOwner', [ENTITY_TASK, $model->user_id]); @@ -86,7 +97,7 @@ class TaskDatatable extends EntityDatatable [ trans('texts.invoice_task'), function ($model) { - return "javascript:invoiceEntity({$model->public_id})"; + return "javascript:submitForm_task('invoice', {$model->public_id})"; }, function ($model) { return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Auth::user()->can('create', ENTITY_INVOICE); diff --git a/app/Ninja/Datatables/VendorDatatable.php b/app/Ninja/Datatables/VendorDatatable.php index 93e059b6ae559..8898f922cc46a 100644 --- a/app/Ninja/Datatables/VendorDatatable.php +++ b/app/Ninja/Datatables/VendorDatatable.php @@ -7,6 +7,7 @@ use Auth; class VendorDatatable extends EntityDatatable { public $entityType = ENTITY_VENDOR; + public $sortCol = 4; public function columns() { @@ -36,7 +37,7 @@ class VendorDatatable extends EntityDatatable } ], [ - 'vendors.created_at', + 'date', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); } diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index c1b127c10685d..c38a2c8704ad7 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -102,7 +102,15 @@ class Mailer private function handleFailure($exception) { if (isset($_ENV['POSTMARK_API_TOKEN']) && method_exists($exception, 'getResponse')) { - $response = $exception->getResponse()->getBody()->getContents(); + $response = $exception->getResponse(); + + if (! $response) { + $error = trans('texts.postmark_error', ['link' => link_to('https://status.postmarkapp.com/')]); + Utils::logError($error); + return $error; + } + + $response = $response->getBody()->getContents(); $response = json_decode($response); $emailError = nl2br($response->Message); } else { diff --git a/app/Ninja/PaymentDrivers/BasePaymentDriver.php b/app/Ninja/PaymentDrivers/BasePaymentDriver.php index 4db33e1989b29..284badc9e1014 100644 --- a/app/Ninja/PaymentDrivers/BasePaymentDriver.php +++ b/app/Ninja/PaymentDrivers/BasePaymentDriver.php @@ -676,6 +676,11 @@ class BasePaymentDriver $company->plan_expires = DateTime::createFromFormat('Y-m-d', $account->company->plan_paid) ->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d'); + if ($company->hasActivePromo()) { + $company->discount_expires = date_create()->modify('1 year')->format('Y-m-d'); + $company->promo_expires = null; + } + $company->save(); } } diff --git a/app/Ninja/PaymentDrivers/WePayPaymentDriver.php b/app/Ninja/PaymentDrivers/WePayPaymentDriver.php index 7612abdb2c5a8..80ad37afff711 100644 --- a/app/Ninja/PaymentDrivers/WePayPaymentDriver.php +++ b/app/Ninja/PaymentDrivers/WePayPaymentDriver.php @@ -61,6 +61,8 @@ class WePayPaymentDriver extends BasePaymentDriver $data['paymentMethodType'] = 'payment_bank'; } + $data['transaction_rbits'] = $this->invoice()->present()->rBits; + return $data; } diff --git a/app/Ninja/Presenters/AccountPresenter.php b/app/Ninja/Presenters/AccountPresenter.php index cd829fd4f44dd..5408d4f1afd4d 100644 --- a/app/Ninja/Presenters/AccountPresenter.php +++ b/app/Ninja/Presenters/AccountPresenter.php @@ -1,5 +1,6 @@ code; } -} \ No newline at end of file + + public function industry() + { + return $this->entity->industry ? $this->entity->industry->name : ''; + } + + public function size() + { + return $this->entity->size ? $this->entity->size->name : ''; + } + + private function createRBit($type, $source, $properties) + { + $data = new stdClass(); + $data->receive_time = time(); + $data->type = $type; + $data->source = $source; + $data->properties = new stdClass(); + + foreach ($properties as $key => $val) { + $data->properties->$key = $val; + } + + return $data; + } + + public function rBits() + { + $account = $this->entity; + $user = $account->users()->first(); + $data = []; + + $data[] = $this->createRBit('business_name', 'user', ['business_name' => $account->name]); + $data[] = $this->createRBit('industry_code', 'user', ['industry_detail' => $account->present()->industry]); + $data[] = $this->createRBit('comment', 'partner_database', ['comment_text' => 'Logo image not present']); + $data[] = $this->createRBit('business_description', 'user', ['business_description' => $account->present()->size]); + + $data[] = $this->createRBit('person', 'user', ['name' => $user->getFullName()]); + $data[] = $this->createRBit('email', 'user', ['email' => $user->email]); + $data[] = $this->createRBit('phone', 'user', ['phone' => $user->phone]); + $data[] = $this->createRBit('website_uri', 'user', ['uri' => $account->website]); + $data[] = $this->createRBit('external_account', 'partner_database', ['is_partner_account' => 'yes', 'account_type' => 'Invoice Ninja', 'create_time' => time()]); + + return $data; + } +} diff --git a/app/Ninja/Presenters/CompanyPresenter.php b/app/Ninja/Presenters/CompanyPresenter.php new file mode 100644 index 0000000000000..730b4f5cfd009 --- /dev/null +++ b/app/Ninja/Presenters/CompanyPresenter.php @@ -0,0 +1,29 @@ +entity->hasActivePromo()) { + return ''; + } + + return trans('texts.promo_message', [ + 'expires' => $this->entity->promo_expires->format('M dS, Y'), + 'amount' => (int)($this->discount * 100) + ]); + } + + public function discountMessage() + { + if ( ! $this->entity->hasActiveDiscount()) { + return ''; + } + + return trans('texts.discount_message', [ + 'expires' => $this->entity->discount_expires->format('M dS, Y'), + 'amount' => (int)($this->discount * 100) + ]); + } + +} diff --git a/app/Ninja/Presenters/EntityPresenter.php b/app/Ninja/Presenters/EntityPresenter.php index 66e6518c87054..5eec3d513cc0d 100644 --- a/app/Ninja/Presenters/EntityPresenter.php +++ b/app/Ninja/Presenters/EntityPresenter.php @@ -1,5 +1,6 @@ entity->getEntityType(); + $type = Utils::pluralizeEntityType($this->entity->getEntityType()); $id = $this->entity->public_id; - $link = sprintf('/%ss/%s', $type, $id); + $link = sprintf('/%s/%s', $type, $id); return URL::to($link); } + public function editUrl() + { + return $this->url() . '/edit'; + } + public function statusLabel() { $class = $text = ''; diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php index 4760515720bbe..018ca273e76e1 100644 --- a/app/Ninja/Presenters/ExpensePresenter.php +++ b/app/Ninja/Presenters/ExpensePresenter.php @@ -24,4 +24,14 @@ class ExpensePresenter extends EntityPresenter return Utils::fromSqlDate($this->entity->expense_date); } + public function amount() + { + return Utils::formatMoney($this->entity->amount, $this->entity->expense_currency_id); + } + + public function category() + { + return $this->entity->expense_category ? $this->entity->expense_category->name : ''; + } + } diff --git a/app/Ninja/Presenters/InvoiceItemPresenter.php b/app/Ninja/Presenters/InvoiceItemPresenter.php new file mode 100644 index 0000000000000..67150df771ca6 --- /dev/null +++ b/app/Ninja/Presenters/InvoiceItemPresenter.php @@ -0,0 +1,17 @@ +description = $this->entity->notes; + $data->item_price = floatval($this->entity->cost); + $data->quantity = floatval($this->entity->qty); + $data->amount = round($data->item_price * $data->quantity, 2); + + return $data; + } +} diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 9f9870cb1fe03..16554c3b168c0 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -1,5 +1,6 @@ entity); } + + public function rBits() + { + $properties = new stdClass(); + $properties->terms_text = $this->entity->terms; + $properties->note = $this->entity->public_notes; + $properties->itemized_receipt = []; + + foreach ($this->entity->invoice_items as $item) { + $properties->itemized_receipt[] = $item->present()->rBits; + } + + $data = new stdClass(); + $data->receive_time = time(); + $data->type = 'transaction_details'; + $data->source = 'user'; + $data->properties = $properties; + + return [$data]; + } } diff --git a/app/Ninja/Presenters/ProductPresenter.php b/app/Ninja/Presenters/ProductPresenter.php index 0e3acba47d96e..786e61cba75c1 100644 --- a/app/Ninja/Presenters/ProductPresenter.php +++ b/app/Ninja/Presenters/ProductPresenter.php @@ -4,6 +4,10 @@ use App\Libraries\Skype\HeroCard; class ProductPresenter extends EntityPresenter { + public function user() + { + return $this->entity->user->getDisplayName(); + } public function skypeBot($account) { diff --git a/app/Ninja/Repositories/AccountGatewayRepository.php b/app/Ninja/Repositories/AccountGatewayRepository.php index c59b0d77ababa..03b41be7e92c3 100644 --- a/app/Ninja/Repositories/AccountGatewayRepository.php +++ b/app/Ninja/Repositories/AccountGatewayRepository.php @@ -13,11 +13,8 @@ class AccountGatewayRepository extends BaseRepository { $query = DB::table('account_gateways') ->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id') - ->where('account_gateways.account_id', '=', $accountId); - - if (!\Session::get('show_trash:gateway')) { - $query->where('account_gateways.deleted_at', '=', null); - } + ->where('account_gateways.account_id', '=', $accountId) + ->whereNull('account_gateways.deleted_at'); return $query->select('account_gateways.id', 'account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at', 'account_gateways.gateway_id'); } diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index b80bd1321e88f..2b6ec5b9aa7df 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -187,7 +187,8 @@ class AccountRepository ENTITY_VENDOR, ENTITY_RECURRING_INVOICE, ENTITY_PAYMENT, - ENTITY_CREDIT + ENTITY_CREDIT, + ENTITY_PROJECT, ]; foreach ($entityTypes as $entityType) { @@ -277,16 +278,29 @@ class AccountRepository $account = $this->getNinjaAccount(); $lastInvoice = Invoice::withTrashed()->whereAccountId($account->id)->orderBy('public_id', 'DESC')->first(); + $renewalDate = $clientAccount->getRenewalDate(); $publicId = $lastInvoice ? ($lastInvoice->public_id + 1) : 1; + $invoice = new Invoice(); + $invoice->is_public = true; $invoice->account_id = $account->id; $invoice->user_id = $account->users()->first()->id; $invoice->public_id = $publicId; $invoice->client_id = $client->id; $invoice->invoice_number = $account->getNextInvoiceNumber($invoice); - $invoice->invoice_date = $clientAccount->getRenewalDate(); + $invoice->invoice_date = $renewalDate->format('Y-m-d'); $invoice->amount = $invoice->balance = $plan_cost - $credit; $invoice->invoice_type_id = INVOICE_TYPE_STANDARD; + + // check for promo/discount + $clientCompany = $clientAccount->company; + if ($clientCompany->hasActivePromo() || $clientCompany->hasActiveDiscount($renewalDate)) { + $discount = $invoice->amount * $clientCompany->discount; + $invoice->discount = $clientCompany->discount * 100; + $invoice->amount -= $discount; + $invoice->balance -= $discount; + } + $invoice->save(); if ($credit) { diff --git a/app/Ninja/Repositories/BaseRepository.php b/app/Ninja/Repositories/BaseRepository.php index 6b213a25e2ae5..71d8c1b9fbdcb 100644 --- a/app/Ninja/Repositories/BaseRepository.php +++ b/app/Ninja/Repositories/BaseRepository.php @@ -1,5 +1,8 @@ findByPublicIdsWithTrashed($ids); + + foreach ($entities as $entity) { + if (Auth::user()->can('edit', $entity)) { + $this->$action($entity); + } + } + + return count($entities); + } + /** * @param $ids * @return mixed @@ -113,4 +138,37 @@ class BaseRepository { return $this->getInstance()->scope($ids)->withTrashed()->get(); } + + protected function applyFilters($query, $entityType, $table = false) + { + $table = Utils::pluralizeEntityType($table ?: $entityType); + + if ($filter = session('entity_state_filter:' . $entityType, STATUS_ACTIVE)) { + $filters = explode(',', $filter); + $query->where(function ($query) use ($filters, $table) { + $query->whereNull($table . '.id'); + + if (in_array(STATUS_ACTIVE, $filters)) { + $query->orWhereNull($table . '.deleted_at'); + } + if (in_array(STATUS_ARCHIVED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table . '.deleted_at'); + + if ( ! in_array($table, ['users'])) { + $query->where($table . '.is_deleted', '=', 0); + } + }); + } + if (in_array(STATUS_DELETED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table . '.deleted_at') + ->where($table . '.is_deleted', '=', 1); + }); + } + }); + } + + return $query; + } } diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index b5bcd551968ef..a465a4767abd3 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -35,6 +35,7 @@ class ClientRepository extends BaseRepository ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), + DB::raw("CONCAT(contacts.first_name, ' ', contacts.last_name) contact"), 'clients.public_id', 'clients.name', 'contacts.first_name', @@ -42,6 +43,7 @@ class ClientRepository extends BaseRepository 'clients.balance', 'clients.last_login', 'clients.created_at', + 'clients.created_at as client_created_at', 'clients.work_phone', 'contacts.email', 'clients.deleted_at', @@ -49,9 +51,7 @@ class ClientRepository extends BaseRepository 'clients.user_id' ); - if (!\Session::get('show_trash:client')) { - $query->where('clients.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_CLIENT); if ($filter) { $query->where(function ($query) use ($filter) { diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php index e20be664eae53..f7f4637ca3a06 100644 --- a/app/Ninja/Repositories/CreditRepository.php +++ b/app/Ninja/Repositories/CreditRepository.php @@ -19,7 +19,6 @@ class CreditRepository extends BaseRepository ->join('clients', 'clients.id', '=', 'credits.client_id') ->join('contacts', 'contacts.client_id', '=', 'clients.id') ->where('clients.account_id', '=', \Auth::user()->account_id) - ->where('clients.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->select( @@ -43,11 +42,11 @@ class CreditRepository extends BaseRepository if ($clientPublicId) { $query->where('clients.public_id', '=', $clientPublicId); + } else { + $query->whereNull('clients.deleted_at'); } - if (!\Session::get('show_trash:credit')) { - $query->where('credits.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_CREDIT); if ($filter) { $query->where(function ($query) use ($filter) { @@ -95,9 +94,9 @@ class CreditRepository extends BaseRepository \Log::warning('Entity not set in credit repo save'); } else { $credit = Credit::createNew(); + $credit->client_id = Client::getPrivateId($input['client']); } - $credit->client_id = Client::getPrivateId($input['client']); $credit->credit_date = Utils::toSqlDate($input['credit_date']); $credit->amount = Utils::parseFloat($input['amount']); $credit->balance = Utils::parseFloat($input['amount']); diff --git a/app/Ninja/Repositories/DashboardRepository.php b/app/Ninja/Repositories/DashboardRepository.php index be43d1ce993f2..325ee77c56117 100644 --- a/app/Ninja/Repositories/DashboardRepository.php +++ b/app/Ninja/Repositories/DashboardRepository.php @@ -181,7 +181,7 @@ class DashboardRepository return $metrics->groupBy('accounts.id')->first(); } - public function paidToDate($account, $userId, $viewAll) + public function paidToDate($account, $userId, $viewAll, $startDate = false) { $accountId = $account->id; $select = DB::raw( @@ -202,7 +202,9 @@ class DashboardRepository $paidToDate->where('invoices.user_id', '=', $userId); } - if ($account->financial_year_start) { + if ($startDate) { + $paidToDate->where('payments.payment_date', '>=', $startDate); + } elseif ($account->financial_year_start) { $yearStart = str_replace('2000', date('Y'), $account->financial_year_start); $paidToDate->where('payments.payment_date', '>=', $yearStart); } diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index e0308a5ef9d65..2a950a7726e8c 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -190,6 +190,7 @@ class DocumentRepository extends BaseRepository ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_public', '=', true) // TODO: This needs to be a setting to also hide the activity on the dashboard page //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( diff --git a/app/Ninja/Repositories/ExpenseCategoryRepository.php b/app/Ninja/Repositories/ExpenseCategoryRepository.php index 9622a30ddbec3..a9224b4266a5b 100644 --- a/app/Ninja/Repositories/ExpenseCategoryRepository.php +++ b/app/Ninja/Repositories/ExpenseCategoryRepository.php @@ -25,12 +25,11 @@ class ExpenseCategoryRepository extends BaseRepository 'expense_categories.name as category', 'expense_categories.public_id', 'expense_categories.user_id', - 'expense_categories.deleted_at' + 'expense_categories.deleted_at', + 'expense_categories.is_deleted' ); - if (!\Session::get('show_trash:expense_category')) { - $query->where('expense_categories.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_EXPENSE_CATEGORY); if ($filter) { $query->where(function ($query) use ($filter) { diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index de36b38c87061..85bae1501adfb 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -54,12 +54,12 @@ class ExpenseRepository extends BaseRepository ->where('contacts.deleted_at', '=', null) ->where('vendors.deleted_at', '=', null) ->where('clients.deleted_at', '=', null) - ->where(function ($query) { + ->where(function ($query) { // handle when client isn't set $query->where('contacts.is_primary', '=', true) ->orWhere('contacts.is_primary', '=', null); }) ->select( - DB::raw('COALESCE(expenses.invoice_id, expenses.should_be_invoiced) expense_status_id'), + DB::raw('COALESCE(expenses.invoice_id, expenses.should_be_invoiced) status'), 'expenses.account_id', 'expenses.amount', 'expenses.deleted_at', @@ -94,10 +94,28 @@ class ExpenseRepository extends BaseRepository 'clients.country_id as client_country_id' ); - $showTrashed = \Session::get('show_trash:expense'); + $this->applyFilters($query, ENTITY_EXPENSE); - if (!$showTrashed) { - $query->where('expenses.deleted_at', '=', null); + if ($statuses = session('entity_status_filter:' . ENTITY_EXPENSE)) { + $statuses = explode(',', $statuses); + $query->where(function ($query) use ($statuses) { + $query->whereNull('expenses.id'); + + if (in_array(EXPENSE_STATUS_LOGGED, $statuses)) { + $query->orWhere('expenses.invoice_id', '=', 0) + ->orWhereNull('expenses.invoice_id'); + } + if (in_array(EXPENSE_STATUS_INVOICED, $statuses)) { + $query->orWhere('expenses.invoice_id', '>', 0); + if ( ! in_array(EXPENSE_STATUS_PAID, $statuses)) { + $query->where('invoices.balance', '>', 0); + } + } + if (in_array(EXPENSE_STATUS_PAID, $statuses)) { + $query->orWhere('invoices.balance', '=', 0) + ->where('expenses.invoice_id', '>', 0); + } + }); } if ($filter) { diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index d020fd6a4d950..afa5760e7d677 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -22,10 +22,11 @@ class InvoiceRepository extends BaseRepository return 'App\Models\Invoice'; } - public function __construct(PaymentService $paymentService, DocumentRepository $documentRepo) + public function __construct(PaymentService $paymentService, DocumentRepository $documentRepo, PaymentRepository $paymentRepo) { $this->documentRepo = $documentRepo; $this->paymentService = $paymentService; + $this->paymentRepo = $paymentRepo; } public function all() @@ -46,7 +47,6 @@ class InvoiceRepository extends BaseRepository ->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id') ->join('contacts', 'contacts.client_id', '=', 'clients.id') ->where('invoices.account_id', '=', $accountId) - ->where('clients.deleted_at', '=', null) ->where('contacts.deleted_at', '=', null) ->where('invoices.is_recurring', '=', false) ->where('contacts.is_primary', '=', true) @@ -57,13 +57,16 @@ class InvoiceRepository extends BaseRepository 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'invoice_number', + 'invoice_number as quote_number', 'invoice_status_id', DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"), 'invoices.public_id', 'invoices.amount', 'invoices.balance', - 'invoices.invoice_date', + 'invoices.invoice_date as date', 'invoices.due_date', + 'invoices.due_date as valid_until', + 'invoice_statuses.name as status', 'invoice_statuses.name as invoice_status_name', 'contacts.first_name', 'contacts.last_name', @@ -73,25 +76,43 @@ class InvoiceRepository extends BaseRepository 'invoices.deleted_at', 'invoices.is_deleted', 'invoices.partial', - 'invoices.user_id' + 'invoices.user_id', + 'invoices.is_public' ); - if (!\Session::get('show_trash:'.$entityType)) { - $query->where('invoices.deleted_at', '=', null); + $this->applyFilters($query, $entityType, ENTITY_INVOICE); + + if ($statuses = session('entity_status_filter:' . $entityType)) { + $statuses = explode(',', $statuses); + $query->where(function ($query) use ($statuses) { + foreach ($statuses as $status) { + if (in_array($status, \App\Models\EntityModel::$statuses)) { + continue; + } + $query->orWhere('invoice_status_id', '=', $status); + } + if (in_array(INVOICE_STATUS_OVERDUE, $statuses)) { + $query->orWhere(function ($query) use ($statuses) { + $query->where('invoices.balance', '>', 0) + ->where('invoices.due_date', '<', date('Y-m-d')); + }); + } + }); } if ($clientPublicId) { $query->where('clients.public_id', '=', $clientPublicId); + } else { + $query->whereNull('clients.deleted_at'); } if ($filter) { $query->where(function ($query) use ($filter) { $query->where('clients.name', 'like', '%'.$filter.'%') ->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%') - ->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%') - ->orWhere('contacts.first_name', 'like', '%'.$filter.'%') - ->orWhere('contacts.last_name', 'like', '%'.$filter.'%') - ->orWhere('contacts.email', 'like', '%'.$filter.'%'); + ->orWhere('contacts.first_name', 'like', '%'.$filter.'%') + ->orWhere('contacts.last_name', 'like', '%'.$filter.'%') + ->orWhere('contacts.email', 'like', '%'.$filter.'%'); }); } @@ -110,7 +131,6 @@ class InvoiceRepository extends BaseRepository ->where('contacts.deleted_at', '=', null) ->where('invoices.is_recurring', '=', true) ->where('contacts.is_primary', '=', true) - ->where('clients.deleted_at', '=', null) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), @@ -131,11 +151,11 @@ class InvoiceRepository extends BaseRepository if ($clientPublicId) { $query->where('clients.public_id', '=', $clientPublicId); + } else { + $query->whereNull('clients.deleted_at'); } - if (!\Session::get('show_trash:recurring_invoice')) { - $query->where('invoices.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_RECURRING_INVOICE, ENTITY_INVOICE); if ($filter) { $query->where(function ($query) use ($filter) { @@ -160,6 +180,7 @@ class InvoiceRepository extends BaseRepository ->where('invoices.is_deleted', '=', false) ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', true) + ->where('invoices.is_public', '=', true) ->whereIn('invoices.auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT]) //->where('invoices.start_date', '>=', date('Y-m-d H:i:s')) ->select( @@ -209,6 +230,7 @@ class InvoiceRepository extends BaseRepository ->where('contacts.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_public', '=', true) // This needs to be a setting to also hide the activity on the dashboard page //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( @@ -290,6 +312,14 @@ class InvoiceRepository extends BaseRepository return $invoice; } + // set default to true for backwards compatability + if ( ! isset($data['is_public']) || filter_var($data['is_public'], FILTER_VALIDATE_BOOLEAN)) { + $invoice->is_public = true; + if ( ! $invoice->isSent()) { + $invoice->invoice_status_id = INVOICE_STATUS_SENT; + } + } + $invoice->fill($data); if ((isset($data['set_default_terms']) && $data['set_default_terms']) @@ -654,7 +684,8 @@ class InvoiceRepository extends BaseRepository 'custom_taxes2', 'partial', 'custom_text_value1', - 'custom_text_value2', ] as $field) { + 'custom_text_value2', + ] as $field) { $clone->$field = $invoice->$field; } @@ -664,6 +695,10 @@ class InvoiceRepository extends BaseRepository if ($account->invoice_terms) { $clone->terms = $account->invoice_terms; } + if ($account->auto_convert_quote) { + $clone->is_public = true; + $clone->invoice_status_id = INVOICE_STATUS_SENT; + } } $clone->save(); @@ -713,9 +748,34 @@ class InvoiceRepository extends BaseRepository */ public function markSent(Invoice $invoice) { + if ( ! $invoice->isSent()) { + $invoice->invoice_status_id = INVOICE_STATUS_SENT; + } + + $invoice->is_public = true; + $invoice->save(); + $invoice->markInvitationsSent(); } + /** + * @param Invoice $invoice + */ + public function markPaid(Invoice $invoice) + { + if (floatval($invoice->balance) <= 0) { + return; + } + + $data = [ + 'client_id' => $invoice->client_id, + 'invoice_id' => $invoice->id, + 'amount' => $invoice->balance, + ]; + + return $this->paymentRepo->save($data); + } + /** * @param $invitationKey * @return Invitation|bool @@ -730,7 +790,7 @@ class InvoiceRepository extends BaseRepository } $invoice = $invitation->invoice; - if (!$invoice || $invoice->is_deleted) { + if (!$invoice || $invoice->is_deleted || ! $invoice->is_public) { return false; } @@ -788,6 +848,7 @@ class InvoiceRepository extends BaseRepository } $invoice = Invoice::createNew($recurInvoice); + $invoice->is_public = true; $invoice->invoice_type_id = INVOICE_TYPE_STANDARD; $invoice->client_id = $recurInvoice->client_id; $invoice->recurring_invoice_id = $recurInvoice->id; diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 15abd2b1d8d1c..0c99521368c9e 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -25,7 +25,6 @@ class PaymentRepository extends BaseRepository ->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id') ->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id') ->where('payments.account_id', '=', \Auth::user()->account_id) - ->where('clients.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->where('contacts.deleted_at', '=', null) ->where('invoices.is_deleted', '=', false) @@ -40,12 +39,15 @@ class PaymentRepository extends BaseRepository 'payments.payment_date', 'payments.payment_status_id', 'payments.payment_type_id', + 'payments.payment_type_id as source', 'invoices.public_id as invoice_public_id', 'invoices.user_id as invoice_user_id', 'invoices.invoice_number', + 'invoices.invoice_number as invoice_name', 'contacts.first_name', 'contacts.last_name', 'contacts.email', + 'payment_types.name as method', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', @@ -63,12 +65,12 @@ class PaymentRepository extends BaseRepository 'payment_statuses.name as payment_status_name' ); - if (!\Session::get('show_trash:payment')) { - $query->where('payments.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_PAYMENT); if ($clientPublicId) { $query->where('clients.public_id', '=', $clientPublicId); + } else { + $query->whereNull('clients.deleted_at'); } if ($filter) { @@ -104,6 +106,7 @@ class PaymentRepository extends BaseRepository ->where('payments.is_deleted', '=', false) ->where('invitations.deleted_at', '=', null) ->where('invoices.is_deleted', '=', false) + ->where('invoices.is_public', '=', true) ->where('invitations.contact_id', '=', $contactId) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), diff --git a/app/Ninja/Repositories/ProductRepository.php b/app/Ninja/Repositories/ProductRepository.php index 7371581df207e..6500124c513af 100644 --- a/app/Ninja/Repositories/ProductRepository.php +++ b/app/Ninja/Repositories/ProductRepository.php @@ -32,7 +32,8 @@ class ProductRepository extends BaseRepository 'products.cost', 'tax_rates.name as tax_name', 'tax_rates.rate as tax_rate', - 'products.deleted_at' + 'products.deleted_at', + 'products.is_deleted' ); if ($filter) { @@ -42,9 +43,7 @@ class ProductRepository extends BaseRepository }); } - if (!\Session::get('show_trash:product')) { - $query->where('products.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_PRODUCT); return $query; } diff --git a/app/Ninja/Repositories/ProjectRepository.php b/app/Ninja/Repositories/ProjectRepository.php new file mode 100644 index 0000000000000..64ba210ba3a91 --- /dev/null +++ b/app/Ninja/Repositories/ProjectRepository.php @@ -0,0 +1,76 @@ +get(); + } + + public function find($filter = null, $userId = false) + { + $query = DB::table('projects') + ->where('projects.account_id', '=', Auth::user()->account_id) + ->leftjoin('clients', 'clients.id', '=', 'projects.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->where('contacts.deleted_at', '=', null) + ->where('clients.deleted_at', '=', null) + ->where(function ($query) { // handle when client isn't set + $query->where('contacts.is_primary', '=', true) + ->orWhere('contacts.is_primary', '=', null); + }) + ->select( + 'projects.name as project', + 'projects.public_id', + 'projects.user_id', + 'projects.deleted_at', + 'projects.is_deleted', + DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"), + 'clients.user_id as client_user_id', + 'clients.public_id as client_public_id' + ); + + $this->applyFilters($query, ENTITY_PROJECT); + + if ($filter) { + $query->where(function ($query) use ($filter) { + $query->where('clients.name', 'like', '%'.$filter.'%') + ->orWhere('contacts.first_name', 'like', '%'.$filter.'%') + ->orWhere('contacts.last_name', 'like', '%'.$filter.'%') + ->orWhere('contacts.email', 'like', '%'.$filter.'%') + ->orWhere('projects.name', 'like', '%'.$filter.'%'); + }); + } + + if ($userId) { + $query->where('projects.user_id', '=', $userId); + } + + return $query; + } + + public function save($input, $project = false) + { + $publicId = isset($data['public_id']) ? $data['public_id'] : false; + + if ( ! $project) { + $project = Project::createNew(); + $project['client_id'] = $input['client_id']; + } + + $project->fill($input); + $project->save(); + + return $project; + } +} diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 879cc5c1061f9..579ef71e684ce 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -3,6 +3,7 @@ use Auth; use Session; use App\Models\Client; +use App\Models\Project; use App\Models\Task; class TaskRepository extends BaseRepository @@ -18,13 +19,13 @@ class TaskRepository extends BaseRepository ->leftJoin('clients', 'tasks.client_id', '=', 'clients.id') ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') ->leftJoin('invoices', 'invoices.id', '=', 'tasks.invoice_id') + ->leftJoin('projects', 'projects.id', '=', 'tasks.project_id') ->where('tasks.account_id', '=', Auth::user()->account_id) - ->where(function ($query) { + ->where(function ($query) { // handle when client isn't set $query->where('contacts.is_primary', '=', true) ->orWhere('contacts.is_primary', '=', null); }) ->where('contacts.deleted_at', '=', null) - ->where('clients.deleted_at', '=', null) ->select( 'tasks.public_id', \DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"), @@ -38,21 +39,49 @@ class TaskRepository extends BaseRepository 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', + 'invoices.invoice_number as status', 'invoices.public_id as invoice_public_id', 'invoices.user_id as invoice_user_id', 'invoices.balance', 'tasks.is_running', 'tasks.time_log', + 'tasks.time_log as duration', 'tasks.created_at', - 'tasks.user_id' + 'tasks.created_at as date', + 'tasks.user_id', + 'projects.name as project', + 'projects.public_id as project_public_id', + 'projects.user_id as project_user_id' ); if ($clientPublicId) { $query->where('clients.public_id', '=', $clientPublicId); + } else { + $query->whereNull('clients.deleted_at'); } - if (!Session::get('show_trash:task')) { - $query->where('tasks.deleted_at', '=', null); + $this->applyFilters($query, ENTITY_TASK); + + if ($statuses = session('entity_status_filter:' . ENTITY_TASK)) { + $statuses = explode(',', $statuses); + $query->where(function ($query) use ($statuses) { + if (in_array(TASK_STATUS_LOGGED, $statuses)) { + $query->orWhere('tasks.invoice_id', '=', 0) + ->orWhereNull('tasks.invoice_id'); + } + if (in_array(TASK_STATUS_RUNNING, $statuses)) { + $query->orWhere('tasks.is_running', '=', 1); + } + if (in_array(TASK_STATUS_INVOICED, $statuses)) { + $query->orWhere('tasks.invoice_id', '>', 0); + if ( ! in_array(TASK_STATUS_PAID, $statuses)) { + $query->where('invoices.balance', '>', 0); + } + } + if (in_array(TASK_STATUS_PAID, $statuses)) { + $query->orWhere('invoices.balance', '=', 0); + } + }); } if ($filter) { @@ -60,7 +89,9 @@ class TaskRepository extends BaseRepository $query->where('clients.name', 'like', '%'.$filter.'%') ->orWhere('contacts.first_name', 'like', '%'.$filter.'%') ->orWhere('contacts.last_name', 'like', '%'.$filter.'%') - ->orWhere('tasks.description', 'like', '%'.$filter.'%'); + ->orWhere('tasks.description', 'like', '%'.$filter.'%') + ->orWhere('contacts.email', 'like', '%'.$filter.'%') + ->orWhere('projects.name', 'like', '%'.$filter.'%'); }); } @@ -81,9 +112,13 @@ class TaskRepository extends BaseRepository return $task; } - if (isset($data['client']) && $data['client']) { - $task->client_id = Client::getPrivateId($data['client']); + if (isset($data['client'])) { + $task->client_id = $data['client'] ? Client::getPrivateId($data['client']) : null; } + if (isset($data['project_id'])) { + $task->project_id = $data['project_id'] ? Project::getPrivateId($data['project_id']) : null; + } + if (isset($data['description'])) { $task->description = trim($data['description']); } diff --git a/app/Ninja/Repositories/TokenRepository.php b/app/Ninja/Repositories/TokenRepository.php index 16fa02f800f8b..5d3fc6bf26c1d 100644 --- a/app/Ninja/Repositories/TokenRepository.php +++ b/app/Ninja/Repositories/TokenRepository.php @@ -14,11 +14,8 @@ class TokenRepository extends BaseRepository public function find($userId) { $query = DB::table('account_tokens') - ->where('account_tokens.user_id', '=', $userId); - - if (!Session::get('show_trash:token')) { - $query->where('account_tokens.deleted_at', '=', null); - } + ->where('account_tokens.user_id', '=', $userId) + ->whereNull('account_tokens.deleted_at');; return $query->select('account_tokens.public_id', 'account_tokens.name', 'account_tokens.token', 'account_tokens.public_id', 'account_tokens.deleted_at'); } diff --git a/app/Ninja/Repositories/UserRepository.php b/app/Ninja/Repositories/UserRepository.php index 771dfb8cdebe3..0d634aaed99ba 100644 --- a/app/Ninja/Repositories/UserRepository.php +++ b/app/Ninja/Repositories/UserRepository.php @@ -16,9 +16,7 @@ class UserRepository extends BaseRepository $query = DB::table('users') ->where('users.account_id', '=', $accountId); - if (!Session::get('show_trash:user')) { - $query->where('users.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_USER); $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at', 'users.is_admin', 'users.permissions'); diff --git a/app/Ninja/Repositories/VendorRepository.php b/app/Ninja/Repositories/VendorRepository.php index 43500c0f1ae2b..b64716fe7b636 100644 --- a/app/Ninja/Repositories/VendorRepository.php +++ b/app/Ninja/Repositories/VendorRepository.php @@ -37,6 +37,7 @@ class VendorRepository extends BaseRepository 'vendor_contacts.first_name', 'vendor_contacts.last_name', 'vendors.created_at', + 'vendors.created_at as date', 'vendors.work_phone', 'vendors.city', 'vendor_contacts.email', @@ -45,9 +46,7 @@ class VendorRepository extends BaseRepository 'vendors.user_id' ); - if (!\Session::get('show_trash:vendor')) { - $query->where('vendors.deleted_at', '=', null); - } + $this->applyFilters($query, ENTITY_VENDOR); if ($filter) { $query->where(function ($query) use ($filter) { diff --git a/app/Ninja/Transformers/AccountTransformer.php b/app/Ninja/Transformers/AccountTransformer.php index fce83760aee00..4ddbf8f5831bd 100644 --- a/app/Ninja/Transformers/AccountTransformer.php +++ b/app/Ninja/Transformers/AccountTransformer.php @@ -14,7 +14,8 @@ class AccountTransformer extends EntityTransformer 'users', 'products', 'tax_rates', - 'expense_categories' + 'expense_categories', + 'projects', ]; /** @@ -36,6 +37,16 @@ class AccountTransformer extends EntityTransformer return $this->includeCollection($account->expense_categories, $transformer, 'expense_categories'); } + /** + * @param Account $account + * @return \League\Fractal\Resource\Collection + */ + public function includeProjects(Account $account) + { + $transformer = new ProjectTransformer($account, $this->serializer); + return $this->includeCollection($account->projects, $transformer, 'projects'); + } + /** * @param Account $account * @return \League\Fractal\Resource\Collection diff --git a/app/Ninja/Transformers/InvoiceItemTransformer.php b/app/Ninja/Transformers/InvoiceItemTransformer.php index b6c74cddfb454..92aaf04048eff 100644 --- a/app/Ninja/Transformers/InvoiceItemTransformer.php +++ b/app/Ninja/Transformers/InvoiceItemTransformer.php @@ -11,7 +11,6 @@ class InvoiceItemTransformer extends EntityTransformer 'product_key' => $item->product_key, 'updated_at' => $this->getTimestamp($item->updated_at), 'archived_at' => $this->getTimestamp($item->deleted_at), - 'product_key' => $item->product_key, 'notes' => $item->notes, 'cost' => (float) $item->cost, 'qty' => (float) $item->qty, diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index 62b997c15ebaa..7887f74ef74dc 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -119,6 +119,7 @@ class InvoiceTransformer extends EntityTransformer 'custom_text_value1' => $invoice->custom_text_value1, 'custom_text_value2' => $invoice->custom_text_value2, 'is_quote' => (bool) $invoice->isType(INVOICE_TYPE_QUOTE), // Temp to support mobile app + 'is_public' => (bool) $invoice->is_public, ]); } } diff --git a/app/Ninja/Transformers/ProjectTransformer.php b/app/Ninja/Transformers/ProjectTransformer.php new file mode 100644 index 0000000000000..1de02e3b4ee8e --- /dev/null +++ b/app/Ninja/Transformers/ProjectTransformer.php @@ -0,0 +1,18 @@ +getDefaults($project), [ + 'id' => (int) $project->public_id, + 'name' => $project->name, + 'client_id' => $project->client ? (int) $project->client->public_id : null, + 'updated_at' => $this->getTimestamp($project->updated_at), + 'archived_at' => $this->getTimestamp($project->deleted_at), + 'is_deleted' => (bool) $project->is_deleted, + ]); + } +} diff --git a/app/Ninja/Transformers/QuoteTransformer.php b/app/Ninja/Transformers/QuoteTransformer.php deleted file mode 100644 index 7443230449eb9..0000000000000 --- a/app/Ninja/Transformers/QuoteTransformer.php +++ /dev/null @@ -1,26 +0,0 @@ -account, $this->serializer); - return $this->includeCollection($invoice->invoice_items, $transformer, 'invoice_items'); - } - - public function transform(Invoice $invoice) - { - return [ - 'id' => (int) $invoice->public_id, - 'quote_number' => $invoice->invoice_number, - 'amount' => (float) $invoice->amount, - 'quote_terms' => $invoice->terms, - ]; - } -} \ No newline at end of file diff --git a/app/Ninja/Transformers/TaskTransformer.php b/app/Ninja/Transformers/TaskTransformer.php index 86dcea0686774..24acd0d977e63 100644 --- a/app/Ninja/Transformers/TaskTransformer.php +++ b/app/Ninja/Transformers/TaskTransformer.php @@ -41,7 +41,15 @@ class TaskTransformer extends EntityTransformer return array_merge($this->getDefaults($task), [ 'id' => (int) $task->public_id, 'description' => $task->description, - 'duration' => $task->getDuration() + 'duration' => $task->getDuration(), + 'updated_at' => (int) $this->getTimestamp($task->updated_at), + 'archived_at' => (int) $this->getTimestamp($task->deleted_at), + 'invoice_id' => $task->invoice ? (int) $task->invoice->public_id : false, + 'client_id' => $task->client ? (int) $task->client->public_id : false, + 'project_id' => $task->project ? (int) $task->project->public_id : false, + 'is_deleted' => (bool) $task->is_deleted, + 'time_log' => $task->time_log, + 'is_running' => (bool) $task->is_running, ]); } -} \ No newline at end of file +} diff --git a/app/Policies/EntityPolicy.php b/app/Policies/EntityPolicy.php index 9a5216ec87c81..e2fa9af8e6de4 100644 --- a/app/Policies/EntityPolicy.php +++ b/app/Policies/EntityPolicy.php @@ -73,6 +73,7 @@ class EntityPolicy private static function checkModuleEnabled(User $user, $item) { $entityType = is_string($item) ? $item : $item->getEntityType(); + return $user->account->isModuleEnabled($entityType); } } diff --git a/app/Policies/GenericEntityPolicy.php b/app/Policies/GenericEntityPolicy.php index fc18c7c550c93..e2afc039e45cf 100644 --- a/app/Policies/GenericEntityPolicy.php +++ b/app/Policies/GenericEntityPolicy.php @@ -3,8 +3,9 @@ namespace App\Policies; -use App\Models\User; use Utils; +use App\Models\User; +use Illuminate\Support\Str; use Illuminate\Auth\Access\HandlesAuthorization; /** @@ -16,14 +17,14 @@ class GenericEntityPolicy /** * @param User $user - * @param $itemType + * @param $entityType * @param $ownerUserId * @return bool|mixed */ - public static function editByOwner(User $user, $itemType, $ownerUserId) { - $itemType = Utils::getEntityName($itemType); - if (method_exists("App\\Policies\\{$itemType}Policy", 'editByOwner')) { - return call_user_func(["App\\Policies\\{$itemType}Policy", 'editByOwner'], $user, $ownerUserId); + public static function editByOwner(User $user, $entityType, $ownerUserId) { + $className = static::className($entityType); + if (method_exists($className, 'editByOwner')) { + return call_user_func([$className, 'editByOwner'], $user, $ownerUserId); } return false; @@ -31,14 +32,14 @@ class GenericEntityPolicy /** * @param User $user - * @param $itemType + * @param $entityTypee * @param $ownerUserId * @return bool|mixed */ - public static function viewByOwner(User $user, $itemType, $ownerUserId) { - $itemType = Utils::getEntityName($itemType); - if (method_exists("App\\Policies\\{$itemType}Policy", 'viewByOwner')) { - return call_user_func(["App\\Policies\\{$itemType}Policy", 'viewByOwner'], $user, $ownerUserId); + public static function viewByOwner(User $user, $entityType, $ownerUserId) { + $className = static::className($entityType); + if (method_exists($className, 'viewByOwner')) { + return call_user_func([$className, 'viewByOwner'], $user, $ownerUserId); } return false; @@ -46,13 +47,13 @@ class GenericEntityPolicy /** * @param User $user - * @param $itemType + * @param $entityType * @return bool|mixed */ - public static function create(User $user, $itemType) { - $entityName = Utils::getEntityName($itemType); - if (method_exists("App\\Policies\\{$entityName}Policy", 'create')) { - return call_user_func(["App\\Policies\\{$entityName}Policy", 'create'], $user, $itemType); + public static function create(User $user, $entityType) { + $className = static::className($entityType); + if (method_exists($className, 'create')) { + return call_user_func([$className, 'create'], $user, $entityType); } return false; @@ -60,16 +61,28 @@ class GenericEntityPolicy /** * @param User $user - * @param $itemType + * @param $entityType * @return bool|mixed */ - public static function view(User $user, $itemType) { - $entityName = Utils::getEntityName($itemType); - if (method_exists("App\\Policies\\{$entityName}Policy", 'view')) { - return call_user_func(["App\\Policies\\{$entityName}Policy", 'view'], $user, $itemType); + public static function view(User $user, $entityType) { + $className = static::className($entityType); + if (method_exists($className, 'view')) { + return call_user_func([$className, 'view'], $user, $entityType); } return false; } + private static function className($entityType) + { + if ( ! Utils::isNinjaProd()) { + if ($module = \Module::find($entityType)) { + return "Modules\\{$module->getName()}\\Policies\\{$module->getName()}Policy"; + } + } + + $studly = Str::studly($entityType); + return "App\\Policies\\{$studly}Policy"; + } + } diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php new file mode 100644 index 0000000000000..a88d3a37902a7 --- /dev/null +++ b/app/Policies/ProjectPolicy.php @@ -0,0 +1,10 @@ +$name"; } else { diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index b395cca2c668e..05a9ec98b8ef2 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -28,6 +28,7 @@ class AuthServiceProvider extends ServiceProvider \App\Models\AccountToken::class => \App\Policies\TokenPolicy::class, \App\Models\BankAccount::class => \App\Policies\BankAccountPolicy::class, \App\Models\PaymentTerm::class => \App\Policies\PaymentTermPolicy::class, + \App\Models\Project::class => \App\Policies\ProjectPolicy::class, ]; /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 0561a1a1f629d..5d0be86d36bc0 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -112,7 +112,6 @@ class EventServiceProvider extends ServiceProvider { 'App\Events\PaymentWasRefunded' => [ 'App\Listeners\ActivityListener@refundedPayment', 'App\Listeners\InvoiceListener@refundedPayment', - 'App\Listeners\CreditListener@refundedPayment', ], 'App\Events\PaymentWasVoided' => [ 'App\Listeners\ActivityListener@voidedPayment', diff --git a/app/Services/CreditService.php b/app/Services/CreditService.php index b355d5f78ccc9..5b83b3640cf47 100644 --- a/app/Services/CreditService.php +++ b/app/Services/CreditService.php @@ -44,9 +44,9 @@ class CreditService extends BaseService * @param $data * @return mixed|null */ - public function save($data) + public function save($data, $credit = null) { - return $this->creditRepo->save($data); + return $this->creditRepo->save($data, $credit); } /** @@ -57,7 +57,7 @@ class CreditService extends BaseService public function getDatatable($clientPublicId, $search) { // we don't support bulk edit and hide the client on the individual client page - $datatable = new CreditDatatable( ! $clientPublicId, $clientPublicId); + $datatable = new CreditDatatable(true, $clientPublicId); $query = $this->creditRepo->find($clientPublicId, $search); if(!Utils::hasPermission('view_all')){ diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 82223ffc324fb..b9ddc20c5297c 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -116,17 +116,17 @@ class DatatableService } if (($datatable->entityType != ENTITY_USER || $model->public_id) && $can_edit) { - $dropdown_contents .= "
  • public_id})\">" - . trans("texts.archive_{$datatable->entityType}") . '
  • '; + $dropdown_contents .= "
  • entityType}('archive', {$model->public_id})\">" + . mtrans($datatable->entityType, "archive_{$datatable->entityType}") . '
  • '; } } else if($can_edit) { - $dropdown_contents .= "
  • public_id})\">" - . trans("texts.restore_{$datatable->entityType}") . '
  • '; + $dropdown_contents .= "
  • entityType}('restore', {$model->public_id})\">" + . mtrans($datatable->entityType, "restore_{$datatable->entityType}") . '
  • '; } if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) { - $dropdown_contents .= "
  • public_id})\">" - . trans("texts.delete_{$datatable->entityType}") . '
  • '; + $dropdown_contents .= "
  • entityType}('delete', {$model->public_id})\">" + . mtrans($datatable->entityType, "delete_{$datatable->entityType}") . '
  • '; } if (!empty($dropdown_contents)) { diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index 040f7a4057054..c07e3f29e0495 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -82,7 +82,7 @@ class ExpenseService extends BaseService */ public function getDatatableVendor($vendorPublicId) { - $datatable = new ExpenseDatatable(false, true); + $datatable = new ExpenseDatatable(true, true); $query = $this->expenseRepo->findVendor($vendorPublicId); diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index 82bbfe35d6629..ff0ec559f5d9f 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -110,6 +110,10 @@ class InvoiceService extends BaseService } } + if ($invoice->is_public && ! $invoice->areInvitationsSent()) { + $invoice->markInvitationsSent(); + } + return $invoice; } @@ -155,7 +159,7 @@ class InvoiceService extends BaseService public function getDatatable($accountId, $clientPublicId = null, $entityType, $search) { - $datatable = new InvoiceDatatable( ! $clientPublicId, $clientPublicId); + $datatable = new InvoiceDatatable(true, $clientPublicId); $datatable->entityType = $entityType; $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index c1a7c36f04bb0..68c696695e49c 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -125,7 +125,7 @@ class PaymentService extends BaseService public function getDatatable($clientPublicId, $search) { - $datatable = new PaymentDatatable( ! $clientPublicId, $clientPublicId); + $datatable = new PaymentDatatable(true, $clientPublicId); $query = $this->paymentRepo->find($clientPublicId, $search); if(!Utils::hasPermission('view_all')){ diff --git a/app/Services/ProjectService.php b/app/Services/ProjectService.php new file mode 100644 index 0000000000000..0e479475bddc2 --- /dev/null +++ b/app/Services/ProjectService.php @@ -0,0 +1,71 @@ +projectRepo = $projectRepo; + $this->datatableService = $datatableService; + } + + /** + * @return CreditRepository + */ + protected function getRepo() + { + return $this->projectRepo; + } + + /** + * @param $data + * @return mixed|null + */ + public function save($data, $project = false) + { + if (isset($data['client_id']) && $data['client_id']) { + $data['client_id'] = Client::getPrivateId($data['client_id']); + } + + return $this->projectRepo->save($data, $project); + } + + /** + * @param $clientPublicId + * @param $search + * @return \Illuminate\Http\JsonResponse + */ + public function getDatatable($search, $userId) + { + // we don't support bulk edit and hide the client on the individual client page + $datatable = new ProjectDatatable(); + + $query = $this->projectRepo->find($search, $userId); + + return $this->datatableService->createDatatable($datatable, $query); + } +} diff --git a/app/Services/RecurringInvoiceService.php b/app/Services/RecurringInvoiceService.php index 659870f60bf45..9c90b37b7a83b 100644 --- a/app/Services/RecurringInvoiceService.php +++ b/app/Services/RecurringInvoiceService.php @@ -18,7 +18,7 @@ class RecurringInvoiceService extends BaseService public function getDatatable($accountId, $clientPublicId = null, $entityType, $search) { - $datatable = new RecurringInvoiceDatatable( ! $clientPublicId, $clientPublicId); + $datatable = new RecurringInvoiceDatatable(true, $clientPublicId); $query = $this->invoiceRepo->getRecurringInvoices($accountId, $clientPublicId, $search); if(!Utils::hasPermission('view_all')){ diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index 812848ac1b392..a0b8c67911512 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -40,7 +40,7 @@ class TaskService extends BaseService */ public function getDatatable($clientPublicId, $search) { - $datatable = new TaskDatatable( ! $clientPublicId, $clientPublicId); + $datatable = new TaskDatatable(true, $clientPublicId); $query = $this->taskRepo->find($clientPublicId, $search); if(!Utils::hasPermission('view_all')){ diff --git a/bower.json b/bower.json index 24abc56e68b99..7c474751dc9b5 100644 --- a/bower.json +++ b/bower.json @@ -32,7 +32,9 @@ "nouislider": "~8.5.1", "bootstrap-daterangepicker": "~2.1.24", "sweetalert2": "^5.3.8", - "jSignature": "brinley/jSignature#^2.1.0" + "jSignature": "brinley/jSignature#^2.1.0", + "select2": "select2-dist#^4.0.3", + "mousetrap": "^1.6.0" }, "resolutions": { "jquery": "~1.11" diff --git a/composer.json b/composer.json index 15ff96fa34a5c..9bb37aa663157 100644 --- a/composer.json +++ b/composer.json @@ -83,7 +83,8 @@ "fzaninotto/faker": "^1.5", "jaybizzle/laravel-crawler-detect": "1.*", "codedge/laravel-selfupdater": "5.x-dev", - "predis/predis": "^1.1" + "predis/predis": "^1.1", + "nwidart/laravel-modules": "^1.14" }, "require-dev": { "phpunit/phpunit": "~4.0", @@ -103,7 +104,8 @@ "database" ], "psr-4": { - "App\\": "app/" + "App\\": "app/", + "Modules\\": "Modules/" }, "files": [ "app/Libraries/lib_autolink.php", diff --git a/composer.lock b/composer.lock index 7cf67b3039b6f..2e870d6d0182c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "cf642e3384eec7504bcdace749d2bb88", - "content-hash": "c0a5b571bc2305c4b0d9eae18bf5011b", + "hash": "5268e0c573a4094ea670e2eb7555b962", + "content-hash": "cf263546eb8ba91f81f26bdc5470573e", "packages": [ { "name": "agmscode/omnipay-agms", @@ -859,12 +859,12 @@ "version": "v2.0.4", "source": { "type": "git", - "url": "https://github.com/coatesap/omnipay-datacash.git", + "url": "https://github.com/digitickets/omnipay-datacash.git", "reference": "77915db87635c514576550dd15355987cadefc78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/coatesap/omnipay-datacash/zipball/77915db87635c514576550dd15355987cadefc78", + "url": "https://api.github.com/repos/digitickets/omnipay-datacash/zipball/77915db87635c514576550dd15355987cadefc78", "reference": "77915db87635c514576550dd15355987cadefc78", "shasum": "" }, @@ -909,6 +909,7 @@ "pay", "payment" ], + "abandoned": "digitickets/omnipay-datacash", "time": "2015-01-08 11:19:06" }, { @@ -916,12 +917,12 @@ "version": "v2.0.0", "source": { "type": "git", - "url": "https://github.com/coatesap/omnipay-paymentsense.git", + "url": "https://github.com/digitickets/omnipay-paymentsense.git", "reference": "4a5a87ef140abf8e09ff27cd0e6502ac1e79e434" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/coatesap/omnipay-paymentsense/zipball/4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", + "url": "https://api.github.com/repos/digitickets/omnipay-paymentsense/zipball/4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", "reference": "4a5a87ef140abf8e09ff27cd0e6502ac1e79e434", "shasum": "" }, @@ -967,6 +968,7 @@ "payment sense", "paymentsense" ], + "abandoned": "digitickets/omnipay-paymentsense", "time": "2014-03-18 17:17:57" }, { @@ -974,12 +976,12 @@ "version": "v2.1.3", "source": { "type": "git", - "url": "https://github.com/coatesap/omnipay-realex.git", + "url": "https://github.com/digitickets/omnipay-realex.git", "reference": "9a751a66c7fdd610a7c41493a57587d59944bbfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/coatesap/omnipay-realex/zipball/9a751a66c7fdd610a7c41493a57587d59944bbfa", + "url": "https://api.github.com/repos/digitickets/omnipay-realex/zipball/9a751a66c7fdd610a7c41493a57587d59944bbfa", "reference": "9a751a66c7fdd610a7c41493a57587d59944bbfa", "shasum": "" }, @@ -1021,6 +1023,7 @@ "purchase", "realex" ], + "abandoned": "digitickets/omnipay-realex", "time": "2015-01-08 10:05:58" }, { @@ -1085,12 +1088,12 @@ "source": { "type": "git", "url": "https://github.com/hillelcoren/omnipay-wepay.git", - "reference": "916785146c5433e9216f295d09d1cbcec2fdf33a" + "reference": "f8bb3e4010c236018e4bd936f1b930b87f17e063" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hillelcoren/omnipay-wepay/zipball/916785146c5433e9216f295d09d1cbcec2fdf33a", - "reference": "916785146c5433e9216f295d09d1cbcec2fdf33a", + "url": "https://api.github.com/repos/hillelcoren/omnipay-wepay/zipball/f8bb3e4010c236018e4bd936f1b930b87f17e063", + "reference": "f8bb3e4010c236018e4bd936f1b930b87f17e063", "shasum": "" }, "require": { @@ -1122,7 +1125,7 @@ "support": { "source": "https://github.com/hillelcoren/omnipay-wepay/tree/address-fix" }, - "time": "2016-11-01 10:54:54" + "time": "2016-12-12 18:28:29" }, { "name": "container-interop/container-interop", @@ -4431,6 +4434,60 @@ ], "time": "2016-04-19 13:41:41" }, + { + "name": "nwidart/laravel-modules", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/nWidart/laravel-modules.git", + "reference": "2d05b9c5ac23cb800725ba760d9a323820603d9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/2d05b9c5ac23cb800725ba760d9a323820603d9f", + "reference": "2d05b9c5ac23cb800725ba760d9a323820603d9f", + "shasum": "" + }, + "require": { + "laravel/framework": "5.1.*|5.2.*|5.3.*", + "laravelcollective/html": "5.1.*|5.2.*|5.3.*", + "php": ">=5.5" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.11", + "mockery/mockery": "~0.9", + "orchestra/testbench": "^3.1|^3.2|^3.3", + "phpro/grumphp": "^0.9.1", + "phpunit/phpunit": "~4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nwidart\\Modules\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Widart", + "email": "n.widart@gmail.com", + "homepage": "https://nicolaswidart.com", + "role": "Developer" + } + ], + "description": "Laravel Module management", + "keywords": [ + "laravel", + "module", + "modules", + "nwidart", + "rad" + ], + "time": "2016-10-19 09:27:32" + }, { "name": "omnipay/2checkout", "version": "dev-master", @@ -6714,6 +6771,7 @@ "pay", "payment" ], + "abandoned": "digitickets/omnipay-barclays-epdq", "time": "2016-06-09 16:42:25" }, { @@ -7212,7 +7270,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.13", + "version": "v2.8.14", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -7321,16 +7379,16 @@ }, { "name": "symfony/http-foundation", - "version": "v2.8.13", + "version": "v2.8.14", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a6e6c34d337f3c74c39b29c5f54d33023de8897c" + "reference": "4f8c167732bbf3ba4284c0915f57b332091f6b68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a6e6c34d337f3c74c39b29c5f54d33023de8897c", - "reference": "a6e6c34d337f3c74c39b29c5f54d33023de8897c", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4f8c167732bbf3ba4284c0915f57b332091f6b68", + "reference": "4f8c167732bbf3ba4284c0915f57b332091f6b68", "shasum": "" }, "require": { @@ -7372,7 +7430,7 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-10-24 15:52:36" + "time": "2016-11-15 23:02:12" }, { "name": "symfony/http-kernel", @@ -7512,16 +7570,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "dff51f72b0706335131b00a7f49606168c582594" + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", - "reference": "dff51f72b0706335131b00a7f49606168c582594", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", "shasum": "" }, "require": { @@ -7533,7 +7591,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -7567,20 +7625,20 @@ "portable", "shim" ], - "time": "2016-05-18 14:26:46" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/polyfill-php54", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "34d761992f6f2cc6092cc0e5e93f38b53ba5e4f1" + "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/34d761992f6f2cc6092cc0e5e93f38b53ba5e4f1", - "reference": "34d761992f6f2cc6092cc0e5e93f38b53ba5e4f1", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/90e085822963fdcc9d1c5b73deb3d2e5783b16a0", + "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0", "shasum": "" }, "require": { @@ -7589,7 +7647,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -7625,20 +7683,20 @@ "portable", "shim" ], - "time": "2016-05-18 14:26:46" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/polyfill-php55", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "bf2ff9ad6be1a4772cb873e4eea94d70daa95c6d" + "reference": "03e3f0350bca2220e3623a0e340eef194405fc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/bf2ff9ad6be1a4772cb873e4eea94d70daa95c6d", - "reference": "bf2ff9ad6be1a4772cb873e4eea94d70daa95c6d", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/03e3f0350bca2220e3623a0e340eef194405fc67", + "reference": "03e3f0350bca2220e3623a0e340eef194405fc67", "shasum": "" }, "require": { @@ -7648,7 +7706,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -7681,7 +7739,7 @@ "portable", "shim" ], - "time": "2016-05-18 14:26:46" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/polyfill-php56", diff --git a/config/app.php b/config/app.php index 985fdcc6d40ba..9e17845290472 100644 --- a/config/app.php +++ b/config/app.php @@ -156,6 +156,7 @@ return [ Websight\GcsProvider\CloudStorageServiceProvider::class, 'Jaybizzle\LaravelCrawlerDetect\LaravelCrawlerDetectServiceProvider', Codedge\Updater\UpdaterServiceProvider::class, + Nwidart\Modules\LaravelModulesServiceProvider::class, /* * Application Service Providers... @@ -259,6 +260,7 @@ return [ 'PushNotification' => 'Davibennun\LaravelPushNotification\Facades\PushNotification', 'Crawler' => 'Jaybizzle\LaravelCrawlerDetect\Facades\LaravelCrawlerDetect', 'Updater' => Codedge\Updater\UpdaterFacade::class, + 'Module' => Nwidart\Modules\Facades\Module::class, ], ]; diff --git a/config/modules.php b/config/modules.php new file mode 100644 index 0000000000000..62c2d823bcb1d --- /dev/null +++ b/config/modules.php @@ -0,0 +1,176 @@ + 'Modules', + + /* + |-------------------------------------------------------------------------- + | Module Stubs + |-------------------------------------------------------------------------- + | + | Default module stubs. + | + */ + + 'stubs' => [ + 'enabled' => true, + 'path' => base_path() . '/app/Console/Commands/stubs', + 'files' => [ + 'start' => 'start.php', + 'routes' => 'Http/routes.php', + 'json' => 'module.json', + 'views/master' => 'Resources/views/layouts/master.blade.php', + 'scaffold/config' => 'Config/config.php', + 'composer' => 'composer.json', + ], + 'replacements' => [ + 'start' => ['LOWER_NAME'], + 'routes' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE'], + 'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE'], + 'views/master' => ['STUDLY_NAME'], + 'scaffold/config' => ['STUDLY_NAME'], + 'composer' => [ + 'LOWER_NAME', + 'STUDLY_NAME', + 'VENDOR', + 'AUTHOR_NAME', + 'AUTHOR_EMAIL', + 'MODULE_NAMESPACE', + ], + ], + ], + 'paths' => [ + /* + |-------------------------------------------------------------------------- + | Modules path + |-------------------------------------------------------------------------- + | + | This path used for save the generated module. This path also will added + | automatically to list of scanned folders. + | + */ + + 'modules' => base_path('Modules'), + /* + |-------------------------------------------------------------------------- + | Modules assets path + |-------------------------------------------------------------------------- + | + | Here you may update the modules assets path. + | + */ + + 'assets' => public_path('modules'), + /* + |-------------------------------------------------------------------------- + | The migrations path + |-------------------------------------------------------------------------- + | + | Where you run 'module:publish-migration' command, where do you publish the + | the migration files? + | + */ + + 'migration' => base_path('database/migrations'), + /* + |-------------------------------------------------------------------------- + | Generator path + |-------------------------------------------------------------------------- + | + | Here you may update the modules generator path. + | + */ + + 'generator' => [ + 'assets' => 'Assets', + 'config' => 'Config', + 'command' => 'Console', + 'event' => 'Events', + 'listener' => 'Events/Handlers', + 'migration' => 'Database/Migrations', + 'model' => 'Models', + 'repository' => 'Repositories', + 'seeder' => 'Database/Seeders', + 'controller' => 'Http/Controllers', + 'filter' => 'Http/Middleware', + 'request' => 'Http/Requests', + 'provider' => 'Providers', + 'lang' => 'Resources/lang/en', + 'views' => 'Resources/views', + 'test' => 'Tests', + 'jobs' => 'Jobs', + 'emails' => 'Emails', + 'notifications' => 'Notifications', + 'datatable' => 'Datatables', + 'policy' => 'Policies', + 'presenter' => 'Presenters', + 'api-controller' => 'Http/ApiControllers', + 'transformer' => 'Transformers', + ], + ], + /* + |-------------------------------------------------------------------------- + | Scan Path + |-------------------------------------------------------------------------- + | + | Here you define which folder will be scanned. By default will scan vendor + | directory. This is useful if you host the package in packagist website. + | + */ + + 'scan' => [ + 'enabled' => false, + 'paths' => [ + base_path('vendor/*/*'), + ], + ], + /* + |-------------------------------------------------------------------------- + | Composer File Template + |-------------------------------------------------------------------------- + | + | Here is the config for composer.json file, generated by this package + | + */ + + 'composer' => [ + 'vendor' => 'invoiceninja', + 'author' => [ + 'name' => 'Hillel Coren', + 'email' => 'contact@invoiceninja.com', + ], + ], + /* + |-------------------------------------------------------------------------- + | Caching + |-------------------------------------------------------------------------- + | + | Here is the config for setting up caching feature. + | + */ + 'cache' => [ + 'enabled' => false, + 'key' => 'laravel-modules', + 'lifetime' => 60, + ], + /* + |-------------------------------------------------------------------------- + | Choose what laravel-modules will register as custom namespaces. + | Setting one to false will require to register that part + | in your own Service Provider class. + |-------------------------------------------------------------------------- + */ + 'register' => [ + 'translations' => true, + ], +]; diff --git a/database/migrations/2016_11_03_161149_add_bluevine_fields.php b/database/migrations/2016_11_03_161149_add_bluevine_fields.php new file mode 100644 index 0000000000000..bc7e57ff5dd16 --- /dev/null +++ b/database/migrations/2016_11_03_161149_add_bluevine_fields.php @@ -0,0 +1,28 @@ +enum( 'bluevine_status', array( 'ignored', 'signed_up' ) )->nullable(); + } ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() { + Schema::table( 'accounts', function ( $table ) { + $table->dropColumn( 'bluevine_status' ); + } ); + } +} diff --git a/database/migrations/2016_11_28_092904_add_task_projects.php b/database/migrations/2016_11_28_092904_add_task_projects.php new file mode 100644 index 0000000000000..62b231e545efd --- /dev/null +++ b/database/migrations/2016_11_28_092904_add_task_projects.php @@ -0,0 +1,107 @@ +increments('id'); + $table->unsignedInteger('user_id'); + $table->unsignedInteger('account_id')->index(); + $table->unsignedInteger('client_id')->index()->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->string('name')->nullable(); + $table->boolean('is_deleted')->default(false); + + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); + + $table->unsignedInteger('public_id')->index(); + $table->unique( array('account_id','public_id') ); + }); + + Schema::table('tasks', function ($table) + { + $table->unsignedInteger('project_id')->nullable()->index(); + $table->text('description')->change(); + }); + + DB::statement('SET FOREIGN_KEY_CHECKS=0;'); + Schema::table('tasks', function ($table) + { + $table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade'); + }); + DB::statement('SET FOREIGN_KEY_CHECKS=1;'); + + // is_deleted to standardize tables + Schema::table('expense_categories', function ($table) + { + $table->boolean('is_deleted')->default(false); + }); + + Schema::table('products', function ($table) + { + $table->boolean('is_deleted')->default(false); + }); + + // add 'delete cascase' to resolve error when deleting an account + Schema::table('account_gateway_tokens', function($table) + { + $table->dropForeign('account_gateway_tokens_default_payment_method_id_foreign'); + }); + + Schema::table('account_gateway_tokens', function($table) + { + $table->foreign('default_payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade'); + }); + + Schema::table('invoices', function ($table) + { + $table->boolean('is_public')->default(false); + }); + DB::table('invoices')->update(['is_public' => true]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tasks', function ($table) + { + $table->dropForeign('tasks_project_id_foreign'); + $table->dropColumn('project_id'); + }); + + Schema::dropIfExists('projects'); + + Schema::table('expense_categories', function ($table) + { + $table->dropColumn('is_deleted'); + }); + + Schema::table('products', function ($table) + { + $table->dropColumn('is_deleted'); + }); + + Schema::table('invoices', function ($table) + { + $table->dropColumn('is_public'); + }); + } +} diff --git a/database/migrations/2016_12_13_113955_add_pro_plan_discount.php b/database/migrations/2016_12_13_113955_add_pro_plan_discount.php new file mode 100644 index 0000000000000..c04b810e2b20f --- /dev/null +++ b/database/migrations/2016_12_13_113955_add_pro_plan_discount.php @@ -0,0 +1,37 @@ +float('discount'); + $table->date('discount_expires')->nullable(); + $table->date('promo_expires')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('companies', function ($table) + { + $table->dropColumn('discount'); + $table->dropColumn('discount_expires'); + $table->dropColumn('promo_expires'); + }); + } +} diff --git a/database/seeds/UserTableSeeder.php b/database/seeds/UserTableSeeder.php index 80b88b23368c8..7d68e5ad9c2fd 100644 --- a/database/seeds/UserTableSeeder.php +++ b/database/seeds/UserTableSeeder.php @@ -81,6 +81,7 @@ class UserTableSeeder extends Seeder 'public_id' => 1, 'email' => env('TEST_EMAIL', TEST_USERNAME), 'is_primary' => true, + 'send_invoice' => true, ]); Product::create([ diff --git a/docs/conf.py b/docs/conf.py index 88a25d2c9497d..10e13747ab878 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -57,9 +57,9 @@ author = u'Invoice Ninja' # built documents. # # The short X.Y version. -version = u'2.6' +version = u'2.8' # The full version, including alpha/beta/rc tags. -release = u'2.6.10' +release = u'2.8.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/custom_modules.rst b/docs/custom_modules.rst new file mode 100644 index 0000000000000..1548d912db8de --- /dev/null +++ b/docs/custom_modules.rst @@ -0,0 +1,76 @@ +Custom Modules +============== + +Invoice Ninja support customs modules using https://github.com/nWidart/laravel-modules + +You can watch this `short video `_ for a quick overview of the feature. + +.. Note:: This currently requires using the develop branch + +Install Module +"""""""""""""" + +To install a module run: + +.. code-block:: php + + php artisan module:install --type=github + +For example: + +.. code-block:: php + + php artisan module:install invoiceninja/sprockets --type=github + +You can check the current module status with: + +.. code-block:: php + + php artisan module:list + + +Create Module +""""""""""""" + +Run the following command to create a CRUD module: + +.. code-block:: php + + php artisan ninja:make-module + +.. code-block:: php + + php artisan ninja:make-module Inventory 'name:string,description:text' + +To edit the migration before it's run add ``--migrate=false`` + +.. code-block:: php + + php artisan ninja:make-module --migrate=false + +After making adjustments to the migration file you can run: + +.. code-block:: php + + php artisan module:migrate + + +.. Tip:: You can specify the module icon by setting a value from http://fontawesome.io/icons/ for "icon" in modules.json. + +Share Module +"""""""""""" + +To share your module create a new project on GitHub and then commit the code: + +.. code-block:: php + + cd Modules/ + git init + git add . + git commit -m "Initial commit" + git remote add origin git@github.com:.git + git push -f origin master + +.. Tip:: Add ``"type": "invoiceninja-module"`` to the composer.json file to help people find your module. + +Finally, submit the project to https://packagist.org. diff --git a/docs/index.rst b/docs/index.rst index 411e67bf98ecf..9bc599237f6bc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,11 +36,12 @@ Want to find out everything there is to know about how to use your Invoice Ninja data_visualizations api_tokens user_management - + .. _self_host: .. toctree:: :maxdepth: 1 :caption: Self Host - + iphone_app + custom_modules diff --git a/gulpfile.js b/gulpfile.js index 02abd7f27c63c..ee46ceeaa9e72 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -62,6 +62,10 @@ elixir(function(mix) { bowerDir + '/bootstrap-daterangepicker/daterangepicker.css' ], 'public/css/daterangepicker.css'); + mix.styles([ + bowerDir + '/select2/dist/css/select2.css' + ], 'public/css/select2.css'); + /** * JS configuration @@ -80,6 +84,11 @@ elixir(function(mix) { bowerDir + '/bootstrap-daterangepicker/daterangepicker.js' ], 'public/js/daterangepicker.min.js'); + mix.scripts([ + bowerDir + '/select2/dist/js/select2.js', + 'resources/assets/js/maximize-select2-height.js', + ], 'public/js/select2.min.js'); + mix.scripts([ bowerDir + '/jSignature/libs/jSignature.min.js' ], 'public/js/jSignature.min.js'); @@ -117,6 +126,7 @@ elixir(function(mix) { bowerDir + '/sweetalert2/dist/sweetalert2.js', //bowerDir + '/sweetalert/dist/sweetalert-dev.js', bowerDir + '/nouislider/distribute/nouislider.js', + bowerDir + '/mousetrap/mousetrap.js', bowerDir + '/fuse.js/src/fuse.js', 'bootstrap-combobox.js', 'script.js', diff --git a/public/built.js b/public/built.js index 615988d0b3f82..0be2e1d1c8f3f 100644 --- a/public/built.js +++ b/public/built.js @@ -1,31 +1,31 @@ -function generatePDF(t,e,n,i){if(t&&e){if(!n)return refreshTimer&&clearTimeout(refreshTimer),void(refreshTimer=setTimeout(function(){generatePDF(t,e,!0,i)},500));refreshTimer=null,t=calculateAmounts(t);var o=GetPdfMake(t,e,i);return i&&o.getDataUrl(i),o}}function copyObject(t){return!!t&&JSON.parse(JSON.stringify(t))}function processVariables(t){if(!t)return"";for(var e=["MONTH","QUARTER","YEAR"],n=0;n1?c=a.split("+")[1]:a.split("-").length>1&&(c=parseInt(a.split("-")[1])*-1),t=t.replace(a,getDatePart(i,c))}}return t}function getDatePart(t,e){return e=parseInt(e),e||(e=0),"MONTH"==t?getMonth(e):"QUARTER"==t?getQuarter(e):"YEAR"==t?getYear(e):void 0}function getMonth(t){var e=new Date,n=["January","February","March","April","May","June","July","August","September","October","November","December"],i=e.getMonth();return i=parseInt(i)+t,i%=12,i<0&&(i+=12),n[i]}function getYear(t){var e=new Date,n=e.getFullYear();return parseInt(n)+t}function getQuarter(t){var e=new Date,n=Math.floor((e.getMonth()+3)/3);return n+=t,n%=4,0==n&&(n=4),"Q"+n}function isStorageSupported(){try{return"localStorage"in window&&null!==window.localStorage}catch(t){return!1}}function isValidEmailAddress(t){var e=new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);return e.test(t)}function enableHoverClick(t,e,n){}function setAsLink(t,e){e?(t.css("text-decoration","underline"),t.css("cursor","pointer")):(t.css("text-decoration","none"),t.css("cursor","text"))}function setComboboxValue(t,e,n){t.find("input").val(e),t.find("input.form-control").val(n),e&&n?(t.find("select").combobox("setSelected"),t.find(".combobox-container").addClass("combobox-selected")):t.find(".combobox-container").removeClass("combobox-selected")}function convertDataURIToBinary(t){var e=t.indexOf(BASE64_MARKER)+BASE64_MARKER.length,n=t.substring(e);return base64DecToArr(n)}function getContactDisplayName(t){return t.first_name||t.last_name?(t.first_name||"")+" "+(t.last_name||""):t.email}function getClientDisplayName(t){var e=!!t.contacts&&t.contacts[0];return t.name?t.name:e?getContactDisplayName(e):""}function populateInvoiceComboboxes(t,e){for(var n={},i={},o={},r=$("select#client"),s=0;s1?t+=", ":n64&&t<91?t-65:t>96&&t<123?t-71:t>47&&t<58?t+4:43===t?62:47===t?63:0}function base64DecToArr(t,e){for(var n,i,o=t.replace(/[^A-Za-z0-9\+\/]/g,""),r=o.length,s=e?Math.ceil((3*r+1>>2)/e)*e:3*r+1>>2,a=new Uint8Array(s),c=0,l=0,u=0;u>>(16>>>n&24)&255;c=0}return a}function uint6ToB64(t){return t<26?t+65:t<52?t+71:t<62?t-4:62===t?43:63===t?47:65}function base64EncArr(t){for(var e=2,n="",i=t.length,o=0,r=0;r0&&4*r/3%76===0&&(n+="\r\n"),o|=t[r]<<(16>>>e&24),2!==e&&t.length-r!==1||(n+=String.fromCharCode(uint6ToB64(o>>>18&63),uint6ToB64(o>>>12&63),uint6ToB64(o>>>6&63),uint6ToB64(63&o)),o=0);return n.substr(0,n.length-2+e)+(2===e?"":1===e?"=":"==")}function UTF8ArrToStr(t){for(var e,n="",i=t.length,o=0;o251&&e<254&&o+5247&&e<252&&o+4239&&e<248&&o+3223&&e<240&&o+2191&&e<224&&o+1>>6),e[s++]=128+(63&n)):n<65536?(e[s++]=224+(n>>>12),e[s++]=128+(n>>>6&63),e[s++]=128+(63&n)):n<2097152?(e[s++]=240+(n>>>18),e[s++]=128+(n>>>12&63),e[s++]=128+(n>>>6&63),e[s++]=128+(63&n)):n<67108864?(e[s++]=248+(n>>>24),e[s++]=128+(n>>>18&63),e[s++]=128+(n>>>12&63),e[s++]=128+(n>>>6&63),e[s++]=128+(63&n)):(e[s++]=252+n/1073741824,e[s++]=128+(n>>>24&63),e[s++]=128+(n>>>18&63),e[s++]=128+(n>>>12&63),e[s++]=128+(n>>>6&63),e[s++]=128+(63&n));return e}function hexToR(t){return parseInt(cutHex(t).substring(0,2),16)}function hexToG(t){return parseInt(cutHex(t).substring(2,4),16)}function hexToB(t){return parseInt(cutHex(t).substring(4,6),16)}function cutHex(t){return"#"==t.charAt(0)?t.substring(1,7):t}function setDocHexColor(t,e){var n=hexToR(e),i=hexToG(e),o=hexToB(e);return t.setTextColor(n,i,o)}function setDocHexFill(t,e){var n=hexToR(e),i=hexToG(e),o=hexToB(e);return t.setFillColor(n,i,o)}function setDocHexDraw(t,e){var n=hexToR(e),i=hexToG(e),o=hexToB(e);return t.setDrawColor(n,i,o)}function toggleDatePicker(t){$("#"+t).datepicker("show")}function roundToTwo(t,e){var n=+(Math.round(t+"e+2")+"e-2");return e?n.toFixed(2):n||0}function roundToFour(t,e){var n=+(Math.round(t+"e+4")+"e-4");return e?n.toFixed(4):n||0}function truncate(t,e){return t&&t.length>e?t.substr(0,e-1)+"...":t}function endsWith(t,e){return t.indexOf(e,t.length-e.length)!==-1}function secondsToTime(t){t=Math.round(t);var e=Math.floor(t/3600),n=t%3600,i=Math.floor(n/60),o=n%60,r=Math.ceil(o),s={h:e,m:i,s:r};return s}function twoDigits(t){return t<10?"0"+t:t}function toSnakeCase(t){return t?t.replace(/([A-Z])/g,function(t){return"_"+t.toLowerCase()}):""}function snakeToCamel(t){return t.replace(/_([a-z])/g,function(t){return t[1].toUpperCase()})}function getDescendantProp(t,e){for(var n=e.split(".");n.length&&(t=t[n.shift()]););return t}function doubleDollarSign(t){return t?t.replace?t.replace(/\$/g,"$$$"):t:""}function truncate(t,e){return t.length>e?t.substring(0,e)+"...":t}function actionListHandler(){$("tbody tr .tr-action").closest("tr").mouseover(function(){$(this).closest("tr").find(".tr-action").show(),$(this).closest("tr").find(".tr-status").hide()}).mouseout(function(){$dropdown=$(this).closest("tr").find(".tr-action"),$dropdown.hasClass("open")||($dropdown.hide(),$(this).closest("tr").find(".tr-status").show())})}function loadImages(t){$(t+" img").each(function(t,e){var n=$(e).attr("data-src");$(e).attr("src",n),$(e).attr("data-src",n)})}function prettyJson(t){return"string"!=typeof t&&(t=JSON.stringify(t,void 0,2)),t=t.replace(/&/g,"&").replace(//g,">"),t.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,function(t){var e="number";return/^"/.test(t)?e=/:$/.test(t)?"key":"string":/true|false/.test(t)?e="boolean":/null/.test(t)&&(e="null"),t=snakeToCamel(t),''+t+""})}function searchData(t,e,n){return function(i,o){var r;if(n){var s={keys:[e]},a=new Fuse(t,s);r=a.search(i)}else r=[],substrRegex=new RegExp(escapeRegExp(i),"i"),$.each(t,function(t,n){substrRegex.test(n[e])&&r.push(n)});o(r)}}function escapeRegExp(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}function firstJSONError(t){for(var e in t)if(t.hasOwnProperty(e)){var n=t[e];for(var i in n)if(n.hasOwnProperty(i))return n[i]}return!1}function GetPdfMake(t,e,n){function i(e,n){if("string"==typeof n){if(0===n.indexOf("$firstAndLast")){var i=n.split(":");return function(t,e){return 0===t||t===e.table.body.length?parseFloat(i[1]):0}}if(0===n.indexOf("$none"))return function(t,e){return 0};if(0===n.indexOf("$notFirstAndLastColumn")){var i=n.split(":");return function(t,e){return 0===t||t===e.table.widths.length?0:parseFloat(i[1])}}if(0===n.indexOf("$notFirst")){var i=n.split(":");return function(t,e){return 0===t?0:parseFloat(i[1])}}if(0===n.indexOf("$amount")){var i=n.split(":");return function(t,e){return parseFloat(i[1])}}if(0===n.indexOf("$primaryColor")){var i=n.split(":");return NINJA.primaryColor||i[1]}if(0===n.indexOf("$secondaryColor")){var i=n.split(":");return NINJA.secondaryColor||i[1]}}if(t.features.customize_invoice_design){if("header"===e)return function(e,i){return 1===e||"1"==t.account.all_pages_header?n:""};if("footer"===e)return function(e,i){return e===i||"1"==t.account.all_pages_footer?n:""}}return"text"===e&&(n=NINJA.parseMarkdownText(n,!0)),n}function o(t){window.ninjaFontVfs[t.folder]&&(folder="fonts/"+t.folder,pdfMake.fonts[t.name]={normal:folder+"/"+t.normal,italics:folder+"/"+t.italics,bold:folder+"/"+t.bold,bolditalics:folder+"/"+t.bolditalics})}e=NINJA.decodeJavascript(t,e);var r=JSON.parse(e,i);t.invoice_design_id;if(!t.features.remove_created_by&&!isEdge){var s="function"==typeof r.footer?r.footer():r.footer;if(s)if(s.hasOwnProperty("columns"))s.columns.push({image:logoImages.imageLogo1,alignment:"right",width:130,margin:[0,0,0,0]});else{for(var a,c=0;c0&&e-1 in t))}function i(t,e,n){if(ot.isFunction(e))return ot.grep(t,function(t,i){return!!e.call(t,i,t)!==n});if(e.nodeType)return ot.grep(t,function(t){return t===e!==n});if("string"==typeof e){if(dt.test(e))return ot.filter(e,t,n);e=ot.filter(e,t)}return ot.grep(t,function(t){return ot.inArray(t,e)>=0!==n})}function o(t,e){do t=t[e];while(t&&1!==t.nodeType);return t}function r(t){var e=yt[t]={};return ot.each(t.match(Mt)||[],function(t,n){e[n]=!0}),e}function s(){ft.addEventListener?(ft.removeEventListener("DOMContentLoaded",a,!1),t.removeEventListener("load",a,!1)):(ft.detachEvent("onreadystatechange",a),t.detachEvent("onload",a))}function a(){(ft.addEventListener||"load"===event.type||"complete"===ft.readyState)&&(s(),ot.ready())}function c(t,e,n){if(void 0===n&&1===t.nodeType){var i="data-"+e.replace(Tt,"-$1").toLowerCase();if(n=t.getAttribute(i),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:_t.test(n)?ot.parseJSON(n):n)}catch(o){}ot.data(t,e,n)}else n=void 0}return n}function l(t){var e;for(e in t)if(("data"!==e||!ot.isEmptyObject(t[e]))&&"toJSON"!==e)return!1;return!0}function u(t,e,n,i){if(ot.acceptData(t)){var o,r,s=ot.expando,a=t.nodeType,c=a?ot.cache:t,l=a?t[s]:t[s]&&s;if(l&&c[l]&&(i||c[l].data)||void 0!==n||"string"!=typeof e)return l||(l=a?t[s]=Y.pop()||ot.guid++:s),c[l]||(c[l]=a?{}:{toJSON:ot.noop}),"object"!=typeof e&&"function"!=typeof e||(i?c[l]=ot.extend(c[l],e):c[l].data=ot.extend(c[l].data,e)),r=c[l],i||(r.data||(r.data={}),r=r.data),void 0!==n&&(r[ot.camelCase(e)]=n),"string"==typeof e?(o=r[e],null==o&&(o=r[ot.camelCase(e)])):o=r,o}}function h(t,e,n){if(ot.acceptData(t)){var i,o,r=t.nodeType,s=r?ot.cache:t,a=r?t[ot.expando]:ot.expando;if(s[a]){if(e&&(i=n?s[a]:s[a].data)){ot.isArray(e)?e=e.concat(ot.map(e,ot.camelCase)):e in i?e=[e]:(e=ot.camelCase(e),e=e in i?[e]:e.split(" ")),o=e.length;for(;o--;)delete i[e[o]];if(n?!l(i):!ot.isEmptyObject(i))return}(n||(delete s[a].data,l(s[a])))&&(r?ot.cleanData([t],!0):nt.deleteExpando||s!=s.window?delete s[a]:s[a]=null)}}}function d(){return!0}function p(){return!1}function f(){try{return ft.activeElement}catch(t){}}function m(t){var e=Et.split("|"),n=t.createDocumentFragment();if(n.createElement)for(;e.length;)n.createElement(e.pop());return n}function g(t,e){var n,i,o=0,r=typeof t.getElementsByTagName!==zt?t.getElementsByTagName(e||"*"):typeof t.querySelectorAll!==zt?t.querySelectorAll(e||"*"):void 0;if(!r)for(r=[],n=t.childNodes||t;null!=(i=n[o]);o++)!e||ot.nodeName(i,e)?r.push(i):ot.merge(r,g(i,e));return void 0===e||e&&ot.nodeName(t,e)?ot.merge([t],r):r}function b(t){Nt.test(t.type)&&(t.defaultChecked=t.checked)}function v(t,e){return ot.nodeName(t,"table")&&ot.nodeName(11!==e.nodeType?e:e.firstChild,"tr")?t.getElementsByTagName("tbody")[0]||t.appendChild(t.ownerDocument.createElement("tbody")):t}function M(t){return t.type=(null!==ot.find.attr(t,"type"))+"/"+t.type,t}function y(t){var e=Vt.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function A(t,e){for(var n,i=0;null!=(n=t[i]);i++)ot._data(n,"globalEval",!e||ot._data(e[i],"globalEval"))}function w(t,e){if(1===e.nodeType&&ot.hasData(t)){var n,i,o,r=ot._data(t),s=ot._data(e,r),a=r.events;if(a){delete s.handle,s.events={};for(n in a)for(i=0,o=a[n].length;i")).appendTo(e.documentElement),e=(Qt[0].contentWindow||Qt[0].contentDocument).document,e.write(),e.close(),n=_(t,e),Qt.detach()),Zt[t]=n),n}function x(t,e){return{get:function(){var n=t();if(null!=n)return n?void delete this.get:(this.get=e).apply(this,arguments)}}}function C(t,e){if(e in t)return e;for(var n=e.charAt(0).toUpperCase()+e.slice(1),i=e,o=de.length;o--;)if(e=de[o]+n,e in t)return e;return i}function S(t,e){for(var n,i,o,r=[],s=0,a=t.length;s=0&&n=0},isEmptyObject:function(t){var e;for(e in t)return!1;return!0},isPlainObject:function(t){var e;if(!t||"object"!==ot.type(t)||t.nodeType||ot.isWindow(t))return!1;try{if(t.constructor&&!et.call(t,"constructor")&&!et.call(t.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}if(nt.ownLast)for(e in t)return et.call(t,e);for(e in t);return void 0===e||et.call(t,e)},type:function(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?Z[tt.call(t)]||"object":typeof t},globalEval:function(e){e&&ot.trim(e)&&(t.execScript||function(e){t.eval.call(t,e)})(e)},camelCase:function(t){return t.replace(st,"ms-").replace(at,ct)},nodeName:function(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()},each:function(t,e,i){var o,r=0,s=t.length,a=n(t);if(i){if(a)for(;rw.cacheLength&&delete t[e.shift()],t[n+" "]=i}var e=[];return t}function i(t){return t[P]=!0,t}function o(t){var e=k.createElement("div");try{return!!t(e)}catch(n){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function r(t,e){for(var n=t.split("|"),i=t.length;i--;)w.attrHandle[n[i]]=e}function s(t,e){var n=e&&t,i=n&&1===t.nodeType&&1===e.nodeType&&(~e.sourceIndex||V)-(~t.sourceIndex||V);if(i)return i;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function a(t){return function(e){var n=e.nodeName.toLowerCase(); -return"input"===n&&e.type===t}}function c(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function l(t){return i(function(e){return e=+e,i(function(n,i){for(var o,r=t([],n.length,e),s=r.length;s--;)n[o=r[s]]&&(n[o]=!(i[o]=n[o]))})})}function u(t){return t&&"undefined"!=typeof t.getElementsByTagName&&t}function h(){}function d(t){for(var e=0,n=t.length,i="";e1?function(e,n,i){for(var o=t.length;o--;)if(!t[o](e,n,i))return!1;return!0}:t[0]}function m(t,n,i){for(var o=0,r=n.length;o-1&&(i[l]=!(s[l]=h))}}else M=g(M===s?M.splice(f,M.length):M),r?r(null,s,M,c):Q.apply(s,M)})}function v(t){for(var e,n,i,o=t.length,r=w.relative[t[0].type],s=r||w.relative[" "],a=r?1:0,c=p(function(t){return t===e},s,!0),l=p(function(t){return tt(e,t)>-1},s,!0),u=[function(t,n,i){var o=!r&&(i||n!==S)||((e=n).nodeType?c(t,n,i):l(t,n,i));return e=null,o}];a1&&f(u),a>1&&d(t.slice(0,a-1).concat({value:" "===t[a-2].type?"*":""})).replace(ct,"$1"),n,a0,r=t.length>0,s=function(i,s,a,c,l){var u,h,d,p=0,f="0",m=i&&[],b=[],v=S,M=i||r&&w.find.TAG("*",l),y=X+=null==v?1:Math.random()||.1,A=M.length;for(l&&(S=s!==k&&s);f!==A&&null!=(u=M[f]);f++){if(r&&u){for(h=0;d=t[h++];)if(d(u,s,a)){c.push(u);break}l&&(X=y)}o&&((u=!d&&u)&&p--,i&&m.push(u))}if(p+=f,o&&f!==p){for(h=0;d=n[h++];)d(m,b,s,a);if(i){if(p>0)for(;f--;)m[f]||b[f]||(b[f]=G.call(c));b=g(b)}Q.apply(c,b),l&&!i&&b.length>0&&p+n.length>1&&e.uniqueSort(c)}return l&&(X=y,S=v),m};return o?i(s):s}var y,A,w,z,_,T,x,C,S,O,N,L,k,D,q,W,E,B,I,P="sizzle"+1*new Date,R=t.document,X=0,F=0,H=n(),j=n(),U=n(),$=function(t,e){return t===e&&(N=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,tt=function(t,e){for(var n=0,i=t.length;n+~]|"+nt+")"+nt+"*"),ht=new RegExp("="+nt+"*([^\\]'\"]*?)"+nt+"*\\]","g"),dt=new RegExp(st),pt=new RegExp("^"+ot+"$"),ft={ID:new RegExp("^#("+it+")"),CLASS:new RegExp("^\\.("+it+")"),TAG:new RegExp("^("+it.replace("w","w*")+")"),ATTR:new RegExp("^"+rt),PSEUDO:new RegExp("^"+st),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+nt+"*(even|odd|(([+-]|)(\\d*)n|)"+nt+"*(?:([+-]|)"+nt+"*(\\d+)|))"+nt+"*\\)|)","i"),bool:new RegExp("^(?:"+et+")$","i"),needsContext:new RegExp("^"+nt+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+nt+"*((?:-\\d)?\\d*)"+nt+"*\\)|)(?=[^-]|$)","i")},mt=/^(?:input|select|textarea|button)$/i,gt=/^h\d$/i,bt=/^[^{]+\{\s*\[native \w/,vt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Mt=/[+~]/,yt=/'|\\/g,At=new RegExp("\\\\([\\da-f]{1,6}"+nt+"?|("+nt+")|.)","ig"),wt=function(t,e,n){var i="0x"+e-65536;return i!==i||n?e:i<0?String.fromCharCode(i+65536):String.fromCharCode(i>>10|55296,1023&i|56320)},zt=function(){L()};try{Q.apply(J=Z.call(R.childNodes),R.childNodes),J[R.childNodes.length].nodeType}catch(_t){Q={apply:J.length?function(t,e){K.apply(t,Z.call(e))}:function(t,e){for(var n=t.length,i=0;t[n++]=e[i++];);t.length=n-1}}}A=e.support={},_=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},L=e.setDocument=function(t){var e,n,i=t?t.ownerDocument||t:R;return i!==k&&9===i.nodeType&&i.documentElement?(k=i,D=i.documentElement,n=i.defaultView,n&&n!==n.top&&(n.addEventListener?n.addEventListener("unload",zt,!1):n.attachEvent&&n.attachEvent("onunload",zt)),q=!_(i),A.attributes=o(function(t){return t.className="i",!t.getAttribute("className")}),A.getElementsByTagName=o(function(t){return t.appendChild(i.createComment("")),!t.getElementsByTagName("*").length}),A.getElementsByClassName=bt.test(i.getElementsByClassName),A.getById=o(function(t){return D.appendChild(t).id=P,!i.getElementsByName||!i.getElementsByName(P).length}),A.getById?(w.find.ID=function(t,e){if("undefined"!=typeof e.getElementById&&q){var n=e.getElementById(t);return n&&n.parentNode?[n]:[]}},w.filter.ID=function(t){var e=t.replace(At,wt);return function(t){return t.getAttribute("id")===e}}):(delete w.find.ID,w.filter.ID=function(t){var e=t.replace(At,wt);return function(t){var n="undefined"!=typeof t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}}),w.find.TAG=A.getElementsByTagName?function(t,e){return"undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t):A.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,i=[],o=0,r=e.getElementsByTagName(t);if("*"===t){for(;n=r[o++];)1===n.nodeType&&i.push(n);return i}return r},w.find.CLASS=A.getElementsByClassName&&function(t,e){if(q)return e.getElementsByClassName(t)},E=[],W=[],(A.qsa=bt.test(i.querySelectorAll))&&(o(function(t){D.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&W.push("[*^$]="+nt+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||W.push("\\["+nt+"*(?:value|"+et+")"),t.querySelectorAll("[id~="+P+"-]").length||W.push("~="),t.querySelectorAll(":checked").length||W.push(":checked"),t.querySelectorAll("a#"+P+"+*").length||W.push(".#.+[+~]")}),o(function(t){var e=i.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&W.push("name"+nt+"*[*^$|!~]?="),t.querySelectorAll(":enabled").length||W.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),W.push(",.*:")})),(A.matchesSelector=bt.test(B=D.matches||D.webkitMatchesSelector||D.mozMatchesSelector||D.oMatchesSelector||D.msMatchesSelector))&&o(function(t){A.disconnectedMatch=B.call(t,"div"),B.call(t,"[s!='']:x"),E.push("!=",st)}),W=W.length&&new RegExp(W.join("|")),E=E.length&&new RegExp(E.join("|")),e=bt.test(D.compareDocumentPosition),I=e||bt.test(D.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},$=e?function(t,e){if(t===e)return N=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n?n:(n=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1,1&n||!A.sortDetached&&e.compareDocumentPosition(t)===n?t===i||t.ownerDocument===R&&I(R,t)?-1:e===i||e.ownerDocument===R&&I(R,e)?1:O?tt(O,t)-tt(O,e):0:4&n?-1:1)}:function(t,e){if(t===e)return N=!0,0;var n,o=0,r=t.parentNode,a=e.parentNode,c=[t],l=[e];if(!r||!a)return t===i?-1:e===i?1:r?-1:a?1:O?tt(O,t)-tt(O,e):0;if(r===a)return s(t,e);for(n=t;n=n.parentNode;)c.unshift(n);for(n=e;n=n.parentNode;)l.unshift(n);for(;c[o]===l[o];)o++;return o?s(c[o],l[o]):c[o]===R?-1:l[o]===R?1:0},i):k},e.matches=function(t,n){return e(t,null,null,n)},e.matchesSelector=function(t,n){if((t.ownerDocument||t)!==k&&L(t),n=n.replace(ht,"='$1']"),A.matchesSelector&&q&&(!E||!E.test(n))&&(!W||!W.test(n)))try{var i=B.call(t,n);if(i||A.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(o){}return e(n,k,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==k&&L(t),I(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==k&&L(t);var n=w.attrHandle[e.toLowerCase()],i=n&&Y.call(w.attrHandle,e.toLowerCase())?n(t,e,!q):void 0;return void 0!==i?i:A.attributes||!q?t.getAttribute(e):(i=t.getAttributeNode(e))&&i.specified?i.value:null},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,n=[],i=0,o=0;if(N=!A.detectDuplicates,O=!A.sortStable&&t.slice(0),t.sort($),N){for(;e=t[o++];)e===t[o]&&(i=n.push(o));for(;i--;)t.splice(n[i],1)}return O=null,t},z=e.getText=function(t){var e,n="",i=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=z(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[i++];)n+=z(e);return n},w=e.selectors={cacheLength:50,createPseudo:i,match:ft,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(At,wt),t[3]=(t[3]||t[4]||t[5]||"").replace(At,wt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return ft.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&dt.test(n)&&(e=T(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(At,wt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=H[t+" "];return e||(e=new RegExp("(^|"+nt+")"+t+"("+nt+"|$)"))&&H(t,function(t){return e.test("string"==typeof t.className&&t.className||"undefined"!=typeof t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,n,i){return function(o){var r=e.attr(o,t);return null==r?"!="===n:!n||(r+="","="===n?r===i:"!="===n?r!==i:"^="===n?i&&0===r.indexOf(i):"*="===n?i&&r.indexOf(i)>-1:"$="===n?i&&r.slice(-i.length)===i:"~="===n?(" "+r.replace(at," ")+" ").indexOf(i)>-1:"|="===n&&(r===i||r.slice(0,i.length+1)===i+"-"))}},CHILD:function(t,e,n,i,o){var r="nth"!==t.slice(0,3),s="last"!==t.slice(-4),a="of-type"===e;return 1===i&&0===o?function(t){return!!t.parentNode}:function(e,n,c){var l,u,h,d,p,f,m=r!==s?"nextSibling":"previousSibling",g=e.parentNode,b=a&&e.nodeName.toLowerCase(),v=!c&&!a;if(g){if(r){for(;m;){for(h=e;h=h[m];)if(a?h.nodeName.toLowerCase()===b:1===h.nodeType)return!1;f=m="only"===t&&!f&&"nextSibling"}return!0}if(f=[s?g.firstChild:g.lastChild],s&&v){for(u=g[P]||(g[P]={}),l=u[t]||[],p=l[0]===X&&l[1],d=l[0]===X&&l[2],h=p&&g.childNodes[p];h=++p&&h&&h[m]||(d=p=0)||f.pop();)if(1===h.nodeType&&++d&&h===e){u[t]=[X,p,d];break}}else if(v&&(l=(e[P]||(e[P]={}))[t])&&l[0]===X)d=l[1];else for(;(h=++p&&h&&h[m]||(d=p=0)||f.pop())&&((a?h.nodeName.toLowerCase()!==b:1!==h.nodeType)||!++d||(v&&((h[P]||(h[P]={}))[t]=[X,d]),h!==e)););return d-=o,d===i||d%i===0&&d/i>=0}}},PSEUDO:function(t,n){var o,r=w.pseudos[t]||w.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return r[P]?r(n):r.length>1?(o=[t,t,"",n],w.setFilters.hasOwnProperty(t.toLowerCase())?i(function(t,e){for(var i,o=r(t,n),s=o.length;s--;)i=tt(t,o[s]),t[i]=!(e[i]=o[s])}):function(t){return r(t,0,o)}):r}},pseudos:{not:i(function(t){var e=[],n=[],o=x(t.replace(ct,"$1"));return o[P]?i(function(t,e,n,i){for(var r,s=o(t,null,i,[]),a=t.length;a--;)(r=s[a])&&(t[a]=!(e[a]=r))}):function(t,i,r){return e[0]=t,o(e,null,r,n),e[0]=null,!n.pop()}}),has:i(function(t){return function(n){return e(t,n).length>0}}),contains:i(function(t){return t=t.replace(At,wt),function(e){return(e.textContent||e.innerText||z(e)).indexOf(t)>-1}}),lang:i(function(t){return pt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(At,wt).toLowerCase(),function(e){var n;do if(n=q?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return n=n.toLowerCase(),n===t||0===n.indexOf(t+"-");while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===D},focus:function(t){return t===k.activeElement&&(!k.hasFocus||k.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:function(t){return t.disabled===!1},disabled:function(t){return t.disabled===!0},checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,t.selected===!0},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!w.pseudos.empty(t)},header:function(t){return gt.test(t.nodeName)},input:function(t){return mt.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:l(function(){return[0]}),last:l(function(t,e){return[e-1]}),eq:l(function(t,e,n){return[n<0?n+e:n]}),even:l(function(t,e){for(var n=0;n=0;)t.push(i);return t}),gt:l(function(t,e,n){for(var i=n<0?n+e:n;++i2&&"ID"===(s=r[0]).type&&A.getById&&9===e.nodeType&&q&&w.relative[r[1].type]){if(e=(w.find.ID(s.matches[0].replace(At,wt),e)||[])[0],!e)return n;l&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(o=ft.needsContext.test(t)?0:r.length;o--&&(s=r[o],!w.relative[a=s.type]);)if((c=w.find[a])&&(i=c(s.matches[0].replace(At,wt),Mt.test(r[0].type)&&u(e.parentNode)||e))){if(r.splice(o,1),t=i.length&&d(r),!t)return Q.apply(n,i),n;break}}return(l||x(t,h))(i,e,!q,n,Mt.test(t)&&u(e.parentNode)||e),n},A.sortStable=P.split("").sort($).join("")===P,A.detectDuplicates=!!N,L(),A.sortDetached=o(function(t){return 1&t.compareDocumentPosition(k.createElement("div"))}),o(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||r("type|href|height|width",function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),A.attributes&&o(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||r("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),o(function(t){return null==t.getAttribute("disabled")})||r(et,function(t,e,n){var i;if(!n)return t[e]===!0?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null}),e}(t);ot.find=lt,ot.expr=lt.selectors,ot.expr[":"]=ot.expr.pseudos,ot.unique=lt.uniqueSort,ot.text=lt.getText,ot.isXMLDoc=lt.isXML,ot.contains=lt.contains;var ut=ot.expr.match.needsContext,ht=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,dt=/^.[^:#\[\.,]*$/;ot.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?ot.find.matchesSelector(i,t)?[i]:[]:ot.find.matches(t,ot.grep(e,function(t){return 1===t.nodeType}))},ot.fn.extend({find:function(t){var e,n=[],i=this,o=i.length;if("string"!=typeof t)return this.pushStack(ot(t).filter(function(){for(e=0;e1?ot.unique(n):n),n.selector=this.selector?this.selector+" "+t:t,n},filter:function(t){return this.pushStack(i(this,t||[],!1))},not:function(t){return this.pushStack(i(this,t||[],!0))},is:function(t){return!!i(this,"string"==typeof t&&ut.test(t)?ot(t):t||[],!1).length}});var pt,ft=t.document,mt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,gt=ot.fn.init=function(t,e){var n,i;if(!t)return this;if("string"==typeof t){if(n="<"===t.charAt(0)&&">"===t.charAt(t.length-1)&&t.length>=3?[null,t,null]:mt.exec(t),!n||!n[1]&&e)return!e||e.jquery?(e||pt).find(t):this.constructor(e).find(t);if(n[1]){if(e=e instanceof ot?e[0]:e,ot.merge(this,ot.parseHTML(n[1],e&&e.nodeType?e.ownerDocument||e:ft,!0)),ht.test(n[1])&&ot.isPlainObject(e))for(n in e)ot.isFunction(this[n])?this[n](e[n]):this.attr(n,e[n]);return this}if(i=ft.getElementById(n[2]),i&&i.parentNode){if(i.id!==n[2])return pt.find(t);this.length=1,this[0]=i}return this.context=ft,this.selector=t,this}return t.nodeType?(this.context=this[0]=t,this.length=1,this):ot.isFunction(t)?"undefined"!=typeof pt.ready?pt.ready(t):t(ot):(void 0!==t.selector&&(this.selector=t.selector,this.context=t.context),ot.makeArray(t,this))};gt.prototype=ot.fn,pt=ot(ft);var bt=/^(?:parents|prev(?:Until|All))/,vt={children:!0,contents:!0,next:!0,prev:!0};ot.extend({dir:function(t,e,n){for(var i=[],o=t[e];o&&9!==o.nodeType&&(void 0===n||1!==o.nodeType||!ot(o).is(n));)1===o.nodeType&&i.push(o),o=o[e];return i},sibling:function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n}}),ot.fn.extend({has:function(t){var e,n=ot(t,this),i=n.length;return this.filter(function(){for(e=0;e-1:1===n.nodeType&&ot.find.matchesSelector(n,t))){r.push(n);break}return this.pushStack(r.length>1?ot.unique(r):r)},index:function(t){return t?"string"==typeof t?ot.inArray(this[0],ot(t)):ot.inArray(t.jquery?t[0]:t,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(ot.unique(ot.merge(this.get(),ot(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),ot.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return ot.dir(t,"parentNode")},parentsUntil:function(t,e,n){return ot.dir(t,"parentNode",n)},next:function(t){return o(t,"nextSibling")},prev:function(t){return o(t,"previousSibling")},nextAll:function(t){return ot.dir(t,"nextSibling")},prevAll:function(t){return ot.dir(t,"previousSibling")},nextUntil:function(t,e,n){return ot.dir(t,"nextSibling",n)},prevUntil:function(t,e,n){return ot.dir(t,"previousSibling",n)},siblings:function(t){return ot.sibling((t.parentNode||{}).firstChild,t)},children:function(t){return ot.sibling(t.firstChild)},contents:function(t){return ot.nodeName(t,"iframe")?t.contentDocument||t.contentWindow.document:ot.merge([],t.childNodes)}},function(t,e){ot.fn[t]=function(n,i){var o=ot.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(o=ot.filter(i,o)),this.length>1&&(vt[t]||(o=ot.unique(o)),bt.test(t)&&(o=o.reverse())),this.pushStack(o)}});var Mt=/\S+/g,yt={};ot.Callbacks=function(t){t="string"==typeof t?yt[t]||r(t):ot.extend({},t);var e,n,i,o,s,a,c=[],l=!t.once&&[],u=function(r){for(n=t.memory&&r,i=!0,s=a||0,a=0,o=c.length,e=!0;c&&s-1;)c.splice(i,1),e&&(i<=o&&o--,i<=s&&s--)}),this},has:function(t){return t?ot.inArray(t,c)>-1:!(!c||!c.length)},empty:function(){return c=[],o=0,this},disable:function(){return c=l=n=void 0,this},disabled:function(){return!c},lock:function(){return l=void 0,n||h.disable(),this},locked:function(){return!l},fireWith:function(t,n){return!c||i&&!l||(n=n||[],n=[t,n.slice?n.slice():n],e?l.push(n):u(n)),this},fire:function(){return h.fireWith(this,arguments),this},fired:function(){return!!i}};return h},ot.extend({Deferred:function(t){var e=[["resolve","done",ot.Callbacks("once memory"),"resolved"],["reject","fail",ot.Callbacks("once memory"),"rejected"],["notify","progress",ot.Callbacks("memory")]],n="pending",i={state:function(){return n},always:function(){return o.done(arguments).fail(arguments),this},then:function(){var t=arguments;return ot.Deferred(function(n){ot.each(e,function(e,r){var s=ot.isFunction(t[e])&&t[e];o[r[1]](function(){var t=s&&s.apply(this,arguments);t&&ot.isFunction(t.promise)?t.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[r[0]+"With"](this===i?n.promise():this,s?[t]:arguments)})}),t=null}).promise()},promise:function(t){return null!=t?ot.extend(t,i):i}},o={};return i.pipe=i.then,ot.each(e,function(t,r){var s=r[2],a=r[3];i[r[1]]=s.add,a&&s.add(function(){n=a},e[1^t][2].disable,e[2][2].lock),o[r[0]]=function(){return o[r[0]+"With"](this===o?i:this,arguments),this},o[r[0]+"With"]=s.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(t){var e,n,i,o=0,r=J.call(arguments),s=r.length,a=1!==s||t&&ot.isFunction(t.promise)?s:0,c=1===a?t:ot.Deferred(),l=function(t,n,i){return function(o){n[t]=this,i[t]=arguments.length>1?J.call(arguments):o,i===e?c.notifyWith(n,i):--a||c.resolveWith(n,i)}};if(s>1)for(e=new Array(s),n=new Array(s),i=new Array(s);o0||(At.resolveWith(ft,[ot]),ot.fn.triggerHandler&&(ot(ft).triggerHandler("ready"),ot(ft).off("ready")))}}}),ot.ready.promise=function(e){if(!At)if(At=ot.Deferred(),"complete"===ft.readyState)setTimeout(ot.ready);else if(ft.addEventListener)ft.addEventListener("DOMContentLoaded",a,!1),t.addEventListener("load",a,!1);else{ft.attachEvent("onreadystatechange",a),t.attachEvent("onload",a);var n=!1;try{n=null==t.frameElement&&ft.documentElement}catch(i){}n&&n.doScroll&&!function o(){if(!ot.isReady){try{n.doScroll("left")}catch(t){return setTimeout(o,50)}s(),ot.ready()}}()}return At.promise(e)};var wt,zt="undefined";for(wt in ot(nt))break;nt.ownLast="0"!==wt,nt.inlineBlockNeedsLayout=!1,ot(function(){var t,e,n,i;n=ft.getElementsByTagName("body")[0],n&&n.style&&(e=ft.createElement("div"),i=ft.createElement("div"),i.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",n.appendChild(i).appendChild(e),typeof e.style.zoom!==zt&&(e.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",nt.inlineBlockNeedsLayout=t=3===e.offsetWidth,t&&(n.style.zoom=1)),n.removeChild(i))}),function(){var t=ft.createElement("div");if(null==nt.deleteExpando){nt.deleteExpando=!0;try{delete t.test}catch(e){nt.deleteExpando=!1}}t=null}(),ot.acceptData=function(t){var e=ot.noData[(t.nodeName+" ").toLowerCase()],n=+t.nodeType||1;return(1===n||9===n)&&(!e||e!==!0&&t.getAttribute("classid")===e)};var _t=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Tt=/([A-Z])/g;ot.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(t){return t=t.nodeType?ot.cache[t[ot.expando]]:t[ot.expando],!!t&&!l(t)},data:function(t,e,n){return u(t,e,n)},removeData:function(t,e){return h(t,e)},_data:function(t,e,n){return u(t,e,n,!0)},_removeData:function(t,e){return h(t,e,!0)}}),ot.fn.extend({data:function(t,e){var n,i,o,r=this[0],s=r&&r.attributes;if(void 0===t){if(this.length&&(o=ot.data(r),1===r.nodeType&&!ot._data(r,"parsedAttrs"))){for(n=s.length;n--;)s[n]&&(i=s[n].name,0===i.indexOf("data-")&&(i=ot.camelCase(i.slice(5)),c(r,i,o[i])));ot._data(r,"parsedAttrs",!0)}return o}return"object"==typeof t?this.each(function(){ot.data(this,t)}):arguments.length>1?this.each(function(){ot.data(this,t,e)}):r?c(r,t,ot.data(r,t)):void 0},removeData:function(t){return this.each(function(){ot.removeData(this,t)})}}),ot.extend({queue:function(t,e,n){var i;if(t)return e=(e||"fx")+"queue",i=ot._data(t,e),n&&(!i||ot.isArray(n)?i=ot._data(t,e,ot.makeArray(n)):i.push(n)),i||[]},dequeue:function(t,e){e=e||"fx";var n=ot.queue(t,e),i=n.length,o=n.shift(),r=ot._queueHooks(t,e),s=function(){ot.dequeue(t,e)};"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete r.stop,o.call(t,s,r)),!i&&r&&r.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return ot._data(t,n)||ot._data(t,n,{empty:ot.Callbacks("once memory").add(function(){ot._removeData(t,e+"queue"),ot._removeData(t,n)})})}}),ot.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length
    a",nt.leadingWhitespace=3===e.firstChild.nodeType,nt.tbody=!e.getElementsByTagName("tbody").length,nt.htmlSerialize=!!e.getElementsByTagName("link").length,nt.html5Clone="<:nav>"!==ft.createElement("nav").cloneNode(!0).outerHTML,t.type="checkbox",t.checked=!0,n.appendChild(t),nt.appendChecked=t.checked,e.innerHTML="",nt.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,n.appendChild(e),e.innerHTML="",nt.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,nt.noCloneEvent=!0,e.attachEvent&&(e.attachEvent("onclick",function(){nt.noCloneEvent=!1}),e.cloneNode(!0).click()),null==nt.deleteExpando){nt.deleteExpando=!0;try{delete e.test}catch(i){nt.deleteExpando=!1}}}(),function(){var e,n,i=ft.createElement("div");for(e in{submit:!0,change:!0,focusin:!0})n="on"+e,(nt[e+"Bubbles"]=n in t)||(i.setAttribute(n,"t"),nt[e+"Bubbles"]=i.attributes[n].expando===!1);i=null}();var Lt=/^(?:input|select|textarea)$/i,kt=/^key/,Dt=/^(?:mouse|pointer|contextmenu)|click/,qt=/^(?:focusinfocus|focusoutblur)$/,Wt=/^([^.]*)(?:\.(.+)|)$/;ot.event={global:{},add:function(t,e,n,i,o){var r,s,a,c,l,u,h,d,p,f,m,g=ot._data(t);if(g){for(n.handler&&(c=n,n=c.handler,o=c.selector),n.guid||(n.guid=ot.guid++),(s=g.events)||(s=g.events={}),(u=g.handle)||(u=g.handle=function(t){return typeof ot===zt||t&&ot.event.triggered===t.type?void 0:ot.event.dispatch.apply(u.elem,arguments)},u.elem=t),e=(e||"").match(Mt)||[""],a=e.length;a--;)r=Wt.exec(e[a])||[],p=m=r[1],f=(r[2]||"").split(".").sort(),p&&(l=ot.event.special[p]||{},p=(o?l.delegateType:l.bindType)||p,l=ot.event.special[p]||{},h=ot.extend({type:p,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&ot.expr.match.needsContext.test(o),namespace:f.join(".")},c),(d=s[p])||(d=s[p]=[],d.delegateCount=0,l.setup&&l.setup.call(t,i,f,u)!==!1||(t.addEventListener?t.addEventListener(p,u,!1):t.attachEvent&&t.attachEvent("on"+p,u))),l.add&&(l.add.call(t,h),h.handler.guid||(h.handler.guid=n.guid)),o?d.splice(d.delegateCount++,0,h):d.push(h),ot.event.global[p]=!0);t=null}},remove:function(t,e,n,i,o){var r,s,a,c,l,u,h,d,p,f,m,g=ot.hasData(t)&&ot._data(t);if(g&&(u=g.events)){for(e=(e||"").match(Mt)||[""],l=e.length;l--;)if(a=Wt.exec(e[l])||[],p=m=a[1],f=(a[2]||"").split(".").sort(),p){for(h=ot.event.special[p]||{},p=(i?h.delegateType:h.bindType)||p,d=u[p]||[],a=a[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),c=r=d.length;r--;)s=d[r],!o&&m!==s.origType||n&&n.guid!==s.guid||a&&!a.test(s.namespace)||i&&i!==s.selector&&("**"!==i||!s.selector)||(d.splice(r,1),s.selector&&d.delegateCount--,h.remove&&h.remove.call(t,s));c&&!d.length&&(h.teardown&&h.teardown.call(t,f,g.handle)!==!1||ot.removeEvent(t,p,g.handle),delete u[p])}else for(p in u)ot.event.remove(t,p+e[l],n,i,!0);ot.isEmptyObject(u)&&(delete g.handle,ot._removeData(t,"events"))}},trigger:function(e,n,i,o){var r,s,a,c,l,u,h,d=[i||ft],p=et.call(e,"type")?e.type:e,f=et.call(e,"namespace")?e.namespace.split("."):[];if(a=u=i=i||ft,3!==i.nodeType&&8!==i.nodeType&&!qt.test(p+ot.event.triggered)&&(p.indexOf(".")>=0&&(f=p.split("."),p=f.shift(),f.sort()),s=p.indexOf(":")<0&&"on"+p,e=e[ot.expando]?e:new ot.Event(p,"object"==typeof e&&e),e.isTrigger=o?2:3,e.namespace=f.join("."),e.namespace_re=e.namespace?new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=i),n=null==n?[e]:ot.makeArray(n,[e]),l=ot.event.special[p]||{},o||!l.trigger||l.trigger.apply(i,n)!==!1)){if(!o&&!l.noBubble&&!ot.isWindow(i)){for(c=l.delegateType||p,qt.test(c+p)||(a=a.parentNode);a;a=a.parentNode)d.push(a),u=a;u===(i.ownerDocument||ft)&&d.push(u.defaultView||u.parentWindow||t)}for(h=0;(a=d[h++])&&!e.isPropagationStopped();)e.type=h>1?c:l.bindType||p,r=(ot._data(a,"events")||{})[e.type]&&ot._data(a,"handle"),r&&r.apply(a,n),r=s&&a[s],r&&r.apply&&ot.acceptData(a)&&(e.result=r.apply(a,n),e.result===!1&&e.preventDefault());if(e.type=p,!o&&!e.isDefaultPrevented()&&(!l._default||l._default.apply(d.pop(),n)===!1)&&ot.acceptData(i)&&s&&i[p]&&!ot.isWindow(i)){ -u=i[s],u&&(i[s]=null),ot.event.triggered=p;try{i[p]()}catch(m){}ot.event.triggered=void 0,u&&(i[s]=u)}return e.result}},dispatch:function(t){t=ot.event.fix(t);var e,n,i,o,r,s=[],a=J.call(arguments),c=(ot._data(this,"events")||{})[t.type]||[],l=ot.event.special[t.type]||{};if(a[0]=t,t.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,t)!==!1){for(s=ot.event.handlers.call(this,t,c),e=0;(o=s[e++])&&!t.isPropagationStopped();)for(t.currentTarget=o.elem,r=0;(i=o.handlers[r++])&&!t.isImmediatePropagationStopped();)t.namespace_re&&!t.namespace_re.test(i.namespace)||(t.handleObj=i,t.data=i.data,n=((ot.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,a),void 0!==n&&(t.result=n)===!1&&(t.preventDefault(),t.stopPropagation()));return l.postDispatch&&l.postDispatch.call(this,t),t.result}},handlers:function(t,e){var n,i,o,r,s=[],a=e.delegateCount,c=t.target;if(a&&c.nodeType&&(!t.button||"click"!==t.type))for(;c!=this;c=c.parentNode||this)if(1===c.nodeType&&(c.disabled!==!0||"click"!==t.type)){for(o=[],r=0;r=0:ot.find(n,this,null,[c]).length),o[n]&&o.push(i);o.length&&s.push({elem:c,handlers:o})}return a]","i"),Pt=/^\s+/,Rt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Xt=/<([\w:]+)/,Ft=/\s*$/g,Jt={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:nt.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},Gt=m(ft),Kt=Gt.appendChild(ft.createElement("div"));Jt.optgroup=Jt.option,Jt.tbody=Jt.tfoot=Jt.colgroup=Jt.caption=Jt.thead,Jt.th=Jt.td,ot.extend({clone:function(t,e,n){var i,o,r,s,a,c=ot.contains(t.ownerDocument,t);if(nt.html5Clone||ot.isXMLDoc(t)||!It.test("<"+t.nodeName+">")?r=t.cloneNode(!0):(Kt.innerHTML=t.outerHTML,Kt.removeChild(r=Kt.firstChild)),!(nt.noCloneEvent&&nt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||ot.isXMLDoc(t)))for(i=g(r),a=g(t),s=0;null!=(o=a[s]);++s)i[s]&&z(o,i[s]);if(e)if(n)for(a=a||g(t),i=i||g(r),s=0;null!=(o=a[s]);s++)w(o,i[s]);else w(t,r);return i=g(r,"script"),i.length>0&&A(i,!c&&g(t,"script")),i=a=o=null,r},buildFragment:function(t,e,n,i){for(var o,r,s,a,c,l,u,h=t.length,d=m(e),p=[],f=0;f")+u[2],o=u[0];o--;)a=a.lastChild;if(!nt.leadingWhitespace&&Pt.test(r)&&p.push(e.createTextNode(Pt.exec(r)[0])),!nt.tbody)for(r="table"!==c||Ft.test(r)?""!==u[1]||Ft.test(r)?0:a:a.firstChild,o=r&&r.childNodes.length;o--;)ot.nodeName(l=r.childNodes[o],"tbody")&&!l.childNodes.length&&r.removeChild(l);for(ot.merge(p,a.childNodes),a.textContent="";a.firstChild;)a.removeChild(a.firstChild);a=d.lastChild}else p.push(e.createTextNode(r));for(a&&d.removeChild(a),nt.appendChecked||ot.grep(g(p,"input"),b),f=0;r=p[f++];)if((!i||ot.inArray(r,i)===-1)&&(s=ot.contains(r.ownerDocument,r),a=g(d.appendChild(r),"script"),s&&A(a),n))for(o=0;r=a[o++];)$t.test(r.type||"")&&n.push(r);return a=null,d},cleanData:function(t,e){for(var n,i,o,r,s=0,a=ot.expando,c=ot.cache,l=nt.deleteExpando,u=ot.event.special;null!=(n=t[s]);s++)if((e||ot.acceptData(n))&&(o=n[a],r=o&&c[o])){if(r.events)for(i in r.events)u[i]?ot.event.remove(n,i):ot.removeEvent(n,i,r.handle);c[o]&&(delete c[o],l?delete n[a]:typeof n.removeAttribute!==zt?n.removeAttribute(a):n[a]=null,Y.push(o))}}}),ot.fn.extend({text:function(t){return Ot(this,function(t){return void 0===t?ot.text(this):this.empty().append((this[0]&&this[0].ownerDocument||ft).createTextNode(t))},null,t,arguments.length)},append:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=v(this,t);e.appendChild(t)}})},prepend:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=v(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},remove:function(t,e){for(var n,i=t?ot.filter(t,this):this,o=0;null!=(n=i[o]);o++)e||1!==n.nodeType||ot.cleanData(g(n)),n.parentNode&&(e&&ot.contains(n.ownerDocument,n)&&A(g(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){for(var t,e=0;null!=(t=this[e]);e++){for(1===t.nodeType&&ot.cleanData(g(t,!1));t.firstChild;)t.removeChild(t.firstChild);t.options&&ot.nodeName(t,"select")&&(t.options.length=0)}return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return ot.clone(this,t,e)})},html:function(t){return Ot(this,function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t)return 1===e.nodeType?e.innerHTML.replace(Bt,""):void 0;if("string"==typeof t&&!jt.test(t)&&(nt.htmlSerialize||!It.test(t))&&(nt.leadingWhitespace||!Pt.test(t))&&!Jt[(Xt.exec(t)||["",""])[1].toLowerCase()]){t=t.replace(Rt,"<$1>");try{for(;n1&&"string"==typeof d&&!nt.checkClone&&Ut.test(d))return this.each(function(n){var i=u.eq(n);p&&(t[0]=d.call(this,n,i.html())),i.domManip(t,e)});if(l&&(a=ot.buildFragment(t,this[0].ownerDocument,!1,this),n=a.firstChild,1===a.childNodes.length&&(a=n),n)){for(r=ot.map(g(a,"script"),M),o=r.length;c
    t
    ",o=e.getElementsByTagName("td"),o[0].style.cssText="margin:0;border:0;padding:0;display:none",a=0===o[0].offsetHeight,a&&(o[0].style.display="",o[1].style.display="none",a=0===o[0].offsetHeight),n.removeChild(i))}var n,i,o,r,s,a,c;n=ft.createElement("div"),n.innerHTML="
    a",o=n.getElementsByTagName("a")[0],i=o&&o.style,i&&(i.cssText="float:left;opacity:.5",nt.opacity="0.5"===i.opacity,nt.cssFloat=!!i.cssFloat,n.style.backgroundClip="content-box",n.cloneNode(!0).style.backgroundClip="",nt.clearCloneStyle="content-box"===n.style.backgroundClip,nt.boxSizing=""===i.boxSizing||""===i.MozBoxSizing||""===i.WebkitBoxSizing,ot.extend(nt,{reliableHiddenOffsets:function(){return null==a&&e(),a},boxSizingReliable:function(){return null==s&&e(),s},pixelPosition:function(){return null==r&&e(),r},reliableMarginRight:function(){return null==c&&e(),c}}))}(),ot.swap=function(t,e,n,i){var o,r,s={};for(r in e)s[r]=t.style[r],t.style[r]=e[r];o=n.apply(t,i||[]);for(r in e)t.style[r]=s[r];return o};var re=/alpha\([^)]*\)/i,se=/opacity\s*=\s*([^)]*)/,ae=/^(none|table(?!-c[ea]).+)/,ce=new RegExp("^("+xt+")(.*)$","i"),le=new RegExp("^([+-])=("+xt+")","i"),ue={position:"absolute",visibility:"hidden",display:"block"},he={letterSpacing:"0",fontWeight:"400"},de=["Webkit","O","Moz","ms"];ot.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=ee(t,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":nt.cssFloat?"cssFloat":"styleFloat"},style:function(t,e,n,i){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var o,r,s,a=ot.camelCase(e),c=t.style;if(e=ot.cssProps[a]||(ot.cssProps[a]=C(c,a)),s=ot.cssHooks[e]||ot.cssHooks[a],void 0===n)return s&&"get"in s&&void 0!==(o=s.get(t,!1,i))?o:c[e];if(r=typeof n,"string"===r&&(o=le.exec(n))&&(n=(o[1]+1)*o[2]+parseFloat(ot.css(t,e)),r="number"),null!=n&&n===n&&("number"!==r||ot.cssNumber[a]||(n+="px"),nt.clearCloneStyle||""!==n||0!==e.indexOf("background")||(c[e]="inherit"),!(s&&"set"in s&&void 0===(n=s.set(t,n,i)))))try{c[e]=n}catch(l){}}},css:function(t,e,n,i){var o,r,s,a=ot.camelCase(e);return e=ot.cssProps[a]||(ot.cssProps[a]=C(t.style,a)),s=ot.cssHooks[e]||ot.cssHooks[a],s&&"get"in s&&(r=s.get(t,!0,n)),void 0===r&&(r=ee(t,e,i)),"normal"===r&&e in he&&(r=he[e]),""===n||n?(o=parseFloat(r),n===!0||ot.isNumeric(o)?o||0:r):r}}),ot.each(["height","width"],function(t,e){ot.cssHooks[e]={get:function(t,n,i){if(n)return ae.test(ot.css(t,"display"))&&0===t.offsetWidth?ot.swap(t,ue,function(){return L(t,e,i)}):L(t,e,i)},set:function(t,n,i){var o=i&&te(t);return O(t,n,i?N(t,e,i,nt.boxSizing&&"border-box"===ot.css(t,"boxSizing",!1,o),o):0)}}}),nt.opacity||(ot.cssHooks.opacity={get:function(t,e){return se.test((e&&t.currentStyle?t.currentStyle.filter:t.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":e?"1":""},set:function(t,e){var n=t.style,i=t.currentStyle,o=ot.isNumeric(e)?"alpha(opacity="+100*e+")":"",r=i&&i.filter||n.filter||"";n.zoom=1,(e>=1||""===e)&&""===ot.trim(r.replace(re,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===e||i&&!i.filter)||(n.filter=re.test(r)?r.replace(re,o):r+" "+o)}}),ot.cssHooks.marginRight=x(nt.reliableMarginRight,function(t,e){if(e)return ot.swap(t,{display:"inline-block"},ee,[t,"marginRight"])}),ot.each({margin:"",padding:"",border:"Width"},function(t,e){ot.cssHooks[t+e]={expand:function(n){for(var i=0,o={},r="string"==typeof n?n.split(" "):[n];i<4;i++)o[t+Ct[i]+e]=r[i]||r[i-2]||r[0];return o}},ne.test(t)||(ot.cssHooks[t+e].set=O)}),ot.fn.extend({css:function(t,e){return Ot(this,function(t,e,n){var i,o,r={},s=0;if(ot.isArray(e)){for(i=te(t),o=e.length;s1)},show:function(){return S(this,!0)},hide:function(){return S(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){St(this)?ot(this).show():ot(this).hide()})}}),ot.Tween=k,k.prototype={constructor:k,init:function(t,e,n,i,o,r){this.elem=t,this.prop=n,this.easing=o||"swing",this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=r||(ot.cssNumber[n]?"":"px")},cur:function(){var t=k.propHooks[this.prop];return t&&t.get?t.get(this):k.propHooks._default.get(this)},run:function(t){var e,n=k.propHooks[this.prop];return this.options.duration?this.pos=e=ot.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):k.propHooks._default.set(this),this}},k.prototype.init.prototype=k.prototype,k.propHooks={_default:{get:function(t){var e;return null==t.elem[t.prop]||t.elem.style&&null!=t.elem.style[t.prop]?(e=ot.css(t.elem,t.prop,""),e&&"auto"!==e?e:0):t.elem[t.prop]},set:function(t){ot.fx.step[t.prop]?ot.fx.step[t.prop](t):t.elem.style&&(null!=t.elem.style[ot.cssProps[t.prop]]||ot.cssHooks[t.prop])?ot.style(t.elem,t.prop,t.now+t.unit):t.elem[t.prop]=t.now}}},k.propHooks.scrollTop=k.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},ot.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2}},ot.fx=k.prototype.init,ot.fx.step={};var pe,fe,me=/^(?:toggle|show|hide)$/,ge=new RegExp("^(?:([+-])=|)("+xt+")([a-z%]*)$","i"),be=/queueHooks$/,ve=[E],Me={"*":[function(t,e){var n=this.createTween(t,e),i=n.cur(),o=ge.exec(e),r=o&&o[3]||(ot.cssNumber[t]?"":"px"),s=(ot.cssNumber[t]||"px"!==r&&+i)&&ge.exec(ot.css(n.elem,t)),a=1,c=20;if(s&&s[3]!==r){r=r||s[3],o=o||[],s=+i||1;do a=a||".5",s/=a,ot.style(n.elem,t,s+r);while(a!==(a=n.cur()/i)&&1!==a&&--c)}return o&&(s=n.start=+s||+i||0,n.unit=r,n.end=o[1]?s+(o[1]+1)*o[2]:+o[2]),n}]};ot.Animation=ot.extend(I,{tweener:function(t,e){ot.isFunction(t)?(e=t,t=["*"]):t=t.split(" ");for(var n,i=0,o=t.length;i
    a",i=e.getElementsByTagName("a")[0],n=ft.createElement("select"),o=n.appendChild(ft.createElement("option")),t=e.getElementsByTagName("input")[0],i.style.cssText="top:1px",nt.getSetAttribute="t"!==e.className,nt.style=/top/.test(i.getAttribute("style")),nt.hrefNormalized="/a"===i.getAttribute("href"),nt.checkOn=!!t.value,nt.optSelected=o.selected,nt.enctype=!!ft.createElement("form").enctype,n.disabled=!0,nt.optDisabled=!o.disabled,t=ft.createElement("input"),t.setAttribute("value",""),nt.input=""===t.getAttribute("value"),t.value="t",t.setAttribute("type","radio"),nt.radioValue="t"===t.value}();var ye=/\r/g;ot.fn.extend({val:function(t){var e,n,i,o=this[0];{if(arguments.length)return i=ot.isFunction(t),this.each(function(n){var o;1===this.nodeType&&(o=i?t.call(this,n,ot(this).val()):t,null==o?o="":"number"==typeof o?o+="":ot.isArray(o)&&(o=ot.map(o,function(t){return null==t?"":t+""})),e=ot.valHooks[this.type]||ot.valHooks[this.nodeName.toLowerCase()],e&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))});if(o)return e=ot.valHooks[o.type]||ot.valHooks[o.nodeName.toLowerCase()],e&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:(n=o.value,"string"==typeof n?n.replace(ye,""):null==n?"":n)}}}),ot.extend({valHooks:{option:{get:function(t){var e=ot.find.attr(t,"value");return null!=e?e:ot.trim(ot.text(t))}},select:{get:function(t){for(var e,n,i=t.options,o=t.selectedIndex,r="select-one"===t.type||o<0,s=r?null:[],a=r?o+1:i.length,c=o<0?a:r?o:0;c=0)try{i.selected=n=!0}catch(a){i.scrollHeight}else i.selected=!1;return n||(t.selectedIndex=-1),o}}}}),ot.each(["radio","checkbox"],function(){ot.valHooks[this]={set:function(t,e){if(ot.isArray(e))return t.checked=ot.inArray(ot(t).val(),e)>=0}},nt.checkOn||(ot.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})});var Ae,we,ze=ot.expr.attrHandle,_e=/^(?:checked|selected)$/i,Te=nt.getSetAttribute,xe=nt.input;ot.fn.extend({attr:function(t,e){return Ot(this,ot.attr,t,e,arguments.length>1)},removeAttr:function(t){return this.each(function(){ot.removeAttr(this,t)})}}),ot.extend({attr:function(t,e,n){var i,o,r=t.nodeType;if(t&&3!==r&&8!==r&&2!==r)return typeof t.getAttribute===zt?ot.prop(t,e,n):(1===r&&ot.isXMLDoc(t)||(e=e.toLowerCase(),i=ot.attrHooks[e]||(ot.expr.match.bool.test(e)?we:Ae)),void 0===n?i&&"get"in i&&null!==(o=i.get(t,e))?o:(o=ot.find.attr(t,e),null==o?void 0:o):null!==n?i&&"set"in i&&void 0!==(o=i.set(t,n,e))?o:(t.setAttribute(e,n+""),n):void ot.removeAttr(t,e))},removeAttr:function(t,e){var n,i,o=0,r=e&&e.match(Mt);if(r&&1===t.nodeType)for(;n=r[o++];)i=ot.propFix[n]||n,ot.expr.match.bool.test(n)?xe&&Te||!_e.test(n)?t[i]=!1:t[ot.camelCase("default-"+n)]=t[i]=!1:ot.attr(t,n,""),t.removeAttribute(Te?n:i)},attrHooks:{type:{set:function(t,e){if(!nt.radioValue&&"radio"===e&&ot.nodeName(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}}}),we={set:function(t,e,n){return e===!1?ot.removeAttr(t,n):xe&&Te||!_e.test(n)?t.setAttribute(!Te&&ot.propFix[n]||n,n):t[ot.camelCase("default-"+n)]=t[n]=!0,n}},ot.each(ot.expr.match.bool.source.match(/\w+/g),function(t,e){var n=ze[e]||ot.find.attr;ze[e]=xe&&Te||!_e.test(e)?function(t,e,i){var o,r;return i||(r=ze[e],ze[e]=o,o=null!=n(t,e,i)?e.toLowerCase():null,ze[e]=r),o}:function(t,e,n){if(!n)return t[ot.camelCase("default-"+e)]?e.toLowerCase():null}}),xe&&Te||(ot.attrHooks.value={set:function(t,e,n){return ot.nodeName(t,"input")?void(t.defaultValue=e):Ae&&Ae.set(t,e,n)}}),Te||(Ae={set:function(t,e,n){var i=t.getAttributeNode(n);if(i||t.setAttributeNode(i=t.ownerDocument.createAttribute(n)),i.value=e+="","value"===n||e===t.getAttribute(n))return e}},ze.id=ze.name=ze.coords=function(t,e,n){var i;if(!n)return(i=t.getAttributeNode(e))&&""!==i.value?i.value:null},ot.valHooks.button={get:function(t,e){var n=t.getAttributeNode(e);if(n&&n.specified)return n.value},set:Ae.set},ot.attrHooks.contenteditable={set:function(t,e,n){Ae.set(t,""!==e&&e,n)}},ot.each(["width","height"],function(t,e){ot.attrHooks[e]={set:function(t,n){if(""===n)return t.setAttribute(e,"auto"),n}}})),nt.style||(ot.attrHooks.style={get:function(t){return t.style.cssText||void 0},set:function(t,e){return t.style.cssText=e+""}});var Ce=/^(?:input|select|textarea|button|object)$/i,Se=/^(?:a|area)$/i;ot.fn.extend({prop:function(t,e){return Ot(this,ot.prop,t,e,arguments.length>1)},removeProp:function(t){return t=ot.propFix[t]||t,this.each(function(){try{this[t]=void 0,delete this[t]}catch(e){}})}}),ot.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(t,e,n){var i,o,r,s=t.nodeType;if(t&&3!==s&&8!==s&&2!==s)return r=1!==s||!ot.isXMLDoc(t),r&&(e=ot.propFix[e]||e,o=ot.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:t[e]=n:o&&"get"in o&&null!==(i=o.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=ot.find.attr(t,"tabindex");return e?parseInt(e,10):Ce.test(t.nodeName)||Se.test(t.nodeName)&&t.href?0:-1}}}}),nt.hrefNormalized||ot.each(["href","src"],function(t,e){ot.propHooks[e]={get:function(t){return t.getAttribute(e,4)}}}),nt.optSelected||(ot.propHooks.selected={get:function(t){var e=t.parentNode;return e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex),null}}),ot.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){ot.propFix[this.toLowerCase()]=this}),nt.enctype||(ot.propFix.enctype="encoding");var Oe=/[\t\r\n\f]/g;ot.fn.extend({addClass:function(t){var e,n,i,o,r,s,a=0,c=this.length,l="string"==typeof t&&t;if(ot.isFunction(t))return this.each(function(e){ot(this).addClass(t.call(this,e,this.className))});if(l)for(e=(t||"").match(Mt)||[];a=0;)i=i.replace(" "+o+" "," ");s=t?ot.trim(i):"",n.className!==s&&(n.className=s)}return this},toggleClass:function(t,e){ -var n=typeof t;return"boolean"==typeof e&&"string"===n?e?this.addClass(t):this.removeClass(t):ot.isFunction(t)?this.each(function(n){ot(this).toggleClass(t.call(this,n,this.className,e),e)}):this.each(function(){if("string"===n)for(var e,i=0,o=ot(this),r=t.match(Mt)||[];e=r[i++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else n!==zt&&"boolean"!==n||(this.className&&ot._data(this,"__className__",this.className),this.className=this.className||t===!1?"":ot._data(this,"__className__")||"")})},hasClass:function(t){for(var e=" "+t+" ",n=0,i=this.length;n=0)return!0;return!1}}),ot.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(t,e){ot.fn[e]=function(t,n){return arguments.length>0?this.on(e,null,t,n):this.trigger(e)}}),ot.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)},bind:function(t,e,n){return this.on(t,null,e,n)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,n,i){return this.on(e,t,n,i)},undelegate:function(t,e,n){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",n)}});var Ne=ot.now(),Le=/\?/,ke=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;ot.parseJSON=function(e){if(t.JSON&&t.JSON.parse)return t.JSON.parse(e+"");var n,i=null,o=ot.trim(e+"");return o&&!ot.trim(o.replace(ke,function(t,e,o,r){return n&&e&&(i=0),0===i?t:(n=o||e,i+=!r-!o,"")}))?Function("return "+o)():ot.error("Invalid JSON: "+e)},ot.parseXML=function(e){var n,i;if(!e||"string"!=typeof e)return null;try{t.DOMParser?(i=new DOMParser,n=i.parseFromString(e,"text/xml")):(n=new ActiveXObject("Microsoft.XMLDOM"),n.async="false",n.loadXML(e))}catch(o){n=void 0}return n&&n.documentElement&&!n.getElementsByTagName("parsererror").length||ot.error("Invalid XML: "+e),n};var De,qe,We=/#.*$/,Ee=/([?&])_=[^&]*/,Be=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Ie=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Pe=/^(?:GET|HEAD)$/,Re=/^\/\//,Xe=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Fe={},He={},je="*/".concat("*");try{qe=location.href}catch(Ue){qe=ft.createElement("a"),qe.href="",qe=qe.href}De=Xe.exec(qe.toLowerCase())||[],ot.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qe,type:"GET",isLocal:Ie.test(De[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":je,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":ot.parseJSON,"text xml":ot.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?X(X(t,ot.ajaxSettings),e):X(ot.ajaxSettings,t)},ajaxPrefilter:P(Fe),ajaxTransport:P(He),ajax:function(t,e){function n(t,e,n,i){var o,u,b,v,y,w=e;2!==M&&(M=2,a&&clearTimeout(a),l=void 0,s=i||"",A.readyState=t>0?4:0,o=t>=200&&t<300||304===t,n&&(v=F(h,A,n)),v=H(h,v,A,o),o?(h.ifModified&&(y=A.getResponseHeader("Last-Modified"),y&&(ot.lastModified[r]=y),y=A.getResponseHeader("etag"),y&&(ot.etag[r]=y)),204===t||"HEAD"===h.type?w="nocontent":304===t?w="notmodified":(w=v.state,u=v.data,b=v.error,o=!b)):(b=w,!t&&w||(w="error",t<0&&(t=0))),A.status=t,A.statusText=(e||w)+"",o?f.resolveWith(d,[u,w,A]):f.rejectWith(d,[A,w,b]),A.statusCode(g),g=void 0,c&&p.trigger(o?"ajaxSuccess":"ajaxError",[A,h,o?u:b]),m.fireWith(d,[A,w]),c&&(p.trigger("ajaxComplete",[A,h]),--ot.active||ot.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var i,o,r,s,a,c,l,u,h=ot.ajaxSetup({},e),d=h.context||h,p=h.context&&(d.nodeType||d.jquery)?ot(d):ot.event,f=ot.Deferred(),m=ot.Callbacks("once memory"),g=h.statusCode||{},b={},v={},M=0,y="canceled",A={readyState:0,getResponseHeader:function(t){var e;if(2===M){if(!u)for(u={};e=Be.exec(s);)u[e[1].toLowerCase()]=e[2];e=u[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return 2===M?s:null},setRequestHeader:function(t,e){var n=t.toLowerCase();return M||(t=v[n]=v[n]||t,b[t]=e),this},overrideMimeType:function(t){return M||(h.mimeType=t),this},statusCode:function(t){var e;if(t)if(M<2)for(e in t)g[e]=[g[e],t[e]];else A.always(t[A.status]);return this},abort:function(t){var e=t||y;return l&&l.abort(e),n(0,e),this}};if(f.promise(A).complete=m.add,A.success=A.done,A.error=A.fail,h.url=((t||h.url||qe)+"").replace(We,"").replace(Re,De[1]+"//"),h.type=e.method||e.type||h.method||h.type,h.dataTypes=ot.trim(h.dataType||"*").toLowerCase().match(Mt)||[""],null==h.crossDomain&&(i=Xe.exec(h.url.toLowerCase()),h.crossDomain=!(!i||i[1]===De[1]&&i[2]===De[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(De[3]||("http:"===De[1]?"80":"443")))),h.data&&h.processData&&"string"!=typeof h.data&&(h.data=ot.param(h.data,h.traditional)),R(Fe,h,e,A),2===M)return A;c=ot.event&&h.global,c&&0===ot.active++&&ot.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Pe.test(h.type),r=h.url,h.hasContent||(h.data&&(r=h.url+=(Le.test(r)?"&":"?")+h.data,delete h.data),h.cache===!1&&(h.url=Ee.test(r)?r.replace(Ee,"$1_="+Ne++):r+(Le.test(r)?"&":"?")+"_="+Ne++)),h.ifModified&&(ot.lastModified[r]&&A.setRequestHeader("If-Modified-Since",ot.lastModified[r]),ot.etag[r]&&A.setRequestHeader("If-None-Match",ot.etag[r])),(h.data&&h.hasContent&&h.contentType!==!1||e.contentType)&&A.setRequestHeader("Content-Type",h.contentType),A.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+je+"; q=0.01":""):h.accepts["*"]);for(o in h.headers)A.setRequestHeader(o,h.headers[o]);if(h.beforeSend&&(h.beforeSend.call(d,A,h)===!1||2===M))return A.abort();y="abort";for(o in{success:1,error:1,complete:1})A[o](h[o]);if(l=R(He,h,e,A)){A.readyState=1,c&&p.trigger("ajaxSend",[A,h]),h.async&&h.timeout>0&&(a=setTimeout(function(){A.abort("timeout")},h.timeout));try{M=1,l.send(b,n)}catch(w){if(!(M<2))throw w;n(-1,w)}}else n(-1,"No Transport");return A},getJSON:function(t,e,n){return ot.get(t,e,n,"json")},getScript:function(t,e){return ot.get(t,void 0,e,"script")}}),ot.each(["get","post"],function(t,e){ot[e]=function(t,n,i,o){return ot.isFunction(n)&&(o=o||i,i=n,n=void 0),ot.ajax({url:t,type:e,dataType:o,data:n,success:i})}}),ot._evalUrl=function(t){return ot.ajax({url:t,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},ot.fn.extend({wrapAll:function(t){if(ot.isFunction(t))return this.each(function(e){ot(this).wrapAll(t.call(this,e))});if(this[0]){var e=ot(t,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstChild&&1===t.firstChild.nodeType;)t=t.firstChild;return t}).append(this)}return this},wrapInner:function(t){return ot.isFunction(t)?this.each(function(e){ot(this).wrapInner(t.call(this,e))}):this.each(function(){var e=ot(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=ot.isFunction(t);return this.each(function(n){ot(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(){return this.parent().each(function(){ot.nodeName(this,"body")||ot(this).replaceWith(this.childNodes)}).end()}}),ot.expr.filters.hidden=function(t){return t.offsetWidth<=0&&t.offsetHeight<=0||!nt.reliableHiddenOffsets()&&"none"===(t.style&&t.style.display||ot.css(t,"display"))},ot.expr.filters.visible=function(t){return!ot.expr.filters.hidden(t)};var $e=/%20/g,Ve=/\[\]$/,Ye=/\r?\n/g,Je=/^(?:submit|button|image|reset|file)$/i,Ge=/^(?:input|select|textarea|keygen)/i;ot.param=function(t,e){var n,i=[],o=function(t,e){e=ot.isFunction(e)?e():null==e?"":e,i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)};if(void 0===e&&(e=ot.ajaxSettings&&ot.ajaxSettings.traditional),ot.isArray(t)||t.jquery&&!ot.isPlainObject(t))ot.each(t,function(){o(this.name,this.value)});else for(n in t)j(n,t[n],e,o);return i.join("&").replace($e,"+")},ot.fn.extend({serialize:function(){return ot.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=ot.prop(this,"elements");return t?ot.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!ot(this).is(":disabled")&&Ge.test(this.nodeName)&&!Je.test(t)&&(this.checked||!Nt.test(t))}).map(function(t,e){var n=ot(this).val();return null==n?null:ot.isArray(n)?ot.map(n,function(t){return{name:e.name,value:t.replace(Ye,"\r\n")}}):{name:e.name,value:n.replace(Ye,"\r\n")}}).get()}}),ot.ajaxSettings.xhr=void 0!==t.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&U()||$()}:U;var Ke=0,Qe={},Ze=ot.ajaxSettings.xhr();t.attachEvent&&t.attachEvent("onunload",function(){for(var t in Qe)Qe[t](void 0,!0)}),nt.cors=!!Ze&&"withCredentials"in Ze,Ze=nt.ajax=!!Ze,Ze&&ot.ajaxTransport(function(t){if(!t.crossDomain||nt.cors){var e;return{send:function(n,i){var o,r=t.xhr(),s=++Ke;if(r.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)r[o]=t.xhrFields[o];t.mimeType&&r.overrideMimeType&&r.overrideMimeType(t.mimeType),t.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(o in n)void 0!==n[o]&&r.setRequestHeader(o,n[o]+"");r.send(t.hasContent&&t.data||null),e=function(n,o){var a,c,l;if(e&&(o||4===r.readyState))if(delete Qe[s],e=void 0,r.onreadystatechange=ot.noop,o)4!==r.readyState&&r.abort();else{l={},a=r.status,"string"==typeof r.responseText&&(l.text=r.responseText);try{c=r.statusText}catch(u){c=""}a||!t.isLocal||t.crossDomain?1223===a&&(a=204):a=l.text?200:404}l&&i(a,c,l,r.getAllResponseHeaders())},t.async?4===r.readyState?setTimeout(e):r.onreadystatechange=Qe[s]=e:e()},abort:function(){e&&e(void 0,!0)}}}}),ot.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(t){return ot.globalEval(t),t}}}),ot.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET",t.global=!1)}),ot.ajaxTransport("script",function(t){if(t.crossDomain){var e,n=ft.head||ot("head")[0]||ft.documentElement;return{send:function(i,o){e=ft.createElement("script"),e.async=!0,t.scriptCharset&&(e.charset=t.scriptCharset),e.src=t.url,e.onload=e.onreadystatechange=function(t,n){(n||!e.readyState||/loaded|complete/.test(e.readyState))&&(e.onload=e.onreadystatechange=null,e.parentNode&&e.parentNode.removeChild(e),e=null,n||o(200,"success"))},n.insertBefore(e,n.firstChild)},abort:function(){e&&e.onload(void 0,!0)}}}});var tn=[],en=/(=)\?(?=&|$)|\?\?/;ot.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var t=tn.pop()||ot.expando+"_"+Ne++;return this[t]=!0,t}}),ot.ajaxPrefilter("json jsonp",function(e,n,i){var o,r,s,a=e.jsonp!==!1&&(en.test(e.url)?"url":"string"==typeof e.data&&!(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&en.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return o=e.jsonpCallback=ot.isFunction(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(en,"$1"+o):e.jsonp!==!1&&(e.url+=(Le.test(e.url)?"&":"?")+e.jsonp+"="+o),e.converters["script json"]=function(){return s||ot.error(o+" was not called"),s[0]},e.dataTypes[0]="json",r=t[o],t[o]=function(){s=arguments},i.always(function(){t[o]=r,e[o]&&(e.jsonpCallback=n.jsonpCallback,tn.push(o)),s&&ot.isFunction(r)&&r(s[0]),s=r=void 0}),"script"}),ot.parseHTML=function(t,e,n){if(!t||"string"!=typeof t)return null;"boolean"==typeof e&&(n=e,e=!1),e=e||ft;var i=ht.exec(t),o=!n&&[];return i?[e.createElement(i[1])]:(i=ot.buildFragment([t],e,o),o&&o.length&&ot(o).remove(),ot.merge([],i.childNodes))};var nn=ot.fn.load;ot.fn.load=function(t,e,n){if("string"!=typeof t&&nn)return nn.apply(this,arguments);var i,o,r,s=this,a=t.indexOf(" ");return a>=0&&(i=ot.trim(t.slice(a,t.length)),t=t.slice(0,a)),ot.isFunction(e)?(n=e,e=void 0):e&&"object"==typeof e&&(r="POST"),s.length>0&&ot.ajax({url:t,type:r,dataType:"html",data:e}).done(function(t){o=arguments,s.html(i?ot("
    ").append(ot.parseHTML(t)).find(i):t)}).complete(n&&function(t,e){s.each(n,o||[t.responseText,e,t])}),this},ot.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(t,e){ot.fn[e]=function(t){return this.on(e,t)}}),ot.expr.filters.animated=function(t){return ot.grep(ot.timers,function(e){return t===e.elem}).length};var on=t.document.documentElement;ot.offset={setOffset:function(t,e,n){var i,o,r,s,a,c,l,u=ot.css(t,"position"),h=ot(t),d={};"static"===u&&(t.style.position="relative"),a=h.offset(),r=ot.css(t,"top"),c=ot.css(t,"left"),l=("absolute"===u||"fixed"===u)&&ot.inArray("auto",[r,c])>-1,l?(i=h.position(),s=i.top,o=i.left):(s=parseFloat(r)||0,o=parseFloat(c)||0),ot.isFunction(e)&&(e=e.call(t,n,a)),null!=e.top&&(d.top=e.top-a.top+s),null!=e.left&&(d.left=e.left-a.left+o),"using"in e?e.using.call(t,d):h.css(d)}},ot.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ot.offset.setOffset(this,t,e)});var e,n,i={top:0,left:0},o=this[0],r=o&&o.ownerDocument;if(r)return e=r.documentElement,ot.contains(e,o)?(typeof o.getBoundingClientRect!==zt&&(i=o.getBoundingClientRect()),n=V(r),{top:i.top+(n.pageYOffset||e.scrollTop)-(e.clientTop||0),left:i.left+(n.pageXOffset||e.scrollLeft)-(e.clientLeft||0)}):i},position:function(){if(this[0]){var t,e,n={top:0,left:0},i=this[0];return"fixed"===ot.css(i,"position")?e=i.getBoundingClientRect():(t=this.offsetParent(),e=this.offset(),ot.nodeName(t[0],"html")||(n=t.offset()),n.top+=ot.css(t[0],"borderTopWidth",!0),n.left+=ot.css(t[0],"borderLeftWidth",!0)),{top:e.top-n.top-ot.css(i,"marginTop",!0),left:e.left-n.left-ot.css(i,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||on;t&&!ot.nodeName(t,"html")&&"static"===ot.css(t,"position");)t=t.offsetParent;return t||on})}}),ot.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,e){var n=/Y/.test(e);ot.fn[t]=function(i){return Ot(this,function(t,i,o){var r=V(t);return void 0===o?r?e in r?r[e]:r.document.documentElement[i]:t[i]:void(r?r.scrollTo(n?ot(r).scrollLeft():o,n?o:ot(r).scrollTop()):t[i]=o)},t,i,arguments.length,null)}}),ot.each(["top","left"],function(t,e){ot.cssHooks[e]=x(nt.pixelPosition,function(t,n){if(n)return n=ee(t,e),ie.test(n)?ot(t).position()[e]+"px":n})}),ot.each({Height:"height",Width:"width"},function(t,e){ot.each({padding:"inner"+t,content:e,"":"outer"+t},function(n,i){ot.fn[i]=function(i,o){var r=arguments.length&&(n||"boolean"!=typeof i),s=n||(i===!0||o===!0?"margin":"border");return Ot(this,function(e,n,i){var o;return ot.isWindow(e)?e.document.documentElement["client"+t]:9===e.nodeType?(o=e.documentElement,Math.max(e.body["scroll"+t],o["scroll"+t],e.body["offset"+t],o["offset"+t],o["client"+t])):void 0===i?ot.css(e,n,s):ot.style(e,n,i,s)},e,r?i:void 0,r,null)}})}),ot.fn.size=function(){return this.length},ot.fn.andSelf=ot.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return ot});var rn=t.jQuery,sn=t.$;return ot.noConflict=function(e){return t.$===ot&&(t.$=sn),e&&t.jQuery===ot&&(t.jQuery=rn),ot},typeof e===zt&&(t.jQuery=t.$=ot),ot}),function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(t){function e(e,i){var o,r,s,a=e.nodeName.toLowerCase();return"area"===a?(o=e.parentNode,r=o.name,!(!e.href||!r||"map"!==o.nodeName.toLowerCase())&&(s=t("img[usemap='#"+r+"']")[0],!!s&&n(s))):(/input|select|textarea|button|object/.test(a)?!e.disabled:"a"===a?e.href||i:i)&&n(e)}function n(e){return t.expr.filters.visible(e)&&!t(e).parents().addBack().filter(function(){return"hidden"===t.css(this,"visibility")}).length}function i(t){for(var e,n;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(n=parseInt(t.css("zIndex"),10),!isNaN(n)&&0!==n))return n;t=t.parent()}return 0}function o(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=r(t("
    "))}function r(e){var n="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.delegate(n,"mouseout",function(){t(this).removeClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!==-1&&t(this).removeClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!==-1&&t(this).removeClass("ui-datepicker-next-hover")}).delegate(n,"mouseover",s)}function s(){t.datepicker._isDisabledDatepicker(b.inline?b.dpDiv.parent()[0]:b.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!==-1&&t(this).addClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!==-1&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,n){t.extend(e,n);for(var i in n)null==n[i]&&(e[i]=n[i]);return e}function c(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.extend(t.ui,{version:"1.11.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),t.fn.extend({scrollParent:function(e){var n=this.css("position"),i="absolute"===n,o=e?/(auto|scroll|hidden)/:/(auto|scroll)/,r=this.parents().filter(function(){var e=t(this);return(!i||"static"!==e.css("position"))&&o.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==n&&r.length?r:t(this[0].ownerDocument||document)},uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(n){return!!t.data(n,e)}}):function(e,n,i){return!!t.data(e,i[3])},focusable:function(n){return e(n,!isNaN(t.attr(n,"tabindex")))},tabbable:function(n){var i=t.attr(n,"tabindex"),o=isNaN(i);return(o||i>=0)&&e(n,!o)}}),t("").outerWidth(1).jquery||t.each(["Width","Height"],function(e,n){function i(e,n,i,r){return t.each(o,function(){n-=parseFloat(t.css(e,"padding"+this))||0,i&&(n-=parseFloat(t.css(e,"border"+this+"Width"))||0),r&&(n-=parseFloat(t.css(e,"margin"+this))||0)}),n}var o="Width"===n?["Left","Right"]:["Top","Bottom"],r=n.toLowerCase(),s={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+n]=function(e){return void 0===e?s["inner"+n].call(this):this.each(function(){t(this).css(r,i(this,e)+"px")})},t.fn["outer"+n]=function(e,o){return"number"!=typeof e?s["outer"+n].call(this,e):this.each(function(){t(this).css(r,i(this,e,!0,o)+"px")})}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t("").data("a-b","a").removeData("a-b").data("a-b")&&(t.fn.removeData=function(e){return function(n){return arguments.length?e.call(this,t.camelCase(n)):e.call(this)}}(t.fn.removeData)),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),t.fn.extend({focus:function(e){return function(n,i){return"number"==typeof n?this.each(function(){var e=this;setTimeout(function(){t(e).focus(),i&&i.call(e)},n)}):e.apply(this,arguments)}}(t.fn.focus),disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(e){if(void 0!==e)return this.css("zIndex",e);if(this.length)for(var n,i,o=t(this[0]);o.length&&o[0]!==document;){if(n=o.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(i=parseInt(o.css("zIndex"),10),!isNaN(i)&&0!==i))return i;o=o.parent()}return 0}}),t.ui.plugin={add:function(e,n,i){var o,r=t.ui[e].prototype;for(o in i)r.plugins[o]=r.plugins[o]||[],r.plugins[o].push([n,i[o]])},call:function(t,e,n,i){var o,r=t.plugins[e];if(r&&(i||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o",options:{disabled:!1,create:null},_createWidget:function(e,n){n=t(n||this.defaultElement||this)[0],this.element=t(n),this.uuid=l++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),n!==this&&(t.data(n,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===n&&this.destroy()}}),this.document=t(n.style?n.ownerDocument:n.document||n),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(e,n){var i,o,r,s=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(s={},i=e.split("."),e=i.shift(),i.length){for(o=s[e]=t.widget.extend({},this.options[e]),r=0;r=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}});!function(){function e(t,e,n){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?n/100:1)]; -}function n(e,n){return parseInt(t.css(e,n),10)||0}function i(e){var n=e[0];return 9===n.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(n)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:n.preventDefault?{width:0,height:0,offset:{top:n.pageY,left:n.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var o,r,s=Math.max,a=Math.abs,c=Math.round,l=/left|center|right/,u=/top|center|bottom/,h=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==o)return o;var e,n,i=t("
    "),r=i.children()[0];return t("body").append(i),e=r.offsetWidth,i.css("overflow","scroll"),n=r.offsetWidth,e===n&&(n=i[0].clientWidth),i.remove(),o=e-n},getScrollInfo:function(e){var n=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),i=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),o="scroll"===n||"auto"===n&&e.width0?"right":"center",vertical:r<0?"top":i>0?"bottom":"middle"};ms(a(i),a(r))?c.important="horizontal":c.important="vertical",o.using.call(this,t,c)}),u.offset(t.extend(C,{using:l}))})},t.ui.position={fit:{left:function(t,e){var n,i=e.within,o=i.isWindow?i.scrollLeft:i.offset.left,r=i.width,a=t.left-e.collisionPosition.marginLeft,c=o-a,l=a+e.collisionWidth-r-o;e.collisionWidth>r?c>0&&l<=0?(n=t.left+c+e.collisionWidth-r-o,t.left+=c-n):l>0&&c<=0?t.left=o:c>l?t.left=o+r-e.collisionWidth:t.left=o:c>0?t.left+=c:l>0?t.left-=l:t.left=s(t.left-a,t.left)},top:function(t,e){var n,i=e.within,o=i.isWindow?i.scrollTop:i.offset.top,r=e.within.height,a=t.top-e.collisionPosition.marginTop,c=o-a,l=a+e.collisionHeight-r-o;e.collisionHeight>r?c>0&&l<=0?(n=t.top+c+e.collisionHeight-r-o,t.top+=c-n):l>0&&c<=0?t.top=o:c>l?t.top=o+r-e.collisionHeight:t.top=o:c>0?t.top+=c:l>0?t.top-=l:t.top=s(t.top-a,t.top)}},flip:{left:function(t,e){var n,i,o=e.within,r=o.offset.left+o.scrollLeft,s=o.width,c=o.isWindow?o.scrollLeft:o.offset.left,l=t.left-e.collisionPosition.marginLeft,u=l-c,h=l+e.collisionWidth-s-c,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];u<0?(n=t.left+d+p+f+e.collisionWidth-s-r,(n<0||n0&&(i=t.left-e.collisionPosition.marginLeft+d+p+f-c,(i>0||a(i)u&&(i<0||i0&&(n=t.top-e.collisionPosition.marginTop+p+f+m-c,t.top+p+f+m>h&&(n>0||a(n)10&&o<11,e.innerHTML="",n.removeChild(e)}()}();t.ui.position,t.widget("ui.accordion",{version:"1.11.2",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),e.active<0&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e=this.options.icons;e&&(t("").addClass("ui-accordion-header-icon ui-icon "+e.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(e.header).addClass(e.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?void this._activate(e):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void("disabled"===t&&(this.element.toggleClass("ui-state-disabled",!!e).attr("aria-disabled",e),this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!e))))},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var n=t.ui.keyCode,i=this.headers.length,o=this.headers.index(e.target),r=!1;switch(e.keyCode){case n.RIGHT:case n.DOWN:r=this.headers[(o+1)%i];break;case n.LEFT:case n.UP:r=this.headers[(o-1+i)%i];break;case n.SPACE:case n.ENTER:this._eventHandler(e);break;case n.HOME:r=this.headers[0];break;case n.END:r=this.headers[i-1]}r&&(t(e.target).attr("tabIndex",-1),t(r).attr("tabIndex",0),r.focus(),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().focus()},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-state-default ui-corner-all"),this.panels=this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide(),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,n=this.options,i=n.heightStyle,o=this.element.parent();this.active=this._findActive(n.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(){var e=t(this),n=e.uniqueId().attr("id"),i=e.next(),o=i.uniqueId().attr("id");e.attr("aria-controls",o),i.attr("aria-labelledby",n)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(n.event),"fill"===i?(e=o.height(),this.element.siblings(":visible").each(function(){var n=t(this),i=n.css("position");"absolute"!==i&&"fixed"!==i&&(e-=n.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===i&&(e=0,this.headers.next().each(function(){e=Math.max(e,t(this).css("height","").height())}).height(e))},_activate:function(e){var n=this._findActive(e)[0];n!==this.active[0]&&(n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var n={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){n[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,n),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var n=this.options,i=this.active,o=t(e.currentTarget),r=o[0]===i[0],s=r&&n.collapsible,a=s?t():o.next(),c=i.next(),l={oldHeader:i,oldPanel:c,newHeader:s?t():o,newPanel:a};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,l)===!1||(n.active=!s&&this.headers.index(o),this.active=r?t():o,this._toggle(l),i.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),r||(o.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&o.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),o.next().addClass("ui-accordion-content-active")))},_toggle:function(e){var n=e.newPanel,i=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=i,this.options.animate?this._animate(n,i,e):(i.hide(),n.show(),this._toggleComplete(e)),i.attr({"aria-hidden":"true"}),i.prev().attr("aria-selected","false"),n.length&&i.length?i.prev().attr({tabIndex:-1,"aria-expanded":"false"}):n.length&&this.headers.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),n.attr("aria-hidden","false").prev().attr({"aria-selected":"true",tabIndex:0,"aria-expanded":"true"})},_animate:function(t,e,n){var i,o,r,s=this,a=0,c=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},items:"> *",menus:"ul",position:{my:"left-1 top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var n=t(e.target);!this.mouseHandled&&n.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),n.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var n=t(e.currentTarget);n.siblings(".ui-state-active").removeClass("ui-state-active"),this.focus(e,n)}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var n=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,n)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-menu-icons ui-front").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").removeUniqueId().removeClass("ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){var n,i,o,r,s=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:s=!1,i=this.previousFilter||"",o=String.fromCharCode(e.keyCode),r=!1,clearTimeout(this.filterTimer),o===i?r=!0:o=i+o,n=this._filterMenuItems(o),n=r&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(o=String.fromCharCode(e.keyCode),n=this._filterMenuItems(o)),n.length?(this.focus(e,n),this.previousFilter=o,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}s&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.is("[aria-haspopup='true']")?this.expand(t):this.select(t))},refresh:function(){var e,n,i=this,o=this.options.icons.submenu,r=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-front").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),n=e.parent(),i=t("").addClass("ui-menu-icon ui-icon "+o).data("ui-menu-submenu-carat",!0);n.attr("aria-haspopup","true").prepend(i),e.attr("aria-labelledby",n.attr("id"))}),e=r.add(this.element),n=e.find(this.options.items),n.not(".ui-menu-item").each(function(){var e=t(this);i._isDivider(e)&&e.addClass("ui-widget-content ui-menu-divider")}),n.not(".ui-menu-item, .ui-menu-divider").addClass("ui-menu-item").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),n.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),"disabled"===t&&this.element.toggleClass("ui-state-disabled",!!e).attr("aria-disabled",e),this._super(t,e)},focus:function(t,e){var n,i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.addClass("ui-state-focus").removeClass("ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),this.active.parent().closest(".ui-menu-item").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=e.children(".ui-menu"),n.length&&t&&/^mouse/.test(t.type)&&this._startOpening(n),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var n,i,o,r,s,a;this._hasScroll()&&(n=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,i=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,o=e.offset().top-this.activeMenu.offset().top-n-i,r=this.activeMenu.scrollTop(),s=this.activeMenu.height(),a=e.outerHeight(),o<0?this.activeMenu.scrollTop(r+o):o+a>s&&this.activeMenu.scrollTop(r+o-s+a))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var n=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(e,n){clearTimeout(this.timer),this.timer=this._delay(function(){var i=n?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));i.length||(i=this.element),this._close(i),this.blur(e),this.activeMenu=i},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find(".ui-state-active").not(".ui-state-focus").removeClass("ui-state-active")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,n){var i;this.active&&(i="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),i&&i.length&&this.active||(i=this.activeMenu.find(this.options.items)[e]()),this.focus(n,i)},nextPage:function(e){var n,i,o;return this.active?void(this.isLastItem()||(this._hasScroll()?(i=this.active.offset().top,o=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=t(this),n.offset().top-i-o<0}),this.focus(e,n)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]()))):void this.next(e)},previousPage:function(e){var n,i,o;return this.active?void(this.isFirstItem()||(this._hasScroll()?(i=this.active.offset().top,o=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=t(this),n.offset().top-i+o>0}),this.focus(e,n)):this.focus(e,this.activeMenu.find(this.options.items).first()))):void this.next(e)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,n,i,o=this.element[0].nodeName.toLowerCase(),r="textarea"===o,s="input"===o;this.isMultiLine=!!r||!s&&this.element.prop("isContentEditable"),this.valueMethod=this.element[r||s?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(o){if(this.element.prop("readOnly"))return e=!0,i=!0,void(n=!0);e=!1,i=!1,n=!1;var r=t.ui.keyCode;switch(o.keyCode){case r.PAGE_UP:e=!0,this._move("previousPage",o);break;case r.PAGE_DOWN:e=!0,this._move("nextPage",o);break;case r.UP:e=!0,this._keyEvent("previous",o);break;case r.DOWN:e=!0,this._keyEvent("next",o);break;case r.ENTER:this.menu.active&&(e=!0,o.preventDefault(),this.menu.select(o));break;case r.TAB:this.menu.active&&this.menu.select(o);break;case r.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(o),o.preventDefault());break;default:n=!0,this._searchTimeout(o)}},keypress:function(i){if(e)return e=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||i.preventDefault());if(!n){var o=t.ui.keyCode;switch(i.keyCode){case o.PAGE_UP:this._move("previousPage",i);break;case o.PAGE_DOWN:this._move("nextPage",i);break;case o.UP:this._keyEvent("previous",i);break;case o.DOWN:this._keyEvent("next",i)}}},input:function(t){return i?(i=!1,void t.preventDefault()):void this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?void delete this.cancelBlur:(clearTimeout(this.searching),this.close(t),void this._change(t))}}),this._initSource(),this.menu=t("
    ");var a=t("a",n),c=a[0],l=a[1],u=a[2],h=a[3];e.oApi._fnBindAction(c,{action:"first"},s),e.oApi._fnBindAction(l,{action:"previous"},s),e.oApi._fnBindAction(u,{action:"next"},s),e.oApi._fnBindAction(h,{action:"last"},s),e.aanFeatures.p||(n.id=e.sTableId+"_paginate",c.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",u.id=e.sTableId+"_next",h.id=e.sTableId+"_last")},fnUpdate:function(e,n){if(e.aanFeatures.p){var i,o,r,s,a,c=e.oInstance.fnPagingInfo(),l=t.fn.dataTableExt.oPagination.iFullNumbersShowPages,u=Math.floor(l/2),h=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),d=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,p="",f=(e.oClasses,e.aanFeatures.p);for(e._iDisplayLength===-1?(i=1,o=1,d=1):h=h-u?(i=h-l+1,o=h):(i=d-Math.ceil(l/2)+1,o=i+l-1),r=i;r<=o;r++)p+=d!==r?'
  • '+e.fnFormatNumber(r)+"
  • ":'
  • '+e.fnFormatNumber(r)+"
  • ";for(r=0,s=f.length;r",o[0];);return 4h.a.l(e,t[n])&&e.push(t[n]);return e},ya:function(t,e){t=t||[];for(var n=[],i=0,o=t.length;ii?n&&t.push(e):n||t.splice(i,1)},na:l,extend:a,ra:c,sa:l?c:a,A:s,Oa:function(t,e){if(!t)return t;var n,i={};for(n in t)t.hasOwnProperty(n)&&(i[n]=e(t[n],n,t));return i},Fa:function(t){for(;t.firstChild;)h.removeNode(t.firstChild)},ec:function(t){t=h.a.R(t);for(var e=n.createElement("div"),i=0,o=t.length;if?t.setAttribute("selected",e):t.selected=e},ta:function(e){return null===e||e===t?"":e.trim?e.trim():e.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},oc:function(t,e){for(var n=[],i=(t||"").split(e),o=0,r=i.length;ot.length)&&t.substring(0,e.length)===e},Sb:function(t,e){if(t===e)return!0;if(11===t.nodeType)return!1;if(e.contains)return e.contains(3===t.nodeType?t.parentNode:t);if(e.compareDocumentPosition)return 16==(16&e.compareDocumentPosition(t));for(;t&&t!=e;)t=t.parentNode;return!!t},Ea:function(t){return h.a.Sb(t,t.ownerDocument.documentElement)},eb:function(t){return!!h.a.hb(t,h.a.Ea)},B:function(t){return t&&t.tagName&&t.tagName.toLowerCase()},q:function(t,e,n){var i=f&&p[e];if(!i&&o)o(t).bind(e,n);else if(i||"function"!=typeof t.addEventListener){if("undefined"==typeof t.attachEvent)throw Error("Browser doesn't support addEventListener or attachEvent");var r=function(e){n.call(t,e)},s="on"+e;t.attachEvent(s,r),h.a.u.ja(t,function(){t.detachEvent(s,r)})}else t.addEventListener(e,n,!1)},ha:function(t,i){if(!t||!t.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var r;if("input"===h.a.B(t)&&t.type&&"click"==i.toLowerCase()?(r=t.type,r="checkbox"==r||"radio"==r):r=!1,o&&!r)o(t).trigger(i);else if("function"==typeof n.createEvent){if("function"!=typeof t.dispatchEvent)throw Error("The supplied element doesn't support dispatchEvent");r=n.createEvent(d[i]||"HTMLEvents"),r.initEvent(i,!0,!0,e,0,0,0,0,0,!1,!1,!1,!1,0,t),t.dispatchEvent(r)}else if(r&&t.click)t.click();else{if("undefined"==typeof t.fireEvent)throw Error("Browser doesn't support triggering events");t.fireEvent("on"+i)}},c:function(t){return h.v(t)?t():t},Sa:function(t){return h.v(t)?t.o():t},ua:function(t,e,n){if(e){var i=/\S+/g,o=t.className.match(i)||[];h.a.r(e.match(i),function(t){h.a.Y(o,t,n)}),t.className=o.join(" ")}},Xa:function(e,n){var i=h.a.c(n);null!==i&&i!==t||(i="");var o=h.e.firstChild(e);!o||3!=o.nodeType||h.e.nextSibling(o)?h.e.U(e,[e.ownerDocument.createTextNode(i)]):o.data=i,h.a.Vb(e)},Cb:function(t,e){if(t.name=e,7>=f)try{t.mergeAttributes(n.createElement(""),!1)}catch(i){}},Vb:function(t){9<=f&&(t=1==t.nodeType?t:t.parentNode,t.style&&(t.style.zoom=t.style.zoom))},Tb:function(t){if(f){var e=t.style.width;t.style.width=0,t.style.width=e}},ic:function(t,e){t=h.a.c(t),e=h.a.c(e);for(var n=[],i=t;i<=e;i++)n.push(i);return n},R:function(t){for(var e=[],n=0,i=t.length;n",""]||!r.indexOf("",""]||(!r.indexOf("",""]||[0,"",""],t="ignored
    "+r[1]+t+r[2]+"
    ","function"==typeof e.innerShiv?i.appendChild(e.innerShiv(t)):i.innerHTML=t;r[0]--;)i=i.lastChild;i=h.a.R(i.lastChild.childNodes)}return i},h.a.Va=function(e,n){if(h.a.Fa(e),n=h.a.c(n),null!==n&&n!==t)if("string"!=typeof n&&(n=n.toString()),o)o(e).html(n);else for(var i=h.a.Qa(n),r=0;r"},Hb:function(e,i){var o=n[e];if(o===t)throw Error("Couldn't find any memo with ID "+e+". Perhaps it's already been unmemoized.");try{return o.apply(null,i||[]),!0}finally{delete n[e]}},Ib:function(t,n){var i=[];e(t,i);for(var o=0,r=i.length;or[0]?c+r[0]:r[0]),c);for(var c=1===l?c:Math.min(e+(r[1]||0),c),l=e+l-2,u=Math.max(c,l),d=[],p=[],f=2;ee;e++)t=t();return t})},h.toJSON=function(t,e,n){return t=h.Gb(t),h.a.Ya(t,e,n)},i.prototype={save:function(t,e){var n=h.a.l(this.keys,t);0<=n?this.ab[n]=e:(this.keys.push(t),this.ab.push(e))},get:function(e){return e=h.a.l(this.keys,e),0<=e?this.ab[e]:t}}}(),h.b("toJS",h.Gb),h.b("toJSON",h.toJSON),function(){h.i={p:function(e){switch(h.a.B(e)){case"option":return!0===e.__ko__hasDomDataOptionValue__?h.a.f.get(e,h.d.options.Pa):7>=h.a.oa?e.getAttributeNode("value")&&e.getAttributeNode("value").specified?e.value:e.text:e.value;case"select":return 0<=e.selectedIndex?h.i.p(e.options[e.selectedIndex]):t;default:return e.value}},X:function(e,n,i){switch(h.a.B(e)){case"option":switch(typeof n){case"string":h.a.f.set(e,h.d.options.Pa,t),"__ko__hasDomDataOptionValue__"in e&&delete e.__ko__hasDomDataOptionValue__,e.value=n;break;default:h.a.f.set(e,h.d.options.Pa,n),e.__ko__hasDomDataOptionValue__=!0,e.value="number"==typeof n?n:""}break;case"select":""!==n&&null!==n||(n=t);for(var o,r=-1,s=0,a=e.options.length;s=c){e&&s.push(n?{key:e,value:n.join("")}:{unknown:e}),e=n=c=0;continue}}else if(58===d){if(!n)continue}else if(47===d&&u&&1"===n.createComment("test").text,s=r?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,a=r?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,c={ul:!0,ol:!0};h.e={Q:{},childNodes:function(e){return t(e)?i(e):e.childNodes},da:function(e){if(t(e)){e=h.e.childNodes(e);for(var n=0,i=e.length;n=h.a.oa&&n in g?(n=g[n],o?e.removeAttribute(n):e[n]=i):o||e.setAttribute(n,i.toString()),"name"===n&&h.a.Cb(e,o?"":i.toString())})}},function(){h.d.checked={after:["value","attr"],init:function(e,n,i){function o(){return i.has("checkedValue")?h.a.c(i.get("checkedValue")):e.value}function r(){var t=e.checked,r=d?o():t;if(!h.ca.pa()&&(!c||t)){var s=h.k.t(n);l?u!==r?(t&&(h.a.Y(s,r,!0),h.a.Y(s,u,!1)),u=r):h.a.Y(s,r,t):h.g.va(s,i,"checked",r,!0)}}function s(){var t=h.a.c(n());e.checked=l?0<=h.a.l(t,o()):a?t:o()===t}var a="checkbox"==e.type,c="radio"==e.type;if(a||c){var l=a&&h.a.c(n())instanceof Array,u=l?o():t,d=c||l;c&&!e.name&&h.d.uniqueName.init(e,function(){return!0}),h.ba(r,null,{G:e}),h.a.q(e,"click",r),h.ba(s,null,{G:e})}}},h.g.W.checked=!0,h.d.checkedValue={update:function(t,e){t.value=h.a.c(e())}}}(),h.d.css={update:function(t,e){var n=h.a.c(e());"object"==typeof n?h.a.A(n,function(e,n){n=h.a.c(n),h.a.ua(t,e,n)}):(n=String(n||""),h.a.ua(t,t.__ko__cssValue,!1),t.__ko__cssValue=n,h.a.ua(t,n,!0))}},h.d.enable={update:function(t,e){var n=h.a.c(e());n&&t.disabled?t.removeAttribute("disabled"):n||t.disabled||(t.disabled=!0)}},h.d.disable={update:function(t,e){h.d.enable.update(t,function(){return!h.a.c(e())})}},h.d.event={init:function(t,e,n,i,o){var r=e()||{};h.a.A(r,function(r){"string"==typeof r&&h.a.q(t,r,function(t){var s,a=e()[r];if(a){try{var c=h.a.R(arguments);i=o.$data,c.unshift(i),s=a.apply(i,c)}finally{!0!==s&&(t.preventDefault?t.preventDefault():t.returnValue=!1)}!1===n.get(r+"Bubble")&&(t.cancelBubble=!0,t.stopPropagation&&t.stopPropagation())}})})}},h.d.foreach={vb:function(t){return function(){var e=t(),n=h.a.Sa(e);return n&&"number"!=typeof n.length?(h.a.c(e),{foreach:n.data,as:n.as,includeDestroyed:n.includeDestroyed,afterAdd:n.afterAdd,beforeRemove:n.beforeRemove,afterRender:n.afterRender,beforeMove:n.beforeMove,afterMove:n.afterMove,templateEngine:h.K.Ja}):{foreach:e,templateEngine:h.K.Ja}}},init:function(t,e){return h.d.template.init(t,h.d.foreach.vb(e))},update:function(t,e,n,i,o){return h.d.template.update(t,h.d.foreach.vb(e),n,i,o)}},h.g.aa.foreach=!1,h.e.Q.foreach=!0,h.d.hasfocus={init:function(t,e,n){function i(i){t.__ko_hasfocusUpdating=!0;var o=t.ownerDocument;if("activeElement"in o){var r;try{r=o.activeElement}catch(s){r=o.body}i=r===t}o=e(),h.g.va(o,n,"hasfocus",i,!0),t.__ko_hasfocusLastValue=i,t.__ko_hasfocusUpdating=!1}var o=i.bind(null,!0),r=i.bind(null,!1);h.a.q(t,"focus",o),h.a.q(t,"focusin",o),h.a.q(t,"blur",r),h.a.q(t,"focusout",r)},update:function(t,e){var n=!!h.a.c(e());t.__ko_hasfocusUpdating||t.__ko_hasfocusLastValue===n||(n?t.focus():t.blur(),h.k.t(h.a.ha,null,[t,n?"focusin":"focusout"]))}},h.g.W.hasfocus=!0,h.d.hasFocus=h.d.hasfocus,h.g.W.hasFocus=!0,h.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(t,e){h.a.Va(t,e())}},u("if"),u("ifnot",!1,!0),u("with",!0,!1,function(t,e){return t.createChildContext(e)});var b={};h.d.options={init:function(t){if("select"!==h.a.B(t))throw Error("options binding applies only to SELECT elements");for(;0","#comment",o)})},Mb:function(t,e){return h.w.Na(function(n,i){ -var o=n.nextSibling;o&&o.nodeName.toLowerCase()===e&&h.xa(o,t,i)})}}}(),h.b("__tr_ambtns",h.Za.Mb),function(){h.n={},h.n.j=function(t){this.j=t},h.n.j.prototype.text=function(){var t=h.a.B(this.j),t="script"===t?"text":"textarea"===t?"value":"innerHTML";if(0==arguments.length)return this.j[t];var e=arguments[0];"innerHTML"===t?h.a.Va(this.j,e):this.j[t]=e};var e=h.a.f.L()+"_";h.n.j.prototype.data=function(t){return 1===arguments.length?h.a.f.get(this.j,e+t):void h.a.f.set(this.j,e+t,arguments[1])};var n=h.a.f.L();h.n.Z=function(t){this.j=t},h.n.Z.prototype=new h.n.j,h.n.Z.prototype.text=function(){if(0==arguments.length){var e=h.a.f.get(this.j,n)||{};return e.$a===t&&e.Ba&&(e.$a=e.Ba.innerHTML),e.$a}h.a.f.set(this.j,n,{$a:arguments[0]})},h.n.j.prototype.nodes=function(){return 0==arguments.length?(h.a.f.get(this.j,n)||{}).Ba:void h.a.f.set(this.j,n,{Ba:arguments[0]})},h.b("templateSources",h.n),h.b("templateSources.domElement",h.n.j),h.b("templateSources.anonymousTemplate",h.n.Z)}(),function(){function e(t,e,n){var i;for(e=h.e.nextSibling(e);t&&(i=t)!==e;)t=h.e.nextSibling(i),n(i,t)}function n(t,n){if(t.length){var i=t[0],o=t[t.length-1],r=i.parentNode,s=h.J.instance,a=s.preprocessNode;if(a){if(e(i,o,function(t,e){var n=t.previousSibling,r=a.call(s,t);r&&(t===i&&(i=r[0]||e),t===o&&(o=r[r.length-1]||n))}),t.length=0,!i)return;i===o?t.push(i):(t.push(i,o),h.a.ea(t,r))}e(i,o,function(t){1!==t.nodeType&&8!==t.nodeType||h.fb(n,t)}),e(i,o,function(t){1!==t.nodeType&&8!==t.nodeType||h.w.Ib(t,[n])}),h.a.ea(t,r)}}function i(t){return t.nodeType?t:0h.a.oa?0:t.nodes)?t.nodes():null;return e?h.a.R(e.cloneNode(!0).childNodes):(t=t.text(),h.a.Qa(t))},h.K.Ja=new h.K,h.Wa(h.K.Ja),h.b("nativeTemplateEngine",h.K),function(){h.La=function(){var t=this.ac=function(){if(!o||!o.tmpl)return 0;try{if(0<=o.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(t){}return 1}();this.renderTemplateSource=function(e,i,r){if(r=r||{},2>t)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var s=e.data("precompiled");return s||(s=e.text()||"",s=o.template(null,"{{ko_with $item.koBindingContext}}"+s+"{{/ko_with}}"),e.data("precompiled",s)),e=[i.$data],i=o.extend({koBindingContext:i},r.templateOptions),i=o.tmpl(s,e,i),i.appendTo(n.createElement("div")),o.fragments={},i},this.createJavaScriptEvaluatorBlock=function(t){return"{{ko_code ((function() { return "+t+" })()) }}"},this.addTemplate=function(t,e){n.write("")},0=0&&(u&&(u.splice(m,1),t.processAllDeferredBindingUpdates&&t.processAllDeferredBindingUpdates()),p.splice(g,0,A)),l(v,n,null),t.processAllDeferredBindingUpdates&&t.processAllDeferredBindingUpdates(),_.afterMove&&_.afterMove.call(this,b,s,a)}y&&y.apply(this,arguments)},connectWith:!!_.connectClass&&"."+_.connectClass})),void 0!==_.isEnabled&&t.computed({read:function(){A.sortable(a(_.isEnabled)?"enable":"disable")},disposeWhenNodeIsRemoved:u})},0);return t.utils.domNodeDisposal.addDisposeCallback(u,function(){(A.data("ui-sortable")||A.data("sortable"))&&A.sortable("destroy"),clearTimeout(T)}),{controlsDescendantBindings:!0}},update:function(e,n,i,r,s){var a=p(n,"foreach");l(e,o,a.foreach),t.bindingHandlers.template.update(e,function(){return a},i,r,s)},connectClass:"ko_container",allowDrop:!0,afterMove:null,beforeMove:null,options:{}},t.bindingHandlers.draggable={init:function(n,i,o,r,c){var u=a(i())||{},h=u.options||{},d=t.utils.extend({},t.bindingHandlers.draggable.options),f=p(i,"data"),m=u.connectClass||t.bindingHandlers.draggable.connectClass,g=void 0!==u.isEnabled?u.isEnabled:t.bindingHandlers.draggable.isEnabled;return u="data"in u?u.data:u,l(n,s,u),t.utils.extend(d,h),d.connectToSortable=!!m&&"."+m,e(n).draggable(d),void 0!==g&&t.computed({read:function(){e(n).draggable(a(g)?"enable":"disable")},disposeWhenNodeIsRemoved:n}),t.bindingHandlers.template.init(n,function(){return f},o,r,c)},update:function(e,n,i,o,r){var s=p(n,"data");return t.bindingHandlers.template.update(e,function(){return s},i,o,r)},connectClass:t.bindingHandlers.sortable.connectClass,options:{helper:"clone"}}}),function(){var t=this,e=t._,n=Array.prototype,i=Object.prototype,o=Function.prototype,r=n.push,s=n.slice,a=n.concat,c=i.toString,l=i.hasOwnProperty,u=Array.isArray,h=Object.keys,d=o.bind,p=function(t){return t instanceof p?t:this instanceof p?void(this._wrapped=t):new p(t)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=p),exports._=p):t._=p,p.VERSION="1.7.0";var f=function(t,e,n){if(void 0===e)return t;switch(null==n?3:n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)};case 4:return function(n,i,o,r){return t.call(e,n,i,o,r)}}return function(){return t.apply(e,arguments)}};p.iteratee=function(t,e,n){return null==t?p.identity:p.isFunction(t)?f(t,e,n):p.isObject(t)?p.matches(t):p.property(t)},p.each=p.forEach=function(t,e,n){if(null==t)return t;e=f(e,n);var i,o=t.length;if(o===+o)for(i=0;i=0)},p.invoke=function(t,e){var n=s.call(arguments,2),i=p.isFunction(e);return p.map(t,function(t){return(i?e:t[e]).apply(t,n)})},p.pluck=function(t,e){return p.map(t,p.property(e))},p.where=function(t,e){return p.filter(t,p.matches(e))},p.findWhere=function(t,e){return p.find(t,p.matches(e))},p.max=function(t,e,n){var i,o,r=-(1/0),s=-(1/0);if(null==e&&null!=t){t=t.length===+t.length?t:p.values(t);for(var a=0,c=t.length;ar&&(r=i)}else e=p.iteratee(e,n),p.each(t,function(t,n,i){o=e(t,n,i),(o>s||o===-(1/0)&&r===-(1/0))&&(r=t,s=o)});return r},p.min=function(t,e,n){var i,o,r=1/0,s=1/0;if(null==e&&null!=t){t=t.length===+t.length?t:p.values(t);for(var a=0,c=t.length;ai||void 0===n)return 1;if(n>>1;n(t[a])=0;)if(t[i]===e)return i;return-1},p.range=function(t,e,n){arguments.length<=1&&(e=t||0,t=0),n=n||1;for(var i=Math.max(Math.ceil((e-t)/n),0),o=Array(i),r=0;re?(clearTimeout(s),s=null,a=l,r=t.apply(i,o),s||(i=o=null)):s||n.trailing===!1||(s=setTimeout(c,u)),r}},p.debounce=function(t,e,n){var i,o,r,s,a,c=function(){var l=p.now()-s;l0?i=setTimeout(c,e-l):(i=null,n||(a=t.apply(r,o),i||(r=o=null)))};return function(){r=this,o=arguments,s=p.now();var l=n&&!i;return i||(i=setTimeout(c,e)),l&&(a=t.apply(r,o),r=o=null),a}},p.wrap=function(t,e){return p.partial(e,t)},p.negate=function(t){return function(){return!t.apply(this,arguments)}},p.compose=function(){var t=arguments,e=t.length-1;return function(){for(var n=e,i=t[e].apply(this,arguments);n--;)i=t[n].call(this,i);return i}},p.after=function(t,e){return function(){if(--t<1)return e.apply(this,arguments)}},p.before=function(t,e){var n;return function(){return--t>0?n=e.apply(this,arguments):e=null,n}},p.once=p.partial(p.before,2),p.keys=function(t){if(!p.isObject(t))return[];if(h)return h(t);var e=[];for(var n in t)p.has(t,n)&&e.push(n);return e},p.values=function(t){for(var e=p.keys(t),n=e.length,i=Array(n),o=0;o":">",'"':""","'":"'","`":"`"},A=p.invert(y),w=function(t){var e=function(e){return t[e]},n="(?:"+p.keys(t).join("|")+")",i=RegExp(n),o=RegExp(n,"g");return function(t){return t=null==t?"":""+t,i.test(t)?t.replace(o,e):t}};p.escape=w(y),p.unescape=w(A),p.result=function(t,e){if(null!=t){var n=t[e];return p.isFunction(n)?t[e]():n}};var z=0;p.uniqueId=function(t){var e=++z+"";return t?t+e:e},p.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var _=/(.)^/,T={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},x=/\\|'|\r|\n|\u2028|\u2029/g,C=function(t){return"\\"+T[t]};p.template=function(t,e,n){!e&&n&&(e=n),e=p.defaults({},e,p.templateSettings);var i=RegExp([(e.escape||_).source,(e.interpolate||_).source,(e.evaluate||_).source].join("|")+"|$","g"),o=0,r="__p+='";t.replace(i,function(e,n,i,s,a){return r+=t.slice(o,a).replace(x,C),o=a+e.length,n?r+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":i?r+="'+\n((__t=("+i+"))==null?'':__t)+\n'":s&&(r+="';\n"+s+"\n__p+='"),e}),r+="';\n",e.variable||(r="with(obj||{}){\n"+r+"}\n"),r="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+r+"return __p;\n";try{var s=new Function(e.variable||"obj","_",r)}catch(a){throw a.source=r,a}var c=function(t){return s.call(this,t,p)},l=e.variable||"obj";return c.source="function("+l+"){\n"+r+"}",c},p.chain=function(t){var e=p(t);return e._chain=!0,e};var S=function(t){return this._chain?p(t).chain():t};p.mixin=function(t){p.each(p.functions(t),function(e){var n=p[e]=t[e];p.prototype[e]=function(){var t=[this._wrapped];return r.apply(t,arguments),S.call(this,n.apply(p,t))}})},p.mixin(p),p.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=n[t];p.prototype[t]=function(){var n=this._wrapped;return e.apply(n,arguments),"shift"!==t&&"splice"!==t||0!==n.length||delete n[0],S.call(this,n)}}),p.each(["concat","join","slice"],function(t){var e=n[t];p.prototype[t]=function(){return S.call(this,e.apply(this._wrapped,arguments))}}),p.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return p})}.call(this),function(t,e){function n(){return new Date(Date.UTC.apply(Date,arguments))}function i(){var t=new Date;return n(t.getFullYear(),t.getMonth(),t.getDate())}function o(t,e){return t.getUTCFullYear()===e.getUTCFullYear()&&t.getUTCMonth()===e.getUTCMonth()&&t.getUTCDate()===e.getUTCDate()}function r(t){return function(){return this[t].apply(this,arguments)}}function s(e,n){function i(t,e){return e.toLowerCase()}var o,r=t(e).data(),s={},a=new RegExp("^"+n.toLowerCase()+"([A-Z])");n=new RegExp("^"+n.toLowerCase());for(var c in r)n.test(c)&&(o=c.replace(a,i),s[o]=r[c]);return s}function a(e){var n={};if(m[e]||(e=e.split("-")[0],m[e])){var i=m[e];return t.each(f,function(t,e){e in i&&(n[e]=i[e])}),n}}var c=function(){var e={get:function(t){return this.slice(t)[0]},contains:function(t){for(var e=t&&t.valueOf(),n=0,i=this.length;no?(this.picker.addClass("datepicker-orient-right"),p=u.left+d-e):this.picker.addClass("datepicker-orient-left");var m,g,b=this.o.orientation.y;if("auto"===b&&(m=-s+f-n,g=s+r-(f+h+n),b=Math.max(m,g)===g?"top":"bottom"),this.picker.addClass("datepicker-orient-"+b),"top"===b?f+=h:f-=n+parseInt(this.picker.css("padding-top")),this.o.rtl){var v=o-(p+d);this.picker.css({top:f,right:v,zIndex:l})}else this.picker.css({top:f,left:p,zIndex:l});return this},_allow_update:!0,update:function(){if(!this._allow_update)return this;var e=this.dates.copy(),n=[],i=!1;return arguments.length?(t.each(arguments,t.proxy(function(t,e){e instanceof Date&&(e=this._local_to_utc(e)),n.push(e)},this)),i=!0):(n=this.isInput?this.element.val():this.element.data("date")||this.element.find("input").val(),n=n&&this.o.multidate?n.split(this.o.multidateSeparator):[n],delete this.element.data().date),n=t.map(n,t.proxy(function(t){return g.parseDate(t,this.o.format,this.o.language)},this)),n=t.grep(n,t.proxy(function(t){return tthis.o.endDate||!t},this),!0),this.dates.replace(n),this.dates.length?this.viewDate=new Date(this.dates.get(-1)):this.viewDatethis.o.endDate&&(this.viewDate=new Date(this.o.endDate)),i?this.setValue():n.length&&String(e)!==String(this.dates)&&this._trigger("changeDate"),!this.dates.length&&e.length&&this._trigger("clearDate"),this.fill(),this},fillDow:function(){var t=this.o.weekStart,e="";if(this.o.calendarWeeks){this.picker.find(".datepicker-days thead tr:first-child .datepicker-switch").attr("colspan",function(t,e){return parseInt(e)+1});var n=' ';e+=n}for(;t'+m[this.o.language].daysMin[t++%7]+"";e+="",this.picker.find(".datepicker-days thead").append(e)},fillMonths:function(){for(var t="",e=0;e<12;)t+=''+m[this.o.language].monthsShort[e++]+"";this.picker.find(".datepicker-months td").html(t)},setRange:function(e){e&&e.length?this.range=t.map(e,function(t){return t.valueOf()}):delete this.range,this.fill()},getClassNames:function(e){var n=[],i=this.viewDate.getUTCFullYear(),r=this.viewDate.getUTCMonth(),s=new Date;return e.getUTCFullYear()i||e.getUTCFullYear()===i&&e.getUTCMonth()>r)&&n.push("new"),this.focusDate&&e.valueOf()===this.focusDate.valueOf()&&n.push("focused"),this.o.todayHighlight&&e.getUTCFullYear()===s.getFullYear()&&e.getUTCMonth()===s.getMonth()&&e.getUTCDate()===s.getDate()&&n.push("today"),this.dates.contains(e)!==-1&&n.push("active"),(e.valueOf()this.o.endDate||t.inArray(e.getUTCDay(),this.o.daysOfWeekDisabled)!==-1)&&n.push("disabled"),this.o.datesDisabled.length>0&&t.grep(this.o.datesDisabled,function(t){return o(e,t)}).length>0&&n.push("disabled","disabled-date"),this.range&&(e>this.range[0]&&e"),this.o.calendarWeeks)){var y=new Date(+p+(this.o.weekStart-p.getUTCDay()-7)%7*864e5),A=new Date(Number(y)+(11-y.getUTCDay())%7*864e5),w=new Date(Number(w=n(A.getUTCFullYear(),0,1))+(11-w.getUTCDay())%7*864e5),z=(A-w)/864e5/7+1;M.push(''+z+"")}if(v=this.getClassNames(p),v.push("day"),this.o.beforeShowDay!==t.noop){var _=this.o.beforeShowDay(this._utc_to_local(p));_===e?_={}:"boolean"==typeof _?_={enabled:_}:"string"==typeof _&&(_={classes:_}),_.enabled===!1&&v.push("disabled"),_.classes&&(v=v.concat(_.classes.split(/\s+/))),_.tooltip&&(i=_.tooltip)}v=t.unique(v),M.push('"+p.getUTCDate()+""),i=null,p.getUTCDay()===this.o.weekEnd&&M.push(""),p.setUTCDate(p.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").empty().append(M.join(""));var T=this.picker.find(".datepicker-months").find("th:eq(1)").text(r).end().find("span").removeClass("active");if(t.each(this.dates,function(t,e){e.getUTCFullYear()===r&&T.eq(e.getUTCMonth()).addClass("active")}),(rl)&&T.addClass("disabled"),r===a&&T.slice(0,c).addClass("disabled"),r===l&&T.slice(u+1).addClass("disabled"),this.o.beforeShowMonth!==t.noop){var x=this;t.each(T,function(e,n){if(!t(n).hasClass("disabled")){var i=new Date(r,e,1),o=x.o.beforeShowMonth(i);o===!1&&t(n).addClass("disabled")}})}M="",r=10*parseInt(r/10,10);var C=this.picker.find(".datepicker-years").find("th:eq(1)").text(r+"-"+(r+9)).end().find("td");r-=1;for(var S,O=t.map(this.dates,function(t){return t.getUTCFullYear()}),N=-1;N<11;N++)S=["year"],N===-1?S.push("old"):10===N&&S.push("new"),t.inArray(r,O)!==-1&&S.push("active"),(rl)&&S.push("disabled"),M+=''+r+"",r+=1;C.html(M)}},updateNavArrows:function(){if(this._allow_update){var t=new Date(this.viewDate),e=t.getUTCFullYear(),n=t.getUTCMonth();switch(this.viewMode){case 0:this.o.startDate!==-(1/0)&&e<=this.o.startDate.getUTCFullYear()&&n<=this.o.startDate.getUTCMonth()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.o.endDate!==1/0&&e>=this.o.endDate.getUTCFullYear()&&n>=this.o.endDate.getUTCMonth()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"});break;case 1:case 2:this.o.startDate!==-(1/0)&&e<=this.o.startDate.getUTCFullYear()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.o.endDate!==1/0&&e>=this.o.endDate.getUTCFullYear()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"})}}},click:function(e){e.preventDefault();var i,o,r,s=t(e.target).closest("span, td, th");if(1===s.length)switch(s[0].nodeName.toLowerCase()){case"th":switch(s[0].className){case"datepicker-switch":this.showMode(1);break;case"prev":case"next":var a=g.modes[this.viewMode].navStep*("prev"===s[0].className?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,a),this._trigger("changeMonth",this.viewDate);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,a),1===this.viewMode&&this._trigger("changeYear",this.viewDate)}this.fill();break;case"today":var c=new Date;c=n(c.getFullYear(),c.getMonth(),c.getDate(),0,0,0),this.showMode(-2);var l="linked"===this.o.todayBtn?null:"view";this._setDate(c,l);break;case"clear":this.clearDates()}break;case"span":s.hasClass("disabled")||(this.viewDate.setUTCDate(1),s.hasClass("month")?(r=1,o=s.parent().find("span").index(s),i=this.viewDate.getUTCFullYear(),this.viewDate.setUTCMonth(o),this._trigger("changeMonth",this.viewDate),1===this.o.minViewMode&&this._setDate(n(i,o,r))):(r=1,o=0,i=parseInt(s.text(),10)||0,this.viewDate.setUTCFullYear(i),this._trigger("changeYear",this.viewDate),2===this.o.minViewMode&&this._setDate(n(i,o,r))),this.showMode(-1),this.fill());break;case"td":s.hasClass("day")&&!s.hasClass("disabled")&&(r=parseInt(s.text(),10)||1,i=this.viewDate.getUTCFullYear(),o=this.viewDate.getUTCMonth(),s.hasClass("old")?0===o?(o=11,i-=1):o-=1:s.hasClass("new")&&(11===o?(o=0,i+=1):o+=1),this._setDate(n(i,o,r)))}this.picker.is(":visible")&&this._focused_from&&t(this._focused_from).focus(),delete this._focused_from},_toggle_multidate:function(t){var e=this.dates.contains(t);if(t||this.dates.clear(),e!==-1?(this.o.multidate===!0||this.o.multidate>1||this.o.toggleActive)&&this.dates.remove(e):this.o.multidate===!1?(this.dates.clear(),this.dates.push(t)):this.dates.push(t),"number"==typeof this.o.multidate)for(;this.dates.length>this.o.multidate;)this.dates.remove(0)},_setDate:function(t,e){e&&"date"!==e||this._toggle_multidate(t&&new Date(t)),e&&"view"!==e||(this.viewDate=t&&new Date(t)),this.fill(),this.setValue(),e&&"view"===e||this._trigger("changeDate");var n;this.isInput?n=this.element:this.component&&(n=this.element.find("input")),n&&n.change(),!this.o.autoclose||e&&"date"!==e||this.hide()},moveMonth:function(t,n){if(!t)return e;if(!n)return t;var i,o,r=new Date(t.valueOf()),s=r.getUTCDate(),a=r.getUTCMonth(),c=Math.abs(n);if(n=n>0?1:-1,1===c)o=n===-1?function(){return r.getUTCMonth()===a}:function(){return r.getUTCMonth()!==i},i=a+n,r.setUTCMonth(i),(i<0||i>11)&&(i=(i+12)%12);else{for(var l=0;l=this.o.startDate&&t<=this.o.endDate},keydown:function(t){if(!this.picker.is(":visible"))return void(27===t.keyCode&&this.show());var e,n,o,r=!1,s=this.focusDate||this.viewDate;switch(t.keyCode){case 27:this.focusDate?(this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill()):this.hide(),t.preventDefault();break;case 37:case 39:if(!this.o.keyboardNavigation)break;e=37===t.keyCode?-1:1,t.ctrlKey?(n=this.moveYear(this.dates.get(-1)||i(),e),o=this.moveYear(s,e),this._trigger("changeYear",this.viewDate)):t.shiftKey?(n=this.moveMonth(this.dates.get(-1)||i(),e),o=this.moveMonth(s,e),this._trigger("changeMonth",this.viewDate)):(n=new Date(this.dates.get(-1)||i()),n.setUTCDate(n.getUTCDate()+e),o=new Date(s),o.setUTCDate(s.getUTCDate()+e)),this.dateWithinRange(o)&&(this.focusDate=this.viewDate=o,this.setValue(),this.fill(),t.preventDefault());break;case 38:case 40:if(!this.o.keyboardNavigation)break;e=38===t.keyCode?-1:1,t.ctrlKey?(n=this.moveYear(this.dates.get(-1)||i(),e),o=this.moveYear(s,e),this._trigger("changeYear",this.viewDate)):t.shiftKey?(n=this.moveMonth(this.dates.get(-1)||i(),e),o=this.moveMonth(s,e),this._trigger("changeMonth",this.viewDate)):(n=new Date(this.dates.get(-1)||i()),n.setUTCDate(n.getUTCDate()+7*e),o=new Date(s),o.setUTCDate(s.getUTCDate()+7*e)),this.dateWithinRange(o)&&(this.focusDate=this.viewDate=o,this.setValue(),this.fill(),t.preventDefault());break;case 32:break;case 13:s=this.focusDate||this.dates.get(-1)||this.viewDate,this.o.keyboardNavigation&&(this._toggle_multidate(s),r=!0),this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.setValue(),this.fill(),this.picker.is(":visible")&&(t.preventDefault(),"function"==typeof t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,this.o.autoclose&&this.hide());break;case 9:this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill(),this.hide()}if(r){this.dates.length?this._trigger("changeDate"):this._trigger("clearDate");var a;this.isInput?a=this.element:this.component&&(a=this.element.find("input")),a&&a.change()}},showMode:function(t){t&&(this.viewMode=Math.max(this.o.minViewMode,Math.min(2,this.viewMode+t))),this.picker.children("div").hide().filter(".datepicker-"+g.modes[this.viewMode].clsName).css("display","block"),this.updateNavArrows()}};var u=function(e,n){this.element=t(e),this.inputs=t.map(n.inputs,function(t){return t.jquery?t[0]:t}),delete n.inputs,d.call(t(this.inputs),n).bind("changeDate",t.proxy(this.dateUpdated,this)),this.pickers=t.map(this.inputs,function(e){return t(e).data("datepicker")}),this.updateDates()};u.prototype={updateDates:function(){this.dates=t.map(this.pickers,function(t){return t.getUTCDate()}),this.updateRanges()},updateRanges:function(){var e=t.map(this.dates,function(t){return t.valueOf()});t.each(this.pickers,function(t,n){n.setRange(e)})},dateUpdated:function(e){if(!this.updating){this.updating=!0;var n=t(e.target).data("datepicker"),i=n.getUTCDate(),o=t.inArray(e.target,this.inputs),r=o-1,s=o+1,a=this.inputs.length;if(o!==-1){if(t.each(this.pickers,function(t,e){e.getUTCDate()||e.setUTCDate(i)}),i=0&&ithis.dates[s])for(;sthis.dates[s];)this.pickers[s++].setUTCDate(i);this.updateDates(),delete this.updating}}},remove:function(){t.map(this.pickers,function(t){t.remove()}),delete this.element.data().datepicker}};var h=t.fn.datepicker,d=function(n){var i=Array.apply(null,arguments);i.shift();var o;return this.each(function(){var r=t(this),c=r.data("datepicker"),h="object"==typeof n&&n;if(!c){var d=s(this,"date"),f=t.extend({},p,d,h),m=a(f.language),g=t.extend({},p,m,d,h);if(r.hasClass("input-daterange")||g.inputs){var b={inputs:g.inputs||r.find("input").toArray()};r.data("datepicker",c=new u(this,t.extend(g,b)))}else r.data("datepicker",c=new l(this,g))}if("string"==typeof n&&"function"==typeof c[n]&&(o=c[n].apply(c,i),o!==e))return!1}),o!==e?o:this};t.fn.datepicker=d;var p=t.fn.datepicker.defaults={autoclose:!1,beforeShowDay:t.noop,beforeShowMonth:t.noop,calendarWeeks:!1,clearBtn:!1,toggleActive:!1,daysOfWeekDisabled:[],datesDisabled:[],endDate:1/0,forceParse:!0,format:"mm/dd/yyyy",keyboardNavigation:!0,language:"en",minViewMode:0,multidate:!1,multidateSeparator:",",orientation:"auto",rtl:!1,startDate:-(1/0),startView:0,todayBtn:!1,todayHighlight:!1,weekStart:0,disableTouchKeyboard:!1,enableOnReadonly:!0,container:"body"},f=t.fn.datepicker.locale_opts=["format","rtl","weekStart"];t.fn.datepicker.Constructor=l;var m=t.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear"}},g={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(t){return t%4===0&&t%100!==0||t%400===0},getDaysInMonth:function(t,e){return[31,g.isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]},validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,parseFormat:function(t){var e=t.replace(this.validParts,"\0").split("\0"),n=t.match(this.validParts);if(!e||!e.length||!n||0===n.length)throw new Error("Invalid date format.");return{separators:e,parts:n}},parseDate:function(i,o,r){function s(){var t=this.slice(0,d[u].length),e=d[u].slice(0,t.length);return t.toLowerCase()===e.toLowerCase()}if(!i)return e;if(i instanceof Date)return i;"string"==typeof o&&(o=g.parseFormat(o));var a,c,u,h=/([\-+]\d+)([dmwy])/,d=i.match(/([\-+]\d+)([dmwy])/g);if(/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(i)){for(i=new Date,u=0;u«»',contTemplate:'',footTemplate:''};g.template='
    '+g.headTemplate+""+g.footTemplate+'
    '+g.headTemplate+g.contTemplate+g.footTemplate+'
    '+g.headTemplate+g.contTemplate+g.footTemplate+"
    ",t.fn.datepicker.DPGlobal=g,t.fn.datepicker.noConflict=function(){return t.fn.datepicker=h,this},t.fn.datepicker.version="1.4.0",t(document).on("focus.datepicker.data-api click.datepicker.data-api",'[data-provide="datepicker"]',function(e){var n=t(this);n.data("datepicker")||(e.preventDefault(),d.call(n,"show"))}),t(function(){d.call(t('[data-provide="datepicker-inline"]'))})}(window.jQuery),!function(t){t.fn.datepicker.dates.de={days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"],daysShort:["Son","Mon","Die","Mit","Don","Fre","Sam","Son"],daysMin:["So","Mo","Di","Mi","Do","Fr","Sa","So"],months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],today:"Heute",clear:"Löschen",weekStart:1,format:"dd.mm.yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.da={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag","Søndag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør","Søn"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø","Sø"],months:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"I Dag",clear:"Nulstil"}}(jQuery),!function(t){t.fn.datepicker.dates["pt-BR"]={days:["Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado","Domingo"],daysShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb","Dom"],daysMin:["Do","Se","Te","Qu","Qu","Se","Sa","Do"],months:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthsShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],today:"Hoje",clear:"Limpar"}}(jQuery),!function(t){t.fn.datepicker.dates.nl={days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag","zondag"],daysShort:["zo","ma","di","wo","do","vr","za","zo"],daysMin:["zo","ma","di","wo","do","vr","za","zo"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],today:"Vandaag",clear:"Wissen",weekStart:1,format:"dd-mm-yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.fr={days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"],daysShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam.","dim."],daysMin:["d","l","ma","me","j","v","s","d"],months:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthsShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],today:"Aujourd'hui",clear:"Effacer",weekStart:1,format:"dd/mm/yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato","Domenica"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab","Dom"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa","Do"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",clear:"Cancella",weekStart:1,format:"dd/mm/yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.lt={days:["Sekmadienis","Pirmadienis","Antradienis","Trečiadienis","Ketvirtadienis","Penktadienis","Šeštadienis","Sekmadienis"],daysShort:["S","Pr","A","T","K","Pn","Š","S"],daysMin:["Sk","Pr","An","Tr","Ke","Pn","Št","Sk"],months:["Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis","Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis"],monthsShort:["Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rugp","Rugs","Spa","Lap","Gru"],today:"Šiandien",weekStart:1}}(jQuery),!function(t){t.fn.datepicker.dates.no={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø"],months:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"I dag",clear:"Nullstill",weekStart:1,format:"dd.mm.yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.es={days:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado","Domingo"],daysShort:["Dom","Lun","Mar","Mié","Jue","Vie","Sáb","Dom"],daysMin:["Do","Lu","Ma","Mi","Ju","Vi","Sa","Do"],months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],monthsShort:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],today:"Hoy",clear:"Borrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.sv={days:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag","Söndag"],daysShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör","Sön"],daysMin:["Sö","Må","Ti","On","To","Fr","Lö","Sö"],months:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"Idag",format:"yyyy-mm-dd",weekStart:1,clear:"Rensa"}}(jQuery),function(){var t,e,n,i,o,r,s,a,c=[].slice,l={}.hasOwnProperty,u=function(t,e){function n(){this.constructor=t}for(var i in e)l.call(e,i)&&(t[i]=e[i]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t};s=function(){},e=function(){function t(){}return t.prototype.addEventListener=t.prototype.on,t.prototype.on=function(t,e){return this._callbacks=this._callbacks||{},this._callbacks[t]||(this._callbacks[t]=[]),this._callbacks[t].push(e),this},t.prototype.emit=function(){var t,e,n,i,o,r;if(i=arguments[0],t=2<=arguments.length?c.call(arguments,1):[],this._callbacks=this._callbacks||{},n=this._callbacks[i])for(o=0,r=n.length;o
    '),this.element.appendChild(e)),i=e.getElementsByTagName("span")[0],i&&(null!=i.textContent?i.textContent=this.options.dictFallbackMessage:null!=i.innerText&&(i.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(t){var e,n,i;return e={srcX:0,srcY:0,srcWidth:t.width,srcHeight:t.height},n=t.width/t.height,e.optWidth=this.options.thumbnailWidth,e.optHeight=this.options.thumbnailHeight,null==e.optWidth&&null==e.optHeight?(e.optWidth=e.srcWidth,e.optHeight=e.srcHeight):null==e.optWidth?e.optWidth=n*e.optHeight:null==e.optHeight&&(e.optHeight=1/n*e.optWidth),i=e.optWidth/e.optHeight,t.heighti?(e.srcHeight=t.height,e.srcWidth=e.srcHeight*i):(e.srcWidth=t.width,e.srcHeight=e.srcWidth/i),e.srcX=(t.width-e.srcWidth)/2,e.srcY=(t.height-e.srcHeight)/2,e},drop:function(t){return this.element.classList.remove("dz-drag-hover")},dragstart:s,dragend:function(t){return this.element.classList.remove("dz-drag-hover")},dragenter:function(t){return this.element.classList.add("dz-drag-hover")},dragover:function(t){return this.element.classList.add("dz-drag-hover")},dragleave:function(t){return this.element.classList.remove("dz-drag-hover")},paste:s,reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(t){var e,i,o,r,s,a,c,l,u,h,d,p,f;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){for(t.previewElement=n.createElement(this.options.previewTemplate.trim()),t.previewTemplate=t.previewElement,this.previewsContainer.appendChild(t.previewElement),h=t.previewElement.querySelectorAll("[data-dz-name]"),r=0,c=h.length;r'+this.options.dictRemoveFile+""),t.previewElement.appendChild(t._removeLink)),i=function(e){return function(i){return i.preventDefault(),i.stopPropagation(),t.status===n.UPLOADING?n.confirm(e.options.dictCancelUploadConfirmation,function(){return e.removeFile(t)}):e.options.dictRemoveFileConfirmation?n.confirm(e.options.dictRemoveFileConfirmation,function(){return e.removeFile(t)}):e.removeFile(t)}}(this),p=t.previewElement.querySelectorAll("[data-dz-remove]"),f=[],a=0,u=p.length;a\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n \n Check\n \n \n \n \n \n
    \n
    \n \n Error\n \n \n \n \n \n \n \n
    \n'},i=function(){var t,e,n,i,o,r,s;for(i=arguments[0],n=2<=arguments.length?c.call(arguments,1):[],r=0,s=n.length;r'+this.options.dictDefaultMessage+"")),this.clickableElements.length&&(i=function(t){return function(){return t.hiddenFileInput&&t.hiddenFileInput.parentNode.removeChild(t.hiddenFileInput),t.hiddenFileInput=document.createElement("input"),t.hiddenFileInput.setAttribute("type","file"),(null==t.options.maxFiles||t.options.maxFiles>1)&&t.hiddenFileInput.setAttribute("multiple","multiple"),t.hiddenFileInput.className="dz-hidden-input",null!=t.options.acceptedFiles&&t.hiddenFileInput.setAttribute("accept",t.options.acceptedFiles),null!=t.options.capture&&t.hiddenFileInput.setAttribute("capture",t.options.capture),t.hiddenFileInput.style.visibility="hidden",t.hiddenFileInput.style.position="absolute",t.hiddenFileInput.style.top="0",t.hiddenFileInput.style.left="0",t.hiddenFileInput.style.height="0",t.hiddenFileInput.style.width="0",document.querySelector(t.options.hiddenInputContainer).appendChild(t.hiddenFileInput),t.hiddenFileInput.addEventListener("change",function(){var e,n,o,r;if(n=t.hiddenFileInput.files,n.length)for(o=0,r=n.length;o',this.options.dictFallbackText&&(i+="

    "+this.options.dictFallbackText+"

    "),i+='',e=n.createElement(i),"FORM"!==this.element.tagName?(o=n.createElement('
    '),o.appendChild(e)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=o?o:e)},n.prototype.getExistingFallback=function(){var t,e,n,i,o,r;for(e=function(t){var e,n,i;for(n=0,i=t.length;n0){for(s=["TB","GB","MB","KB","b"],n=a=0,c=s.length;a=e){i=t/Math.pow(this.options.filesizeBase,4-n),o=r;break}i=Math.round(10*i)/10}return""+i+" "+o},n.prototype._updateMaxFilesReachedClass=function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")},n.prototype.drop=function(t){var e,n;t.dataTransfer&&(this.emit("drop",t),e=t.dataTransfer.files,this.emit("addedfiles",e),e.length&&(n=t.dataTransfer.items,n&&n.length&&null!=n[0].webkitGetAsEntry?this._addFilesFromItems(n):this.handleFiles(e)))},n.prototype.paste=function(t){var e,n;if(null!=(null!=t&&null!=(n=t.clipboardData)?n.items:void 0))return this.emit("paste",t),e=t.clipboardData.items,e.length?this._addFilesFromItems(e):void 0},n.prototype.handleFiles=function(t){var e,n,i,o;for(o=[],n=0,i=t.length;n0){for(r=0,s=n.length;r1024*this.options.maxFilesize*1024?e(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(t.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):n.isValidFile(t,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(e(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",t)):this.options.accept.call(this,t,e):e(this.options.dictInvalidFileType)},n.prototype.addFile=function(t){return t.upload={progress:0,total:t.size,bytesSent:0},this.files.push(t),t.status=n.ADDED,this.emit("addedfile",t),this._enqueueThumbnail(t),this.accept(t,function(e){return function(n){return n?(t.accepted=!1,e._errorProcessing([t],n)):(t.accepted=!0,e.options.autoQueue&&e.enqueueFile(t)),e._updateMaxFilesReachedClass()}}(this))},n.prototype.enqueueFiles=function(t){var e,n,i;for(n=0,i=t.length;n=e)&&(i=this.getQueuedFiles(),i.length>0)){if(this.options.uploadMultiple)return this.processFiles(i.slice(0,e-n));for(;t=B;u=0<=B?++L:--L)r.append(this._getParamName(u),t[u],this._renameFilename(t[u].name));return this.submitRequest(w,r,t)},n.prototype.submitRequest=function(t,e,n){return t.send(e)},n.prototype._finished=function(t,e,i){var o,r,s;for(r=0,s=t.length;ru;)e=o[4*(c-1)+3],0===e?r=c:u=c,c=r+u>>1;return l=c/s,0===l?1:l},r=function(t,e,n,i,r,s,a,c,l,u){var h;return h=o(e),t.drawImage(e,n,i,r,s,a,c,l,u/h)},i=function(t,e){var n,i,o,r,s,a,c,l,u;if(o=!1,u=!0,i=t.document,l=i.documentElement,n=i.addEventListener?"addEventListener":"attachEvent",c=i.addEventListener?"removeEventListener":"detachEvent",a=i.addEventListener?"":"on",r=function(n){if("readystatechange"!==n.type||"complete"===i.readyState)return("load"===n.type?t:i)[c](a+n.type,r,!1),!o&&(o=!0)?e.call(t,n.type||n):void 0},s=function(){var t;try{l.doScroll("left")}catch(e){return t=e,void setTimeout(s,50)}return r("poll")},"complete"!==i.readyState){if(i.createEventObject&&l.doScroll){try{u=!t.frameElement}catch(h){}u&&s()}return i[n](a+"DOMContentLoaded",r,!1),i[n](a+"readystatechange",r,!1),t[n](a+"load",r,!1)}},t._autoDiscoverFunction=function(){if(t.autoDiscover)return t.discover()},i(window,t._autoDiscoverFunction)}.call(this),function(t,e){"function"==typeof define&&define.amd?define("typeahead.js",["jquery"],function(t){return e(t)}):"object"==typeof exports?module.exports=e(require("jquery")):e(jQuery)}(this,function(t){var e=function(){"use strict";return{isMsie:function(){return!!/(msie|trident)/i.test(navigator.userAgent)&&navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]},isBlankString:function(t){return!t||/^\s*$/.test(t)},escapeRegExChars:function(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(t){return"string"==typeof t},isNumber:function(t){return"number"==typeof t},isArray:t.isArray,isFunction:t.isFunction,isObject:t.isPlainObject,isUndefined:function(t){return"undefined"==typeof t},isElement:function(t){return!(!t||1!==t.nodeType)},isJQuery:function(e){return e instanceof t},toStr:function(t){return e.isUndefined(t)||null===t?"":t+""},bind:t.proxy,each:function(e,n){function i(t,e){return n(e,t)}t.each(e,i)},map:t.map,filter:t.grep,every:function(e,n){var i=!0;return e?(t.each(e,function(t,o){if(!(i=n.call(null,o,t,e)))return!1}),!!i):i},some:function(e,n){var i=!1;return e?(t.each(e,function(t,o){if(i=n.call(null,o,t,e))return!1}),!!i):i},mixin:t.extend,identity:function(t){return t},clone:function(e){return t.extend(!0,{},e)},getIdGenerator:function(){var t=0;return function(){return t++}},templatify:function(e){function n(){return String(e)}return t.isFunction(e)?e:n},defer:function(t){setTimeout(t,0)},debounce:function(t,e,n){var i,o;return function(){var r,s,a=this,c=arguments;return r=function(){i=null,n||(o=t.apply(a,c))},s=n&&!i,clearTimeout(i),i=setTimeout(r,e),s&&(o=t.apply(a,c)),o}},throttle:function(t,e){var n,i,o,r,s,a;return s=0,a=function(){s=new Date,o=null,r=t.apply(n,i)},function(){var c=new Date,l=e-(c-s);return n=this,i=arguments,l<=0?(clearTimeout(o),o=null,s=c,r=t.apply(n,i)):o||(o=setTimeout(a,l)),r}},stringify:function(t){return e.isString(t)?t:JSON.stringify(t)},noop:function(){}}}(),n=function(){"use strict";function t(t){var s,a;return a=e.mixin({},r,t),s={css:o(),classes:a,html:n(a),selectors:i(a)},{css:s.css,html:s.html,classes:s.classes,selectors:s.selectors,mixin:function(t){e.mixin(t,s)}}}function n(t){return{wrapper:'',menu:'
    '}}function i(t){var n={};return e.each(t,function(t,e){n[e]="."+t}),n}function o(){var t={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none",opacity:"1"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},menu:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};return e.isMsie()&&e.mixin(t.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),t}var r={wrapper:"twitter-typeahead",input:"tt-input",hint:"tt-hint",menu:"tt-menu",dataset:"tt-dataset",suggestion:"tt-suggestion",selectable:"tt-selectable",empty:"tt-empty",open:"tt-open",cursor:"tt-cursor",highlight:"tt-highlight"};return t}(),i=function(){"use strict";function n(e){e&&e.el||t.error("EventBus initialized without el"),this.$el=t(e.el)}var i,o;return i="typeahead:",o={render:"rendered",cursorchange:"cursorchanged",select:"selected",autocomplete:"autocompleted"},e.mixin(n.prototype,{_trigger:function(e,n){var o;return o=t.Event(i+e),(n=n||[]).unshift(o),this.$el.trigger.apply(this.$el,n),o},before:function(t){var e,n;return e=[].slice.call(arguments,1),n=this._trigger("before"+t,e),n.isDefaultPrevented()},trigger:function(t){var e;this._trigger(t,[].slice.call(arguments,1)),(e=o[t])&&this._trigger(e,[].slice.call(arguments,1))}}),n}(),o=function(){"use strict";function t(t,e,n,i){var o;if(!n)return this;for(e=e.split(c),n=i?a(n,i):n,this._callbacks=this._callbacks||{};o=e.shift();)this._callbacks[o]=this._callbacks[o]||{sync:[],async:[]},this._callbacks[o][t].push(n);return this}function e(e,n,i){return t.call(this,"async",e,n,i)}function n(e,n,i){return t.call(this,"sync",e,n,i)}function i(t){var e;if(!this._callbacks)return this;for(t=t.split(c);e=t.shift();)delete this._callbacks[e];return this}function o(t){var e,n,i,o,s;if(!this._callbacks)return this;for(t=t.split(c),i=[].slice.call(arguments,1);(e=t.shift())&&(n=this._callbacks[e]);)o=r(n.sync,this,[e].concat(i)),s=r(n.async,this,[e].concat(i)),o()&&l(s);return this}function r(t,e,n){function i(){for(var i,o=0,r=t.length;!i&&o