diff --git a/.env.example b/.env.example index 1ed1ec049..ec281e283 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ APP_DEBUG=false APP_URL=http://ninja.dev APP_KEY=SomeRandomStringSomeRandomString APP_CIPHER=AES-256-CBC +APP_LOCALE=en DB_TYPE=mysql DB_STRICT=false @@ -90,7 +91,8 @@ WEPAY_ENVIRONMENT=production # production or stage WEPAY_AUTO_UPDATE=true # Requires permission from WePay WEPAY_ENABLE_CANADA=true WEPAY_FEE_PAYER=payee -WEPAY_APP_FEE_MULTIPLIER=0.002 +WEPAY_APP_FEE_CC_MULTIPLIER=0 +WEPAY_APP_FEE_ACH_MULTIPLIER=0 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 diff --git a/.travis.yml b/.travis.yml index 92bf4064b..067ed7c15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,7 @@ before_script: - sed -i 's/APP_ENV=production/APP_ENV=development/g' .env - sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env - sed -i 's/MAIL_DRIVER=smtp/MAIL_DRIVER=log/g' .env + - sed -i 's/PHANTOMJS_CLOUD_KEY/#PHANTOMJS_CLOUD_KEY/g' .env - sed -i '$a NINJA_DEV=true' .env - sed -i '$a TRAVIS=true' .env # create the database and user @@ -58,7 +59,6 @@ before_script: # migrate and seed the database - php artisan migrate --no-interaction - php artisan db:seed --no-interaction # default seed - - php artisan db:seed --no-interaction --class=UserTableSeeder # development seed # Start webserver on ninja.dev:8000 - php artisan serve --host=ninja.dev --port=8000 & # '&' allows to run in background # Start PhantomJS @@ -67,10 +67,10 @@ before_script: - sleep 5 # Make sure the app is up-to-date - curl -L http://ninja.dev:8000/update - #- php artisan ninja:create-test-data 25 + - php artisan ninja:create-test-data 4 true + - php artisan db:seed --no-interaction --class=UserTableSeeder # development seed script: - - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php @@ -83,23 +83,29 @@ script: - php ./vendor/codeception/codeception/codecept run --debug acceptance OnlinePaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance GatewayFeesCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php after_script: + - php artisan ninja:check-data --no-interaction - cat .env - mysql -u root -e 'select * from accounts;' ninja + - mysql -u root -e 'select * from users;' ninja - mysql -u root -e 'select * from account_gateways;' ninja - mysql -u root -e 'select * from clients;' ninja + - mysql -u root -e 'select * from contacts;' ninja - mysql -u root -e 'select * from invoices;' ninja - mysql -u root -e 'select * from invoice_items;' ninja + - mysql -u root -e 'select * from invitations;' ninja - mysql -u root -e 'select * from payments;' ninja - mysql -u root -e 'select * from credits;' ninja - mysql -u root -e 'select * from expenses;' ninja - cat storage/logs/laravel-error.log - cat storage/logs/laravel-info.log - - FILES=$(find tests/_output -type f -name '*.png') + - FILES=$(find tests/_output -type f -name '*.png' | sort -nr) - for i in $FILES; do echo $i; base64 "$i"; break; done notifications: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c2d0bbb7..0fad62197 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,8 @@ Thanks for your contributions! ## Submit bug reports or feature requests +Please discuss the changes with us ahead of time to ensure they will be merged. + ### Submit pull requests * [Fork](https://github.com/invoiceninja/invoiceninja#fork-destination-box) the [Invoice Ninja repository](https://github.com/invoiceninja/invoiceninja) * Create a new branch with the name `#issue_number-Short-description` @@ -11,7 +13,7 @@ Thanks for your contributions! * Make your changes and commit * Check if your branch is still in sync with the repositorys **`develop`** branch * _Read:_ [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) - * _Also read:_ [How to rebase a pull request](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request) + * _Also read:_ [How to rebase a pull request](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request) * Push your branch and create a PR against the Invoice Ninja **`develop`** branch * Update the [Changelog](CHANGELOG.md) @@ -21,7 +23,7 @@ To make the contribution process nice and easy for anyone, please follow some ru to give a more detailed explanation. * Only one feature/bugfix per issue. If you want to submit more, create multiple issues. * Only one feature/bugfix per PR(pull request). Split more changes into multiple PRs. - + #### Coding Style Try to follow the [PSR-2 guidlines](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) @@ -29,7 +31,7 @@ _Example styling:_ ```php /** * Gets a preview of the email - * + * * @param TemplateService $templateService * * @return \Illuminate\Http\Response diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index dca1327f4..de02d0d47 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use Carbon; use DB; +use Exception; use Illuminate\Console\Command; use Mail; use Symfony\Component\Console\Input\InputOption; @@ -83,6 +84,8 @@ class CheckData extends Command ->from(CONTACT_EMAIL) ->subject('Check-Data: ' . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE)); }); + } elseif (! $this->isValid) { + throw new Exception('Check data failed!!'); } } @@ -157,9 +160,15 @@ class CheckData extends Command 'products' => [ ENTITY_USER, ], + 'vendors' => [ + ENTITY_USER, + ], 'expense_categories' => [ ENTITY_USER, ], + 'payment_terms' => [ + ENTITY_USER, + ], 'projects' => [ ENTITY_USER, ENTITY_CLIENT, diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index 7e339643e..1606e5eec 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ExpenseRepository; use App\Ninja\Repositories\InvoiceRepository; @@ -25,7 +26,7 @@ class CreateTestData extends Command /** * @var string */ - protected $signature = 'ninja:create-test-data {count=1}'; + protected $signature = 'ninja:create-test-data {count=1} {create_account=false}'; /** * @var @@ -40,13 +41,15 @@ class CreateTestData extends Command * @param PaymentRepository $paymentRepo * @param VendorRepository $vendorRepo * @param ExpenseRepository $expenseRepo + * @param AccountRepository $accountRepo */ public function __construct( ClientRepository $clientRepo, InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, VendorRepository $vendorRepo, - ExpenseRepository $expenseRepo) + ExpenseRepository $expenseRepo, + AccountRepository $accountRepo) { parent::__construct(); @@ -57,6 +60,7 @@ class CreateTestData extends Command $this->paymentRepo = $paymentRepo; $this->vendorRepo = $vendorRepo; $this->expenseRepo = $expenseRepo; + $this->accountRepo = $accountRepo; } /** @@ -69,10 +73,21 @@ class CreateTestData extends Command } $this->info(date('Y-m-d').' Running CreateTestData...'); - - Auth::loginUsingId(1); $this->count = $this->argument('count'); + if (filter_var($this->argument('create_account'), FILTER_VALIDATE_BOOLEAN)) { + $this->info('Creating new account...'); + $account = $this->accountRepo->create( + $this->faker->firstName, + $this->faker->lastName, + $this->faker->safeEmail + ); + Auth::login($account->users[0]); + } else { + $this->info('Using first account...'); + Auth::loginUsingId(1); + } + $this->createClients(); $this->createVendors(); @@ -182,7 +197,7 @@ class CreateTestData extends Command 'vendor_id' => $vendor->id, 'amount' => $this->faker->randomFloat(2, 1, 10), 'expense_date' => null, - 'public_notes' => null, + 'public_notes' => '', ]; $expense = $this->expenseRepo->save($data); diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 88b317df5..0a62ca626 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Models\Account; use App\Models\Invoice; use App\Ninja\Mailers\ContactMailer as Mailer; use App\Ninja\Repositories\InvoiceRepository; @@ -57,9 +58,18 @@ class SendRecurringInvoices extends Command public function fire() { - $this->info(date('Y-m-d').' Running SendRecurringInvoices...'); + $this->info(date('Y-m-d H:i:s') . ' Running SendRecurringInvoices...'); $today = new DateTime(); + // check for counter resets + $accounts = Account::where('reset_counter_frequency_id', '>', 0) + ->orderBy('id', 'asc') + ->get(); + + foreach ($accounts as $account) { + $account->checkCounterReset(); + } + $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 is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today]) ->orderBy('id', 'asc') @@ -74,7 +84,8 @@ class SendRecurringInvoices extends Command continue; } - $recurInvoice->account->loadLocalizationSettings($recurInvoice->client); + $account = $recurInvoice->account; + $account->loadLocalizationSettings($recurInvoice->client); $invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice); if ($invoice && ! $invoice->isPaid()) { @@ -103,7 +114,7 @@ class SendRecurringInvoices extends Command } } - $this->info('Done'); + $this->info(date('Y-m-d H:i:s') . ' Done'); } /** diff --git a/app/Console/Commands/stubs/api-controller.stub b/app/Console/Commands/stubs/api-controller.stub index 8a047352c..bd7a2bc30 100644 --- a/app/Console/Commands/stubs/api-controller.stub +++ b/app/Console/Commands/stubs/api-controller.stub @@ -23,11 +23,12 @@ class $STUDLY_NAME$ApiController extends BaseAPIController /** * @SWG\Get( * path="/$LOWER_NAME$", - * summary="List of $LOWER_NAME$", + * summary="List $LOWER_NAME$", + * operationId="list$STUDLY_NAME$s", * tags={"$LOWER_NAME$"}, * @SWG\Response( * response=200, - * description="A list with $LOWER_NAME$", + * description="A list of $LOWER_NAME$", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) * ), * @SWG\Response( @@ -47,7 +48,14 @@ class $STUDLY_NAME$ApiController extends BaseAPIController * @SWG\Get( * path="/$LOWER_NAME$/{$LOWER_NAME$_id}", * summary="Individual $STUDLY_NAME$", + * operationId="get$STUDLY_NAME$", * tags={"$LOWER_NAME$"}, + * @SWG\Parameter( + * in="path", + * name="$LOWER_NAME$_id", + * type="integer", + * required=true + * ), * @SWG\Response( * response=200, * description="A single $LOWER_NAME$", @@ -59,7 +67,6 @@ class $STUDLY_NAME$ApiController extends BaseAPIController * ) * ) */ - public function show($STUDLY_NAME$Request $request) { return $this->itemResponse($request->entity()); @@ -71,11 +78,12 @@ class $STUDLY_NAME$ApiController extends BaseAPIController /** * @SWG\Post( * path="/$LOWER_NAME$", - * tags={"$LOWER_NAME$"}, * summary="Create a $LOWER_NAME$", + * operationId="create$STUDLY_NAME$", + * tags={"$LOWER_NAME$"}, * @SWG\Parameter( * in="body", - * name="body", + * name="$LOWER_NAME$", * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$") * ), * @SWG\Response( @@ -99,16 +107,23 @@ class $STUDLY_NAME$ApiController extends BaseAPIController /** * @SWG\Put( * path="/$LOWER_NAME$/{$LOWER_NAME$_id}", - * tags={"$LOWER_NAME$"}, * summary="Update a $LOWER_NAME$", + * operationId="update$STUDLY_NAME$", + * tags={"$LOWER_NAME$"}, + * @SWG\Parameter( + * in="path", + * name="$LOWER_NAME$_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="$LOWER_NAME$", * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$") * ), * @SWG\Response( * response=200, - * description="Update $LOWER_NAME$", + * description="Updated $LOWER_NAME$", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) * ), * @SWG\Response( @@ -117,7 +132,6 @@ class $STUDLY_NAME$ApiController extends BaseAPIController * ) * ) */ - public function update(Update$STUDLY_NAME$Request $request, $publicId) { if ($request->action) { @@ -133,16 +147,18 @@ class $STUDLY_NAME$ApiController extends BaseAPIController /** * @SWG\Delete( * path="/$LOWER_NAME$/{$LOWER_NAME$_id}", - * tags={"$LOWER_NAME$"}, * summary="Delete a $LOWER_NAME$", + * operationId="delete$STUDLY_NAME$", + * tags={"$LOWER_NAME$"}, * @SWG\Parameter( - * in="body", - * name="body", - * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$") + * in="path", + * name="$LOWER_NAME$_id", + * type="integer", + * required=true * ), * @SWG\Response( * response=200, - * description="Delete $LOWER_NAME$", + * description="Deleted $LOWER_NAME$", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$")) * ), * @SWG\Response( @@ -151,7 +167,6 @@ class $STUDLY_NAME$ApiController extends BaseAPIController * ) * ) */ - public function destroy(Update$STUDLY_NAME$Request $request) { $$LOWER_NAME$ = $request->entity(); diff --git a/app/Console/Commands/stubs/transformer.stub b/app/Console/Commands/stubs/transformer.stub index 27636445b..e5dc615bb 100644 --- a/app/Console/Commands/stubs/transformer.stub +++ b/app/Console/Commands/stubs/transformer.stub @@ -15,8 +15,8 @@ class $STUDLY_NAME$Transformer extends EntityTransformer * @SWG\Property(property="id", type="integer", example=1, readOnly=true) * @SWG\Property(property="user_id", type="integer", example=1) * @SWG\Property(property="account_key", type="string", example="123456") - * @SWG\Property(property="updated_at", type="timestamp", example="") - * @SWG\Property(property="archived_at", type="timestamp", example="1451160233") + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) */ /** diff --git a/app/Constants.php b/app/Constants.php index 1a568596f..75634ad9d 100644 --- a/app/Constants.php +++ b/app/Constants.php @@ -41,6 +41,11 @@ if (! defined('APP_NAME')) { define('INVOICE_TYPE_STANDARD', 1); define('INVOICE_TYPE_QUOTE', 2); + define('INVOICE_ITEM_TYPE_STANDARD', 1); + define('INVOICE_ITEM_TYPE_TASK', 2); + define('INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE', 3); + define('INVOICE_ITEM_TYPE_PAID_GATEWAY_FEE', 4); + define('PERSON_CONTACT', 'contact'); define('PERSON_USER', 'user'); define('PERSON_VENDOR_CONTACT', 'vendorcontact'); @@ -283,7 +288,6 @@ if (! defined('APP_NAME')) { define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN'); define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID'); - define('PREV_USER_ID', 'PREV_USER_ID'); define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h'); define('NINJA_LICENSE_ACCOUNT_KEY', 'AsFmBAeLXF0IKf7tmi0eiyZfmWW9hxMT'); define('NINJA_GATEWAY_ID', GATEWAY_STRIPE); @@ -292,7 +296,7 @@ if (! defined('APP_NAME')) { 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', '3.1.3' . env('NINJA_VERSION_SUFFIX')); + define('NINJA_VERSION', '3.2.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')); diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index f5567383c..9ed644515 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Validation\ValidationException; use Illuminate\Http\Exception\HttpResponseException; use Illuminate\Support\Facades\Response; +use Illuminate\Session\TokenMismatchException; use Redirect; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -26,10 +27,11 @@ class Handler extends ExceptionHandler * @var array */ protected $dontReport = [ - AuthorizationException::class, - HttpException::class, + TokenMismatchException::class, ModelNotFoundException::class, - ValidationException::class, + //AuthorizationException::class, + //HttpException::class, + //ValidationException::class, ]; /** @@ -43,11 +45,20 @@ class Handler extends ExceptionHandler */ public function report(Exception $e) { + if (! $this->shouldReport($e)) { + return false; + } + // don't show these errors in the logs if ($e instanceof NotFoundHttpException) { if (Crawler::isCrawler()) { return false; } + // The logo can take a few seconds to get synced between servers + // TODO: remove once we're using cloud storage for logos + if (Utils::isNinja() && strpos(request()->url(), '/logo/') !== false) { + return false; + } } elseif ($e instanceof HttpResponseException) { return false; } @@ -74,9 +85,9 @@ class Handler extends ExceptionHandler if ($e instanceof ModelNotFoundException) { return Redirect::to('/'); } - if ($e instanceof \Illuminate\Session\TokenMismatchException) { - // prevent loop since the page auto-submits - if ($request->path() != 'get_started') { + + if ($e instanceof TokenMismatchException) { + if (! in_array($request->path(), ['get_started', 'save_sidebar_state'])) { // https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e return redirect() ->back() diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index cceb58a25..cc20a725c 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -6,6 +6,7 @@ use App\Events\UserSignedUp; use App\Http\Requests\RegisterRequest; use App\Http\Requests\UpdateAccountRequest; use App\Models\Account; +use App\Ninja\OAuth\OAuth; use App\Ninja\Repositories\AccountRepository; use App\Ninja\Transformers\AccountTransformer; use App\Ninja\Transformers\UserAccountTransformer; @@ -121,6 +122,7 @@ class AccountApiController extends BaseAPIController for ($x = 0; $x < count($devices); $x++) { if ($devices[$x]['email'] == Auth::user()->username) { $devices[$x]['token'] = $request->token; //update + $devices[$x]['device'] = $request->device; $account->devices = json_encode($devices); $account->save(); $devices[$x]['account_key'] = $account->account_key; @@ -187,25 +189,15 @@ class AccountApiController extends BaseAPIController $token = $request->input('token'); $provider = $request->input('provider'); - try { - $user = Socialite::driver($provider)->stateless()->userFromToken($token); - } catch (Exception $exception) { - return $this->errorResponse(['message' => $exception->getMessage()], 401); - } + $oAuth = new OAuth(); + $user = $oAuth->getProvider($provider)->getTokenResponse($token); - if ($user) { - $providerId = AuthService::getProviderId($provider); - $user = $this->accountRepo->findUserByOauth($providerId, $user->id); - } - - if ($user) { + if($user) { Auth::login($user); - return $this->processLogin($request); - } else { - sleep(ERROR_DELAY); - - return $this->errorResponse(['message' => 'Invalid credentials'], 401); } + else + return $this->errorResponse(['message' => 'Invalid credentials'], 401); + } } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index c3d65cf0a..e7e04b159 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -9,7 +9,6 @@ use App\Http\Requests\SaveEmailSettings; use App\Http\Requests\UpdateAccountRequest; use App\Models\Account; use App\Models\AccountGateway; -use App\Models\AccountGatewaySettings; use App\Models\Affiliate; use App\Models\Document; use App\Models\Gateway; @@ -38,6 +37,7 @@ use Request; use Response; use Session; use stdClass; +use Exception; use URL; use Utils; @@ -123,17 +123,16 @@ class AccountController extends BaseController { $user = false; $guestKey = Input::get('guest_key'); // local storage key to login until registered - $prevUserId = Session::pull(PREV_USER_ID); // last user id used to link to new account if (Auth::check()) { return Redirect::to('invoices/create'); } - if (! Utils::isNinja() && (Account::count() > 0 && ! $prevUserId)) { + if (! Utils::isNinja() && Account::count() > 0) { return Redirect::to('/login'); } - if ($guestKey && ! $prevUserId) { + if ($guestKey) { $user = User::where('password', '=', $guestKey)->first(); if ($user && $user->registered) { @@ -144,11 +143,6 @@ class AccountController extends BaseController if (! $user) { $account = $this->accountRepo->create(); $user = $account->users()->first(); - - if ($prevUserId) { - $users = $this->accountRepo->associateAccounts($user->id, $prevUserId); - Session::put(SESSION_USER_ACCOUNTS, $users); - } } Auth::login($user, true); @@ -186,22 +180,8 @@ class AccountController extends BaseController $newPlan['price'] = Utils::getPlanPrice($newPlan); $credit = 0; - if (! empty($planDetails['started']) && $plan == PLAN_FREE) { - // Downgrade - $refund_deadline = clone $planDetails['started']; - $refund_deadline->modify('+30 days'); - - if ($plan == PLAN_FREE && $refund_deadline >= date_create()) { - if ($payment = $account->company->payment) { - $ninjaAccount = $this->accountRepo->getNinjaAccount(); - $paymentDriver = $ninjaAccount->paymentDriver(); - $paymentDriver->refundPayment($payment); - Session::flash('message', trans('texts.plan_refunded')); - \Log::info("Refunded Plan Payment: {$account->name} - {$user->email} - Deadline: {$refund_deadline->format('Y-m-d')}"); - } else { - Session::flash('message', trans('texts.updated_plan')); - } - } + if ($plan == PLAN_FREE && $company->processRefund(Auth::user())) { + Session::flash('warning', trans('texts.plan_refunded')); } $hasPaid = false; @@ -241,6 +221,8 @@ class AccountController extends BaseController $company->plan = $plan; $company->save(); + Session::flash('message', trans('texts.updated_plan')); + return Redirect::to('settings/account_management'); } } @@ -488,23 +470,19 @@ class AccountController extends BaseController } } - if ($trashedCount == 0) { - return Redirect::to('gateways/create'); - } else { - $tokenBillingOptions = []; - for ($i = 1; $i <= 4; $i++) { - $tokenBillingOptions[$i] = trans("texts.token_billing_{$i}"); - } - - return View::make('accounts.payments', [ - 'showAdd' => $count < count(Gateway::$alternate) + 1, - 'title' => trans('texts.online_payments'), - 'tokenBillingOptions' => $tokenBillingOptions, - 'currency' => Utils::getFromCache(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY), - 'currencies'), - 'account' => $account, - ]); + $tokenBillingOptions = []; + for ($i = 1; $i <= 4; $i++) { + $tokenBillingOptions[$i] = trans("texts.token_billing_{$i}"); } + + return View::make('accounts.payments', [ + 'showAdd' => $count < count(Gateway::$alternate) + 1, + 'title' => trans('texts.online_payments'), + 'tokenBillingOptions' => $tokenBillingOptions, + 'currency' => Utils::getFromCache(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY), 'currencies'), + 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('rate')->get(['public_id', 'name', 'rate']), + 'account' => $account, + ]); } /** @@ -812,9 +790,12 @@ class AccountController extends BaseController { $account = $request->user()->account; $account->fill($request->all()); - $account->bcc_email = $request->bcc_email; $account->save(); + $settings = $account->account_email_settings; + $settings->fill($request->all()); + $settings->save(); + return redirect('settings/' . ACCOUNT_EMAIL_SETTINGS) ->with('message', trans('texts.updated_settings')); } @@ -830,11 +811,11 @@ class AccountController extends BaseController foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) { $subjectField = "email_subject_{$type}"; $subject = Input::get($subjectField, $account->getEmailSubject($type)); - $account->$subjectField = ($subject == $account->getDefaultEmailSubject($type) ? null : $subject); + $account->account_email_settings->$subjectField = ($subject == $account->getDefaultEmailSubject($type) ? null : $subject); $bodyField = "email_template_{$type}"; $body = Input::get($bodyField, $account->getEmailTemplate($type)); - $account->$bodyField = ($body == $account->getDefaultEmailTemplate($type) ? null : $body); + $account->account_email_settings->$bodyField = ($body == $account->getDefaultEmailTemplate($type) ? null : $body); } foreach ([REMINDER1, REMINDER2, REMINDER3] as $type) { @@ -846,6 +827,7 @@ class AccountController extends BaseController } $account->save(); + $account->account_email_settings->save(); Session::flash('message', trans('texts.updated_settings')); } @@ -932,6 +914,8 @@ class AccountController extends BaseController $account->client_number_prefix = trim(Input::get('client_number_prefix')); $account->client_number_pattern = trim(Input::get('client_number_pattern')); $account->client_number_counter = Input::get('client_number_counter'); + $account->reset_counter_frequency_id = Input::get('reset_counter_frequency_id'); + $account->reset_counter_date = $account->reset_counter_frequency_id ? Utils::toSqlDate(Input::get('reset_counter_date')) : null; if (Input::has('recurring_hour')) { $account->recurring_hour = Input::get('recurring_hour'); @@ -1054,28 +1038,32 @@ class AccountController extends BaseController $size = filesize($filePath); if ($size / 1000 > MAX_DOCUMENT_SIZE) { - Session::flash('warning', 'File too large'); + Session::flash('warning', trans('texts.logo_warning_too_large')); } else { if ($documentType != 'gif') { $account->logo = $account->account_key.'.'.$documentType; - $imageSize = getimagesize($filePath); - $account->logo_width = $imageSize[0]; - $account->logo_height = $imageSize[1]; - $account->logo_size = $size; + try { + $imageSize = getimagesize($filePath); + $account->logo_width = $imageSize[0]; + $account->logo_height = $imageSize[1]; + $account->logo_size = $size; - // make sure image isn't interlaced - if (extension_loaded('fileinfo')) { - $image = Image::make($path); - $image->interlace(false); - $imageStr = (string) $image->encode($documentType); - $disk->put($account->logo, $imageStr); + // make sure image isn't interlaced + if (extension_loaded('fileinfo')) { + $image = Image::make($path); + $image->interlace(false); + $imageStr = (string) $image->encode($documentType); + $disk->put($account->logo, $imageStr); - $account->logo_size = strlen($imageStr); - } else { - $stream = fopen($filePath, 'r'); - $disk->getDriver()->putStream($account->logo, $stream, ['mimetype' => $documentTypeData['mime']]); - fclose($stream); + $account->logo_size = strlen($imageStr); + } else { + $stream = fopen($filePath, 'r'); + $disk->getDriver()->putStream($account->logo, $stream, ['mimetype' => $documentTypeData['mime']]); + fclose($stream); + } + } catch (Exception $exception) { + Session::flash('warning', trans('texts.logo_warning_invalid')); } } else { if (extension_loaded('fileinfo')) { @@ -1093,7 +1081,7 @@ class AccountController extends BaseController $account->logo_width = $image->width(); $account->logo_height = $image->height(); } else { - Session::flash('warning', 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.'); + Session::flash('warning', trans('texts.logo_warning_fileinfo')); } } } @@ -1142,9 +1130,6 @@ class AccountController extends BaseController $user->referral_code = $this->accountRepo->getReferralCode(); } } - if (Utils::isNinjaDev()) { - $user->dark_mode = Input::get('dark_mode') ? true : false; - } $user->save(); @@ -1189,6 +1174,8 @@ class AccountController extends BaseController $account = Auth::user()->account; $account->token_billing_type_id = Input::get('token_billing_type_id'); $account->auto_bill_on_due_date = boolval(Input::get('auto_bill_on_due_date')); + $account->gateway_fee_enabled = boolval(Input::get('gateway_fee_enabled')); + $account->save(); event(new UserSettingsChanged()); @@ -1198,35 +1185,6 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_PAYMENTS); } - /** - * @return \Illuminate\Http\RedirectResponse - */ - public function savePaymentGatewayLimits() - { - $gateway_type_id = intval(Input::get('gateway_type_id')); - $gateway_settings = AccountGatewaySettings::scope()->where('gateway_type_id', '=', $gateway_type_id)->first(); - - if (! $gateway_settings) { - $gateway_settings = AccountGatewaySettings::createNew(); - $gateway_settings->gateway_type_id = $gateway_type_id; - } - - $gateway_settings->min_limit = Input::get('limit_min_enable') ? intval(Input::get('limit_min')) : null; - $gateway_settings->max_limit = Input::get('limit_max_enable') ? intval(Input::get('limit_max')) : null; - - if ($gateway_settings->max_limit !== null && $gateway_settings->min_limit > $gateway_settings->max_limit) { - $gateway_settings->max_limit = $gateway_settings->min_limit; - } - - $gateway_settings->save(); - - event(new UserSettingsChanged()); - - Session::flash('message', trans('texts.updated_settings')); - - return Redirect::to('settings/' . ACCOUNT_PAYMENTS); - } - /** * @return \Illuminate\Http\RedirectResponse */ @@ -1255,7 +1213,7 @@ class AccountController extends BaseController public function checkEmail() { $email = User::withTrashed()->where('email', '=', Input::get('email')) - ->where('id', '<>', Auth::user()->id) + ->where('id', '<>', Auth::user()->registered ? 0 : Auth::user()->id) ->first(); if ($email) { @@ -1270,36 +1228,58 @@ class AccountController extends BaseController */ public function submitSignup() { + $user = Auth::user(); + $account = $user->account; + $rules = [ 'new_first_name' => 'required', 'new_last_name' => 'required', 'new_password' => 'required|min:6', - 'new_email' => 'email|required|unique:users,email,'.Auth::user()->id.',id', + 'new_email' => 'email|required|unique:users,email', ]; + if (! $user->registered) { + $rules['new_email'] .= ',' . Auth::user()->id . ',id'; + } + $validator = Validator::make(Input::all(), $rules); if ($validator->fails()) { return ''; } - /** @var \App\Models\User $user */ - $user = Auth::user(); - $user->first_name = trim(Input::get('new_first_name')); - $user->last_name = trim(Input::get('new_last_name')); - $user->email = trim(strtolower(Input::get('new_email'))); - $user->username = $user->email; - $user->password = bcrypt(trim(Input::get('new_password'))); - $user->registered = true; - $user->save(); + $firstName = trim(Input::get('new_first_name')); + $lastName = trim(Input::get('new_last_name')); + $email = trim(strtolower(Input::get('new_email'))); + $password = trim(Input::get('new_password')); - $user->account->startTrial(PLAN_PRO); + if ($user->registered) { + $newAccount = $this->accountRepo->create($firstName, $lastName, $email, $password, $account->company); + $newUser = $newAccount->users()->first(); + $users = $this->accountRepo->associateAccounts($user->id, $newUser->id); - if (Input::get('go_pro') == 'true') { - Session::set(REQUESTED_PRO_PLAN, true); + Session::flash('message', trans('texts.created_new_company')); + Session::put(SESSION_USER_ACCOUNTS, $users); + Auth::loginUsingId($newUser->id); + + return RESULT_SUCCESS; + } else { + $user->first_name = $firstName; + $user->last_name = $lastName; + $user->email = $email; + $user->username = $user->email; + $user->password = bcrypt($password); + $user->registered = true; + $user->save(); + + $user->account->startTrial(PLAN_PRO); + + if (Input::get('go_pro') == 'true') { + Session::set(REQUESTED_PRO_PLAN, true); + } + + return "{$user->first_name} {$user->last_name}"; } - - return "{$user->first_name} {$user->last_name}"; } /** @@ -1328,6 +1308,16 @@ class AccountController extends BaseController return RESULT_SUCCESS; } + /** + * @return \Illuminate\Http\RedirectResponse + */ + public function purgeData() + { + $this->dispatch(new \App\Jobs\PurgeAccountData()); + + return redirect('/settings/account_management')->withMessage(trans('texts.purge_successful')); + } + /** * @return \Illuminate\Http\RedirectResponse */ @@ -1350,6 +1340,9 @@ class AccountController extends BaseController $account = Auth::user()->account; \Log::info("Canceled Account: {$account->name} - {$user->email}"); + $company = $account->company; + $refunded = $company->processRefund(Auth::user()); + Document::scope()->each(function ($item, $key) { $item->delete(); }); @@ -1365,6 +1358,10 @@ class AccountController extends BaseController Auth::logout(); Session::flush(); + if ($refunded) { + Session::flash('warning', trans('texts.plan_refunded')); + } + return Redirect::to('/')->with('clearGuestKey', true); } @@ -1414,18 +1411,17 @@ class AccountController extends BaseController public function previewEmail(TemplateService $templateService) { $template = Input::get('template'); - $invoice = Invoice::scope() - ->invoices() - ->withTrashed() - ->first(); + $invitation = \App\Models\Invitation::scope() + ->with('invoice.client.contacts') + ->first(); - if (! $invoice) { + if (! $invitation) { return trans('texts.create_invoice_for_sample'); } /** @var \App\Models\Account $account */ $account = Auth::user()->account; - $invitation = $invoice->invitations->first(); + $invoice = $invitation->invoice; // replace the variables with sample data $data = [ diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 56f2dc5d7..44bbd4000 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\Account; +use App\Models\AccountGatewaySettings; use App\Models\AccountGateway; use App\Models\Gateway; use App\Services\AccountGatewayService; @@ -131,6 +132,10 @@ class AccountGatewayController extends BaseController $currentGateways = $account->account_gateways; $gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name')->get(); + if ($accountGateway) { + $accountGateway->fields = []; + } + foreach ($gateways as $gateway) { $fields = $gateway->getFields(); if (! $gateway->isCustom()) { @@ -372,7 +377,7 @@ class AccountGatewayController extends BaseController 'tos_agree' => 'required', 'first_name' => 'required', 'last_name' => 'required', - 'email' => 'required', + 'email' => 'required|email', ]; if (WEPAY_ENABLE_CANADA) { @@ -387,6 +392,13 @@ class AccountGatewayController extends BaseController ->withInput(); } + if (! $user->email) { + $user->email = trim(Input::get('email')); + $user->first_name = trim(Input::get('first_name')); + $user->last_name = trim(Input::get('last_name')); + $user->save(); + } + try { $wepay = Utils::setupWePay(); @@ -494,4 +506,33 @@ class AccountGatewayController extends BaseController return Redirect::to("gateways/{$accountGateway->public_id}/edit"); } + + /** + * @return \Illuminate\Http\RedirectResponse + */ + public function savePaymentGatewayLimits() + { + $gateway_type_id = intval(Input::get('gateway_type_id')); + $gateway_settings = AccountGatewaySettings::scope()->where('gateway_type_id', '=', $gateway_type_id)->first(); + + if (! $gateway_settings) { + $gateway_settings = AccountGatewaySettings::createNew(); + $gateway_settings->gateway_type_id = $gateway_type_id; + } + + $gateway_settings->min_limit = Input::get('limit_min_enable') ? intval(Input::get('limit_min')) : null; + $gateway_settings->max_limit = Input::get('limit_max_enable') ? intval(Input::get('limit_max')) : null; + + if ($gateway_settings->max_limit !== null && $gateway_settings->min_limit > $gateway_settings->max_limit) { + $gateway_settings->max_limit = $gateway_settings->min_limit; + } + + $gateway_settings->fill(Input::all()); + $gateway_settings->save(); + + Session::flash('message', trans('texts.updated_settings')); + + return Redirect::to('settings/' . ACCOUNT_PAYMENTS); + } + } diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index c87b333fe..13e96ae74 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -56,7 +56,7 @@ class AppController extends BaseController $test = Input::get('test'); $app = Input::get('app'); - $app['key'] = env('APP_KEY') ?: str_random(RANDOM_KEY_LENGTH); + $app['key'] = env('APP_KEY') ?: strtolower(str_random(RANDOM_KEY_LENGTH)); $app['debug'] = Input::get('debug') ? 'true' : 'false'; $app['https'] = Input::get('https') ? 'true' : 'false'; @@ -101,7 +101,7 @@ class AppController extends BaseController $_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['PHANTOMJS_SECRET'] = str_random(RANDOM_KEY_LENGTH); + $_ENV['PHANTOMJS_SECRET'] = strtolower(str_random(RANDOM_KEY_LENGTH)); $_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain']; $_ENV['MAILGUN_SECRET'] = $mail['mailgun_secret']; @@ -191,7 +191,8 @@ class AppController extends BaseController $config .= "{$key}={$val}\n"; } - $fp = fopen(base_path().'/.env', 'w'); + $filePath = base_path().'/.env'; + $fp = fopen($filePath, 'w'); fwrite($fp, $config); fclose($fp); @@ -345,6 +346,16 @@ class AppController extends BaseController return RESULT_SUCCESS; } + public function checkData() + { + try { + Artisan::call('ninja:check-data'); + return RESULT_SUCCESS; + } catch (Exception $exception) { + return RESULT_FAILURE; + } + } + public function stats() { if (! hash_equals(Input::get('password'), env('RESELLER_PASSWORD'))) { diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index c5fbccb78..efe471557 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -173,13 +173,17 @@ class AuthController extends Controller public function getLogoutWrapper() { if (Auth::check() && ! Auth::user()->registered) { - $account = Auth::user()->account; - $this->accountRepo->unlinkAccount($account); + if (request()->force_logout) { + $account = Auth::user()->account; + $this->accountRepo->unlinkAccount($account); - if (! $account->hasMultipleAccounts()) { - $account->company->forceDelete(); + if (! $account->hasMultipleAccounts()) { + $account->company->forceDelete(); + } + $account->forceDelete(); + } else { + return redirect('/'); } - $account->forceDelete(); } $response = self::getLogout(); diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php index 0a07f8b0b..ef7460b64 100644 --- a/app/Http/Controllers/BaseAPIController.php +++ b/app/Http/Controllers/BaseAPIController.php @@ -20,6 +20,7 @@ use Utils; * schemes={"http","https"}, * host="ninja.dev", * basePath="/api/v1", + * produces={"application/json"}, * @SWG\Info( * version="1.0.0", * title="Invoice Ninja API", @@ -37,11 +38,12 @@ use Utils; * description="Find out more about Invoice Ninja", * url="https://www.invoiceninja.com" * ), + * security={"api_key": {}}, * @SWG\SecurityScheme( * securityDefinition="api_key", * type="apiKey", * in="header", - * name="TOKEN" + * name="X-Ninja-Token" * ) * ) */ diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 90e85c312..55ebaeb55 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -37,7 +37,7 @@ class BaseController extends Controller // when restoring redirect to entity if ($action == 'restore' && count($ids) == 1) { - return redirect("{$entityTypes}/" . $ids[0]); + return redirect("{$entityTypes}/" . $ids[0] . '/edit'); // when viewing from a datatable list } elseif (strpos($referer, '/clients/')) { return redirect($referer); @@ -45,7 +45,7 @@ class BaseController extends Controller return redirect("{$entityTypes}"); // when viewing individual entity } elseif (count($ids)) { - return redirect("{$entityTypes}/" . $ids[0]); + return redirect("{$entityTypes}/" . $ids[0] . '/edit'); } else { return redirect("{$entityTypes}"); } diff --git a/app/Http/Controllers/ClientApiController.php b/app/Http/Controllers/ClientApiController.php index 928fe68b8..d32968eac 100644 --- a/app/Http/Controllers/ClientApiController.php +++ b/app/Http/Controllers/ClientApiController.php @@ -26,11 +26,12 @@ class ClientApiController extends BaseAPIController /** * @SWG\Get( * path="/clients", - * summary="List of clients", + * summary="List clients", + * operationId="listClients", * tags={"client"}, * @SWG\Response( * response=200, - * description="A list with clients", + * description="A list of clients", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Client")) * ), * @SWG\Response( @@ -45,11 +46,12 @@ class ClientApiController extends BaseAPIController ->orderBy('created_at', 'desc') ->withTrashed(); - // Filter by email if ($email = Input::get('email')) { $clients = $clients->whereHas('contacts', function ($query) use ($email) { $query->where('email', $email); }); + } elseif ($idNumber = Input::get('id_number')) { + $clients = $clients->whereIdNumber($idNumber); } return $this->listResponse($clients); @@ -58,8 +60,15 @@ class ClientApiController extends BaseAPIController /** * @SWG\Get( * path="/clients/{client_id}", - * summary="Individual Client", + * summary="Retrieve a client", + * operationId="getClient", * tags={"client"}, + * @SWG\Parameter( + * in="path", + * name="client_id", + * type="integer", + * required=true + * ), * @SWG\Response( * response=200, * description="A single client", @@ -79,11 +88,12 @@ class ClientApiController extends BaseAPIController /** * @SWG\Post( * path="/clients", - * tags={"client"}, * summary="Create a client", + * operationId="createClient", + * tags={"client"}, * @SWG\Parameter( * in="body", - * name="body", + * name="client", * @SWG\Schema(ref="#/definitions/Client") * ), * @SWG\Response( @@ -107,16 +117,23 @@ class ClientApiController extends BaseAPIController /** * @SWG\Put( * path="/clients/{client_id}", - * tags={"client"}, * summary="Update a client", + * operationId="updateClient", + * tags={"client"}, + * @SWG\Parameter( + * in="path", + * name="client_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="client", * @SWG\Schema(ref="#/definitions/Client") * ), * @SWG\Response( * response=200, - * description="Update client", + * description="Updated client", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client")) * ), * @SWG\Response( @@ -145,16 +162,18 @@ class ClientApiController extends BaseAPIController /** * @SWG\Delete( * path="/clients/{client_id}", - * tags={"client"}, * summary="Delete a client", + * operationId="deleteClient", + * tags={"client"}, * @SWG\Parameter( - * in="body", - * name="body", - * @SWG\Schema(ref="#/definitions/Client") + * in="path", + * name="client_id", + * type="integer", + * required=true * ), * @SWG\Response( * response=200, - * description="Delete client", + * description="Deleted client", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client")) * ), * @SWG\Response( diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index 40a46176f..c583835fd 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -68,6 +68,7 @@ class ClientPortalController extends BaseController } $account->loadLocalizationSettings($client); + $this->invoiceRepo->clearGatewayFee($invoice); if (! Input::has('phantomjs') && ! session('silent:' . $client->id) && ! Session::has($invitation->invitation_key) && (! Auth::check() || Auth::user()->account_id != $invoice->account_id)) { @@ -146,6 +147,7 @@ class ClientPortalController extends BaseController 'paymentTypes' => $paymentTypes, 'paymentURL' => $paymentURL, 'phantomjs' => Input::has('phantomjs'), + 'gatewayTypeId' => count($paymentTypes) == 1 ? $paymentTypes[0]['gatewayTypeId'] : false, ]; if ($paymentDriver = $account->paymentDriver($invitation, GATEWAY_TYPE_CREDIT_CARD)) { @@ -521,7 +523,7 @@ class ClientPortalController extends BaseController 'account' => $account, 'title' => trans('texts.credits'), 'entityType' => ENTITY_CREDIT, - 'columns' => Utils::trans(['credit_date', 'credit_amount', 'credit_balance']), + 'columns' => Utils::trans(['credit_date', 'credit_amount', 'credit_balance', 'notes']), ]; return response()->view('public_list', $data); diff --git a/app/Http/Controllers/ContactApiController.php b/app/Http/Controllers/ContactApiController.php new file mode 100644 index 000000000..a74658077 --- /dev/null +++ b/app/Http/Controllers/ContactApiController.php @@ -0,0 +1,182 @@ +contactRepo = $contactRepo; + $this->contactService = $contactService; + } + + /** + * @SWG\Get( + * path="/contacts", + * summary="List contacts", + * tags={"contact"}, + * @SWG\Response( + * response=200, + * description="A list of contacts", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Contact")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function index() + { + $contacts = Contact::scope() + ->withTrashed() + ->orderBy('created_at', 'desc'); + + return $this->listResponse($contacts); + } + + /** + * @SWG\Get( + * path="/contacts/{contact_id}", + * summary="Retrieve a contact", + * tags={"contact"}, + * @SWG\Parameter( + * in="path", + * name="contact_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single contact", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(ContactRequest $request) + { + return $this->itemResponse($request->entity()); + } + + /** + * @SWG\Post( + * path="/contacts", + * tags={"contact"}, + * summary="Create a contact", + * @SWG\Parameter( + * in="body", + * name="contact", + * @SWG\Schema(ref="#/definitions/Contact") + * ), + * @SWG\Response( + * response=200, + * description="New contact", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function store(CreateContactRequest $request) + { + $contact = $this->contactService->save($request->input()); + + return $this->itemResponse($contact); + } + + /** + * @SWG\Put( + * path="/contacts/{contact_id}", + * tags={"contact"}, + * summary="Update a contact", + * @SWG\Parameter( + * in="path", + * name="contact_id", + * type="integer", + * required=true + * ), + * @SWG\Parameter( + * in="body", + * name="contact", + * @SWG\Schema(ref="#/definitions/Contact") + * ), + * @SWG\Response( + * response=200, + * description="Updated contact", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + * + * @param mixed $publicId + */ + public function update(UpdateContactRequest $request, $publicId) + { + if ($request->action) { + return $this->handleAction($request); + } + + $data = $request->input(); + $data['public_id'] = $publicId; + $contact = $this->contactService->save($data, $request->entity()); + + return $this->itemResponse($contact); + } + + /** + * @SWG\Delete( + * path="/contacts/{contact_id}", + * tags={"contact"}, + * summary="Delete a contact", + * @SWG\Parameter( + * in="path", + * name="contact_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted contact", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateContactRequest $request) + { + $contact = $request->entity(); + + $this->contactRepo->delete($contact); + + return $this->itemResponse($contact); + } +} diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php index fb85331eb..df3691599 100644 --- a/app/Http/Controllers/DocumentAPIController.php +++ b/app/Http/Controllers/DocumentAPIController.php @@ -2,8 +2,9 @@ namespace App\Http\Controllers; -use App\Http\Requests\CreateDocumentRequest; use App\Http\Requests\DocumentRequest; +use App\Http\Requests\CreateDocumentRequest; +use App\Http\Requests\UpdateDocumentRequest; use App\Models\Document; use App\Ninja\Repositories\DocumentRepository; @@ -37,11 +38,12 @@ class DocumentAPIController extends BaseAPIController /** * @SWG\Get( * path="/documents", - * summary="List of document", + * summary="List document", + * operationId="listDocuments", * tags={"document"}, * @SWG\Response( * response=200, - * description="A list with documents", + * description="A list of documents", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Document")) * ), * @SWG\Response( @@ -61,6 +63,29 @@ class DocumentAPIController extends BaseAPIController * @param DocumentRequest $request * * @return \Illuminate\Http\Response|\Redirect|\Symfony\Component\HttpFoundation\StreamedResponse + * + * @SWG\Get( + * path="/documents/{document_id}", + * summary="Download a document", + * operationId="getDocument", + * tags={"document"}, + * produces={"application/octet-stream"}, + * @SWG\Parameter( + * in="path", + * name="document_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A file", + * @SWG\Schema(type="file") + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function show(DocumentRequest $request) { @@ -76,11 +101,12 @@ class DocumentAPIController extends BaseAPIController /** * @SWG\Post( * path="/documents", - * tags={"document"}, * summary="Create a document", + * operationId="createDocument", + * tags={"document"}, * @SWG\Parameter( * in="body", - * name="body", + * name="document", * @SWG\Schema(ref="#/definitions/Document") * ), * @SWG\Response( @@ -100,4 +126,36 @@ class DocumentAPIController extends BaseAPIController return $this->itemResponse($document); } + + /** + * @SWG\Delete( + * path="/documents/{document_id}", + * summary="Delete a document", + * operationId="deleteDocument", + * tags={"document"}, + * @SWG\Parameter( + * in="path", + * name="document_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted document", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Document")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateDocumentRequest $request) + { + $entity = $request->entity(); + + $this->documentRepo->delete($entity); + + return $this->itemResponse($entity); + } } diff --git a/app/Http/Controllers/ExpenseApiController.php b/app/Http/Controllers/ExpenseApiController.php index 07bbcb995..1b67c1e8d 100644 --- a/app/Http/Controllers/ExpenseApiController.php +++ b/app/Http/Controllers/ExpenseApiController.php @@ -2,8 +2,8 @@ namespace App\Http\Controllers; -use App\Http\Requests\CreateExpenseRequest; use App\Http\Requests\ExpenseRequest; +use App\Http\Requests\CreateExpenseRequest; use App\Http\Requests\UpdateExpenseRequest; use App\Models\Expense; use App\Ninja\Repositories\ExpenseRepository; @@ -28,11 +28,12 @@ class ExpenseApiController extends BaseAPIController /** * @SWG\Get( * path="/expenses", - * summary="List of expenses", + * summary="List expenses", + * operationId="listExpenses", * tags={"expense"}, * @SWG\Response( * response=200, - * description="A list with expenses", + * description="A list of expenses", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Expense")) * ), * @SWG\Response( @@ -51,14 +52,43 @@ class ExpenseApiController extends BaseAPIController return $this->listResponse($expenses); } + /** + * @SWG\Get( + * path="/expenses/{expense_id}", + * summary="Retrieve an expense", + * operationId="getExpense", + * tags={"expense"}, + * @SWG\Parameter( + * in="path", + * name="expense_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single expense", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(ExpenseRequest $request) + { + return $this->itemResponse($request->entity()); + } + /** * @SWG\Post( * path="/expenses", + * summary="Create an expense", + * operationId="createExpense", * tags={"expense"}, - * summary="Create a expense", * @SWG\Parameter( * in="body", - * name="body", + * name="expense", * @SWG\Schema(ref="#/definitions/Expense") * ), * @SWG\Response( @@ -86,16 +116,23 @@ class ExpenseApiController extends BaseAPIController /** * @SWG\Put( * path="/expenses/{expense_id}", + * summary="Update an expense", + * operationId="updateExpense", * tags={"expense"}, - * summary="Update a expense", + * @SWG\Parameter( + * in="path", + * name="expense_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="expense", * @SWG\Schema(ref="#/definitions/Expense") * ), * @SWG\Response( * response=200, - * description="Update expense", + * description="Updated expense", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense")) * ), * @SWG\Response( @@ -122,16 +159,18 @@ class ExpenseApiController extends BaseAPIController /** * @SWG\Delete( * path="/expenses/{expense_id}", + * summary="Delete an expense", + * operationId="deleteExpense", * tags={"expense"}, - * summary="Delete a expense", * @SWG\Parameter( - * in="body", - * name="body", - * @SWG\Schema(ref="#/definitions/Expense") + * in="path", + * name="expense_id", + * type="integer", + * required=true * ), * @SWG\Response( * response=200, - * description="Delete expense", + * description="Deleted expense", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense")) * ), * @SWG\Response( @@ -140,7 +179,7 @@ class ExpenseApiController extends BaseAPIController * ) * ) */ - public function destroy(ExpenseRequest $request) + public function destroy(UpdateExpenseRequest $request) { $expense = $request->entity(); diff --git a/app/Http/Controllers/ExpenseCategoryApiController.php b/app/Http/Controllers/ExpenseCategoryApiController.php index cbdcb3f5e..1a2dea026 100644 --- a/app/Http/Controllers/ExpenseCategoryApiController.php +++ b/app/Http/Controllers/ExpenseCategoryApiController.php @@ -2,8 +2,10 @@ namespace App\Http\Controllers; +use App\Http\Requests\ExpenseCategoryRequest; use App\Http\Requests\CreateExpenseCategoryRequest; use App\Http\Requests\UpdateExpenseCategoryRequest; +use App\Models\ExpenseCategory; use App\Ninja\Repositories\ExpenseCategoryRepository; use App\Services\ExpenseCategoryService; use Input; @@ -22,14 +24,69 @@ class ExpenseCategoryApiController extends BaseAPIController $this->categoryService = $categoryService; } + /** + * @SWG\Get( + * path="/expense_categories", + * summary="List expense categories", + * operationId="listExpenseCategories", + * tags={"expense_category"}, + * @SWG\Response( + * response=200, + * description="A list of expense categories", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/ExpenseCategory")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function index() + { + $clients = ExpenseCategory::scope() + ->orderBy('created_at', 'desc') + ->withTrashed(); + + return $this->listResponse($clients); + } + + /** + * @SWG\Get( + * path="/expense_categories/{expense_category_id}", + * summary="Retrieve an Expense Category", + * operationId="getExpenseCategory", + * tags={"expense_category"}, + * @SWG\Parameter( + * in="path", + * name="expense_category_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single expense categroy", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(ExpenseCategory $request) + { + return $this->itemResponse($request->entity()); + } + /** * @SWG\Post( * path="/expense_categories", - * tags={"expense_category"}, * summary="Create an expense category", + * operationId="createExpenseCategory", + * tags={"expense_category"}, * @SWG\Parameter( * in="body", - * name="body", + * name="expense_category", * @SWG\Schema(ref="#/definitions/ExpenseCategory") * ), * @SWG\Response( @@ -53,16 +110,23 @@ class ExpenseCategoryApiController extends BaseAPIController /** * @SWG\Put( * path="/expense_categories/{expense_category_id}", - * tags={"expense_category"}, * summary="Update an expense category", + * operationId="updateExpenseCategory", + * tags={"expense_category"}, + * @SWG\Parameter( + * in="path", + * name="expense_category_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="expense_category", * @SWG\Schema(ref="#/definitions/ExpenseCategory") * ), * @SWG\Response( * response=200, - * description="Update expense category", + * description="Updated expense category", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory")) * ), * @SWG\Response( @@ -77,4 +141,36 @@ class ExpenseCategoryApiController extends BaseAPIController return $this->itemResponse($category); } + + /** + * @SWG\Delete( + * path="/expense_categories/{expense_category_id}", + * summary="Delete an expense category", + * operationId="deleteExpenseCategory", + * tags={"expense_category"}, + * @SWG\Parameter( + * in="path", + * name="expense_category_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted expense category", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateExpenseCategoryRequest $request) + { + $entity = $request->entity(); + + $this->expenseCategoryRepo->delete($entity); + + return $this->itemResponse($entity); + } } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 7f26529fe..7948d210e 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -65,12 +65,6 @@ class HomeController extends BaseController */ public function invoiceNow() { - if (Auth::check() && Input::get('new_company')) { - Session::put(PREV_USER_ID, Auth::user()->id); - Auth::user()->clearSession(); - Auth::logout(); - } - // Track the referral/campaign code if (Input::has('rc')) { Session::set(SESSION_REFERRAL_CODE, Input::get('rc')); diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 6fde46cd1..49618a7ab 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -2,57 +2,92 @@ namespace App\Http\Controllers; +use Illuminate\Http\Request; use App\Services\ImportService; +use App\Jobs\ImportData; use Exception; use Input; use Redirect; use Session; use Utils; use View; +use Auth; class ImportController extends BaseController { public function __construct(ImportService $importService) { - //parent::__construct(); - $this->importService = $importService; } - public function doImport() + public function doImport(Request $request) { + if (! Auth::user()->confirmed) { + return redirect('/settings/' . ACCOUNT_IMPORT_EXPORT)->withError(trans('texts.confirm_account_to_import')); + } + $source = Input::get('source'); $files = []; + $timestamp = time(); foreach (ImportService::$entityTypes as $entityType) { - if (Input::file("{$entityType}_file")) { - $files[$entityType] = Input::file("{$entityType}_file")->getRealPath(); - if ($source === IMPORT_CSV) { - Session::forget("{$entityType}-data"); + $fileName = $entityType; + if ($request->hasFile($fileName)) { + $file = $request->file($fileName); + $destinationPath = storage_path() . '/import'; + $extension = $file->getClientOriginalExtension(); + + if (! in_array($extension, ['csv', 'xls', 'xlsx', 'json'])) { + continue; } + + $newFileName = sprintf('%s_%s_%s.%s', Auth::user()->account_id, $timestamp, $fileName, $extension); + $file->move($destinationPath, $newFileName); + $files[$entityType] = $newFileName; } } if (! count($files)) { Session::flash('error', trans('texts.select_file')); - return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); } try { if ($source === IMPORT_CSV) { $data = $this->importService->mapCSV($files); - - return View::make('accounts.import_map', ['data' => $data]); + return View::make('accounts.import_map', [ + 'data' => $data, + 'timestamp' => $timestamp, + ]); } elseif ($source === IMPORT_JSON) { - $results = $this->importService->importJSON($files[IMPORT_JSON]); - - return $this->showResult($results); + $includeData = filter_var(Input::get('data'), FILTER_VALIDATE_BOOLEAN); + $includeSettings = filter_var(Input::get('settings'), FILTER_VALIDATE_BOOLEAN); + if (config('queue.default') === 'sync') { + $results = $this->importService->importJSON($files[IMPORT_JSON], $includeData, $includeSettings); + $message = $this->importService->presentResults($results, $includeSettings); + } else { + $settings = [ + 'files' => $files, + 'include_data' => $includeData, + 'include_settings' => $includeSettings, + ]; + $this->dispatch(new ImportData(Auth::user(), IMPORT_JSON, $settings)); + $message = trans('texts.import_started'); + } } else { - $results = $this->importService->importFiles($source, $files); - - return $this->showResult($results); + if (config('queue.default') === 'sync') { + $results = $this->importService->importFiles($source, $files); + $message = $this->importService->presentResults($results); + } else { + $settings = [ + 'files' => $files, + 'source' => $source, + ]; + $this->dispatch(new ImportData(Auth::user(), false, $settings)); + $message = trans('texts.import_started'); + } } + return redirect('/settings/' . ACCOUNT_IMPORT_EXPORT)->withWarning($message); } catch (Exception $exception) { Utils::logError($exception); Session::flash('error', $exception->getMessage()); @@ -63,13 +98,24 @@ class ImportController extends BaseController public function doImportCSV() { - $map = Input::get('map'); - $headers = Input::get('headers'); - try { - $results = $this->importService->importCSV($map, $headers); + $map = Input::get('map'); + $headers = Input::get('headers'); + $timestamp = Input::get('timestamp'); + if (config('queue.default') === 'sync') { + $results = $this->importService->importCSV($map, $headers, $timestamp); + $message = $this->importService->presentResults($results); + } else { + $settings = [ + 'timestamp' => $timestamp, + 'map' => $map, + 'headers' => $headers, + ]; + $this->dispatch(new ImportData(Auth::user(), IMPORT_CSV, $settings)); + $message = trans('texts.import_started'); + } - return $this->showResult($results); + return redirect('/settings/' . ACCOUNT_IMPORT_EXPORT)->withWarning($message); } catch (Exception $exception) { Utils::logError($exception); Session::flash('error', $exception->getMessage()); @@ -77,32 +123,4 @@ class ImportController extends BaseController return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); } } - - private function showResult($results) - { - $message = ''; - $skipped = []; - - foreach ($results as $entityType => $entityResults) { - if ($count = count($entityResults[RESULT_SUCCESS])) { - $message .= trans("texts.created_{$entityType}s", ['count' => $count]) . '
'; - } - if (count($entityResults[RESULT_FAILURE])) { - $skipped = array_merge($skipped, $entityResults[RESULT_FAILURE]); - } - } - - if (count($skipped)) { - $message .= '

' . trans('texts.failed_to_import') . '
'; - foreach ($skipped as $skip) { - $message .= json_encode($skip) . '
'; - } - } - - if ($message) { - Session::flash('warning', $message); - } - - return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); - } } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index d310f6f9e..b948e2896 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -2,8 +2,8 @@ namespace App\Http\Controllers; -use App\Http\Requests\CreateInvoiceAPIRequest; use App\Http\Requests\InvoiceRequest; +use App\Http\Requests\CreateInvoiceAPIRequest; use App\Http\Requests\UpdateInvoiceAPIRequest; use App\Jobs\SendInvoiceEmail; use App\Jobs\SendPaymentEmail; @@ -42,11 +42,12 @@ class InvoiceApiController extends BaseAPIController /** * @SWG\Get( * path="/invoices", - * summary="List of invoices", + * summary="List invoices", + * operationId="listInvoices", * tags={"invoice"}, * @SWG\Response( * response=200, - * description="A list with invoices", + * description="A list of invoices", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice")) * ), * @SWG\Response( @@ -62,14 +63,25 @@ class InvoiceApiController extends BaseAPIController ->with('invoice_items', 'client') ->orderBy('created_at', 'desc'); + // Filter by invoice number + if ($invoiceNumber = Input::get('invoice_number')) { + $invoices->whereInvoiceNumber($invoiceNumber); + } + return $this->listResponse($invoices); } /** * @SWG\Get( * path="/invoices/{invoice_id}", - * summary="Individual Invoice", + * summary="Retrieve an Invoice", * tags={"invoice"}, + * @SWG\Parameter( + * in="path", + * name="invoice_id", + * type="integer", + * required=true + * ), * @SWG\Response( * response=200, * description="A single invoice", @@ -89,11 +101,11 @@ class InvoiceApiController extends BaseAPIController /** * @SWG\Post( * path="/invoices", - * tags={"invoice"}, * summary="Create an invoice", + * tags={"invoice"}, * @SWG\Parameter( * in="body", - * name="body", + * name="invoice", * @SWG\Schema(ref="#/definitions/Invoice") * ), * @SWG\Response( @@ -297,27 +309,38 @@ class InvoiceApiController extends BaseAPIController { $invoice = $request->entity(); - $this->dispatch(new SendInvoiceEmail($invoice)); + //$this->dispatch(new SendInvoiceEmail($invoice)); + $result = app('App\Ninja\Mailers\ContactMailer')->sendInvoice($invoice); + + if ($result !== true) { + return $this->errorResponse($result, 500); + } - $response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT); $headers = Utils::getApiHeaders(); + $response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT); return Response::make($response, 200, $headers); } /** * @SWG\Put( - * path="/invoices", - * tags={"invoice"}, + * path="/invoices/{invoice_id}", * summary="Update an invoice", + * tags={"invoice"}, + * @SWG\Parameter( + * in="path", + * name="invoice_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="invoice", * @SWG\Schema(ref="#/definitions/Invoice") * ), * @SWG\Response( * response=200, - * description="Update invoice", + * description="Updated invoice", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice")) * ), * @SWG\Response( @@ -352,17 +375,18 @@ class InvoiceApiController extends BaseAPIController /** * @SWG\Delete( - * path="/invoices", - * tags={"invoice"}, + * path="/invoices/{invoice_id}", * summary="Delete an invoice", + * tags={"invoice"}, * @SWG\Parameter( - * in="body", - * name="body", - * @SWG\Schema(ref="#/definitions/Invoice") + * in="path", + * name="invoice_id", + * type="integer", + * required=true * ), * @SWG\Response( * response=200, - * description="Delete invoice", + * description="Deleted invoice", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice")) * ), * @SWG\Response( diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 640a6503f..18d562dfb 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -289,7 +289,7 @@ class InvoiceController extends BaseController $taxRateOptions = $account->present()->taxRateOptions; if ($invoice->exists) { foreach ($invoice->getTaxes() as $key => $rate) { - $key = '0 ' . $key; + $key = '0 ' . $key; // mark it as a standard exclusive rate option if (isset($taxRateOptions[$key])) { continue; } diff --git a/app/Http/Controllers/OnlinePaymentController.php b/app/Http/Controllers/OnlinePaymentController.php index ab53284bc..399e73cde 100644 --- a/app/Http/Controllers/OnlinePaymentController.php +++ b/app/Http/Controllers/OnlinePaymentController.php @@ -302,6 +302,7 @@ class OnlinePaymentController extends BaseController } Auth::onceUsingId($account->users[0]->id); + $account->loadLocalizationSettings(); $product = Product::scope(Input::get('product_id'))->first(); if (! $product) { @@ -330,6 +331,8 @@ class OnlinePaymentController extends BaseController $data = [ 'currency_id' => $account->currency_id, 'contact' => Input::all(), + 'custom_value1' => Input::get('custom_client1'), + 'custom_value2' => Input::get('custom_client2'), ]; $client = $clientRepo->save($data, $client); } @@ -343,6 +346,8 @@ class OnlinePaymentController extends BaseController 'start_date' => Input::get('start_date', date('Y-m-d')), 'tax_rate1' => $account->default_tax_rate ? $account->default_tax_rate->rate : 0, 'tax_name1' => $account->default_tax_rate ? $account->default_tax_rate->name : '', + 'custom_text_value1' => Input::get('custom_invoice1'), + 'custom_text_value2' => Input::get('custom_invoice2'), 'invoice_items' => [[ 'product_key' => $product->product_key, 'notes' => $product->notes, @@ -350,6 +355,8 @@ class OnlinePaymentController extends BaseController 'qty' => 1, 'tax_rate1' => $product->default_tax_rate ? $product->default_tax_rate->rate : 0, 'tax_name1' => $product->default_tax_rate ? $product->default_tax_rate->name : '', + 'custom_value1' => Input::get('custom_product1') ?: $product->custom_value1, + 'custom_value2' => Input::get('custom_product2') ?: $product->custom_value2, ]], ]; $invoice = $invoiceService->save($data); @@ -364,9 +371,15 @@ class OnlinePaymentController extends BaseController } if ($gatewayTypeAlias) { - return redirect()->to($invitation->getLink('payment') . "/{$gatewayTypeAlias}"); + $link = $invitation->getLink('payment') . "/{$gatewayTypeAlias}"; } else { - return redirect()->to($invitation->getLink()); + $link = $invitation->getLink(); + } + + if (filter_var(Input::get('return_link'), FILTER_VALIDATE_BOOLEAN)) { + return $link; + } else { + return redirect()->to($link); } } } diff --git a/app/Http/Controllers/PaymentApiController.php b/app/Http/Controllers/PaymentApiController.php index 207177e17..9710fdc4f 100644 --- a/app/Http/Controllers/PaymentApiController.php +++ b/app/Http/Controllers/PaymentApiController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\PaymentRequest; use App\Http\Requests\CreatePaymentAPIRequest; use App\Http\Requests\UpdatePaymentRequest; use App\Models\Invoice; @@ -28,11 +29,12 @@ class PaymentApiController extends BaseAPIController /** * @SWG\Get( * path="/payments", + * summary="List payments", + * operationId="listPayments", * tags={"payment"}, - * summary="List of payments", * @SWG\Response( * response=200, - * description="A list with payments", + * description="A list of payments", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Payment")) * ), * @SWG\Response( @@ -52,18 +54,20 @@ class PaymentApiController extends BaseAPIController } /** - * @SWG\Put( - * path="/payments/{payment_id", - * summary="Update a payment", + * @SWG\Get( + * path="/payments/{payment_id}", + * summary="Retrieve a payment", + * operationId="getPayment", * tags={"payment"}, * @SWG\Parameter( - * in="body", - * name="body", - * @SWG\Schema(ref="#/definitions/Payment") + * in="path", + * name="payment_id", + * type="integer", + * required=true * ), * @SWG\Response( * response=200, - * description="Update payment", + * description="A single payment", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment")) * ), * @SWG\Response( @@ -71,30 +75,21 @@ class PaymentApiController extends BaseAPIController * description="an ""unexpected"" error" * ) * ) - * - * @param mixed $publicId */ - public function update(UpdatePaymentRequest $request, $publicId) + public function show(PaymentRequest $request) { - if ($request->action) { - return $this->handleAction($request); - } - - $data = $request->input(); - $data['public_id'] = $publicId; - $payment = $this->paymentRepo->save($data, $request->entity()); - - return $this->itemResponse($payment); + return $this->itemResponse($request->entity()); } /** * @SWG\Post( * path="/payments", * summary="Create a payment", + * operationId="createPayment", * tags={"payment"}, * @SWG\Parameter( * in="body", - * name="body", + * name="payment", * @SWG\Schema(ref="#/definitions/Payment") * ), * @SWG\Response( @@ -123,18 +118,63 @@ class PaymentApiController extends BaseAPIController } /** - * @SWG\Delete( + * @SWG\Put( * path="/payments/{payment_id}", - * summary="Delete a payment", + * summary="Update a payment", + * operationId="updatePayment", * tags={"payment"}, * @SWG\Parameter( + * in="path", + * name="payment_id", + * type="integer", + * required=true + * ), + * @SWG\Parameter( * in="body", - * name="body", + * name="payment", * @SWG\Schema(ref="#/definitions/Payment") * ), * @SWG\Response( * response=200, - * description="Delete payment", + * description="Updated payment", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + * + * @param mixed $publicId + */ + public function update(UpdatePaymentRequest $request, $publicId) + { + if ($request->action) { + return $this->handleAction($request); + } + + $data = $request->input(); + $data['public_id'] = $publicId; + $payment = $this->paymentRepo->save($data, $request->entity()); + + return $this->itemResponse($payment); + } + + /** + * @SWG\Delete( + * path="/payments/{payment_id}", + * summary="Delete a payment", + * operationId="deletePayment", + * tags={"payment"}, + * @SWG\Parameter( + * in="path", + * name="payment_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted payment", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment")) * ), * @SWG\Response( @@ -147,7 +187,7 @@ class PaymentApiController extends BaseAPIController { $payment = $request->entity(); - $this->clientRepo->delete($payment); + $this->paymentRepo->delete($payment); return $this->itemResponse($payment); } diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 3597c3f47..5037a3063 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -6,6 +6,7 @@ use App\Http\Requests\CreatePaymentRequest; use App\Http\Requests\PaymentRequest; use App\Http\Requests\UpdatePaymentRequest; use App\Models\Client; +use App\Models\Credit; use App\Models\Invoice; use App\Ninja\Datatables\PaymentDatatable; use App\Ninja\Mailers\ContactMailer; @@ -89,7 +90,7 @@ class PaymentController extends BaseController { $invoices = Invoice::scope() ->invoices() - ->where('invoices.balance', '>', 0) + ->where('invoices.balance', '!=', 0) ->with('client', 'invoice_status') ->orderBy('invoice_number')->get(); @@ -180,17 +181,28 @@ class PaymentController extends BaseController { // check payment has been marked sent $request->invoice->markSentIfUnsent(); - $input = $request->input(); - $input['invoice_id'] = Invoice::getPrivateId($input['invoice']); - $input['client_id'] = Client::getPrivateId($input['client']); - $payment = $this->paymentRepo->save($input); + $amount = Utils::parseFloat($input['amount']); + $credit = false; + + // if the payment amount is more than the balance create a credit + if ($amount > $request->invoice->balance) { + $credit = Credit::createNew(); + $credit->client_id = $request->invoice->client_id; + $credit->credit_date = date_create()->format('Y-m-d'); + $credit->amount = $credit->balance = $amount - $request->invoice->balance; + $credit->private_notes = trans('texts.credit_created_by', ['transaction_reference' => $input['transaction_reference']]); + $credit->save(); + $input['amount'] = $request->invoice->balance; + } + + $payment = $this->paymentService->save($input); if (Input::get('email_receipt')) { $this->contactMailer->sendPaymentConfirmation($payment); - Session::flash('message', trans('texts.created_payment_emailed_client')); + Session::flash('message', trans($credit ? 'texts.created_payment_and_credit_emailed_client' : 'texts.created_payment_emailed_client')); } else { - Session::flash('message', trans('texts.created_payment')); + Session::flash('message', trans($credit ? 'texts.created_payment_and_credit' : 'texts.created_payment')); } return redirect()->to($payment->client->getRoute() . '#payments'); diff --git a/app/Http/Controllers/ProductApiController.php b/app/Http/Controllers/ProductApiController.php index 9dc5915d9..d45eb47fc 100644 --- a/app/Http/Controllers/ProductApiController.php +++ b/app/Http/Controllers/ProductApiController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\ProductRequest; use App\Http\Requests\CreateProductRequest; use App\Http\Requests\UpdateProductRequest; use App\Models\Product; @@ -37,11 +38,12 @@ class ProductApiController extends BaseAPIController /** * @SWG\Get( * path="/products", - * summary="List of products", + * summary="List products", + * operationId="listProducts", * tags={"product"}, * @SWG\Response( * response=200, - * description="A list with products", + * description="A list of products", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Product")) * ), * @SWG\Response( @@ -59,11 +61,40 @@ class ProductApiController extends BaseAPIController return $this->listResponse($products); } + /** + * @SWG\Get( + * path="/products/{product_id}", + * summary="Retrieve a product", + * operationId="getProduct", + * tags={"product"}, + * @SWG\Parameter( + * in="path", + * name="product_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single product", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(ProductRequest $request) + { + return $this->itemResponse($request->entity()); + } + /** * @SWG\Post( * path="/products", - * tags={"product"}, * summary="Create a product", + * operationId="createProduct", + * tags={"product"}, * @SWG\Parameter( * in="body", * name="body", @@ -90,16 +121,23 @@ class ProductApiController extends BaseAPIController /** * @SWG\Put( * path="/products/{product_id}", - * tags={"product"}, * summary="Update a product", + * operationId="updateProduct", + * tags={"product"}, + * @SWG\Parameter( + * in="path", + * name="product_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="product", * @SWG\Schema(ref="#/definitions/Product") * ), * @SWG\Response( * response=200, - * description="Update product", + * description="Updated product", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product")) * ), * @SWG\Response( @@ -122,4 +160,36 @@ class ProductApiController extends BaseAPIController return $this->itemResponse($product); } + + /** + * @SWG\Delete( + * path="/products/{product_id}", + * summary="Delete a product", + * operationId="deleteProduct", + * tags={"product"}, + * @SWG\Parameter( + * in="path", + * name="product_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted product", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateProductRequest $request) + { + $product = $request->entity(); + + $this->productRepo->delete($product); + + return $this->itemResponse($product); + } } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index daba96174..0661fb375 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Models\Product; use App\Models\TaxRate; use App\Ninja\Datatables\ProductDatatable; +use App\Ninja\Repositories\ProductRepository; use App\Services\ProductService; use Auth; use Input; @@ -24,16 +25,22 @@ class ProductController extends BaseController */ protected $productService; + /** + * @var ProductRepository + */ + protected $productRepo; + /** * ProductController constructor. * * @param ProductService $productService */ - public function __construct(ProductService $productService) + public function __construct(ProductService $productService, ProductRepository $productRepo) { //parent::__construct(); $this->productService = $productService; + $this->productRepo = $productRepo; } /** @@ -137,11 +144,7 @@ class ProductController extends BaseController $product = Product::createNew(); } - $product->product_key = trim(Input::get('product_key')); - $product->notes = trim(Input::get('notes')); - $product->cost = trim(Input::get('cost')); - $product->fill(Input::all()); - $product->save(); + $this->productRepo->save(Input::all(), $product); $message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product'); Session::flash('message', $message); diff --git a/app/Http/Controllers/QuoteApiController.php b/app/Http/Controllers/QuoteApiController.php index 959bc8281..5eadb2a47 100644 --- a/app/Http/Controllers/QuoteApiController.php +++ b/app/Http/Controllers/QuoteApiController.php @@ -6,7 +6,7 @@ use App\Models\Invoice; use App\Ninja\Repositories\InvoiceRepository; use Response; -class QuoteApiController extends BaseAPIController +class QuoteApiController extends InvoiceAPIController { protected $invoiceRepo; @@ -19,22 +19,23 @@ class QuoteApiController extends BaseAPIController $this->invoiceRepo = $invoiceRepo; } - /** - * @SWG\Get( - * path="/quotes", - * tags={"quote"}, - * summary="List of quotes", - * @SWG\Response( - * response=200, - * description="A list with quotes", - * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice")) - * ), - * @SWG\Response( - * response="default", - * description="an ""unexpected"" error" - * ) - * ) - */ + /** + * @SWG\Get( + * path="/quotes", + * summary="List quotes", + * operationId="listQuotes", + * tags={"quote"}, + * @SWG\Response( + * response=200, + * description="A list of quotes", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ public function index() { $invoices = Invoice::scope() diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 69d85eecc..6e2f43a59 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -67,6 +67,7 @@ class ReportController extends BaseController } $reportTypes = [ + 'activity', 'aging', 'client', 'expense', @@ -76,6 +77,7 @@ class ReportController extends BaseController 'profit_and_loss', 'task', 'tax_rate', + 'quote', ]; $params = [ diff --git a/app/Http/Controllers/TaskApiController.php b/app/Http/Controllers/TaskApiController.php index ca24c0394..208d176eb 100644 --- a/app/Http/Controllers/TaskApiController.php +++ b/app/Http/Controllers/TaskApiController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\TaskRequest; use App\Http\Requests\UpdateTaskRequest; use App\Models\Task; use App\Ninja\Repositories\TaskRepository; @@ -26,11 +27,12 @@ class TaskApiController extends BaseAPIController /** * @SWG\Get( * path="/tasks", + * summary="List tasks", + * operationId="listTasks", * tags={"task"}, - * summary="List of tasks", * @SWG\Response( * response=200, - * description="A list with tasks", + * description="A list of tasks", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Task")) * ), * @SWG\Response( @@ -49,14 +51,43 @@ class TaskApiController extends BaseAPIController return $this->listResponse($tasks); } + /** + * @SWG\Get( + * path="/tasks/{task_id}", + * summary="Retrieve a task", + * operationId="getTask", + * tags={"task"}, + * @SWG\Parameter( + * in="path", + * name="task_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single task", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Task")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(TaskRequest $request) + { + return $this->itemResponse($request->entity()); + } + /** * @SWG\Post( * path="/tasks", - * tags={"task"}, * summary="Create a task", + * operationId="createTask", + * tags={"task"}, * @SWG\Parameter( * in="body", - * name="body", + * name="task", * @SWG\Schema(ref="#/definitions/Task") * ), * @SWG\Response( @@ -90,9 +121,16 @@ class TaskApiController extends BaseAPIController /** * @SWG\Put( - * path="/task/{task_id}", - * tags={"task"}, + * path="/tasks/{task_id}", * summary="Update a task", + * operationId="updateTask", + * tags={"task"}, + * @SWG\Parameter( + * in="path", + * name="task_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", * name="body", @@ -117,4 +155,36 @@ class TaskApiController extends BaseAPIController return $this->itemResponse($task); } + + /** + * @SWG\Delete( + * path="/tasks/{task_id}", + * summary="Delete a task", + * operationId="deleteTask", + * tags={"task"}, + * @SWG\Parameter( + * in="path", + * name="task_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted task", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Task")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateTaskRequest $request) + { + $task = $request->entity(); + + $this->taskRepo->delete($task); + + return $this->itemResponse($task); + } } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 412aeb67c..860fee6e9 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -96,7 +96,7 @@ class TaskController extends BaseController */ public function store(CreateTaskRequest $request) { - return $this->save(); + return $this->save($request); } /** @@ -202,7 +202,7 @@ class TaskController extends BaseController { $task = $request->entity(); - return $this->save($task->public_id); + return $this->save($request, $task->public_id); } /** @@ -222,7 +222,7 @@ class TaskController extends BaseController * * @return \Illuminate\Http\RedirectResponse */ - private function save($publicId = null) + private function save($request, $publicId = null) { $action = Input::get('action'); @@ -230,7 +230,7 @@ class TaskController extends BaseController return self::bulk(); } - $task = $this->taskRepo->save($publicId, Input::all()); + $task = $this->taskRepo->save($publicId, $request->input()); if ($publicId) { Session::flash('message', trans('texts.updated_task')); diff --git a/app/Http/Controllers/TaxRateApiController.php b/app/Http/Controllers/TaxRateApiController.php index cebab115f..6b24acf36 100644 --- a/app/Http/Controllers/TaxRateApiController.php +++ b/app/Http/Controllers/TaxRateApiController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\TaxRateRequest; use App\Http\Requests\CreateTaxRateRequest; use App\Http\Requests\UpdateTaxRateRequest; use App\Models\TaxRate; @@ -34,11 +35,12 @@ class TaxRateApiController extends BaseAPIController /** * @SWG\Get( * path="/tax_rates", - * summary="List of tax rates", + * summary="List tax rates", + * operationId="listTaxRates", * tags={"tax_rate"}, * @SWG\Response( * response=200, - * description="A list with tax rates", + * description="A list of tax rates", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/TaxRate")) * ), * @SWG\Response( @@ -56,14 +58,43 @@ class TaxRateApiController extends BaseAPIController return $this->listResponse($taxRates); } + /** + * @SWG\Get( + * path="/tax_rates/{tax_rate_id}", + * summary="Retrieve a tax rate", + * operationId="getTaxRate", + * tags={"tax_rate"}, + * @SWG\Parameter( + * in="path", + * name="tax_rate_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single tax rate", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(TaxRateRequest $request) + { + return $this->itemResponse($request->entity()); + } + /** * @SWG\Post( * path="/tax_rates", - * tags={"tax_rate"}, * summary="Create a tax rate", + * operationId="createTaxRate", + * tags={"tax_rate"}, * @SWG\Parameter( * in="body", - * name="body", + * name="tax_rate", * @SWG\Schema(ref="#/definitions/TaxRate") * ), * @SWG\Response( @@ -87,16 +118,23 @@ class TaxRateApiController extends BaseAPIController /** * @SWG\Put( * path="/tax_rates/{tax_rate_id}", - * tags={"tax_rate"}, * summary="Update a tax rate", + * operationId="updateTaxRate", + * tags={"tax_rate"}, + * @SWG\Parameter( + * in="path", + * name="tax_rate_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="tax_rate", * @SWG\Schema(ref="#/definitions/TaxRate") * ), * @SWG\Response( * response=200, - * description="Update tax rate", + * description="Updated tax rate", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate")) * ), * @SWG\Response( @@ -119,4 +157,36 @@ class TaxRateApiController extends BaseAPIController return $this->itemResponse($taxRate); } + + /** + * @SWG\Delete( + * path="/tax_rates/{tax_rate_id}", + * summary="Delete a tax rate", + * operationId="deleteTaxRate", + * tags={"tax_rate"}, + * @SWG\Parameter( + * in="path", + * name="tax_rate_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted tax rate", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateTaxRateRequest $request) + { + $entity = $request->entity(); + + $this->taxRateRepo->delete($entity); + + return $this->itemResponse($entity); + } } diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php index 414151fd3..db0c0d715 100644 --- a/app/Http/Controllers/TokenController.php +++ b/app/Http/Controllers/TokenController.php @@ -145,7 +145,7 @@ class TokenController extends BaseController } else { $token = AccountToken::createNew(); $token->name = trim(Input::get('name')); - $token->token = str_random(RANDOM_KEY_LENGTH); + $token->token = strtolower(str_random(RANDOM_KEY_LENGTH)); } $token->save(); diff --git a/app/Http/Controllers/UserApiController.php b/app/Http/Controllers/UserApiController.php index 64365c910..f505517c5 100644 --- a/app/Http/Controllers/UserApiController.php +++ b/app/Http/Controllers/UserApiController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\UserRequest; use App\Http\Requests\CreateUserRequest; use App\Http\Requests\UpdateUserRequest; use App\Models\User; @@ -25,22 +26,117 @@ class UserApiController extends BaseAPIController $this->userRepo = $userRepo; } + /** + * @SWG\Get( + * path="/users", + * summary="List users", + * operationId="listUsers", + * tags={"user"}, + * @SWG\Response( + * response=200, + * description="A list of users", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/User")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ public function index() { $users = User::whereAccountId(Auth::user()->account_id) ->withTrashed() ->orderBy('created_at', 'desc'); - + return $this->listResponse($users); } - /* + /** + * @SWG\Get( + * path="/users/{user_id}", + * summary="Retrieve a user", + * operationId="getUser", + * tags={"client"}, + * @SWG\Parameter( + * in="path", + * name="user_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single user", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/User")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(UserRequest $request) + { + return $this->itemResponse($request->entity()); + } + + /** + * @SWG\Post( + * path="/users", + * summary="Create a user", + * operationId="createUser", + * tags={"user"}, + * @SWG\Parameter( + * in="body", + * name="user", + * @SWG\Schema(ref="#/definitions/User") + * ), + * @SWG\Response( + * response=200, + * description="New user", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/User")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ public function store(CreateUserRequest $request) { return $this->save($request); } - */ + /** + * @SWG\Put( + * path="/users/{user_id}", + * summary="Update a user", + * operationId="updateUser", + * tags={"user"}, + * @SWG\Parameter( + * in="path", + * name="user_id", + * type="integer", + * required=true + * ), + * @SWG\Parameter( + * in="body", + * name="user", + * @SWG\Schema(ref="#/definitions/User") + * ), + * @SWG\Response( + * response=200, + * description="Updated user", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/User")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + * + * @param mixed $userPublicId + */ public function update(UpdateUserRequest $request, $userPublicId) { $user = Auth::user(); @@ -66,4 +162,36 @@ class UserApiController extends BaseAPIController return $this->response($data); } + + /** + * @SWG\Delete( + * path="/users/{user_id}", + * summary="Delete a user", + * operationId="deleteUser", + * tags={"user"}, + * @SWG\Parameter( + * in="path", + * name="user_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="Deleted user", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/User")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function destroy(UpdateUserRequest $request) + { + $entity = $request->entity(); + + $this->userRepo->delete($entity); + + return $this->itemResponse($entity); + } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 70f1780d4..c6429521a 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -199,8 +199,8 @@ class UserController extends BaseController $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); $user->registered = true; - $user->password = str_random(RANDOM_KEY_LENGTH); - $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); + $user->password = strtolower(str_random(RANDOM_KEY_LENGTH)); + $user->confirmation_code = strtolower(str_random(RANDOM_KEY_LENGTH)); $user->public_id = $lastUser->public_id + 1; if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) { $user->is_admin = boolval(Input::get('is_admin')); @@ -210,7 +210,7 @@ class UserController extends BaseController $user->save(); - if (! $user->confirmed) { + if (! $user->confirmed && Input::get('action') === 'email') { $this->userMailer->sendConfirmation($user, Auth::user()); $message = trans('texts.sent_invite'); } else { diff --git a/app/Http/Controllers/VendorApiController.php b/app/Http/Controllers/VendorApiController.php index f7227a97a..573f4fea4 100644 --- a/app/Http/Controllers/VendorApiController.php +++ b/app/Http/Controllers/VendorApiController.php @@ -2,10 +2,9 @@ namespace App\Http\Controllers; -// vendor +use App\Http\Requests\VendorRequest; use App\Http\Requests\CreateVendorRequest; use App\Http\Requests\UpdateVendorRequest; -use App\Http\Requests\VendorRequest; use App\Models\Vendor; use App\Ninja\Repositories\VendorRepository; use Input; @@ -35,11 +34,12 @@ class VendorApiController extends BaseAPIController /** * @SWG\Get( * path="/vendors", - * summary="List of vendors", + * summary="List vendors", + * operationId="listVendors", * tags={"vendor"}, * @SWG\Response( * response=200, - * description="A list with vendors", + * description="A list of vendors", * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Vendor")) * ), * @SWG\Response( @@ -57,14 +57,43 @@ class VendorApiController extends BaseAPIController return $this->listResponse($vendors); } + /** + * @SWG\Get( + * path="/vendors/{vendor_id}", + * summary="Retrieve a vendor", + * operationId="getVendor", + * tags={"client"}, + * @SWG\Parameter( + * in="path", + * name="vendor_id", + * type="integer", + * required=true + * ), + * @SWG\Response( + * response=200, + * description="A single vendor", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function show(VendorRequest $request) + { + return $this->itemResponse($request->entity()); + } + /** * @SWG\Post( * path="/vendors", - * tags={"vendor"}, * summary="Create a vendor", + * operationId="createVendor", + * tags={"vendor"}, * @SWG\Parameter( * in="body", - * name="body", + * name="vendor", * @SWG\Schema(ref="#/definitions/Vendor") * ), * @SWG\Response( @@ -92,16 +121,23 @@ class VendorApiController extends BaseAPIController /** * @SWG\Put( * path="/vendors/{vendor_id}", - * tags={"vendor"}, * summary="Update a vendor", + * operationId="updateVendor", + * tags={"vendor"}, + * @SWG\Parameter( + * in="path", + * name="vendor_id", + * type="integer", + * required=true + * ), * @SWG\Parameter( * in="body", - * name="body", + * name="vendor", * @SWG\Schema(ref="#/definitions/Vendor") * ), * @SWG\Response( * response=200, - * description="Update vendor", + * description="Updated vendor", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor")) * ), * @SWG\Response( @@ -130,16 +166,18 @@ class VendorApiController extends BaseAPIController /** * @SWG\Delete( * path="/vendors/{vendor_id}", - * tags={"vendor"}, * summary="Delete a vendor", + * operationId="deleteVendor", + * tags={"vendor"}, * @SWG\Parameter( - * in="body", - * name="body", - * @SWG\Schema(ref="#/definitions/Vendor") + * in="path", + * name="vendor_id", + * type="integer", + * required=true * ), * @SWG\Response( * response=200, - * description="Delete vendor", + * description="Deleted vendor", * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor")) * ), * @SWG\Response( @@ -148,7 +186,7 @@ class VendorApiController extends BaseAPIController * ) * ) */ - public function destroy(VendorRequest $request) + public function destroy(UpdateVendorRequest $request) { $vendor = $request->entity(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 995fc8298..e508f8d0c 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -1,7 +1,7 @@ 'App\Http\Middleware\PermissionsRequired', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'api' => 'App\Http\Middleware\ApiCheck', - 'cors' => '\App\Http\Middleware\Cors', + 'cors' => '\Barryvdh\Cors\HandleCors', ]; } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 9a59d953b..fef40c5cf 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -56,12 +56,14 @@ class Authenticate $contact_key = session('contact_key'); } + $contact = false; if ($contact_key) { $contact = $this->getContact($contact_key); } elseif ($invitation = $this->getInvitation($request->invitation_key)) { $contact = $invitation->contact; Session::put('contact_key', $contact->contact_key); - } else { + } + if (! $contact) { return \Redirect::to('client/sessionexpired'); } $account = $contact->account; @@ -113,6 +115,7 @@ class Authenticate // check for extra params at end of value (from website feature) list($key) = explode('&', $key); + $key = substr($key, 0, RANDOM_KEY_LENGTH); $invitation = Invitation::withTrashed()->where('invitation_key', '=', $key)->first(); if ($invitation && ! $invitation->is_deleted) { diff --git a/app/Http/Requests/ContactRequest.php b/app/Http/Requests/ContactRequest.php new file mode 100644 index 000000000..9c8750ad5 --- /dev/null +++ b/app/Http/Requests/ContactRequest.php @@ -0,0 +1,8 @@ +user()->can('create', ENTITY_CONTACT); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'first_name' => 'required', + 'last_name' => 'required', + 'email' => 'required', + 'client_id' => 'required', + ]; + } +} diff --git a/app/Http/Requests/CreatePaymentAPIRequest.php b/app/Http/Requests/CreatePaymentAPIRequest.php index 6f5cda038..04fe56320 100644 --- a/app/Http/Requests/CreatePaymentAPIRequest.php +++ b/app/Http/Requests/CreatePaymentAPIRequest.php @@ -26,7 +26,7 @@ class CreatePaymentAPIRequest extends PaymentRequest if (! $this->invoice_id || ! $this->amount) { return [ 'invoice_id' => 'required|numeric|min:1', - 'amount' => 'required|numeric|min:0.01', + 'amount' => 'required|numeric|not_in:0', ]; } diff --git a/app/Http/Requests/CreatePaymentRequest.php b/app/Http/Requests/CreatePaymentRequest.php index f2e1d4684..d9e187a6e 100644 --- a/app/Http/Requests/CreatePaymentRequest.php +++ b/app/Http/Requests/CreatePaymentRequest.php @@ -28,10 +28,15 @@ class CreatePaymentRequest extends PaymentRequest ->invoices() ->firstOrFail(); + $this->merge([ + 'invoice_id' => $invoice->id, + 'client_id' => $invoice->client->id, + ]); + $rules = [ 'client' => 'required', // TODO: change to client_id once views are updated 'invoice' => 'required', // TODO: change to invoice_id once views are updated - 'amount' => "required|numeric|between:0.01,{$invoice->balance}", + 'amount' => 'required|numeric|not_in:0', 'payment_date' => 'required', ]; diff --git a/app/Http/Requests/ExpenseRequest.php b/app/Http/Requests/ExpenseRequest.php index 03ee478eb..17388a6f3 100644 --- a/app/Http/Requests/ExpenseRequest.php +++ b/app/Http/Requests/ExpenseRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests; use App\Models\ExpenseCategory; +use App\Models\Vendor; class ExpenseRequest extends EntityRequest { @@ -24,11 +25,37 @@ class ExpenseRequest extends EntityRequest { $input = $this->all(); - if ($this->expense_category_id) { + // check if we're creating a new expense category + if ($this->expense_category_id == '-1') { + $data = [ + 'name' => trim($this->expense_category_name) + ]; + if (ExpenseCategory::validate($data) === true) { + $category = app('App\Ninja\Repositories\ExpenseCategoryRepository')->save($data); + $input['expense_category_id'] = $category->id; + } else { + $input['expense_category_id'] = null; + } + } elseif ($this->expense_category_id) { $input['expense_category_id'] = ExpenseCategory::getPrivateId($this->expense_category_id); - $this->replace($input); } + // check if we're creating a new vendor + if ($this->vendor_id == '-1') { + $data = [ + 'name' => trim($this->vendor_name) + ]; + if (Vendor::validate($data) === true) { + $vendor = app('App\Ninja\Repositories\VendorRepository')->save($data); + // TODO change to private id once service is refactored + $input['vendor_id'] = $vendor->public_id; + } else { + $input['vendor_id'] = null; + } + } + + $this->replace($input); + return $this->all(); } } diff --git a/app/Http/Requests/SaveClientPortalSettings.php b/app/Http/Requests/SaveClientPortalSettings.php index 94d6fba71..7912c0df4 100644 --- a/app/Http/Requests/SaveClientPortalSettings.php +++ b/app/Http/Requests/SaveClientPortalSettings.php @@ -53,7 +53,7 @@ class SaveClientPortalSettings extends Request $input['subdomain'] = null; } } - + $this->replace($input); return $this->all(); diff --git a/app/Http/Requests/SaveEmailSettings.php b/app/Http/Requests/SaveEmailSettings.php index 25696c6ff..1c7d91a3b 100644 --- a/app/Http/Requests/SaveEmailSettings.php +++ b/app/Http/Requests/SaveEmailSettings.php @@ -23,6 +23,7 @@ class SaveEmailSettings extends Request { return [ 'bcc_email' => 'email', + 'reply_to_email' => 'email', ]; } } diff --git a/app/Http/Requests/TaskRequest.php b/app/Http/Requests/TaskRequest.php index 141bf89f0..b5bd2cd3e 100644 --- a/app/Http/Requests/TaskRequest.php +++ b/app/Http/Requests/TaskRequest.php @@ -2,7 +2,33 @@ namespace App\Http\Requests; +use App\Models\Client; +use App\Models\Project; + class TaskRequest extends EntityRequest { protected $entityType = ENTITY_TASK; + + public function sanitize() + { + $input = $this->all(); + + // check if we're creating a new project + if ($this->project_id == '-1') { + $project = [ + 'name' => trim($this->project_name), + 'client_id' => Client::getPrivateId($this->client), + ]; + if (Project::validate($project) === true) { + $project = app('App\Ninja\Repositories\ProjectRepository')->save($project); + $input['project_id'] = $project->public_id; + } else { + $input['project_id'] = null; + } + } + + $this->replace($input); + + return $this->all(); + } } diff --git a/app/Http/Requests/UpdateContactRequest.php b/app/Http/Requests/UpdateContactRequest.php new file mode 100644 index 000000000..e6a40d18b --- /dev/null +++ b/app/Http/Requests/UpdateContactRequest.php @@ -0,0 +1,30 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'first_name' => 'required', + 'last_name' => 'required', + 'email' => 'required', + ]; + } +} diff --git a/app/Http/Requests/UserRequest.php b/app/Http/Requests/UserRequest.php new file mode 100644 index 000000000..48f6d0e9b --- /dev/null +++ b/app/Http/Requests/UserRequest.php @@ -0,0 +1,8 @@ + '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'); @@ -126,7 +130,7 @@ Route::group(['middleware' => 'auth:user'], function () { Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); - Route::post('settings/payment_gateway_limits', 'AccountController@savePaymentGatewayLimits'); + Route::post('settings/payment_gateway_limits', 'AccountGatewayController@savePaymentGatewayLimits'); Route::post('users/change_password', 'UserController@changePassword'); Route::resource('clients', 'ClientController'); @@ -253,6 +257,7 @@ Route::group([ Route::post('settings/change_plan', 'AccountController@changePlan'); Route::post('settings/cancel_account', 'AccountController@cancelAccount'); + Route::post('settings/purge_data', 'AccountController@purgeData'); Route::post('settings/company_details', 'AccountController@updateDetails'); Route::post('settings/{section?}', 'AccountController@doSection'); @@ -303,12 +308,11 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function () { Route::get('accounts', 'AccountApiController@show'); Route::put('accounts', 'AccountApiController@update'); Route::resource('clients', 'ClientApiController'); + Route::resource('contacts', 'ContactApiController'); Route::get('quotes', 'QuoteApiController@index'); - Route::get('invoices', 'InvoiceApiController@index'); Route::get('download/{invoice_id}', 'InvoiceApiController@download'); Route::resource('invoices', 'InvoiceApiController'); Route::resource('payments', 'PaymentApiController'); - Route::get('tasks', 'TaskApiController@index'); Route::resource('tasks', 'TaskApiController'); Route::post('hooks', 'IntegrationController@subscribe'); Route::post('email_invoice', 'InvoiceApiController@emailInvoice'); @@ -359,6 +363,9 @@ Route::get('/feed', function () { Route::get('/comments/feed', function () { return Redirect::to(NINJA_WEB_URL.'/comments/feed', 301); }); +Route::get('/terms', function () { + return Redirect::to(NINJA_WEB_URL.'/terms', 301); +}); /* if (Utils::isNinjaDev()) diff --git a/app/Jobs/ImportData.php b/app/Jobs/ImportData.php new file mode 100644 index 000000000..9ffe33413 --- /dev/null +++ b/app/Jobs/ImportData.php @@ -0,0 +1,80 @@ +user = $user; + $this->type = $type; + $this->settings = $settings; + } + + /** + * Execute the job. + * + * @param ContactMailer $mailer + */ + public function handle(ImportService $importService, UserMailer $userMailer) + { + $includeSettings = false; + + Auth::onceUsingId($this->user->id); + $this->user->account->loadLocalizationSettings(); + + if ($this->type === IMPORT_JSON) { + $includeData = $this->settings['include_data']; + $includeSettings = $this->settings['include_settings']; + $files = $this->settings['files']; + $results = $importService->importJSON($files[IMPORT_JSON], $includeData, $includeSettings); + } elseif ($this->type === IMPORT_CSV) { + $map = $this->settings['map']; + $headers = $this->settings['headers']; + $timestamp = $this->settings['timestamp']; + $results = $importService->importCSV($map, $headers, $timestamp); + } else { + $source = $this->settings['source']; + $files = $this->settings['files']; + $results = $importService->importFiles($source, $files); + } + + $subject = trans('texts.import_complete'); + $message = $importService->presentResults($results, $includeSettings); + $userMailer->sendMessage($this->user, $subject, $message); + } +} diff --git a/app/Jobs/PurgeAccountData.php b/app/Jobs/PurgeAccountData.php new file mode 100644 index 000000000..c1f289e2c --- /dev/null +++ b/app/Jobs/PurgeAccountData.php @@ -0,0 +1,61 @@ +account; + + if (! $user->is_admin) { + throw new Exception(trans('texts.forbidden')); + } + + // delete the documents from cloud storage + Document::scope()->each(function ($item, $key) { + $item->delete(); + }); + + $tables = [ + 'activities', + 'invitations', + 'account_gateway_tokens', + 'payment_methods', + 'credits', + 'expense_categories', + 'expenses', + 'invoice_items', + 'payments', + 'invoices', + 'tasks', + 'projects', + 'products', + 'vendor_contacts', + 'vendors', + 'contacts', + 'clients', + ]; + + foreach ($tables as $table) { + DB::table($table)->where('account_id', '=', $user->account_id)->delete(); + } + + $account->invoice_number_counter = 1; + $account->quote_number_counter = 1; + $account->client_number_counter = 1; + $account->save(); + } +} diff --git a/app/Jobs/SendInvoiceEmail.php b/app/Jobs/SendInvoiceEmail.php index a638b2b04..f37abac24 100644 --- a/app/Jobs/SendInvoiceEmail.php +++ b/app/Jobs/SendInvoiceEmail.php @@ -26,11 +26,6 @@ class SendInvoiceEmail extends Job implements ShouldQueue */ protected $reminder; - /** - * @var string - */ - protected $pdfString; - /** * @var array */ @@ -44,11 +39,10 @@ class SendInvoiceEmail extends Job implements ShouldQueue * @param bool $reminder * @param mixed $pdfString */ - public function __construct(Invoice $invoice, $reminder = false, $pdfString = false, $template = false) + public function __construct(Invoice $invoice, $reminder = false, $template = false) { $this->invoice = $invoice; $this->reminder = $reminder; - $this->pdfString = $pdfString; $this->template = $template; } @@ -59,7 +53,7 @@ class SendInvoiceEmail extends Job implements ShouldQueue */ public function handle(ContactMailer $mailer) { - $mailer->sendInvoice($this->invoice, $this->reminder, $this->pdfString, $this->template); + $mailer->sendInvoice($this->invoice, $this->reminder, $this->template); } /* diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 243448064..2dfb3366a 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -394,6 +394,7 @@ class Utils 'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '', 'method' => Request::method(), 'url' => Input::get('url', Request::url()), + 'previous' => url()->previous(), 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', 'ip' => Request::getClientIp(), 'count' => Session::get('error_count', 0), diff --git a/app/Listeners/HandleUserLoggedIn.php b/app/Listeners/HandleUserLoggedIn.php index 73053ef10..eb59cc9b6 100644 --- a/app/Listeners/HandleUserLoggedIn.php +++ b/app/Listeners/HandleUserLoggedIn.php @@ -5,7 +5,9 @@ namespace App\Listeners; use App\Events\UserLoggedIn; use App\Events\UserSignedUp; use App\Libraries\HistoryUtils; +use App\Models\Gateway; use App\Ninja\Repositories\AccountRepository; +use Utils; use Auth; use Carbon; use Session; @@ -65,5 +67,10 @@ class HandleUserLoggedIn } elseif ($account->isLogoTooLarge()) { Session::flash('warning', trans('texts.logo_too_large', ['size' => $account->getLogoSize() . 'KB'])); } + + // check custom gateway id is correct + if (! Utils::isNinja() && Gateway::find(GATEWAY_CUSTOM)->name !== 'Custom') { + Session::flash('error', trans('texts.error_incorrect_gateway_ids')); + } } } diff --git a/app/Listeners/InvoiceListener.php b/app/Listeners/InvoiceListener.php index 014cbe64b..3888ccbda 100644 --- a/app/Listeners/InvoiceListener.php +++ b/app/Listeners/InvoiceListener.php @@ -153,6 +153,14 @@ class InvoiceListener public function jobFailed(JobExceptionOccurred $exception) { + if ($errorEmail = env('ERROR_EMAIL')) { + \Mail::raw(print_r($exception->data, true), function ($message) use ($errorEmail) { + $message->to($errorEmail) + ->from(CONTACT_EMAIL) + ->subject('Job failed'); + }); + } + Utils::logError($exception->exception); } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 6d5c3ca90..03ada0d39 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -48,51 +48,131 @@ class Account extends Eloquent * @var array */ protected $fillable = [ + 'timezone_id', + 'date_format_id', + 'datetime_format_id', + 'currency_id', 'name', - 'id_number', - 'vat_number', - 'work_email', - 'website', - 'work_phone', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', - 'size_id', - 'industry_id', + 'invoice_terms', 'email_footer', - 'timezone_id', - 'date_format_id', - 'datetime_format_id', - 'currency_id', - 'language_id', - 'military_time', + 'industry_id', + 'size_id', 'invoice_taxes', 'invoice_item_taxes', + 'invoice_design_id', + 'work_phone', + 'work_email', + 'language_id', + 'custom_label1', + 'custom_value1', + 'custom_label2', + 'custom_value2', + 'custom_client_label1', + 'custom_client_label2', + 'fill_products', + 'update_products', + 'primary_color', + 'secondary_color', + 'hide_quantity', + 'hide_paid_to_date', + 'custom_invoice_label1', + 'custom_invoice_label2', + 'custom_invoice_taxes1', + 'custom_invoice_taxes2', + 'vat_number', + 'invoice_number_prefix', + 'invoice_number_counter', + 'quote_number_prefix', + 'quote_number_counter', + 'share_counter', + 'id_number', + 'email_template_invoice', + 'email_template_quote', + 'email_template_payment', + 'token_billing_type_id', + 'invoice_footer', + 'pdf_email_attachment', + 'font_size', + 'invoice_labels', + 'custom_design', 'show_item_taxes', + 'military_time', + 'email_subject_invoice', + 'email_subject_quote', + 'email_subject_payment', + 'email_subject_reminder1', + 'email_subject_reminder2', + 'email_subject_reminder3', + 'email_template_reminder1', + 'email_template_reminder2', + 'email_template_reminder3', + 'enable_reminder1', + 'enable_reminder2', + 'enable_reminder3', + 'num_days_reminder1', + 'num_days_reminder2', + 'num_days_reminder3', + 'custom_invoice_text_label1', + 'custom_invoice_text_label2', 'default_tax_rate_id', - 'enable_second_tax_rate', - 'include_item_taxes_inline', - 'start_of_week', - 'financial_year_start', - 'enable_client_portal', - 'enable_client_portal_dashboard', + 'recurring_hour', + 'invoice_number_pattern', + 'quote_number_pattern', + 'quote_terms', + 'email_design_id', + 'enable_email_markup', + 'website', + 'direction_reminder1', + 'direction_reminder2', + 'direction_reminder3', + 'field_reminder1', + 'field_reminder2', + 'field_reminder3', + 'header_font_id', + 'body_font_id', + 'auto_convert_quote', + 'all_pages_footer', + 'all_pages_header', + 'show_currency_code', 'enable_portal_password', 'send_portal_password', + 'custom_invoice_item_label1', + 'custom_invoice_item_label2', + 'recurring_invoice_number_prefix', + 'enable_client_portal', + 'invoice_fields', + 'invoice_embed_documents', + 'document_email_attachment', + 'enable_client_portal_dashboard', + 'page_size', + 'live_preview', + 'invoice_number_padding', + 'enable_second_tax_rate', + 'auto_bill_on_due_date', + 'start_of_week', 'enable_buy_now_buttons', + 'include_item_taxes_inline', + 'financial_year_start', + 'enabled_modules', + 'enabled_dashboard_sections', 'show_accept_invoice_terms', 'show_accept_quote_terms', 'require_invoice_signature', 'require_quote_signature', - 'pdf_email_attachment', - 'document_email_attachment', - 'email_design_id', - 'enable_email_markup', - 'domain_id', + 'client_number_prefix', + 'client_number_counter', + 'client_number_pattern', 'payment_terms', + 'reset_counter_frequency_id', 'payment_type_id', + 'gateway_fee_enabled', + 'reset_counter_date', ]; /** @@ -189,6 +269,22 @@ class Account extends Eloquent return $this->hasMany('App\Models\AccountGateway'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function account_gateway_settings() + { + return $this->hasMany('App\Models\AccountGatewaySettings'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function account_email_settings() + { + return $this->hasOne('App\Models\AccountEmailSettings'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ @@ -402,6 +498,22 @@ class Account extends Eloquent return $user->getDisplayName(); } + public function getGatewaySettings($gatewayTypeId) + { + if (! $this->relationLoaded('account_gateway_settings')) { + $this->load('account_gateway_settings'); + } + + foreach ($this->account_gateway_settings as $settings) { + if ($settings->gateway_type_id == $gatewayTypeId) { + return $settings; + } + } + + return false; + } + + /** * @return string */ @@ -887,6 +999,8 @@ class Account extends Eloquent $invoice->start_date = Utils::today(); $invoice->invoice_design_id = $this->invoice_design_id; $invoice->client_id = $clientId; + $invoice->custom_taxes1 = $this->custom_invoice_taxes1; + $invoice->custom_taxes2 = $this->custom_invoice_taxes2; if ($entityType === ENTITY_RECURRING_INVOICE) { $invoice->invoice_number = microtime(true); diff --git a/app/Models/AccountEmailSettings.php b/app/Models/AccountEmailSettings.php new file mode 100644 index 000000000..752b2aea1 --- /dev/null +++ b/app/Models/AccountEmailSettings.php @@ -0,0 +1,32 @@ +fee_amount) || floatval($this->fee_percent); + } + + public function hasTaxes() + { + return floatval($this->fee_tax_rate1) || floatval($this->fee_tax_rate1); + } + + public function feesToString() + { + $parts = []; + + if (floatval($this->fee_amount) != 0) { + $parts[] = Utils::formatMoney($this->fee_amount); + } + + if (floatval($this->fee_percent) != 0) { + $parts[] = (floor($this->fee_percent * 1000) / 1000) . '%'; + } + + return join(' + ', $parts); + } } diff --git a/app/Models/Client.php b/app/Models/Client.php index b3c72fd2d..5a42983d3 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -49,6 +49,8 @@ class Client extends EntityModel 'language_id', 'payment_terms', 'website', + 'invoice_number_counter', + 'quote_number_counter', ]; /** @@ -136,7 +138,7 @@ class Client extends EntityModel 'email' => 'email', 'mobile|phone' => 'phone', 'name|organization' => 'name', - 'street2|address2' => 'address2', + 'apt|street2|address2' => 'address2', 'street|address|address1' => 'address1', 'city' => 'city', 'state|province' => 'state', @@ -145,7 +147,7 @@ class Client extends EntityModel 'note' => 'notes', 'site|website' => 'website', 'vat' => 'vat_number', - 'id|number' => 'id_number', + 'number' => 'id_number', ]; } @@ -288,7 +290,7 @@ class Client extends EntityModel if (isset($data['contact_key']) && $this->account->account_key == env('NINJA_LICENSE_ACCOUNT_KEY')) { $contact->contact_key = $data['contact_key']; } else { - $contact->contact_key = str_random(RANDOM_KEY_LENGTH); + $contact->contact_key = strtolower(str_random(RANDOM_KEY_LENGTH)); } } diff --git a/app/Models/Company.php b/app/Models/Company.php index 95c6b614c..afe117a5d 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -145,4 +145,32 @@ class Company extends Eloquent return false; } + + public function processRefund($user) + { + if (! $this->payment) { + return false; + } + + $account = $this->accounts()->first(); + $planDetails = $account->getPlanDetails(false, false); + + if (! empty($planDetails['started'])) { + $deadline = clone $planDetails['started']; + $deadline->modify('+30 days'); + + if ($deadline >= date_create()) { + $accountRepo = app('App\Ninja\Repositories\AccountRepository'); + $ninjaAccount = $accountRepo->getNinjaAccount(); + $paymentDriver = $ninjaAccount->paymentDriver(); + $paymentDriver->refundPayment($this->payment); + + \Log::info("Refunded Plan Payment: {$account->name} - {$user->email} - Deadline: {$deadline->format('Y-m-d')}"); + + return true; + } + } + + return false; + } } diff --git a/app/Models/Contact.php b/app/Models/Contact.php index c19ba4bda..087b1a356 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -119,7 +119,7 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa public function getContactKeyAttribute($contact_key) { if (empty($contact_key) && $this->id) { - $this->contact_key = $contact_key = str_random(RANDOM_KEY_LENGTH); + $this->contact_key = $contact_key = strtolower(str_random(RANDOM_KEY_LENGTH)); static::where('id', $this->id)->update(['contact_key' => $contact_key]); } diff --git a/app/Models/Credit.php b/app/Models/Credit.php index e1b4243b8..1205d981b 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -23,6 +23,14 @@ class Credit extends EntityModel */ protected $presenter = 'App\Ninja\Presenters\CreditPresenter'; + /** + * @var array + */ + protected $fillable = [ + 'public_notes', + 'private_notes', + ]; + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 6bc0c5b29..3cb8a052c 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -2,6 +2,7 @@ namespace App\Models; +use Str; use Auth; use Eloquent; use Utils; @@ -95,6 +96,10 @@ class EntityModel extends Eloquent */ public static function getPrivateId($publicId) { + if (! $publicId) { + return null; + } + $className = get_called_class(); return $className::scope($publicId)->withTrashed()->value('id'); @@ -254,16 +259,22 @@ class EntityModel extends Eloquent * @param $data * @param $entityType * @param mixed $entity - * + * TODO Remove $entityType parameter * @return bool|string */ - public static function validate($data, $entityType, $entity = false) + public static function validate($data, $entityType = false, $entity = false) { + if (! $entityType) { + $className = get_called_class(); + $entityBlank = new $className(); + $entityType = $entityBlank->getEntityType(); + } + // Use the API request if it exists $action = $entity ? 'update' : 'create'; - $requestClass = sprintf('App\\Http\\Requests\\%s%sAPIRequest', ucwords($action), ucwords($entityType)); + $requestClass = sprintf('App\\Http\\Requests\\%s%sAPIRequest', ucwords($action), Str::studly($entityType)); if (! class_exists($requestClass)) { - $requestClass = sprintf('App\\Http\\Requests\\%s%sRequest', ucwords($action), ucwords($entityType)); + $requestClass = sprintf('App\\Http\\Requests\\%s%sRequest', ucwords($action), Str::studly($entityType)); } $request = new $requestClass(); diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index c4619d64e..1765686ad 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -59,6 +59,7 @@ class Gateway extends Eloquent GATEWAY_PAYPAL_EXPRESS, GATEWAY_BITPAY, GATEWAY_DWOLLA, + GATEWAY_CUSTOM, ]; /** diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 37c89b895..0e493bf20 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -76,6 +76,10 @@ class Invitation extends EntityModel $iframe_url = $account->iframe_url; $url = trim(SITE_URL, '/'); + if (env('REQUIRE_HTTPS')) { + $url = str_replace('http://', 'https://', $url); + } + if ($account->hasFeature(FEATURE_CUSTOM_URL)) { if (Utils::isNinjaProd()) { $url = $account->present()->clientPortalLink(); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 0ab2a8faf..7e5b3b66c 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -10,6 +10,7 @@ use App\Events\QuoteWasCreated; use App\Events\QuoteWasUpdated; use App\Libraries\CurlUtils; use App\Models\Activity; +use App\Models\Traits\ChargesFees; use DateTime; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; @@ -23,6 +24,7 @@ class Invoice extends EntityModel implements BalanceAffecting { use PresentableTrait; use OwnedByClientTrait; + use ChargesFees; use SoftDeletes { SoftDeletes::trashed as parentTrashed; } @@ -62,9 +64,10 @@ class Invoice extends EntityModel implements BalanceAffecting */ public static $patternFields = [ 'counter', - 'custom1', - 'custom2', - 'idNumber', + 'clientCounter', + 'clientIdNumber', + 'clientCustom1', + 'clientCustom2', 'userId', 'year', 'date:', @@ -539,7 +542,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function updatePaidStatus($save = true) { $statusId = false; - if ($this->amount > 0 && $this->balance == 0) { + if ($this->amount != 0 && $this->balance == 0) { $statusId = INVOICE_STATUS_PAID; } elseif ($this->balance > 0 && $this->balance < $this->amount) { $statusId = INVOICE_STATUS_PARTIAL; @@ -573,6 +576,13 @@ class Invoice extends EntityModel implements BalanceAffecting return; } + $balanceAdjustment = floatval($balanceAdjustment); + $partial = floatval($partial); + + if (! $balanceAdjustment && $this->partial == $partial) { + return; + } + $this->balance = $this->balance + $balanceAdjustment; if ($this->partial > 0) { @@ -580,6 +590,13 @@ class Invoice extends EntityModel implements BalanceAffecting } $this->save(); + + // mark fees as paid + if ($balanceAdjustment != 0 && $this->account->gateway_fee_enabled) { + if ($invoiceItem = $this->getGatewayFeeItem()) { + $invoiceItem->markFeePaid(); + } + } } /** @@ -610,7 +627,7 @@ class Invoice extends EntityModel implements BalanceAffecting public function canBePaid() { - return floatval($this->balance) > 0 && ! $this->is_deleted && $this->isInvoice(); + return floatval($this->balance) != 0 && ! $this->is_deleted && $this->isInvoice(); } public static function calcStatusLabel($status, $class, $entityType, $quoteInvoiceId) @@ -752,7 +769,16 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function getRequestedAmount() { - return $this->partial > 0 ? $this->partial : $this->balance; + $fee = 0; + if ($this->account->gateway_fee_enabled) { + $fee = $this->getGatewayFee(); + } + + if ($this->partial > 0) { + return $this->partial + $fee; + } else { + return $this->balance; + } } /** @@ -868,6 +894,7 @@ class Invoice extends EntityModel implements BalanceAffecting 'page_size', 'include_item_taxes_inline', 'invoice_fields', + 'show_currency_code', ]); foreach ($this->invoice_items as $invoiceItem) { @@ -1231,6 +1258,10 @@ class Invoice extends EntityModel implements BalanceAffecting return false; } + if (Utils::isTravis()) { + return false; + } + $invitation = $this->invitations[0]; $link = $invitation->getLink('view', true); $pdfString = false; @@ -1238,26 +1269,35 @@ class Invoice extends EntityModel implements BalanceAffecting try { if (env('PHANTOMJS_BIN_PATH')) { $pdfString = CurlUtils::phantom('GET', $link . '?phantomjs=true&phantomjs_secret=' . env('PHANTOMJS_SECRET')); - } elseif ($key = env('PHANTOMJS_CLOUD_KEY')) { - if (Utils::isNinjaDev()) { - $link = env('TEST_LINK'); + } + + if (! $pdfString && (Utils::isNinja() || ! env('PHANTOMJS_BIN_PATH'))) { + if ($key = env('PHANTOMJS_CLOUD_KEY')) { + if (Utils::isNinjaDev()) { + $link = env('TEST_LINK'); + } + $url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D"; + $pdfString = CurlUtils::get($url); } - $url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D"; - $pdfString = CurlUtils::get($url); } $pdfString = strip_tags($pdfString); } catch (\Exception $exception) { - Utils::logError("PhantomJS - Failed to create pdf: {$exception->getMessage()}"); + Utils::logError("PhantomJS - Failed to load: {$exception->getMessage()}"); return false; } if (! $pdfString || strlen($pdfString) < 200) { - Utils::logError("PhantomJS - Failed to create pdf: {$pdfString}"); + Utils::logError("PhantomJS - Invalid response: {$pdfString}"); return false; } - return Utils::decodePDF($pdfString); + if ($pdf = Utils::decodePDF($pdfString)) { + return $pdf; + } else { + Utils::logError("PhantomJS - Unable to decode: {$pdfString}"); + return false; + } } /** @@ -1473,7 +1513,7 @@ class Invoice extends EntityModel implements BalanceAffecting } Invoice::creating(function ($invoice) { - if (! $invoice->is_recurring) { + if (! $invoice->is_recurring && $invoice->amount >= 0) { $invoice->account->incrementCounter($invoice); } }); diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index aecb9cc51..ba12d00ae 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -39,6 +39,7 @@ class InvoiceItem extends EntityModel 'tax_rate1', 'tax_name2', 'tax_rate2', + 'invoice_item_type_id', ]; /** @@ -72,4 +73,28 @@ class InvoiceItem extends EntityModel { return $this->belongsTo('App\Models\Account'); } + + public function amount() + { + $amount = $this->cost * $this->qty; + $preTaxAmount = $amount; + + if ($this->tax_rate1) { + $amount += $preTaxAmount * $this->tax_rate1 / 100; + } + + if ($this->tax_rate2) { + $amount += $preTaxAmount * $this->tax_rate2 / 100; + } + + return $amount; + } + + public function markFeePaid() + { + if ($this->invoice_item_type_id == INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE) { + $this->invoice_item_type_id = INVOICE_ITEM_TYPE_PAID_GATEWAY_FEE; + $this->save(); + } + } } diff --git a/app/Models/Task.php b/app/Models/Task.php index 782f1668d..139627f46 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -205,7 +205,7 @@ class Task extends EntityModel public function scopeDateRange($query, $startDate, $endDate) { $query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) >= ' . $startDate->format('U')); - $query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) <= ' . $endDate->format('U')); + $query->whereRaw('cast(substring(time_log, 3, 10) as unsigned) <= ' . $endDate->modify('+1 day')->format('U')); return $query; } diff --git a/app/Models/Traits/ChargesFees.php b/app/Models/Traits/ChargesFees.php new file mode 100644 index 000000000..710f84215 --- /dev/null +++ b/app/Models/Traits/ChargesFees.php @@ -0,0 +1,75 @@ +account; + $settings = $account->getGatewaySettings($gatewayTypeId); + $fee = 0; + + if (! $account->gateway_fee_enabled) { + return false; + } + + if ($settings->fee_amount) { + $fee += $settings->fee_amount; + } + + if ($settings->fee_percent) { + $amount = $this->partial > 0 ? $this->partial : $this->balance; + $fee += $amount * $settings->fee_percent / 100; + } + + // calculate final amount with tax + if ($includeTax) { + $preTaxFee = $fee; + + if ($settings->fee_tax_rate1) { + $fee += $preTaxFee * $settings->fee_tax_rate1 / 100; + } + + if ($settings->fee_tax_rate2) { + $fee += $preTaxFee * $settings->fee_tax_rate2 / 100; + } + } + + return round($fee, 2); + } + + public function getGatewayFee() + { + $account = $this->account; + + if (! $account->gateway_fee_enabled) { + return 0; + } + + $item = $this->getGatewayFeeItem(); + return $item ? $item->amount() : 0; + } + + public function getGatewayFeeItem() + { + if (! $this->relationLoaded('invoice_items')) { + $this->load('invoice_items'); + } + + foreach ($this->invoice_items as $item) { + if ($item->invoice_item_type_id == INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE) { + return $item; + } + } + + return false; + } +} diff --git a/app/Models/Traits/GeneratesNumbers.php b/app/Models/Traits/GeneratesNumbers.php index 352293419..c94e50e3e 100644 --- a/app/Models/Traits/GeneratesNumbers.php +++ b/app/Models/Traits/GeneratesNumbers.php @@ -39,6 +39,10 @@ trait GeneratesNumbers $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); } + if ($entity->recurring_invoice_id) { + $number = $this->recurring_invoice_number_prefix . $number; + } + if ($entity->isEntityType(ENTITY_CLIENT)) { $check = Client::scope(false, $this->id)->whereIdNumber($number)->withTrashed()->first(); } else { @@ -66,10 +70,6 @@ trait GeneratesNumbers } } - if ($entity->recurring_invoice_id) { - $number = $this->recurring_invoice_number_prefix . $number; - } - return $number; } @@ -125,7 +125,7 @@ trait GeneratesNumbers { $pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern; - return strstr($pattern, '$custom') || strstr($pattern, '$idNumber'); + return strstr($pattern, '$client') !== false || strstr($pattern, '$idNumber') !== false; } /** @@ -167,10 +167,7 @@ trait GeneratesNumbers } $pattern = str_replace($search, $replace, $pattern); - - if ($entity->client_id) { - $pattern = $this->getClientInvoiceNumber($pattern, $entity); - } + $pattern = $this->getClientInvoiceNumber($pattern, $entity); return $pattern; } @@ -183,7 +180,7 @@ trait GeneratesNumbers */ private function getClientInvoiceNumber($pattern, $invoice) { - if (! $invoice->client) { + if (! $invoice->client_id) { return $pattern; } @@ -191,12 +188,21 @@ trait GeneratesNumbers '{$custom1}', '{$custom2}', '{$idNumber}', + '{$clientCustom1}', + '{$clientCustom2}', + '{$clientIdNumber}', + '{$clientCounter}', ]; $replace = [ $invoice->client->custom_value1, $invoice->client->custom_value2, $invoice->client->id_number, + $invoice->client->custom_value1, // backwards compatibility + $invoice->client->custom_value2, + $invoice->client->id_number, + str_pad($invoice->client->invoice_number_counter, $this->invoice_number_padding, '0', STR_PAD_LEFT), + str_pad($invoice->client->quote_number_counter, $this->invoice_number_padding, '0', STR_PAD_LEFT), ]; return str_replace($search, $replace, $pattern); @@ -225,7 +231,9 @@ trait GeneratesNumbers */ public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE) { - $invoice = $this->createInvoice($entityType); + $client = \App\Models\Client::scope()->first(); + + $invoice = $this->createInvoice($entityType, $client ? $client->id : 0); return $this->getNextNumber($invoice); } @@ -239,17 +247,87 @@ trait GeneratesNumbers if ($this->client_number_counter) { $this->client_number_counter += 1; } - } elseif ($entity->isType(INVOICE_TYPE_QUOTE) && ! $this->share_counter) { - $this->quote_number_counter += 1; - } else { - $this->invoice_number_counter += 1; + $this->save(); + return; } - $this->save(); + if ($this->usesClientInvoiceCounter()) { + if ($entity->isType(INVOICE_TYPE_QUOTE) && ! $this->share_counter) { + $entity->client->quote_number_counter += 1; + } else { + $entity->client->invoice_number_counter += 1; + } + $entity->client->save(); + } + + if ($this->usesInvoiceCounter()) { + if ($entity->isType(INVOICE_TYPE_QUOTE) && ! $this->share_counter) { + $this->quote_number_counter += 1; + } else { + $this->invoice_number_counter += 1; + } + $this->save(); + } + } + + public function usesInvoiceCounter() + { + return strpos($this->invoice_number_pattern, '{$counter}') !== false; + } + + public function usesClientInvoiceCounter() + { + return strpos($this->invoice_number_pattern, '{$clientCounter}') !== false; } public function clientNumbersEnabled() { - return $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->client_number_counter; + return $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->client_number_counter > 0; + } + + public function checkCounterReset() + { + if (! $this->reset_counter_frequency_id || ! $this->reset_counter_date) { + return false; + } + + $timezone = $this->getTimezone(); + $resetDate = Carbon::parse($this->reset_counter_date, $timezone); + + if (! $resetDate->isToday()) { + return false; + } + + switch ($this->reset_counter_frequency_id) { + case FREQUENCY_WEEKLY: + $resetDate->addWeek(); + break; + case FREQUENCY_TWO_WEEKS: + $resetDate->addWeeks(2); + break; + case FREQUENCY_FOUR_WEEKS: + $resetDate->addWeeks(4); + break; + case FREQUENCY_MONTHLY: + $resetDate->addMonth(); + break; + case FREQUENCY_TWO_MONTHS: + $resetDate->addMonths(2); + break; + case FREQUENCY_THREE_MONTHS: + $resetDate->addMonths(3); + break; + case FREQUENCY_SIX_MONTHS: + $resetDate->addMonths(6); + break; + case FREQUENCY_ANNUALLY: + $resetDate->addYear(); + break; + } + + $this->reset_counter_date = $resetDate->format('Y-m-d'); + $this->invoice_number_counter = 1; + $this->quote_number_counter = 1; + $this->save(); } } diff --git a/app/Models/Traits/PresentsInvoice.php b/app/Models/Traits/PresentsInvoice.php index ca415b1e6..353506979 100644 --- a/app/Models/Traits/PresentsInvoice.php +++ b/app/Models/Traits/PresentsInvoice.php @@ -96,14 +96,16 @@ trait PresentsInvoice 'client.client_name', 'client.id_number', 'client.vat_number', + 'client.website', + 'client.work_phone', 'client.address1', 'client.address2', 'client.city_state_postal', 'client.postal_city_state', 'client.country', + 'client.contact_name', 'client.email', 'client.phone', - 'client.contact_name', 'client.custom_value1', 'client.custom_value2', '.blank', @@ -138,6 +140,8 @@ trait PresentsInvoice list($entityType, $fieldName) = explode('.', $field); if (substr($fieldName, 0, 6) == 'custom') { $fields[$section][$field] = $labels[$field]; + } elseif (in_array($field, ['client.phone', 'client.email'])) { + $fields[$section][$field] = trans('texts.contact_' . $fieldName); } else { $fields[$section][$field] = $labels[$fieldName]; } @@ -208,7 +212,7 @@ trait PresentsInvoice 'website', 'phone', 'blank', - 'adjustment', + 'surcharge', 'tax_invoice', 'tax_quote', 'statement', @@ -216,6 +220,13 @@ trait PresentsInvoice 'your_statement', 'statement_issued_to', 'statement_to', + 'credit_note', + 'credit_date', + 'credit_number', + 'credit_issued_to', + 'credit_to', + 'your_credit', + 'work_phone', ]; foreach ($fields as $field) { diff --git a/app/Models/Traits/SendsEmails.php b/app/Models/Traits/SendsEmails.php index 2510373de..ecd7cd323 100644 --- a/app/Models/Traits/SendsEmails.php +++ b/app/Models/Traits/SendsEmails.php @@ -33,7 +33,7 @@ trait SendsEmails { if ($this->hasFeature(FEATURE_CUSTOM_EMAILS)) { $field = "email_subject_{$entityType}"; - $value = $this->$field; + $value = $this->account_email_settings->$field; if ($value) { return preg_replace("/\r\n|\r|\n/", ' ', $value); @@ -84,7 +84,7 @@ trait SendsEmails if ($this->hasFeature(FEATURE_CUSTOM_EMAILS)) { $field = "email_template_{$entityType}"; - $template = $this->$field; + $template = $this->account_email_settings->$field; } if (! $template) { @@ -158,20 +158,27 @@ trait SendsEmails public function setTemplateDefaults($type, $subject, $body) { + $settings = $this->account_email_settings; + if ($subject) { - $this->{"email_subject_" . $type} = $subject; + $settings->{"email_subject_" . $type} = $subject; } if ($body) { - $this->{"email_template_" . $type} = $body; + $settings->{"email_template_" . $type} = $body; } - $this->save(); + $settings->save(); } public function getBccEmail() { - return $this->isPro() ? $this->bcc_email : false; + return $this->isPro() ? $this->account_email_settings->bcc_email : false; + } + + public function getReplyToEmail() + { + return $this->isPro() ? $this->account_email_settings->reply_to_email : false; } public function getFromEmail() diff --git a/app/Models/User.php b/app/Models/User.php index 1688fd408..240234d87 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -263,7 +263,7 @@ class User extends Authenticatable // if the user changes their email then they need to reconfirm it if ($user->isEmailBeingChanged()) { $user->confirmed = 0; - $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); + $user->confirmation_code = strtolower(str_random(RANDOM_KEY_LENGTH)); } } diff --git a/app/Ninja/Datatables/AccountGatewayDatatable.php b/app/Ninja/Datatables/AccountGatewayDatatable.php index d67644931..e427ed5e1 100644 --- a/app/Ninja/Datatables/AccountGatewayDatatable.php +++ b/app/Ninja/Datatables/AccountGatewayDatatable.php @@ -12,6 +12,7 @@ use Utils; class AccountGatewayDatatable extends EntityDatatable { private static $accountGateways; + private static $accountGatewaySettings; public $entityType = ENTITY_ACCOUNT_GATEWAY; @@ -19,7 +20,7 @@ class AccountGatewayDatatable extends EntityDatatable { return [ [ - 'name', + 'gateway', function ($model) { if ($model->deleted_at) { return $model->name; @@ -62,26 +63,16 @@ class AccountGatewayDatatable extends EntityDatatable [ 'limit', function ($model) { - if ($model->gateway_id == GATEWAY_CUSTOM) { - $gatewayTypes = [GATEWAY_TYPE_CUSTOM]; - } else { - $accountGateway = $this->getAccountGateway($model->id); - $paymentDriver = $accountGateway->paymentDriver(); - $gatewayTypes = $paymentDriver->gatewayTypes(); - $gatewayTypes = array_diff($gatewayTypes, [GATEWAY_TYPE_TOKEN]); - } - + $gatewayTypes = $this->getGatewayTypes($model->id, $model->gateway_id); $html = ''; foreach ($gatewayTypes as $gatewayTypeId) { - $accountGatewaySettings = AccountGatewaySettings::scope()->where('account_gateway_settings.gateway_type_id', - '=', $gatewayTypeId)->first(); - $gatewayType = GatewayType::find($gatewayTypeId); + $accountGatewaySettings = $this->getAccountGatewaySetting($gatewayTypeId); + $gatewayType = Utils::getFromCache($gatewayTypeId, 'gatewayTypes'); if (count($gatewayTypes) > 1) { if ($html) { $html .= '
'; } - $html .= $gatewayType->name . ' — '; } @@ -103,6 +94,38 @@ class AccountGatewayDatatable extends EntityDatatable return $html; }, ], + [ + 'fees', + function ($model) { + if (! $model->gateway_fee_enabled) { + return trans('texts.fees_disabled'); + } + + $gatewayTypes = $this->getGatewayTypes($model->id, $model->gateway_id); + $html = ''; + foreach ($gatewayTypes as $gatewayTypeId) { + $accountGatewaySettings = $this->getAccountGatewaySetting($gatewayTypeId); + if (! $accountGatewaySettings || ! $accountGatewaySettings->areFeesEnabled()) { + continue; + } + + $gatewayType = Utils::getFromCache($gatewayTypeId, 'gatewayTypes'); + + if (count($gatewayTypes) > 1) { + if ($html) { + $html .= '
'; + } + $html .= $gatewayType->name . ' — '; + } + $html .= $accountGatewaySettings->feesToString(); + + if ($accountGatewaySettings->hasTaxes()) { + $html .= ' + ' . trans('texts.tax'); + } + }; + return $html ?: trans('texts.no_fees'); + }, + ], ]; } @@ -160,15 +183,9 @@ class AccountGatewayDatatable extends EntityDatatable foreach (Cache::get('gatewayTypes') as $gatewayType) { $actions[] = [ - trans('texts.set_limits', ['gateway_type' => $gatewayType->name]), + trans('texts.set_limits_fees', ['gateway_type' => $gatewayType->name]), function () use ($gatewayType) { - $accountGatewaySettings = AccountGatewaySettings::scope() - ->where('account_gateway_settings.gateway_type_id', '=', $gatewayType->id) - ->first(); - $min = $accountGatewaySettings && $accountGatewaySettings->min_limit !== null ? $accountGatewaySettings->min_limit : 'null'; - $max = $accountGatewaySettings && $accountGatewaySettings->max_limit !== null ? $accountGatewaySettings->max_limit : 'null'; - - return "javascript:showLimitsModal('{$gatewayType->name}', {$gatewayType->id}, $min, $max)"; + return "javascript:showLimitsModal('{$gatewayType->name}', {$gatewayType->id})"; }, function ($model) use ($gatewayType) { // Only show this action if the given gateway supports this gateway type @@ -176,10 +193,7 @@ class AccountGatewayDatatable extends EntityDatatable return $gatewayType->id == GATEWAY_TYPE_CUSTOM; } else { $accountGateway = $this->getAccountGateway($model->id); - $paymentDriver = $accountGateway->paymentDriver(); - $gatewayTypes = $paymentDriver->gatewayTypes(); - - return in_array($gatewayType->id, $gatewayTypes); + return $accountGateway->paymentDriver()->supportsGatewayType($gatewayType->id); } }, ]; @@ -198,4 +212,30 @@ class AccountGatewayDatatable extends EntityDatatable return static::$accountGateways[$id]; } + + private function getAccountGatewaySetting($gatewayTypeId) + { + if (isset(static::$accountGatewaySettings[$gatewayTypeId])) { + return static::$accountGatewaySettings[$gatewayTypeId]; + } + + static::$accountGatewaySettings[$gatewayTypeId] = AccountGatewaySettings::scope() + ->where('account_gateway_settings.gateway_type_id', '=', $gatewayTypeId)->first(); + + return static::$accountGatewaySettings[$gatewayTypeId]; + } + + private function getGatewayTypes($id, $gatewayId) + { + if ($gatewayId == GATEWAY_CUSTOM) { + $gatewayTypes = [GATEWAY_TYPE_CUSTOM]; + } else { + $accountGateway = $this->getAccountGateway($id); + $paymentDriver = $accountGateway->paymentDriver(); + $gatewayTypes = $paymentDriver->gatewayTypes(); + $gatewayTypes = array_diff($gatewayTypes, [GATEWAY_TYPE_TOKEN]); + } + + return $gatewayTypes; + } } diff --git a/app/Ninja/Datatables/ClientDatatable.php b/app/Ninja/Datatables/ClientDatatable.php index 6913274f9..ad20e15bc 100644 --- a/app/Ninja/Datatables/ClientDatatable.php +++ b/app/Ninja/Datatables/ClientDatatable.php @@ -32,6 +32,13 @@ class ClientDatatable extends EntityDatatable return link_to("clients/{$model->public_id}", $model->email ?: '')->toHtml(); }, ], + [ + 'id_number', + function ($model) { + return $model->id_number; + }, + Auth::user()->account->clientNumbersEnabled() + ], [ 'client_created_at', function ($model) { diff --git a/app/Ninja/Datatables/CreditDatatable.php b/app/Ninja/Datatables/CreditDatatable.php index 3bbf4430f..2fd96e091 100644 --- a/app/Ninja/Datatables/CreditDatatable.php +++ b/app/Ninja/Datatables/CreditDatatable.php @@ -41,10 +41,16 @@ class CreditDatatable extends EntityDatatable 'credit_date', function ($model) { if (! Auth::user()->can('viewByOwner', [ENTITY_CREDIT, $model->user_id])) { - return Utils::fromSqlDate($model->credit_date); + return Utils::fromSqlDate($model->credit_date_sql); } - return link_to("credits/{$model->public_id}/edit", Utils::fromSqlDate($model->credit_date))->toHtml(); + return link_to("credits/{$model->public_id}/edit", Utils::fromSqlDate($model->credit_date_sql))->toHtml(); + }, + ], + [ + 'public_notes', + function ($model) { + return $model->public_notes; }, ], [ diff --git a/app/Ninja/Datatables/ExpenseDatatable.php b/app/Ninja/Datatables/ExpenseDatatable.php index 1073bef63..d29408ab3 100644 --- a/app/Ninja/Datatables/ExpenseDatatable.php +++ b/app/Ninja/Datatables/ExpenseDatatable.php @@ -49,10 +49,10 @@ class ExpenseDatatable extends EntityDatatable 'expense_date', function ($model) { if (! Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])) { - return Utils::fromSqlDate($model->expense_date); + return Utils::fromSqlDate($model->expense_date_sql); } - return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml(); + return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date_sql))->toHtml(); }, ], [ @@ -73,7 +73,12 @@ class ExpenseDatatable extends EntityDatatable [ 'category', function ($model) { - return $model->category != null ? substr($model->category, 0, 100) : ''; + $category = $model->category != null ? substr($model->category, 0, 100) : ''; + if (! Auth::user()->can('editByOwner', [ENTITY_EXPENSE_CATEGORY, $model->category_user_id])) { + return $category; + } + + return $model->category_public_id ? link_to("expense_categories/{$model->category_public_id}/edit", $category)->toHtml() : ''; }, ], [ diff --git a/app/Ninja/Datatables/InvoiceDatatable.php b/app/Ninja/Datatables/InvoiceDatatable.php index 655b924c3..f76144b3f 100644 --- a/app/Ninja/Datatables/InvoiceDatatable.php +++ b/app/Ninja/Datatables/InvoiceDatatable.php @@ -41,7 +41,7 @@ class InvoiceDatatable extends EntityDatatable [ 'date', function ($model) { - return Utils::fromSqlDate($model->date); + return Utils::fromSqlDate($model->invoice_date); }, ], [ @@ -65,7 +65,7 @@ class InvoiceDatatable extends EntityDatatable [ $entityType == ENTITY_INVOICE ? 'due_date' : 'valid_until', function ($model) { - return Utils::fromSqlDate($model->due_date); + return Utils::fromSqlDate($model->due_date_sql); }, ], [ @@ -129,7 +129,7 @@ class InvoiceDatatable extends EntityDatatable return "javascript:submitForm_{$entityType}('markPaid', {$model->public_id})"; }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->balance > 0 && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); + return $entityType == ENTITY_INVOICE && $model->balance != 0 && Auth::user()->can('editByOwner', [ENTITY_INVOICE, $model->user_id]); }, ], [ @@ -173,7 +173,7 @@ class InvoiceDatatable extends EntityDatatable private function getStatusLabel($model) { - $class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->due_date, $model->is_recurring); + $class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->due_date_sql, $model->is_recurring); $label = Invoice::calcStatusLabel($model->invoice_status_name, $class, $this->entityType, $model->quote_invoice_id); return "

$label

"; diff --git a/app/Ninja/Datatables/PaymentDatatable.php b/app/Ninja/Datatables/PaymentDatatable.php index f536b454e..b4593bd92 100644 --- a/app/Ninja/Datatables/PaymentDatatable.php +++ b/app/Ninja/Datatables/PaymentDatatable.php @@ -92,7 +92,7 @@ class PaymentDatatable extends EntityDatatable }, ], [ - 'payment_date', + 'date', function ($model) { if ($model->is_deleted) { return Utils::dateToString($model->payment_date); diff --git a/app/Ninja/Datatables/RecurringInvoiceDatatable.php b/app/Ninja/Datatables/RecurringInvoiceDatatable.php index 3f0f159df..b40378310 100644 --- a/app/Ninja/Datatables/RecurringInvoiceDatatable.php +++ b/app/Ninja/Datatables/RecurringInvoiceDatatable.php @@ -5,6 +5,7 @@ namespace App\Ninja\Datatables; use Auth; use URL; use Utils; +use App\Models\Invoice; class RecurringInvoiceDatatable extends EntityDatatable { @@ -32,19 +33,19 @@ class RecurringInvoiceDatatable extends EntityDatatable [ 'start_date', function ($model) { - return Utils::fromSqlDate($model->start_date); + return Utils::fromSqlDate($model->start_date_sql); }, ], [ 'last_sent', function ($model) { - return Utils::fromSqlDate($model->last_sent_date); + return Utils::fromSqlDate($model->last_sent_date_sql); }, ], [ 'end_date', function ($model) { - return Utils::fromSqlDate($model->end_date); + return Utils::fromSqlDate($model->end_date_sql); }, ], [ @@ -53,9 +54,27 @@ class RecurringInvoiceDatatable extends EntityDatatable return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }, ], + [ + 'status', + function ($model) { + return self::getStatusLabel($model); + }, + ], ]; } + private function getStatusLabel($model) + { + $class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->due_date_sql, $model->is_recurring); + $label = Invoice::calcStatusLabel($model->invoice_status_name, $class, $this->entityType, $model->quote_invoice_id); + + if ($model->invoice_status_id == INVOICE_STATUS_SENT && (! $model->last_sent_date_sql || $model->last_sent_date_sql == '0000-00-00')) { + $label = trans('texts.pending'); + } + + return "

$label

"; + } + public function actions() { return [ diff --git a/app/Ninja/Datatables/VendorDatatable.php b/app/Ninja/Datatables/VendorDatatable.php index 98ba3c66f..d226a7807 100644 --- a/app/Ninja/Datatables/VendorDatatable.php +++ b/app/Ninja/Datatables/VendorDatatable.php @@ -39,7 +39,7 @@ class VendorDatatable extends EntityDatatable }, ], [ - 'date', + 'client_created_at', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); }, diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 12939f5ec..bc2cbaa4c 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -36,7 +36,7 @@ class ContactMailer extends Mailer * * @return bool|null|string */ - public function sendInvoice(Invoice $invoice, $reminder = false, $pdfString = false, $template = false) + public function sendInvoice(Invoice $invoice, $reminder = false, $template = false) { if ($invoice->is_recurring) { return false; @@ -61,8 +61,9 @@ class ContactMailer extends Mailer $emailSubject = !empty($template['subject']) ? $template['subject'] : $account->getEmailSubject($reminder ?: $entityType); $sent = false; + $pdfString = false; - if ($account->attachPDF() && ! $pdfString) { + if ($account->attachPDF()) { $pdfString = $invoice->getPDFString(); } @@ -198,7 +199,7 @@ class ContactMailer extends Mailer } $subject = $this->templateService->processVariables($subject, $variables); - $fromEmail = $user->email; + $fromEmail = $account->getReplyToEmail() ?: $user->email; $view = $account->getTemplateView(ENTITY_INVOICE); $response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, $view, $data); @@ -290,9 +291,10 @@ class ContactMailer extends Mailer $data['invoice_id'] = $payment->invoice->id; $view = $account->getTemplateView('payment_confirmation'); + $fromEmail = $account->getReplyToEmail() ?: $user->email; if ($user->email && $contact->email) { - $this->sendTo($contact->email, $user->email, $accountName, $subject, $view, $data); + $this->sendTo($contact->email, $fromEmail, $accountName, $subject, $view, $data); } $account->loadLocalizationSettings(); diff --git a/app/Ninja/Mailers/UserMailer.php b/app/Ninja/Mailers/UserMailer.php index c509bd66a..302e7d6b8 100644 --- a/app/Ninja/Mailers/UserMailer.php +++ b/app/Ninja/Mailers/UserMailer.php @@ -58,12 +58,7 @@ class UserMailer extends Mailer $view = ($notificationType == 'approved' ? ENTITY_QUOTE : ENTITY_INVOICE) . "_{$notificationType}"; $account = $user->account; $client = $invoice->client; - - if ($account->hasMultipleAccounts()) { - $link = url(sprintf('/account/%s?redirect_to=%s', $account->account_key, $invoice->present()->path)); - } else { - $link = $invoice->present()->url; - } + $link = $invoice->present()->multiAccountLink; $data = [ 'entityType' => $entityType, @@ -116,6 +111,26 @@ class UserMailer extends Mailer $this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); } + /** + * @param Invitation $invitation + */ + public function sendMessage($user, $subject, $message, $invoice = false) + { + if (! $user->email) { + return; + } + + $view = 'user_message'; + $data = [ + 'userName' => $user->getDisplayName(), + 'primaryMessage' => $subject, + 'secondaryMessage' => $message, + 'invoiceLink' => $invoice ? $invoice->present()->multiAccountLink : false, + ]; + + $this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); + } + public function sendSecurityCode($user, $code) { if (! $user->email) { diff --git a/app/Ninja/OAuth/OAuth.php b/app/Ninja/OAuth/OAuth.php new file mode 100644 index 000000000..487cd6172 --- /dev/null +++ b/app/Ninja/OAuth/OAuth.php @@ -0,0 +1,44 @@ +providerInstance = new Providers\Google(); + return $this; + + default: + return null; + break; + } + } + + public function getTokenResponse($token) + { + $email = null; + $user = null; + + if($this->providerInstance) + $user = User::where('email', $this->providerInstance->getTokenResponse($token))->first(); + + if ($user) + return $user; + else + return false; + + } + + +} +?> \ No newline at end of file diff --git a/app/Ninja/OAuth/Providers/Google.php b/app/Ninja/OAuth/Providers/Google.php new file mode 100644 index 000000000..0425b3f1d --- /dev/null +++ b/app/Ninja/OAuth/Providers/Google.php @@ -0,0 +1,23 @@ + env('GOOGLE_CLIENT_ID','')]); + $payload = $client->verifyIdToken($token); + if ($payload) + return $this->harvestEmail($payload); + else + return null; + } + + public function harvestEmail($payload) + { + return $payload['email']; + } + + +} diff --git a/app/Ninja/OAuth/Providers/ProviderInterface.php b/app/Ninja/OAuth/Providers/ProviderInterface.php new file mode 100644 index 000000000..689130f38 --- /dev/null +++ b/app/Ninja/OAuth/Providers/ProviderInterface.php @@ -0,0 +1,9 @@ +to('view/' . $this->invitation->invitation_key); } + if (! $this->isGatewayType(GATEWAY_TYPE_TOKEN)) { + // apply gateway fees + $invoicRepo = app('App\Ninja\Repositories\InvoiceRepository'); + $invoicRepo->setGatewayFee($this->invoice(), $this->gatewayType); + } + if ($this->isGatewayType(GATEWAY_TYPE_TOKEN) || $gateway->is_offsite) { if (Session::has('error')) { Session::reflash(); @@ -161,6 +167,7 @@ class BasePaymentDriver 'invoiceNumber' => $this->invoice()->invoice_number, 'client' => $this->client(), 'contact' => $this->invitation->contact, + 'invitation' => $this->invitation, 'gatewayType' => $this->gatewayType, 'currencyId' => $this->client()->getCurrencyId(), 'currencyCode' => $this->client()->getCurrencyCode(), @@ -262,6 +269,9 @@ class BasePaymentDriver ->firstOrFail(); } + $invoicRepo = app('App\Ninja\Repositories\InvoiceRepository'); + $invoicRepo->setGatewayFee($this->invoice(), $paymentMethod->payment_type->gateway_type_id); + if (! $this->meetsGatewayTypeLimits($paymentMethod->payment_type->gateway_type_id)) { // The customer must have hacked the URL Session::flash('error', trans('texts.limits_not_met')); @@ -854,6 +864,8 @@ class BasePaymentDriver $label = trans('texts.payment_type_on_file', ['type' => $paymentMethod->payment_type->name]); } + $label .= $this->invoice()->present()->gatewayFee($paymentMethod->payment_type->gateway_type_id); + $links[] = [ 'url' => $url, 'label' => $label, @@ -886,6 +898,8 @@ class BasePaymentDriver $label = trans("texts.{$gatewayTypeAlias}"); } + $label .= $this->invoice()->present()->gatewayFee($gatewayTypeId); + $links[] = [ 'gatewayTypeId' => $gatewayTypeId, 'url' => $url, @@ -896,6 +910,11 @@ class BasePaymentDriver return $links; } + public function supportsGatewayType($gatewayTypeId) + { + return in_array($gatewayTypeId, $this->gatewayTypes()); + } + protected function meetsGatewayTypeLimits($gatewayTypeId) { if (! $gatewayTypeId) { @@ -925,17 +944,6 @@ class BasePaymentDriver $account = $this->account(); $url = URL::to("/payment/{$this->invitation->invitation_key}/{$gatewayTypeAlias}"); - $gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias); - - // PayPal doesn't allow being run in an iframe so we need to open in new tab - if ($gatewayTypeId === GATEWAY_TYPE_PAYPAL) { - $url .= '#braintree_paypal'; - - if ($account->iframe_url) { - return 'javascript:window.open("' . $url . '", "_blank")'; - } - } - return $url; } diff --git a/app/Ninja/PaymentDrivers/BraintreePaymentDriver.php b/app/Ninja/PaymentDrivers/BraintreePaymentDriver.php index 7acb3cb79..6a53c8d29 100644 --- a/app/Ninja/PaymentDrivers/BraintreePaymentDriver.php +++ b/app/Ninja/PaymentDrivers/BraintreePaymentDriver.php @@ -5,6 +5,7 @@ namespace App\Ninja\PaymentDrivers; use Braintree\Customer; use Exception; use Session; +use App\Models\GatewayType; class BraintreePaymentDriver extends BasePaymentDriver { @@ -62,6 +63,17 @@ class BraintreePaymentDriver extends BasePaymentDriver return $customer instanceof Customer; } + protected function paymentUrl($gatewayTypeAlias) + { + $url = parent::paymentUrl($gatewayTypeAlias); + + if (GatewayType::getIdFromAlias($gatewayTypeAlias) === GATEWAY_TYPE_PAYPAL) { + $url .= '#braintree_paypal'; + } + + return $url; + } + protected function paymentDetails($paymentMethod = false) { $data = parent::paymentDetails($paymentMethod); diff --git a/app/Ninja/PaymentDrivers/PayPalExpressPaymentDriver.php b/app/Ninja/PaymentDrivers/PayPalExpressPaymentDriver.php index ac3d0c847..ffec414c5 100644 --- a/app/Ninja/PaymentDrivers/PayPalExpressPaymentDriver.php +++ b/app/Ninja/PaymentDrivers/PayPalExpressPaymentDriver.php @@ -17,6 +17,7 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver $data['ButtonSource'] = 'InvoiceNinja_SP'; $data['solutionType'] = 'Sole'; // show 'Pay with credit card' option + $data['transactionId'] = $data['transactionId'] . '-' . time(); return $data; } @@ -27,4 +28,17 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver return $payment; } + + protected function paymentUrl($gatewayTypeAlias) + { + $url = parent::paymentUrl($gatewayTypeAlias); + + // PayPal doesn't allow being run in an iframe so we need to open in new tab + if ($this->account()->iframe_url) { + return 'javascript:window.open("' . $url . '", "_blank")'; + } else { + return $url; + } + } + } diff --git a/app/Ninja/PaymentDrivers/WePayPaymentDriver.php b/app/Ninja/PaymentDrivers/WePayPaymentDriver.php index 1ebdc69e7..7398844c1 100644 --- a/app/Ninja/PaymentDrivers/WePayPaymentDriver.php +++ b/app/Ninja/PaymentDrivers/WePayPaymentDriver.php @@ -56,12 +56,14 @@ class WePayPaymentDriver extends BasePaymentDriver $data['transaction_id'] = $transactionId; } - $data['applicationFee'] = (env('WEPAY_APP_FEE_MULTIPLIER') * $data['amount']) + env('WEPAY_APP_FEE_FIXED'); $data['feePayer'] = env('WEPAY_FEE_PAYER'); $data['callbackUri'] = $this->accountGateway->getWebhookUrl(); if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER, $paymentMethod)) { $data['paymentMethodType'] = 'payment_bank'; + $data['applicationFee'] = (env('WEPAY_APP_FEE_ACH_MULTIPLIER') * $data['amount']) + env('WEPAY_APP_FEE_FIXED'); + } else { + $data['applicationFee'] = (env('WEPAY_APP_FEE_CC_MULTIPLIER') * $data['amount']) + env('WEPAY_APP_FEE_FIXED'); } $data['transaction_rbits'] = $this->invoice()->present()->rBits; diff --git a/app/Ninja/Presenters/AccountPresenter.php b/app/Ninja/Presenters/AccountPresenter.php index 8dce6bdcd..187e9f16f 100644 --- a/app/Ninja/Presenters/AccountPresenter.php +++ b/app/Ninja/Presenters/AccountPresenter.php @@ -149,4 +149,28 @@ class AccountPresenter extends Presenter return $options; } + + public function customTextFields() + { + $fields = [ + 'custom_client_label1' => 'custom_client1', + 'custom_client_label2' => 'custom_client2', + 'custom_invoice_text_label1' => 'custom_invoice1', + 'custom_invoice_text_label2' => 'custom_invoice2', + 'custom_invoice_item_label1' => 'custom_product1', + 'custom_invoice_item_label2' => 'custom_product2', + ]; + $data = []; + + foreach ($fields as $key => $val) { + if ($this->$key) { + $data[$this->$key] = [ + 'value' => $val, + 'name' => $val, + ]; + } + } + + return $data; + } } diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 6ee92f1c2..473423aad 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -225,7 +225,7 @@ class InvoicePresenter extends EntityPresenter $actions[] = ['url' => url("quotes/{$invoice->quote_id}/edit"), 'label' => trans('texts.view_quote')]; } - if (!$invoice->deleted_at && ! $invoice->is_recurring && $invoice->balance > 0) { + if (!$invoice->deleted_at && ! $invoice->is_recurring && $invoice->balance != 0) { $actions[] = ['url' => 'javascript:submitBulkAction("markPaid")', 'label' => trans('texts.mark_paid')]; $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; } @@ -252,4 +252,45 @@ class InvoicePresenter extends EntityPresenter return $actions; } + + public function gatewayFee($gatewayTypeId = false) + { + $invoice = $this->entity; + $account = $invoice->account; + + if (! $account->gateway_fee_enabled) { + return ''; + } + + $settings = $account->getGatewaySettings($gatewayTypeId); + + if (! $settings || ! $settings->areFeesEnabled()) { + return ''; + } + + $fee = $invoice->calcGatewayFee($gatewayTypeId, true); + $fee = $account->formatMoney($fee, $invoice->client); + + if (floatval($settings->fee_amount) < 0 || floatval($settings->fee_percent) < 0) { + $label = trans('texts.discount'); + } else { + $label = trans('texts.fee'); + } + + return ' - ' . $fee . ' ' . $label; + } + + public function multiAccountLink() + { + $invoice = $this->entity; + $account = $invoice->account; + + if ($account->hasMultipleAccounts()) { + $link = url(sprintf('/account/%s?redirect_to=%s', $account->account_key, $invoice->present()->path)); + } else { + $link = $invoice->present()->url; + } + + return $link; + } } diff --git a/app/Ninja/Reports/AbstractReport.php b/app/Ninja/Reports/AbstractReport.php index c1983b3e4..4b6ffa080 100644 --- a/app/Ninja/Reports/AbstractReport.php +++ b/app/Ninja/Reports/AbstractReport.php @@ -25,6 +25,7 @@ class AbstractReport public function run() { + } public function results() @@ -66,7 +67,7 @@ class AbstractReport if (strpos($field, 'date') !== false) { $class[] = 'group-date-' . (isset($this->options['group_dates_by']) ? $this->options['group_dates_by'] : 'monthyear'); - } elseif (in_array($field, ['client', 'vendor', 'product', 'method', 'category'])) { + } elseif (in_array($field, ['client', 'vendor', 'product', 'user', 'method', 'category', 'project'])) { $class[] = 'group-letter-100'; } elseif (in_array($field, ['amount', 'paid', 'balance'])) { $class[] = 'group-number-50'; diff --git a/app/Ninja/Reports/ActivityReport.php b/app/Ninja/Reports/ActivityReport.php new file mode 100644 index 000000000..3e843b25b --- /dev/null +++ b/app/Ninja/Reports/ActivityReport.php @@ -0,0 +1,41 @@ +account; + + $startDate = $this->startDate->format('Y-m-d'); + $endDate = $this->endDate->format('Y-m-d'); + + $activities = Activity::scope() + ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'task', 'expense', 'account') + ->whereRaw("DATE(created_at) >= \"{$startDate}\" and DATE(created_at) <= \"$endDate\"") + ->orderBy('id', 'desc'); + + foreach ($activities->get() as $activity) { + $client = $activity->client; + $this->data[] = [ + $activity->present()->createdAt, + $client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '', + $activity->present()->user, + $activity->getMessage(), + ]; + } + + + } +} diff --git a/app/Ninja/Reports/AgingReport.php b/app/Ninja/Reports/AgingReport.php index a7d1213d9..787992550 100644 --- a/app/Ninja/Reports/AgingReport.php +++ b/app/Ninja/Reports/AgingReport.php @@ -22,6 +22,7 @@ class AgingReport extends AbstractReport $account = Auth::user()->account; $clients = Client::scope() + ->orderBy('name') ->withArchived() ->with('contacts') ->with(['invoices' => function ($query) { diff --git a/app/Ninja/Reports/ClientReport.php b/app/Ninja/Reports/ClientReport.php index b0f12f63e..befde9b3b 100644 --- a/app/Ninja/Reports/ClientReport.php +++ b/app/Ninja/Reports/ClientReport.php @@ -19,6 +19,7 @@ class ClientReport extends AbstractReport $account = Auth::user()->account; $clients = Client::scope() + ->orderBy('name') ->withArchived() ->with('contacts') ->with(['invoices' => function ($query) { diff --git a/app/Ninja/Reports/ExpenseReport.php b/app/Ninja/Reports/ExpenseReport.php index 880845f07..10a4220e1 100644 --- a/app/Ninja/Reports/ExpenseReport.php +++ b/app/Ninja/Reports/ExpenseReport.php @@ -21,6 +21,7 @@ class ExpenseReport extends AbstractReport $account = Auth::user()->account; $expenses = Expense::scope() + ->orderBy('expense_date', 'desc') ->withArchived() ->with('client.contacts', 'vendor') ->where('expense_date', '>=', $this->startDate) diff --git a/app/Ninja/Reports/InvoiceReport.php b/app/Ninja/Reports/InvoiceReport.php index 908e01e96..b9cb66d0f 100644 --- a/app/Ninja/Reports/InvoiceReport.php +++ b/app/Ninja/Reports/InvoiceReport.php @@ -12,6 +12,7 @@ class InvoiceReport extends AbstractReport 'invoice_number', 'invoice_date', 'amount', + 'status', 'payment_date', 'paid', 'method', @@ -23,6 +24,7 @@ class InvoiceReport extends AbstractReport $status = $this->options['invoice_status']; $clients = Client::scope() + ->orderBy('name') ->withArchived() ->with('contacts') ->with(['invoices' => function ($query) use ($status) { @@ -56,6 +58,7 @@ class InvoiceReport extends AbstractReport $this->isExport ? $invoice->invoice_number : $invoice->present()->link, $invoice->present()->invoice_date, $account->formatMoney($invoice->amount, $client), + $invoice->present()->status(), $payment ? $payment->present()->payment_date : '', $payment ? $account->formatMoney($payment->getCompletedAmount(), $client) : '', $payment ? $payment->present()->method : '', diff --git a/app/Ninja/Reports/PaymentReport.php b/app/Ninja/Reports/PaymentReport.php index 5df24a1d1..d448646ab 100644 --- a/app/Ninja/Reports/PaymentReport.php +++ b/app/Ninja/Reports/PaymentReport.php @@ -22,6 +22,7 @@ class PaymentReport extends AbstractReport $account = Auth::user()->account; $payments = Payment::scope() + ->orderBy('payment_date', 'desc') ->withArchived() ->excludeFailed() ->whereHas('client', function ($query) { diff --git a/app/Ninja/Reports/ProductReport.php b/app/Ninja/Reports/ProductReport.php index 19bb5c1fc..a0124a825 100644 --- a/app/Ninja/Reports/ProductReport.php +++ b/app/Ninja/Reports/ProductReport.php @@ -24,6 +24,7 @@ class ProductReport extends AbstractReport $status = $this->options['invoice_status']; $clients = Client::scope() + ->orderBy('name') ->withArchived() ->with('contacts') ->with(['invoices' => function ($query) use ($status) { diff --git a/app/Ninja/Reports/ProfitAndLossReport.php b/app/Ninja/Reports/ProfitAndLossReport.php index 919c57153..4cd0a47c9 100644 --- a/app/Ninja/Reports/ProfitAndLossReport.php +++ b/app/Ninja/Reports/ProfitAndLossReport.php @@ -21,6 +21,7 @@ class ProfitAndLossReport extends AbstractReport $account = Auth::user()->account; $payments = Payment::scope() + ->orderBy('payment_date', 'desc') ->with('client.contacts') ->withArchived() ->excludeFailed() @@ -43,6 +44,7 @@ class ProfitAndLossReport extends AbstractReport } $expenses = Expense::scope() + ->orderBy('expense_date', 'desc') ->with('client.contacts') ->withArchived() ->where('expense_date', '>=', $this->startDate) diff --git a/app/Ninja/Reports/QuoteReport.php b/app/Ninja/Reports/QuoteReport.php new file mode 100644 index 000000000..e53f6e0fb --- /dev/null +++ b/app/Ninja/Reports/QuoteReport.php @@ -0,0 +1,52 @@ +account; + $status = $this->options['invoice_status']; + + $clients = Client::scope() + ->orderBy('name') + ->withArchived() + ->with('contacts') + ->with(['invoices' => function ($query) use ($status) { + if ($status == 'draft') { + $query->whereIsPublic(false); + } + $query->quotes() + ->withArchived() + ->where('invoice_date', '>=', $this->startDate) + ->where('invoice_date', '<=', $this->endDate) + ->with(['invoice_items']); + }]); + + foreach ($clients->get() as $client) { + foreach ($client->invoices as $invoice) { + $this->data[] = [ + $this->isExport ? $client->getDisplayName() : $client->present()->link, + $this->isExport ? $invoice->invoice_number : $invoice->present()->link, + $invoice->present()->invoice_date, + $account->formatMoney($invoice->amount, $client), + $invoice->present()->status(), + ]; + + $this->addToTotals($client->currency_id, 'amount', $invoice->amount); + } + } + } +} diff --git a/app/Ninja/Reports/TaskReport.php b/app/Ninja/Reports/TaskReport.php index 803f799a2..29d83e148 100644 --- a/app/Ninja/Reports/TaskReport.php +++ b/app/Ninja/Reports/TaskReport.php @@ -18,6 +18,7 @@ class TaskReport extends AbstractReport public function run() { $tasks = Task::scope() + ->orderBy('created_at', 'desc') ->with('client.contacts') ->withArchived() ->dateRange($this->startDate, $this->endDate); diff --git a/app/Ninja/Reports/TaxRateReport.php b/app/Ninja/Reports/TaxRateReport.php index 493039341..c964a7f4d 100644 --- a/app/Ninja/Reports/TaxRateReport.php +++ b/app/Ninja/Reports/TaxRateReport.php @@ -20,6 +20,7 @@ class TaxRateReport extends AbstractReport $account = Auth::user()->account; $clients = Client::scope() + ->orderBy('name') ->withArchived() ->with('contacts') ->with(['invoices' => function ($query) { diff --git a/app/Ninja/Repositories/AccountGatewayRepository.php b/app/Ninja/Repositories/AccountGatewayRepository.php index f338ccb31..e047bf162 100644 --- a/app/Ninja/Repositories/AccountGatewayRepository.php +++ b/app/Ninja/Repositories/AccountGatewayRepository.php @@ -15,9 +15,17 @@ class AccountGatewayRepository extends BaseRepository { $query = DB::table('account_gateways') ->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id') + ->join('accounts', 'accounts.id', '=', 'account_gateways.account_id') ->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'); + return $query->select( + 'account_gateways.id', + 'account_gateways.public_id', + 'gateways.name', + 'gateways.name as gateway', + 'account_gateways.deleted_at', + 'account_gateways.gateway_id', + 'accounts.gateway_fee_enabled'); } } diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index fc1446a6a..9ff3d67be 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -3,6 +3,7 @@ namespace App\Ninja\Repositories; use App\Models\Account; +use App\Models\AccountEmailSettings; use App\Models\AccountGateway; use App\Models\AccountToken; use App\Models\Client; @@ -27,19 +28,21 @@ use Validator; class AccountRepository { - public function create($firstName = '', $lastName = '', $email = '', $password = '') + public function create($firstName = '', $lastName = '', $email = '', $password = '', $company = false) { - $company = new Company(); - $company->utm_source = Input::get('utm_source'); - $company->utm_medium = Input::get('utm_medium'); - $company->utm_campaign = Input::get('utm_campaign'); - $company->utm_term = Input::get('utm_term'); - $company->utm_content = Input::get('utm_content'); - $company->save(); + if (! $company) { + $company = new Company(); + $company->utm_source = Input::get('utm_source'); + $company->utm_medium = Input::get('utm_medium'); + $company->utm_campaign = Input::get('utm_campaign'); + $company->utm_term = Input::get('utm_term'); + $company->utm_content = Input::get('utm_content'); + $company->save(); + } $account = new Account(); $account->ip = Request::getClientIp(); - $account->account_key = str_random(RANDOM_KEY_LENGTH); + $account->account_key = strtolower(str_random(RANDOM_KEY_LENGTH)); $account->company_id = $company->id; // Track referal code @@ -59,14 +62,14 @@ class AccountRepository $user = new User(); if (! $firstName && ! $lastName && ! $email && ! $password) { - $user->password = str_random(RANDOM_KEY_LENGTH); - $user->username = str_random(RANDOM_KEY_LENGTH); + $user->password = strtolower(str_random(RANDOM_KEY_LENGTH)); + $user->username = strtolower(str_random(RANDOM_KEY_LENGTH)); } else { $user->first_name = $firstName; $user->last_name = $lastName; $user->email = $user->username = $email; if (! $password) { - $password = str_random(RANDOM_KEY_LENGTH); + $password = strtolower(str_random(RANDOM_KEY_LENGTH)); } $user->password = bcrypt($password); } @@ -75,11 +78,14 @@ class AccountRepository $user->registered = ! Utils::isNinja() || $email; if (! $user->confirmed) { - $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); + $user->confirmation_code = strtolower(str_random(RANDOM_KEY_LENGTH)); } $account->users()->save($user); + $emailSettings = new AccountEmailSettings(); + $account->account_email_settings()->save($emailSettings); + return $account; } @@ -126,7 +132,7 @@ class AccountRepository foreach ($clients as $client) { if ($client->name) { $data['clients'][] = [ - 'value' => $client->name, + 'value' => ($account->clientNumbersEnabled() && $client->id_number ? $client->id_number . ': ' : '') . $client->name, 'tokens' => implode(',', [$client->name, $client->id_number, $client->vat_number, $client->work_phone]), 'url' => $client->present()->url, ]; @@ -326,7 +332,7 @@ class AccountRepository $invitation->public_id = $publicId; $invitation->invoice_id = $invoice->id; $invitation->contact_id = $client->contacts()->first()->id; - $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); + $invitation->invitation_key = strtolower(str_random(RANDOM_KEY_LENGTH)); $invitation->save(); return $invitation; @@ -350,7 +356,7 @@ class AccountRepository $account->company_id = $company->id; $account->save(); - $random = str_random(RANDOM_KEY_LENGTH); + $random = strtolower(str_random(RANDOM_KEY_LENGTH)); $user = new User(); $user->registered = true; $user->confirmed = true; @@ -617,61 +623,7 @@ class AccountRepository $record->save(); - $users = $this->getUserAccounts($record); - - // Pick the primary user - foreach ($users as $user) { - if (! $user->public_id) { - $useAsPrimary = false; - if (empty($primaryUser)) { - $useAsPrimary = true; - } - - $planDetails = $user->account->getPlanDetails(false, false); - $planLevel = 0; - - if ($planDetails) { - $planLevel = 1; - if ($planDetails['plan'] == PLAN_ENTERPRISE) { - $planLevel = 2; - } - - if (! $useAsPrimary && ( - $planLevel > $primaryUserPlanLevel - || ($planLevel == $primaryUserPlanLevel && $planDetails['expires'] > $primaryUserPlanExpires) - )) { - $useAsPrimary = true; - } - } - - if ($useAsPrimary) { - $primaryUser = $user; - $primaryUserPlanLevel = $planLevel; - if ($planDetails) { - $primaryUserPlanExpires = $planDetails['expires']; - } - } - } - } - - // Merge other companies into the primary user's company - if (! empty($primaryUser)) { - foreach ($users as $user) { - if ($user == $primaryUser || $user->public_id) { - continue; - } - - if ($user->account->company_id != $primaryUser->account->company_id) { - foreach ($user->account->company->accounts as $account) { - $account->company_id = $primaryUser->account->company_id; - $account->save(); - } - $user->account->company->forceDelete(); - } - } - } - - return $users; + return $this->loadAccounts($userId1); } public function unlinkAccount($account) @@ -731,7 +683,7 @@ class AccountRepository $token = AccountToken::createNew($user); $token->name = $name; - $token->token = str_random(RANDOM_KEY_LENGTH); + $token->token = strtolower(str_random(RANDOM_KEY_LENGTH)); $token->save(); } } diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index f2c3d19a1..e30a45873 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -51,7 +51,8 @@ class ClientRepository extends BaseRepository 'contacts.email', 'clients.deleted_at', 'clients.is_deleted', - 'clients.user_id' + 'clients.user_id', + 'clients.id_number' ); $this->applyFilters($query, ENTITY_CLIENT); @@ -80,13 +81,15 @@ class ClientRepository extends BaseRepository // do nothing } elseif (! $publicId || $publicId == '-1') { $client = Client::createNew(); - if (Auth::check() && Auth::user()->account->client_number_counter && empty($data['id_number'])) { - $data['id_number'] = Auth::user()->account->getNextNumber(); - } } else { $client = Client::scope($publicId)->with('contacts')->firstOrFail(); } + // auto-set the client id number + if (Auth::check() && Auth::user()->account->client_number_counter && !$client->id_number && empty($data['id_number'])) { + $data['id_number'] = Auth::user()->account->getNextNumber(); + } + if ($client->is_deleted) { return $client; } diff --git a/app/Ninja/Repositories/ContactRepository.php b/app/Ninja/Repositories/ContactRepository.php index 51a30bec4..7a72fda3e 100644 --- a/app/Ninja/Repositories/ContactRepository.php +++ b/app/Ninja/Repositories/ContactRepository.php @@ -6,16 +6,18 @@ use App\Models\Contact; class ContactRepository extends BaseRepository { - public function save($data) + public function save($data, $contact = false) { $publicId = isset($data['public_id']) ? $data['public_id'] : false; - if (! $publicId || $publicId == '-1') { + if ($contact) { + // do nothing + } elseif (! $publicId || $publicId == '-1') { $contact = Contact::createNew(); $contact->send_invoice = true; $contact->client_id = $data['client_id']; $contact->is_primary = Contact::scope()->where('client_id', '=', $contact->client_id)->count() == 0; - $contact->contact_key = str_random(RANDOM_KEY_LENGTH); + $contact->contact_key = strtolower(str_random(RANDOM_KEY_LENGTH)); } else { $contact = Contact::scope($publicId)->firstOrFail(); } diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php index adf89a709..032cf365c 100644 --- a/app/Ninja/Repositories/CreditRepository.php +++ b/app/Ninja/Repositories/CreditRepository.php @@ -32,11 +32,13 @@ class CreditRepository extends BaseRepository 'clients.user_id as client_user_id', 'credits.amount', 'credits.balance', - 'credits.credit_date', + 'credits.credit_date as credit_date_sql', + DB::raw("CONCAT(credits.credit_date, credits.created_at) as credit_date"), 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'credits.private_notes', + 'credits.public_notes', 'credits.deleted_at', 'credits.is_deleted', 'credits.user_id' @@ -73,7 +75,8 @@ class CreditRepository extends BaseRepository DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), 'credits.amount', 'credits.balance', - 'credits.credit_date' + 'credits.credit_date', + 'credits.public_notes' ); $table = \Datatable::query($query) @@ -86,6 +89,9 @@ class CreditRepository extends BaseRepository ->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); }) + ->addColumn('public_notes', function ($model) { + return $model->public_notes; + }) ->make(); return $table; @@ -107,6 +113,7 @@ class CreditRepository extends BaseRepository $credit->client_id = Client::getPrivateId($input['client']); } + $credit->fill($input); $credit->credit_date = Utils::toSqlDate($input['credit_date']); $credit->amount = Utils::parseFloat($input['amount']); $credit->private_notes = trim($input['private_notes']); diff --git a/app/Ninja/Repositories/DashboardRepository.php b/app/Ninja/Repositories/DashboardRepository.php index e1fce0310..1a1c6366b 100644 --- a/app/Ninja/Repositories/DashboardRepository.php +++ b/app/Ninja/Repositories/DashboardRepository.php @@ -74,7 +74,7 @@ class DashboardRepository $records[] = isset($data[$date]) ? $data[$date] : 0; if ($entityType == ENTITY_INVOICE) { - $labels[] = $d->format('r'); + $labels[] = $d->format('m/d/Y'); } } diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index 20077584e..6a322e153 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -66,7 +66,8 @@ class ExpenseRepository extends BaseRepository 'expenses.amount', 'expenses.deleted_at', 'expenses.exchange_rate', - 'expenses.expense_date', + 'expenses.expense_date as expense_date_sql', + DB::raw("CONCAT(expenses.expense_date, expenses.created_at) as expense_date"), 'expenses.id', 'expenses.is_deleted', 'expenses.private_notes', @@ -81,6 +82,8 @@ class ExpenseRepository extends BaseRepository 'expenses.tax_rate1', 'expenses.tax_rate2', 'expense_categories.name as category', + 'expense_categories.user_id as category_user_id', + 'expense_categories.public_id as category_public_id', 'invoices.public_id as invoice_public_id', 'invoices.user_id as invoice_user_id', 'invoices.balance', diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 7210e8e96..7bed63770 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -12,6 +12,7 @@ use App\Models\Invoice; use App\Models\InvoiceItem; use App\Models\Product; use App\Models\Task; +use App\Models\GatewayType; use App\Services\PaymentService; use Auth; use DB; @@ -67,9 +68,11 @@ class InvoiceRepository extends BaseRepository 'invoices.public_id', 'invoices.amount', 'invoices.balance', - 'invoices.invoice_date as date', - 'invoices.due_date', - 'invoices.due_date as valid_until', + 'invoices.invoice_date', + 'invoices.due_date as due_date_sql', + DB::raw("CONCAT(invoices.invoice_date, invoices.created_at) as date"), + DB::raw("CONCAT(invoices.due_date, invoices.created_at) as due_date"), + DB::raw("CONCAT(invoices.due_date, invoices.created_at) as valid_until"), 'invoice_statuses.name as status', 'invoice_statuses.name as invoice_status_name', 'contacts.first_name', @@ -136,6 +139,7 @@ class InvoiceRepository extends BaseRepository $query = DB::table('invoices') ->join('accounts', 'accounts.id', '=', 'invoices.account_id') ->join('clients', 'clients.id', '=', 'invoices.client_id') + ->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id') ->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id') ->join('contacts', 'contacts.client_id', '=', 'clients.id') ->where('invoices.account_id', '=', $accountId) @@ -151,16 +155,25 @@ class InvoiceRepository extends BaseRepository 'invoices.public_id', 'invoices.amount', 'frequencies.name as frequency', - 'invoices.start_date', - 'invoices.end_date', - 'invoices.last_sent_date', - 'invoices.last_sent_date as last_sent', + 'invoices.start_date as start_date_sql', + 'invoices.end_date as end_date_sql', + 'invoices.last_sent_date as last_sent_date_sql', + DB::raw("CONCAT(invoices.start_date, invoices.created_at) as start_date"), + DB::raw("CONCAT(invoices.end_date, invoices.created_at) as end_date"), + DB::raw("CONCAT(invoices.last_sent_date, invoices.created_at) as last_sent"), 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'invoices.deleted_at', 'invoices.is_deleted', - 'invoices.user_id' + 'invoices.user_id', + 'invoice_statuses.name as invoice_status_name', + 'invoices.invoice_status_id', + 'invoices.balance', + 'invoices.due_date', + 'invoices.due_date as due_date_sql', + 'invoices.is_recurring', + 'invoices.quote_invoice_id' ); if ($clientPublicId) { @@ -312,7 +325,7 @@ class InvoiceRepository extends BaseRepository public function save(array $data, Invoice $invoice = null) { /** @var Account $account */ - $account = \Auth::user()->account; + $account = $invoice ? $invoice->account : \Auth::user()->account; $publicId = isset($data['public_id']) ? $data['public_id'] : false; $isNew = ! $publicId || $publicId == '-1'; @@ -329,6 +342,8 @@ class InvoiceRepository extends BaseRepository } $invoice = $account->createInvoice($entityType, $data['client_id']); $invoice->invoice_date = date_create()->format('Y-m-d'); + $invoice->custom_taxes1 = $account->custom_invoice_taxes1 ?: false; + $invoice->custom_taxes2 = $account->custom_invoice_taxes2 ?: false; if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) { $invoice->has_tasks = true; } @@ -505,15 +520,9 @@ class InvoiceRepository extends BaseRepository if (isset($data['custom_value1'])) { $invoice->custom_value1 = round($data['custom_value1'], 2); - if ($isNew) { - $invoice->custom_taxes1 = $account->custom_invoice_taxes1 ?: false; - } } if (isset($data['custom_value2'])) { $invoice->custom_value2 = round($data['custom_value2'], 2); - if ($isNew) { - $invoice->custom_taxes2 = $account->custom_invoice_taxes2 ?: false; - } } if (isset($data['custom_text_value1'])) { @@ -618,28 +627,35 @@ class InvoiceRepository extends BaseRepository } } - if ($productKey = trim($item['product_key'])) { - if (\Auth::user()->account->update_products && ! $invoice->has_tasks && ! $invoice->has_expenses) { - $product = Product::findProductByKey($productKey); - if (! $product) { - if (Auth::user()->can('create', ENTITY_PRODUCT)) { - $product = Product::createNew(); - $product->product_key = trim($item['product_key']); - } else { - $product = null; + if (Auth::check()) { + if ($productKey = trim($item['product_key'])) { + if ($account->update_products + && ! $invoice->has_tasks + && ! $invoice->has_expenses + && $productKey != trans('texts.surcharge') + ) { + $product = Product::findProductByKey($productKey); + if (! $product) { + if (Auth::user()->can('create', ENTITY_PRODUCT)) { + $product = Product::createNew(); + $product->product_key = trim($item['product_key']); + } else { + $product = null; + } + } + if ($product && (Auth::user()->can('edit', $product))) { + $product->notes = ($task || $expense) ? '' : $item['notes']; + $product->cost = $expense ? 0 : $item['cost']; + $product->custom_value1 = isset($item['custom_value1']) ? $item['custom_value1'] : null; + $product->custom_value2 = isset($item['custom_value2']) ? $item['custom_value2'] : null; + $product->save(); } - } - if ($product && (Auth::user()->can('edit', $product))) { - $product->notes = ($task || $expense) ? '' : $item['notes']; - $product->cost = $expense ? 0 : $item['cost']; - $product->custom_value1 = isset($item['custom_value1']) ? $item['custom_value1'] : null; - $product->custom_value2 = isset($item['custom_value2']) ? $item['custom_value2'] : null; - $product->save(); } } } - $invoiceItem = InvoiceItem::createNew(); + $invoiceItem = InvoiceItem::createNew($invoice); + $invoiceItem->fill($item); $invoiceItem->product_id = isset($product) ? $product->id : null; $invoiceItem->product_key = isset($item['product_key']) ? (trim($invoice->is_recurring ? $item['product_key'] : Utils::processVariables($item['product_key']))) : ''; $invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes'])); @@ -659,11 +675,58 @@ class InvoiceRepository extends BaseRepository $item['tax_rate1'] = $item['tax_rate']; } + // provide backwards compatability + if (! isset($item['invoice_item_type_id']) && in_array($invoiceItem->notes, [trans('texts.online_payment_surcharge'), trans('texts.online_payment_discount')])) { + $invoiceItem->invoice_item_type_id = $invoice->balance > 0 ? INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE : INVOICE_ITEM_TYPE_PAID_GATEWAY_FEE; + } + $invoiceItem->fill($item); $invoice->invoice_items()->save($invoiceItem); } + if (Auth::check()) { + $invoice = $this->saveInvitations($invoice); + } + + return $invoice; + } + + private function saveInvitations($invoice) + { + $client = $invoice->client; + $client->load('contacts'); + $sendInvoiceIds = []; + + foreach ($client->contacts as $contact) { + if ($contact->send_invoice) { + $sendInvoiceIds[] = $contact->id; + } + } + + // if no contacts are selected auto-select the first to enusre there's an invitation + if (! count($sendInvoiceIds)) { + $sendInvoiceIds[] = $client->contacts[0]->id; + } + + foreach ($client->contacts as $contact) { + $invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first(); + + if (in_array($contact->id, $sendInvoiceIds) && ! $invitation) { + $invitation = Invitation::createNew($invoice); + $invitation->invoice_id = $invoice->id; + $invitation->contact_id = $contact->id; + $invitation->invitation_key = strtolower(str_random(RANDOM_KEY_LENGTH)); + $invitation->save(); + } elseif (! in_array($contact->id, $sendInvoiceIds) && $invitation) { + $invitation->delete(); + } + } + + if ($invoice->is_public && ! $invoice->areInvitationsSent()) { + $invoice->markInvitationsSent(); + } + return $invoice; } @@ -779,7 +842,7 @@ class InvoiceRepository extends BaseRepository foreach ($invoice->invitations as $invitation) { $cloneInvitation = Invitation::createNew($invoice); $cloneInvitation->contact_id = $invitation->contact_id; - $cloneInvitation->invitation_key = str_random(RANDOM_KEY_LENGTH); + $cloneInvitation->invitation_key = strtolower(str_random(RANDOM_KEY_LENGTH)); $clone->invitations()->save($cloneInvitation); } @@ -836,6 +899,7 @@ class InvoiceRepository extends BaseRepository { // check for extra params at end of value (from website feature) list($invitationKey) = explode('&', $invitationKey); + $invitationKey = substr($invitationKey, 0, RANDOM_KEY_LENGTH); /** @var \App\Models\Invitation $invitation */ $invitation = Invitation::where('invitation_key', '=', $invitationKey)->first(); @@ -959,7 +1023,7 @@ class InvoiceRepository extends BaseRepository foreach ($recurInvoice->invitations as $recurInvitation) { $invitation = Invitation::createNew($recurInvitation); $invitation->contact_id = $recurInvitation->contact_id; - $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); + $invitation->invitation_key = strtolower(str_random(RANDOM_KEY_LENGTH)); $invoice->invitations()->save($invitation); } @@ -1005,4 +1069,57 @@ class InvoiceRepository extends BaseRepository return $invoices; } + + public function clearGatewayFee($invoice) + { + $account = $invoice->account; + + if (! $invoice->relationLoaded('invoice_items')) { + $invoice->load('invoice_items'); + } + + $data = $invoice->toArray(); + foreach ($data['invoice_items'] as $key => $item) { + if ($item['invoice_item_type_id'] == INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE) { + unset($data['invoice_items'][$key]); + $this->save($data, $invoice); + $invoice->load('invoice_items'); + break; + } + } + } + + public function setGatewayFee($invoice, $gatewayTypeId) + { + $account = $invoice->account; + + if (! $account->gateway_fee_enabled) { + return; + } + + $settings = $account->getGatewaySettings($gatewayTypeId); + $this->clearGatewayFee($invoice); + + if (! $settings) { + return; + } + + $data = $invoice->toArray(); + $fee = $invoice->calcGatewayFee($gatewayTypeId); + + $item = []; + $item['product_key'] = $fee >= 0 ? trans('texts.surcharge') : trans('texts.discount'); + $item['notes'] = $fee >= 0 ? trans('texts.online_payment_surcharge') : trans('texts.online_payment_discount'); + $item['qty'] = 1; + $item['cost'] = $fee; + $item['tax_rate1'] = $settings->fee_tax_rate1; + $item['tax_name1'] = $settings->fee_tax_name1; + $item['tax_rate2'] = $settings->fee_tax_rate2; + $item['tax_name2'] = $settings->fee_tax_name2; + $item['invoice_item_type_id'] = INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE; + $data['invoice_items'][] = $item; + + $this->save($data, $invoice); + $invoice->load('invoice_items'); + } } diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 84b0c440b..6d472485a 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -7,6 +7,7 @@ use App\Models\Invoice; use App\Models\Payment; use DB; use Utils; +use Auth; class PaymentRepository extends BaseRepository { @@ -38,6 +39,7 @@ class PaymentRepository extends BaseRepository 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'payments.amount', + DB::raw("CONCAT(payments.payment_date, payments.created_at) as date"), 'payments.payment_date', 'payments.payment_status_id', 'payments.payment_type_id', @@ -160,6 +162,10 @@ class PaymentRepository extends BaseRepository } } else { $payment = Payment::createNew(); + + if (Auth::check()) { + $payment->payment_type_id = Auth::user()->account->payment_type_id; + } } if ($payment->is_deleted) { diff --git a/app/Ninja/Repositories/VendorRepository.php b/app/Ninja/Repositories/VendorRepository.php index 0b5364841..765e9b9f3 100644 --- a/app/Ninja/Repositories/VendorRepository.php +++ b/app/Ninja/Repositories/VendorRepository.php @@ -39,7 +39,6 @@ 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', @@ -85,9 +84,25 @@ class VendorRepository extends BaseRepository $vendor->save(); $first = true; - $vendorcontacts = isset($data['vendor_contact']) ? [$data['vendor_contact']] : $data['vendor_contacts']; + if (isset($data['vendor_contact'])) { + $vendorcontacts = [$data['vendor_contact']]; + } elseif (isset($data['vendor_contacts'])) { + $vendorcontacts = $data['vendor_contacts']; + } else { + $vendorcontacts = [[]]; + } + $vendorcontactIds = []; + // If the primary is set ensure it's listed first + usort($vendorcontacts, function ($left, $right) { + if (isset($right['is_primary']) && isset($left['is_primary'])) { + return $right['is_primary'] - $left['is_primary']; + } else { + return 0; + } + }); + foreach ($vendorcontacts as $vendorcontact) { $vendorcontact = $vendor->addVendorContact($vendorcontact, $first); $vendorcontactIds[] = $vendorcontact->public_id; diff --git a/app/Ninja/Transformers/AccountEmailSettingsTransformer.php b/app/Ninja/Transformers/AccountEmailSettingsTransformer.php new file mode 100644 index 000000000..6239c8452 --- /dev/null +++ b/app/Ninja/Transformers/AccountEmailSettingsTransformer.php @@ -0,0 +1,48 @@ + $settings->reply_to_email, + 'bcc_email' => $settings->bcc_email, + 'email_subject_invoice' => $settings->email_subject_invoice, + 'email_subject_quote' => $settings->email_subject_quote, + 'email_subject_payment' => $settings->email_subject_payment, + 'email_template_invoice' => $settings->email_template_invoice, + 'email_template_quote' => $settings->email_template_quote, + 'email_template_payment' => $settings->email_template_payment, + 'email_subject_reminder1' => $settings->email_subject_reminder1, + 'email_subject_reminder2' => $settings->email_subject_reminder2, + 'email_subject_reminder3' => $settings->email_subject_reminder3, + 'email_template_reminder1' => $settings->email_template_reminder1, + 'email_template_reminder2' => $settings->email_template_reminder2, + 'email_template_reminder3' => $settings->email_template_reminder3, + ]; + } +} diff --git a/app/Ninja/Transformers/AccountTransformer.php b/app/Ninja/Transformers/AccountTransformer.php index 2dcecfe00..7c11d68b8 100644 --- a/app/Ninja/Transformers/AccountTransformer.php +++ b/app/Ninja/Transformers/AccountTransformer.php @@ -18,6 +18,7 @@ class AccountTransformer extends EntityTransformer 'tax_rates', 'expense_categories', 'projects', + 'account_email_settings', ]; /** @@ -29,6 +30,18 @@ class AccountTransformer extends EntityTransformer 'payments', ]; + /** + * @param Account $account + * + * @return \League\Fractal\Resource\Collection + */ + public function includeAccountEmailSettings(Account $account) + { + $transformer = new AccountEmailSettingsTransformer($account, $this->serializer); + + return $this->includeItem($account->account_email_settings, $transformer, 'account_email_settings'); + } + /** * @param Account $account * @@ -136,6 +149,7 @@ class AccountTransformer extends EntityTransformer { return [ 'account_key' => $account->account_key, + 'logo' => $account->logo, 'name' => $account->present()->name, 'id_number' => $account->id_number, 'currency_id' => (int) $account->currency_id, @@ -172,7 +186,86 @@ class AccountTransformer extends EntityTransformer 'custom_label2' => $account->custom_label2, 'custom_value1' => $account->custom_value1, 'custom_value2' => $account->custom_value2, - 'logo' => $account->logo, + 'primary_color' => $account->primary_color, + 'secondary_color' => $account->secondary_color, + 'custom_client_label1' => $account->custom_client_label1, + 'custom_client_label2' => $account->custom_client_label2, + 'hide_quantity' => (bool) $account->hide_quantity, + 'hide_paid_to_date' => (bool) $account->hide_paid_to_date, + 'invoice_number_prefix' => $account->invoice_number_prefix, + 'invoice_number_counter' => $account->invoice_number_counter, + 'quote_number_prefix' => $account->quote_number_prefix, + 'quote_number_counter' => $account->quote_number_counter, + 'share_counter' => (bool) $account->share_counter, + 'token_billing_type_id' => (int) $account->token_billing_type_id, + 'invoice_footer' => $account->invoice_footer, + 'pdf_email_attachment' => (bool) $account->pdf_email_attachment, + 'font_size' => $account->font_size, + 'invoice_labels' => $account->invoice_labels, + 'custom_design' => $account->custom_design, + 'show_item_taxes' => (bool) $account->show_item_taxes, + 'military_time' => (bool) $account->military_time, + 'enable_reminder1' => $account->enable_reminder1, + 'enable_reminder2' => $account->enable_reminder2, + 'enable_reminder3' => $account->enable_reminder3, + 'num_days_reminder1' => $account->num_days_reminder1, + 'num_days_reminder2' => $account->num_days_reminder2, + 'num_days_reminder3' => $account->num_days_reminder3, + 'custom_invoice_text_label1' => $account->custom_invoice_text_label1, + 'custom_invoice_text_label2' => $account->custom_invoice_text_label2, + 'default_tax_rate_id' => $account->default_tax_rate_id, + 'recurring_hour' => $account->recurring_hour, + 'invoice_number_pattern' => $account->invoice_number_pattern, + 'quote_number_pattern' => $account->quote_number_pattern, + 'quote_terms' => $account->quote_terms, + 'email_design_id' => $account->email_design_id, + 'enable_email_markup' => (bool) $account->enable_email_markup, + 'website' => $account->website, + 'direction_reminder1' => (int) $account->direction_reminder1, + 'direction_reminder2' => (int) $account->direction_reminder2, + 'direction_reminder3' => (int) $account->direction_reminder3, + 'field_reminder1' => (int) $account->field_reminder1, + 'field_reminder2' => (int) $account->field_reminder2, + 'field_reminder3' => (int) $account->field_reminder3, + 'header_font_id' => (int) $account->header_font_id, + 'body_font_id' => (int) $account->body_font_id, + 'auto_convert_quote' => (bool) $account->auto_convert_quote, + 'all_pages_footer' => (bool) $account->all_pages_footer, + 'all_pages_header' => (bool) $account->all_pages_header, + 'show_currency_code' => (bool) $account->show_currency_code, + 'enable_portal_password' => (bool) $account->enable_portal_password, + 'send_portal_password' => (bool) $account->send_portal_password, + 'custom_invoice_item_label1' => $account->custom_invoice_item_label1, + 'custom_invoice_item_label2' => $account->custom_invoice_item_label2, + 'recurring_invoice_number_prefix' => $account->recurring_invoice_number_prefix, + 'enable_client_portal' => (bool) $account->enable_client_portal, + 'invoice_fields' => $account->invoice_fields, + 'invoice_embed_documents' => (bool) $account->invoice_embed_documents, + 'document_email_attachment' => (bool) $account->document_email_attachment, + 'enable_client_portal_dashboard' => (bool) $account->enable_client_portal_dashboard, + 'page_size' => $account->page_size, + 'live_preview' => (bool) $account->live_preview, + 'invoice_number_padding' => (int) $account->invoice_number_padding, + 'enable_second_tax_rate' => (bool) $account->enable_second_tax_rate, + 'auto_bill_on_due_date' => (bool) $account->auto_bill_on_due_date, + 'start_of_week' => $account->start_of_week, + 'enable_buy_now_buttons' => (bool) $account->enable_buy_now_buttons, + 'include_item_taxes_inline' => (bool) $account->include_item_taxes_inline, + 'financial_year_start' => $account->financial_year_start, + 'enabled_modules' => (int) $account->enabled_modules, + 'enabled_dashboard_sections' => (int) $account->enabled_dashboard_sections, + 'show_accept_invoice_terms' => (bool) $account->show_accept_invoice_terms, + 'show_accept_quote_terms' => (bool) $account->show_accept_quote_terms, + 'require_invoice_signature' => (bool) $account->require_invoice_signature, + 'require_quote_signature' => (bool) $account->require_quote_signature, + 'client_number_prefix' => $account->client_number_prefix, + 'client_number_counter' => (int) $account->client_number_counter, + 'client_number_pattern' => $account->client_number_pattern, + 'payment_terms' => (int) $account->payment_terms, + 'reset_counter_frequency_id' => (int) $account->reset_counter_frequency_id, + 'payment_type_id' => (int) $account->payment_type_id, + 'gateway_fee_enabled' => (bool) $account->gateway_fee_enabled, + 'reset_counter_date' => $account->reset_counter_date, ]; } } diff --git a/app/Ninja/Transformers/ClientTransformer.php b/app/Ninja/Transformers/ClientTransformer.php index 2986eb73c..4f12d9c0d 100644 --- a/app/Ninja/Transformers/ClientTransformer.php +++ b/app/Ninja/Transformers/ClientTransformer.php @@ -11,12 +11,12 @@ class ClientTransformer extends EntityTransformer { /** * @SWG\Property(property="id", type="integer", example=1, readOnly=true) - * @SWG\Property(property="balance", type="float", example=10, readOnly=true) - * @SWG\Property(property="paid_to_date", type="float", example=10, readOnly=true) + * @SWG\Property(property="balance", type="number", format="float", example=10, readOnly=true) + * @SWG\Property(property="paid_to_date", type="number", format="float", example=10, readOnly=true) * @SWG\Property(property="user_id", type="integer", example=1) * @SWG\Property(property="account_key", type="string", example="123456") - * @SWG\Property(property="updated_at", type="timestamp", example="") - * @SWG\Property(property="archived_at", type="timestamp", example="1451160233") + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) * @SWG\Property(property="address1", type="string", example="10 Main St.") * @SWG\Property(property="address2", type="string", example="1st Floor") * @SWG\Property(property="city", type="string", example="New York") @@ -25,12 +25,12 @@ class ClientTransformer extends EntityTransformer * @SWG\Property(property="country_id", type="integer", example=840) * @SWG\Property(property="work_phone", type="string", example="(212) 555-1212") * @SWG\Property(property="private_notes", type="string", example="Notes...") - * @SWG\Property(property="last_login", type="date-time", example="2016-01-01 12:10:00") + * @SWG\Property(property="last_login", type="string", format="date-time", example="2016-01-01 12:10:00") * @SWG\Property(property="website", type="string", example="http://www.example.com") * @SWG\Property(property="industry_id", type="integer", example=1) * @SWG\Property(property="size_id", type="integer", example=1) * @SWG\Property(property="is_deleted", type="boolean", example=false) - * @SWG\Property(property="payment_terms", type="", example=30) + * @SWG\Property(property="payment_terms", type="integer", example=30) * @SWG\Property(property="custom_value1", type="string", example="Value") * @SWG\Property(property="custom_value2", type="string", example="Value") * @SWG\Property(property="vat_number", type="string", example="123456") @@ -131,6 +131,8 @@ class ClientTransformer extends EntityTransformer 'currency_id' => (int) $client->currency_id, 'custom_value1' => $client->custom_value1, 'custom_value2' => $client->custom_value2, + 'invoice_number_counter' => (int) $client->invoice_number_counter, + 'quote_number_counter' => (int) $client->quote_number_counter, ]); } } diff --git a/app/Ninja/Transformers/ContactTransformer.php b/app/Ninja/Transformers/ContactTransformer.php index 4fd72ffde..4e6ad4a63 100644 --- a/app/Ninja/Transformers/ContactTransformer.php +++ b/app/Ninja/Transformers/ContactTransformer.php @@ -6,6 +6,8 @@ use App\Models\Contact; /** * Class ContactTransformer. + * + * @SWG\Definition(definition="Contact", @SWG\Xml(name="Contact")) */ class ContactTransformer extends EntityTransformer { @@ -13,6 +15,17 @@ class ContactTransformer extends EntityTransformer * @param Contact $contact * * @return array + * + * @SWG\Property(property="id", type="integer", example=1, readOnly=true) + * @SWG\Property(property="first_name", type="string", example="John") + * @SWG\Property(property="last_name", type="string", example="Doe") + * @SWG\Property(property="email", type="string", example="john.doe@company.com") + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="is_primary", type="boolean", example=false) + * @SWG\Property(property="phone", type="string", example="(212) 555-1212") + * @SWG\Property(property="last_login", type="string", format="date-time", example="2016-01-01 12:10:00") + * @SWG\Property(property="send_invoice", type="boolean", example=false) */ public function transform(Contact $contact) { diff --git a/app/Ninja/Transformers/CreditTransformer.php b/app/Ninja/Transformers/CreditTransformer.php index 752e3dbec..f36bef957 100644 --- a/app/Ninja/Transformers/CreditTransformer.php +++ b/app/Ninja/Transformers/CreditTransformer.php @@ -26,6 +26,7 @@ class CreditTransformer extends EntityTransformer 'credit_date' => $credit->credit_date, 'credit_number' => $credit->credit_number, 'private_notes' => $credit->private_notes, + 'public_notes' => $credit->public_notes, ]); } } diff --git a/app/Ninja/Transformers/DocumentTransformer.php b/app/Ninja/Transformers/DocumentTransformer.php index 99ee24a6d..f48a64677 100644 --- a/app/Ninja/Transformers/DocumentTransformer.php +++ b/app/Ninja/Transformers/DocumentTransformer.php @@ -14,8 +14,8 @@ class DocumentTransformer extends EntityTransformer * @SWG\Property(property="name", type="string", example="Test") * @SWG\Property(property="type", type="string", example="CSV") * @SWG\Property(property="invoice_id", type="integer", example=1) - * @SWG\Property(property="updated_at", type="timestamp", example=1451160233, readOnly=true) - * @SWG\Property(property="archived_at", type="timestamp", example=1451160233, readOnly=true) + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) */ public function transform(Document $document) { diff --git a/app/Ninja/Transformers/ExpenseCategoryTransformer.php b/app/Ninja/Transformers/ExpenseCategoryTransformer.php index 16fd8d4e8..a2cea573b 100644 --- a/app/Ninja/Transformers/ExpenseCategoryTransformer.php +++ b/app/Ninja/Transformers/ExpenseCategoryTransformer.php @@ -12,8 +12,8 @@ class ExpenseCategoryTransformer extends EntityTransformer /** * @SWG\Property(property="id", type="integer", example=1, readOnly=true) * @SWG\Property(property="name", type="string", example="Sample") - * @SWG\Property(property="updated_at", type="timestamp", example=1451160233, readOnly=true) - * @SWG\Property(property="archived_at", type="timestamp", example=1451160233, readOnly=true) + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) */ public function transform(ExpenseCategory $expenseCategory) { diff --git a/app/Ninja/Transformers/ExpenseTransformer.php b/app/Ninja/Transformers/ExpenseTransformer.php index 0937f25f4..5b6275583 100644 --- a/app/Ninja/Transformers/ExpenseTransformer.php +++ b/app/Ninja/Transformers/ExpenseTransformer.php @@ -4,8 +4,36 @@ namespace App\Ninja\Transformers; use App\Models\Expense; +/** + * @SWG\Definition(definition="Expense", @SWG\Xml(name="Expense")) + */ class ExpenseTransformer extends EntityTransformer { + /** + * @SWG\Property(property="id", type="integer", example=1, readOnly=true) + * @SWG\Property(property="private_notes", type="string", example="Notes...") + * @SWG\Property(property="public_notes", type="string", example="Notes...") + * @SWG\Property(property="should_be_invoiced", type="boolean", example=false) + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="transaction_id", type="integer", example=1) + * @SWG\Property(property="bank_id", type="integer", example=1) + * @SWG\Property(property="expense_currency_id", type="integer", example=1) + * @SWG\Property(property="expense_category_id", type="integer", example=1) + * @SWG\Property(property="amount", type="number", format="float,", example="17.5") + * @SWG\Property(property="expense_date", type="string", format="date", example="2016-01-01") + * @SWG\Property(property="exchange_rate", type="number", format="float", example="") + * @SWG\Property(property="invoice_currency_id", type="integer", example=1) + * @SWG\Property(property="is_deleted", type="boolean", example=false) + * @SWG\Property(property="tax_name1", type="string", example="VAT") + * @SWG\Property(property="tax_name2", type="string", example="Upkeep") + * @SWG\Property(property="tax_rate1", type="number", format="float", example="17.5") + * @SWG\Property(property="tax_rate2", type="number", format="float", example="30.0") + * @SWG\Property(property="client_id", type="integer", example=1) + * @SWG\Property(property="invoice_id", type="integer", example=1) + * @SWG\Property(property="vendor_id", type="integer", example=1) + */ + public function __construct($account = null, $serializer = null, $client = null) { parent::__construct($account, $serializer); diff --git a/app/Ninja/Transformers/InvoiceItemTransformer.php b/app/Ninja/Transformers/InvoiceItemTransformer.php index c4d8b44d1..a23ba7843 100644 --- a/app/Ninja/Transformers/InvoiceItemTransformer.php +++ b/app/Ninja/Transformers/InvoiceItemTransformer.php @@ -20,6 +20,7 @@ class InvoiceItemTransformer extends EntityTransformer 'tax_rate1' => (float) $item->tax_rate1, 'tax_name2' => $item->tax_name2 ? $item->tax_name2 : '', 'tax_rate2' => (float) $item->tax_rate2, + 'invoice_item_type_id' => (int) $item->invoice_item_type_id, ]); } } diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index 446e6492a..7935fce89 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -13,8 +13,8 @@ class InvoiceTransformer extends EntityTransformer { /** * @SWG\Property(property="id", type="integer", example=1, readOnly=true) - * @SWG\Property(property="amount", type="float", example=10, readOnly=true) - * @SWG\Property(property="balance", type="float", example=10, readOnly=true) + * @SWG\Property(property="amount", type="number", format="float", example=10, readOnly=true) + * @SWG\Property(property="balance", type="number", format="float", example=10, readOnly=true) * @SWG\Property(property="client_id", type="integer", example=1) * @SWG\Property(property="invoice_number", type="string", example="0001") * @SWG\Property(property="invoice_status_id", type="integer", example=1) diff --git a/app/Ninja/Transformers/PaymentTransformer.php b/app/Ninja/Transformers/PaymentTransformer.php index 81e014de4..d5278cc19 100644 --- a/app/Ninja/Transformers/PaymentTransformer.php +++ b/app/Ninja/Transformers/PaymentTransformer.php @@ -14,7 +14,7 @@ class PaymentTransformer extends EntityTransformer { /** * @SWG\Property(property="id", type="integer", example=1, readOnly=true) - * @SWG\Property(property="amount", type="float", example=10, readOnly=true) + * @SWG\Property(property="amount", type="number", format="float", example=10, readOnly=true) * @SWG\Property(property="invoice_id", type="integer", example=1) */ protected $defaultIncludes = []; diff --git a/app/Ninja/Transformers/ProductTransformer.php b/app/Ninja/Transformers/ProductTransformer.php index fd7e47c03..241e3e992 100644 --- a/app/Ninja/Transformers/ProductTransformer.php +++ b/app/Ninja/Transformers/ProductTransformer.php @@ -13,11 +13,11 @@ class ProductTransformer extends EntityTransformer * @SWG\Property(property="id", type="integer", example=1, readOnly=true) * @SWG\Property(property="product_key", type="string", example="Item") * @SWG\Property(property="notes", type="string", example="Notes...") - * @SWG\Property(property="cost", type="float", example=10.00) - * @SWG\Property(property="qty", type="float", example=1) + * @SWG\Property(property="cost", type="number", format="float", example=10.00) + * @SWG\Property(property="qty", type="number", format="float", example=1) * @SWG\Property(property="default_tax_rate_id", type="integer", example=1) - * @SWG\Property(property="updated_at", type="timestamp", example=1451160233, readOnly=true) - * @SWG\Property(property="archived_at", type="timestamp", example=1451160233, readOnly=true) + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) */ public function transform(Product $product) { diff --git a/app/Ninja/Transformers/ProjectTransformer.php b/app/Ninja/Transformers/ProjectTransformer.php index ae2a1acb9..6e6dbcb2b 100644 --- a/app/Ninja/Transformers/ProjectTransformer.php +++ b/app/Ninja/Transformers/ProjectTransformer.php @@ -13,8 +13,8 @@ class ProjectTransformer extends EntityTransformer * @SWG\Property(property="id", type="integer", example=1, readOnly=true) * @SWG\Property(property="name", type="string", example="Sample") * @SWG\Property(property="client_id", type="integer", example=1) - * @SWG\Property(property="updated_at", type="timestamp", example=1451160233, readOnly=true) - * @SWG\Property(property="archived_at", type="timestamp", example=1451160233, readOnly=true) + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) * @SWG\Property(property="is_deleted", type="boolean", example=false, readOnly=true) */ public function transform(Project $project) diff --git a/app/Ninja/Transformers/TaskTransformer.php b/app/Ninja/Transformers/TaskTransformer.php index d7c695588..823b33864 100644 --- a/app/Ninja/Transformers/TaskTransformer.php +++ b/app/Ninja/Transformers/TaskTransformer.php @@ -13,7 +13,7 @@ class TaskTransformer extends EntityTransformer { /** * @SWG\Property(property="id", type="integer", example=1, readOnly=true) - * @SWG\Property(property="amount", type="float", example=10, readOnly=true) + * @SWG\Property(property="amount", type="number", format="float", example=10, readOnly=true) * @SWG\Property(property="invoice_id", type="integer", example=1) */ protected $availableIncludes = [ diff --git a/app/Ninja/Transformers/TaxRateTransformer.php b/app/Ninja/Transformers/TaxRateTransformer.php index 8e3bf57e6..ba50a4a53 100644 --- a/app/Ninja/Transformers/TaxRateTransformer.php +++ b/app/Ninja/Transformers/TaxRateTransformer.php @@ -13,10 +13,10 @@ class TaxRateTransformer extends EntityTransformer * @SWG\Property(property="id", type="integer", example=1, readOnly=true) * @SWG\Property(property="name", type="string", example="GST") * @SWG\Property(property="account_key", type="string", example="asimplestring", readOnly=true) - * @SWG\Property(property="rate", type="float", example=17.5) + * @SWG\Property(property="rate", type="number", format="float", example=17.5) * @SWG\Property(property="is_inclusive", type="boolean", example=false) - * @SWG\Property(property="updated_at", type="date-time", example="2016-01-01 12:10:00") - * @SWG\Property(property="archived_at", type="date-time", example="2016-01-01 12:10:00") + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) */ public function transform(TaxRate $taxRate) { diff --git a/app/Ninja/Transformers/UserTransformer.php b/app/Ninja/Transformers/UserTransformer.php index af2de116f..2bcc87910 100644 --- a/app/Ninja/Transformers/UserTransformer.php +++ b/app/Ninja/Transformers/UserTransformer.php @@ -5,8 +5,31 @@ namespace App\Ninja\Transformers; use App\Models\Account; use App\Models\User; +/** + * @SWG\Definition(definition="User", @SWG\Xml(name="User")) + */ class UserTransformer extends EntityTransformer { + /** + * @SWG\Property(property="id", type="integer", example=1, readOnly=true) + * @SWG\Property(property="first_name", type="string", example="John") + * @SWG\Property(property="last_name", type="string", example="Doe") + * @SWG\Property(property="email", type="string", example="johndoe@isp.com") + * @SWG\Property(property="account_key", type="string", example="123456") + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="deleted_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="phone", type="string", example="(212) 555-1212") + * @SWG\Property(property="registered", type="boolean", example=false) + * @SWG\Property(property="confirmed", type="boolean", example=false) + * @SWG\Property(property="oauth_user_id", type="integer", example=1) + * @SWG\Property(property="oauth_provider_id", type="integer", example=1) + * @SWG\Property(property="notify_sent", type="boolean", example=false) + * @SWG\Property(property="notify_viewed", type="boolean", example=false) + * @SWG\Property(property="notify_paid", type="boolean", example=false) + * @SWG\Property(property="notify_approved", type="boolean", example=false) + * @SWG\Property(property="is_admin", type="boolean", example=false) + * @SWG\Property(property="permissions", type="integer", example=1) + */ public function transform(User $user) { return [ diff --git a/app/Ninja/Transformers/VendorTransformer.php b/app/Ninja/Transformers/VendorTransformer.php index ed44cd4c4..8c4a4d38b 100644 --- a/app/Ninja/Transformers/VendorTransformer.php +++ b/app/Ninja/Transformers/VendorTransformer.php @@ -13,12 +13,12 @@ class VendorTransformer extends EntityTransformer { /** * @SWG\Property(property="id", type="integer", example=1, readOnly=true) - * @SWG\Property(property="balance", type="float", example=10, readOnly=true) - * @SWG\Property(property="paid_to_date", type="float", example=10, readOnly=true) + * @SWG\Property(property="balance", type="number", format="float", example=10, readOnly=true) + * @SWG\Property(property="paid_to_date", type="number", format="float", example=10, readOnly=true) * @SWG\Property(property="user_id", type="integer", example=1) * @SWG\Property(property="account_key", type="string", example="123456") - * @SWG\Property(property="updated_at", type="timestamp", example="") - * @SWG\Property(property="archived_at", type="timestamp", example="1451160233") + * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true) + * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true) * @SWG\Property(property="address1", type="string", example="10 Main St.") * @SWG\Property(property="address2", type="string", example="1st Floor") * @SWG\Property(property="city", type="string", example="New York") @@ -27,7 +27,7 @@ class VendorTransformer extends EntityTransformer * @SWG\Property(property="country_id", type="integer", example=840) * @SWG\Property(property="work_phone", type="string", example="(212) 555-1212") * @SWG\Property(property="private_notes", type="string", example="Notes...") - * @SWG\Property(property="last_login", type="date-time", example="2016-01-01 12:10:00") + * @SWG\Property(property="last_login", type="string", format="date-time", example="2016-01-01 12:10:00") * @SWG\Property(property="website", type="string", example="http://www.example.com") * @SWG\Property(property="is_deleted", type="boolean", example=false) * @SWG\Property(property="vat_number", type="string", example="123456") @@ -36,12 +36,12 @@ class VendorTransformer extends EntityTransformer protected $defaultIncludes = [ 'vendor_contacts', ]; - + protected $availableIncludes = [ 'invoices', //'expenses', ]; - + public function includeVendorContacts(Vendor $vendor) { $transformer = new VendorContactTransformer($this->account, $this->serializer); diff --git a/app/Policies/ContactPolicy.php b/app/Policies/ContactPolicy.php new file mode 100644 index 000000000..2aab428a0 --- /dev/null +++ b/app/Policies/ContactPolicy.php @@ -0,0 +1,7 @@ + \App\Policies\ClientPolicy::class, + \App\Models\Contact::class => \App\Policies\ContactPolicy::class, \App\Models\Credit::class => \App\Policies\CreditPolicy::class, \App\Models\Document::class => \App\Policies\DocumentPolicy::class, \App\Models\Expense::class => \App\Policies\ExpensePolicy::class, diff --git a/app/Services/ContactService.php b/app/Services/ContactService.php new file mode 100644 index 000000000..3b8bbf979 --- /dev/null +++ b/app/Services/ContactService.php @@ -0,0 +1,51 @@ +contactRepo = $contactRepo; + } + + /** + * @return ContactRepository + */ + protected function getRepo() + { + return $this->contactRepo; + } + + /** + * @param $data + * @param null $contact + * + * @return mixed|null + */ + public function save($data, $contact = null) + { + if (isset($data['client_id']) && $data['client_id']) { + $data['client_id'] = Client::getPrivateId($data['client_id']); + } + + return $this->contactRepo->save($data, $contact); + } + +} diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index f2ade844d..f06241c95 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -7,6 +7,7 @@ use App\Models\EntityModel; use App\Models\Expense; use App\Models\ExpenseCategory; use App\Models\Invoice; +use App\Models\Payment; use App\Models\Product; use App\Models\Vendor; use App\Ninja\Import\BaseTransformer; @@ -144,67 +145,92 @@ class ImportService * * @return array */ - public function importJSON($file) + public function importJSON($file, $includeData, $includeSettings) { $this->initMaps(); - - $file = file_get_contents($file); + $fileName = storage_path() . '/import/' . $file; + $this->checkForFile($fileName); + $file = file_get_contents($fileName); $json = json_decode($file, true); $json = $this->removeIdFields($json); $transformer = new BaseTransformer($this->maps); $this->checkClientCount(count($json['clients'])); - foreach ($json['products'] as $jsonProduct) { - if ($transformer->hasProduct($jsonProduct['product_key'])) { - continue; - } - if (EntityModel::validate($jsonProduct, ENTITY_PRODUCT) === true) { - $product = $this->productRepo->save($jsonProduct); - $this->addProductToMaps($product); - $this->addSuccess($product); - } else { - $this->addFailure(ENTITY_PRODUCT, $jsonProduct); - continue; + if ($includeSettings) { + // remove blank id values + $settings = []; + foreach ($json as $field => $value) { + if (strstr($field, '_id') && ! $value) { + // continue; + } else { + $settings[$field] = $value; + } } + + $account = Auth::user()->account; + $account->fill($settings); + $account->save(); + + $emailSettings = $account->account_email_settings; + $emailSettings->fill($settings['account_email_settings']); + $emailSettings->save(); } - foreach ($json['clients'] as $jsonClient) { - if (EntityModel::validate($jsonClient, ENTITY_CLIENT) === true) { - $client = $this->clientRepo->save($jsonClient); - $this->addClientToMaps($client); - $this->addSuccess($client); - } else { - $this->addFailure(ENTITY_CLIENT, $jsonClient); - continue; + if ($includeData) { + foreach ($json['products'] as $jsonProduct) { + if ($transformer->hasProduct($jsonProduct['product_key'])) { + continue; + } + if (EntityModel::validate($jsonProduct, ENTITY_PRODUCT) === true) { + $product = $this->productRepo->save($jsonProduct); + $this->addProductToMaps($product); + $this->addSuccess($product); + } else { + $this->addFailure(ENTITY_PRODUCT, $jsonProduct); + continue; + } } - foreach ($jsonClient['invoices'] as $jsonInvoice) { - $jsonInvoice['client_id'] = $client->id; - if (EntityModel::validate($jsonInvoice, ENTITY_INVOICE) === true) { - $invoice = $this->invoiceRepo->save($jsonInvoice); - $this->addInvoiceToMaps($invoice); - $this->addSuccess($invoice); + foreach ($json['clients'] as $jsonClient) { + if (EntityModel::validate($jsonClient, ENTITY_CLIENT) === true) { + $client = $this->clientRepo->save($jsonClient); + $this->addClientToMaps($client); + $this->addSuccess($client); } else { - $this->addFailure(ENTITY_INVOICE, $jsonInvoice); + $this->addFailure(ENTITY_CLIENT, $jsonClient); continue; } - foreach ($jsonInvoice['payments'] as $jsonPayment) { - $jsonPayment['invoice_id'] = $invoice->public_id; - if (EntityModel::validate($jsonPayment, ENTITY_PAYMENT) === true) { - $jsonPayment['client_id'] = $client->id; - $jsonPayment['invoice_id'] = $invoice->id; - $payment = $this->paymentRepo->save($jsonPayment); - $this->addSuccess($payment); + foreach ($jsonClient['invoices'] as $jsonInvoice) { + $jsonInvoice['client_id'] = $client->id; + if (EntityModel::validate($jsonInvoice, ENTITY_INVOICE) === true) { + $invoice = $this->invoiceRepo->save($jsonInvoice); + $this->addInvoiceToMaps($invoice); + $this->addSuccess($invoice); } else { - $this->addFailure(ENTITY_PAYMENT, $jsonPayment); + $this->addFailure(ENTITY_INVOICE, $jsonInvoice); continue; } + + foreach ($jsonInvoice['payments'] as $jsonPayment) { + $jsonPayment['invoice_id'] = $invoice->public_id; + if (EntityModel::validate($jsonPayment, ENTITY_PAYMENT) === true) { + $jsonPayment['client_id'] = $client->id; + $jsonPayment['invoice_id'] = $invoice->id; + $payment = $this->paymentRepo->save($jsonPayment); + $this->addSuccess($payment); + } else { + $this->addFailure(ENTITY_PAYMENT, $jsonPayment); + continue; + } + } } } } + @unlink($fileName); + return $this->results; } @@ -261,8 +287,10 @@ class ImportService // Convert the data $row_list = []; + $fileName = storage_path() . '/import/' . $file; + $this->checkForFile($fileName); - Excel::load($file, function ($reader) use ($source, $entityType, &$row_list, &$results) { + Excel::load($fileName, function ($reader) use ($source, $entityType, &$row_list, &$results) { $this->checkData($entityType, count($reader->all())); $reader->each(function ($row) use ($source, $entityType, &$row_list, &$results) { @@ -293,6 +321,8 @@ class ImportService } } + @unlink($fileName); + return $results; } @@ -306,6 +336,13 @@ class ImportService private function transformRow($source, $entityType, $row) { $transformer = $this->getTransformer($source, $entityType, $this->maps); + $resource = $transformer->transform($row); + + if (! $resource) { + return false; + } + + $data = $this->fractal->createData($resource)->toArray(); // Create expesnse category if ($entityType == ENTITY_EXPENSE) { @@ -314,24 +351,18 @@ class ImportService if (! $categoryId) { $category = $this->expenseCategoryRepo->save(['name' => $row->expense_category]); $this->addExpenseCategoryToMaps($category); + $data['expense_category_id'] = $category->id; } } if (! empty($row->vendor) && ($vendorName = trim($row->vendor))) { if (! $transformer->getVendorId($vendorName)) { $vendor = $this->vendorRepo->save(['name' => $vendorName, 'vendor_contact' => []]); $this->addVendorToMaps($vendor); + $data['vendor_id'] = $vendor->id; } } } - $resource = $transformer->transform($row); - - if (! $resource) { - return false; - } - - $data = $this->fractal->createData($resource)->toArray(); - // if the invoice number is blank we'll assign it if ($entityType == ENTITY_INVOICE && ! $data['invoice_number']) { $account = Auth::user()->account; @@ -376,7 +407,7 @@ class ImportService if ($entityType == ENTITY_INVOICE) { $data['is_public'] = true; } - + $entity = $this->{"{$entityType}Repo"}->save($data); // update the entity maps @@ -385,7 +416,7 @@ class ImportService // if the invoice is paid we'll also create a payment record if ($entityType === ENTITY_INVOICE && isset($data['paid']) && $data['paid'] > 0) { - $this->createPayment($source, $row, $data['client_id'], $entity->id); + $this->createPayment($source, $row, $data['client_id'], $entity->id, $entity->public_id); } return $entity; @@ -452,16 +483,21 @@ class ImportService * @param $clientId * @param $invoiceId */ - private function createPayment($source, $data, $clientId, $invoiceId) + private function createPayment($source, $row, $clientId, $invoiceId, $invoicePublicId) { $paymentTransformer = $this->getTransformer($source, ENTITY_PAYMENT, $this->maps); - $data->client_id = $clientId; - $data->invoice_id = $invoiceId; + $row->client_id = $clientId; + $row->invoice_id = $invoiceId; - if ($resource = $paymentTransformer->transform($data)) { + if ($resource = $paymentTransformer->transform($row)) { $data = $this->fractal->createData($resource)->toArray(); - $this->paymentRepo->save($data); + $data['amount'] = min($data['amount'], Utils::parseFloat($row->amount)); + $data['invoice_id'] = $invoicePublicId; + if (Payment::validate($data) === true) { + $data['invoice_id'] = $invoiceId; + $this->paymentRepo->save($data); + } } } @@ -510,29 +546,13 @@ class ImportService */ public function mapFile($entityType, $filename, $columns, $map) { - require_once app_path().'/Includes/parsecsv.lib.php'; - $csv = new parseCSV(); - $csv->heading = false; - $csv->auto($filename); - + $data = $this->getCsvData($filename); $headers = false; $hasHeaders = false; $mapped = []; - if (count($csv->data) > 0) { - $headers = $csv->data[0]; - - // Remove Invoice Ninja headers - if (count($headers) && count($csv->data) > 4) { - $firstCell = $headers[0]; - if (strstr($firstCell, APP_NAME)) { - array_shift($csv->data); // Invoice Ninja... - array_shift($csv->data); // - array_shift($csv->data); // Enitty Type Header - } - $headers = $csv->data[0]; - } - + if (count($data) > 0) { + $headers = $data[0]; foreach ($headers as $title) { if (strpos(strtolower($title), 'name') > 0) { $hasHeaders = true; @@ -554,11 +574,9 @@ class ImportService } } - Session::put("{$entityType}-data", $csv->data); - $data = [ 'entityType' => $entityType, - 'data' => $csv->data, + 'data' => $data, 'headers' => $headers, 'hasHeaders' => $hasHeaders, 'columns' => $columns, @@ -568,6 +586,35 @@ class ImportService return $data; } + private function getCsvData($fileName) + { + require_once app_path().'/Includes/parsecsv.lib.php'; + + $fileName = storage_path() . '/import/' . $fileName; + $this->checkForFile($fileName); + + $csv = new parseCSV(); + $csv->heading = false; + $csv->auto($fileName); + $data = $csv->data; + + if (count($data) > 0) { + $headers = $data[0]; + + // Remove Invoice Ninja headers + if (count($headers) && count($data) > 4) { + $firstCell = $headers[0]; + if (strstr($firstCell, APP_NAME)) { + array_shift($data); // Invoice Ninja... + array_shift($data); // + array_shift($data); // Enitty Type Header + } + } + } + + return $data; + } + /** * @param $column * @param $pattern @@ -613,12 +660,12 @@ class ImportService * * @return array */ - public function importCSV(array $maps, $headers) + public function importCSV(array $maps, $headers, $timestamp) { $results = []; foreach ($maps as $entityType => $map) { - $results[$entityType] = $this->executeCSV($entityType, $map, $headers[$entityType]); + $results[$entityType] = $this->executeCSV($entityType, $map, $headers[$entityType], $timestamp); } return $results; @@ -631,7 +678,7 @@ class ImportService * * @return array */ - private function executeCSV($entityType, $map, $hasHeaders) + private function executeCSV($entityType, $map, $hasHeaders, $timestamp) { $results = [ RESULT_SUCCESS => [], @@ -639,7 +686,8 @@ class ImportService ]; $source = IMPORT_CSV; - $data = Session::get("{$entityType}-data"); + $fileName = sprintf('%s_%s_%s.csv', Auth::user()->account_id, $timestamp, $entityType); + $data = $this->getCsvData($fileName); $this->checkData($entityType, count($data)); $this->initMaps(); @@ -678,7 +726,7 @@ class ImportService } } - Session::forget("{$entityType}-data"); + @unlink(storage_path() . '/import/' . $fileName); return $results; } @@ -820,6 +868,10 @@ class ImportService $this->maps['client'][$name] = $client->id; $this->maps['client_ids'][$client->public_id] = $client->id; } + if ($name = strtolower(trim($client->contacts[0]->email))) { + $this->maps['client'][$name] = $client->id; + $this->maps['client_ids'][$client->public_id] = $client->id; + } } /** @@ -861,4 +913,47 @@ class ImportService return $isEmpty; } + + public function presentResults($results, $includeSettings = false) + { + $message = ''; + $skipped = []; + + if ($includeSettings) { + $message = trans('texts.imported_settings') . '
'; + } + + foreach ($results as $entityType => $entityResults) { + if ($count = count($entityResults[RESULT_SUCCESS])) { + $message .= trans("texts.created_{$entityType}s", ['count' => $count]) . '
'; + } + if (count($entityResults[RESULT_FAILURE])) { + $skipped = array_merge($skipped, $entityResults[RESULT_FAILURE]); + } + } + + if (count($skipped)) { + $message .= '

' . trans('texts.failed_to_import') . '
'; + foreach ($skipped as $skip) { + $message .= json_encode($skip) . '
'; + } + } + + return $message; + } + + private function checkForFile($fileName) + { + $counter = 0; + + while (! file_exists($fileName)) { + $counter++; + if ($counter > 60) { + throw new Exception('File not found: ' . $fileName); + } + sleep(2); + } + + return true; + } } diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index b5feb742b..db8400875 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -81,42 +81,7 @@ class InvoiceService extends BaseService } } - $invoice = $this->invoiceRepo->save($data, $invoice); - - $client = $invoice->client; - $client->load('contacts'); - $sendInvoiceIds = []; - - foreach ($client->contacts as $contact) { - if ($contact->send_invoice) { - $sendInvoiceIds[] = $contact->id; - } - } - - // if no contacts are selected auto-select the first to enusre there's an invitation - if (! count($sendInvoiceIds)) { - $sendInvoiceIds[] = $client->contacts[0]->id; - } - - foreach ($client->contacts as $contact) { - $invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first(); - - if (in_array($contact->id, $sendInvoiceIds) && ! $invitation) { - $invitation = Invitation::createNew(); - $invitation->invoice_id = $invoice->id; - $invitation->contact_id = $contact->id; - $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); - $invitation->save(); - } elseif (! in_array($contact->id, $sendInvoiceIds) && $invitation) { - $invitation->delete(); - } - } - - if ($invoice->is_public && ! $invoice->areInvitationsSent()) { - $invoice->markInvitationsSent(); - } - - return $invoice; + return $this->invoiceRepo->save($data, $invoice); } /** diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 627381edc..c6e2829d2 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -136,10 +136,23 @@ class PaymentService extends BaseService try { return $paymentDriver->completeOnsitePurchase(false, $paymentMethod); } catch (Exception $exception) { + if (! Auth::check()) { + $subject = trans('texts.auto_bill_failed', ['invoice_number' => $invoice->invoice_number]); + $message = sprintf('%s: %s', ucwords($paymentDriver->providerName()), $exception->getMessage()); + $mailer = app('App\Ninja\Mailers\UserMailer'); + $mailer->sendMessage($invoice->user, $subject, $message, $invoice); + } + return false; } } + public function save($input, $payment = null) + { + return $this->paymentRepo->save($input, $payment); + } + + public function getDatatable($clientPublicId, $search) { $datatable = new PaymentDatatable(true, $clientPublicId); diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index 9670fe30b..846d4be87 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -57,10 +57,6 @@ class VendorService extends BaseService */ public function save(array $data, Vendor $vendor = null) { - if (Auth::user()->account->isNinjaAccount() && isset($data['plan'])) { - $this->ninjaRepo->updatePlanDetails($data['public_id'], $data); - } - return $this->vendorRepo->save($data, $vendor); } diff --git a/composer.json b/composer.json index 40ef6d8fe..b0ae7b213 100644 --- a/composer.json +++ b/composer.json @@ -82,7 +82,9 @@ "predis/predis": "^1.1", "nwidart/laravel-modules": "^1.14", "jonnyw/php-phantomjs": "4.*", - "collizo4sky/omnipay-wepay": "^1.3" + "collizo4sky/omnipay-wepay": "^1.3", + "barryvdh/laravel-cors": "^0.9.1", + "google/apiclient":"^2.0" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index fe938d6cc..e31cefa78 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": "16c087062022dc367fa7c330cb8f1bea", - "content-hash": "154f02784a3e9bb390f10ec0f23068e4", + "hash": "5c6d080c3a38d42e07ab70bf32760976", + "content-hash": "dcf4534113b5e62eb3f1fa6b453c82be", "packages": [ { "name": "agmscode/omnipay-agms", @@ -123,12 +123,12 @@ "source": { "type": "git", "url": "https://github.com/formers/former.git", - "reference": "1ad9b332e8d8f5b23159aabbca89084276938382" + "reference": "96363d8a0e7a58b80117a68e564104a431cdb49e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/formers/former/zipball/1ad9b332e8d8f5b23159aabbca89084276938382", - "reference": "1ad9b332e8d8f5b23159aabbca89084276938382", + "url": "https://api.github.com/repos/formers/former/zipball/96363d8a0e7a58b80117a68e564104a431cdb49e", + "reference": "96363d8a0e7a58b80117a68e564104a431cdb49e", "shasum": "" }, "require": { @@ -174,7 +174,7 @@ "foundation", "laravel" ], - "time": "2016-07-28 19:36:11" + "time": "2017-02-09 23:05:49" }, { "name": "anahkiasen/html-object", @@ -327,22 +327,22 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.21.5", + "version": "3.24.9", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "adaabe1e1b2c29a8748e52146194ab42222df8b5" + "reference": "26212252dcd0f9b9b7b19702577d0b0b364888df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/adaabe1e1b2c29a8748e52146194ab42222df8b5", - "reference": "adaabe1e1b2c29a8748e52146194ab42222df8b5", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/26212252dcd0f9b9b7b19702577d0b0b364888df", + "reference": "26212252dcd0f9b9b7b19702577d0b0b364888df", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^5.3.1|^6.2.1", "guzzlehttp/promises": "~1.0", - "guzzlehttp/psr7": "~1.3.1", + "guzzlehttp/psr7": "^1.4.1", "mtdowling/jmespath.php": "~2.2", "php": ">=5.5" }, @@ -403,7 +403,7 @@ "s3", "sdk" ], - "time": "2017-01-25 22:06:08" + "time": "2017-03-28 20:19:24" }, { "name": "barracudanetworks/archivestream-php", @@ -445,6 +445,64 @@ ], "time": "2017-01-13 14:52:38" }, + { + "name": "barryvdh/laravel-cors", + "version": "v0.9.2", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-cors.git", + "reference": "0b758188dadda20f4a17f1f4fe03c22ea92ce8e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-cors/zipball/0b758188dadda20f4a17f1f4fe03c22ea92ce8e4", + "reference": "0b758188dadda20f4a17f1f4fe03c22ea92ce8e4", + "shasum": "" + }, + "require": { + "illuminate/support": "5.1.x|5.2.x|5.3.x|5.4.x", + "php": ">=5.5.9", + "symfony/http-foundation": "~2.7|~3.0", + "symfony/http-kernel": "~2.7|~3.0" + }, + "require-dev": { + "orchestra/testbench": "3.x", + "phpunit/phpunit": "^4.8|^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev", + "dev-develop": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\Cors\\": "src/" + }, + "classmap": [ + "tests" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", + "keywords": [ + "api", + "cors", + "crossdomain", + "laravel" + ], + "time": "2017-03-22 08:40:10" + }, { "name": "barryvdh/laravel-debugbar", "version": "v2.3.2", @@ -501,16 +559,16 @@ }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.2.3", + "version": "v2.3.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "a7fc2ec489aada6062d3a63ddc915004a21e38af" + "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/a7fc2ec489aada6062d3a63ddc915004a21e38af", - "reference": "a7fc2ec489aada6062d3a63ddc915004a21e38af", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e82de98cef0d6597b1b686be0b5813a3a4bb53c5", + "reference": "e82de98cef0d6597b1b686be0b5813a3a4bb53c5", "shasum": "" }, "require": { @@ -533,7 +591,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } }, "autoload": { @@ -563,7 +621,7 @@ "phpstorm", "sublime" ], - "time": "2017-01-05 21:20:42" + "time": "2017-02-22 12:27:33" }, { "name": "barryvdh/reflection-docblock", @@ -616,16 +674,16 @@ }, { "name": "braintree/braintree_php", - "version": "3.21.1", + "version": "3.22.0", "source": { "type": "git", "url": "https://github.com/braintree/braintree_php.git", - "reference": "fcfabf9170925cdac0ff97b72dc9e7ab44c88263" + "reference": "402617b803779bed5ae899209afa75ef9950becc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/braintree/braintree_php/zipball/fcfabf9170925cdac0ff97b72dc9e7ab44c88263", - "reference": "fcfabf9170925cdac0ff97b72dc9e7ab44c88263", + "url": "https://api.github.com/repos/braintree/braintree_php/zipball/402617b803779bed5ae899209afa75ef9950becc", + "reference": "402617b803779bed5ae899209afa75ef9950becc", "shasum": "" }, "require": { @@ -659,7 +717,7 @@ } ], "description": "Braintree PHP Client Library", - "time": "2017-01-17 19:40:39" + "time": "2017-02-16 19:59:04" }, { "name": "cardgate/omnipay-cardgate", @@ -959,18 +1017,21 @@ }, { "name": "container-interop/container-interop", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/container-interop/container-interop.git", - "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", - "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", "shasum": "" }, + "require": { + "psr/container": "^1.0" + }, "type": "library", "autoload": { "psr-4": { @@ -982,7 +1043,8 @@ "MIT" ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", - "time": "2014-12-30 15:22:37" + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14 19:40:03" }, { "name": "delatbabel/omnipay-fatzebra", @@ -1441,16 +1503,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.3.1", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558" + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/bd4461328621bde0ae6b1b2675fbc6aca4ceb558", - "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", "shasum": "" }, "require": { @@ -1459,7 +1521,7 @@ }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.6.1" + "phpunit/phpunit": "^5.7" }, "type": "library", "extra": { @@ -1505,7 +1567,7 @@ "docblock", "parser" ], - "time": "2016-12-30 15:59:45" + "time": "2017-02-24 16:22:25" }, { "name": "doctrine/cache", @@ -1719,16 +1781,16 @@ }, { "name": "doctrine/dbal", - "version": "v2.5.10", + "version": "v2.5.12", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "fc376f7a61498e18520cd6fa083752a4ca08072b" + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/fc376f7a61498e18520cd6fa083752a4ca08072b", - "reference": "fc376f7a61498e18520cd6fa083752a4ca08072b", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44", + "reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44", "shasum": "" }, "require": { @@ -1786,7 +1848,7 @@ "persistence", "queryobject" ], - "time": "2017-01-23 23:17:10" + "time": "2017-02-08 12:53:47" }, { "name": "doctrine/inflector", @@ -1968,16 +2030,16 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.9.0", + "version": "v4.9.2", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "a1c09b09e398687deeb8e309a6305def4b43439b" + "reference": "6d50e5282afdfdfc3e0ff6d192aff56c5629b3d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/a1c09b09e398687deeb8e309a6305def4b43439b", - "reference": "a1c09b09e398687deeb8e309a6305def4b43439b", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6d50e5282afdfdfc3e0ff6d192aff56c5629b3d4", + "reference": "6d50e5282afdfdfc3e0ff6d192aff56c5629b3d4", "shasum": "" }, "require": { @@ -2011,7 +2073,7 @@ "keywords": [ "html" ], - "time": "2017-01-13 12:31:37" + "time": "2017-03-13 06:30:53" }, { "name": "fotografde/omnipay-checkoutcom", @@ -2328,21 +2390,21 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.2.2", + "version": "6.2.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60" + "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60", - "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006", + "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006", "shasum": "" }, "require": { "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.3.1", + "guzzlehttp/psr7": "^1.4", "php": ">=5.5" }, "require-dev": { @@ -2386,7 +2448,7 @@ "rest", "web service" ], - "time": "2016-10-08 15:01:37" + "time": "2017-02-28 22:50:30" }, { "name": "guzzlehttp/promises", @@ -2441,16 +2503,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.3.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b" + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", - "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", "shasum": "" }, "require": { @@ -2486,16 +2548,23 @@ "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" } ], - "description": "PSR-7 message implementation", + "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ "http", "message", + "request", + "response", "stream", - "uri" + "uri", + "url" ], - "time": "2016-06-24 23:00:38" + "time": "2017-03-20 17:10:46" }, { "name": "illuminate/html", @@ -2605,12 +2674,12 @@ "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "88911910dcbb7bf617f1370602ba1706489c8505" + "reference": "c9a162592dda7ae4ad3b49305da41e82155f9b9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/88911910dcbb7bf617f1370602ba1706489c8505", - "reference": "88911910dcbb7bf617f1370602ba1706489c8505", + "url": "https://api.github.com/repos/Intervention/image/zipball/c9a162592dda7ae4ad3b49305da41e82155f9b9a", + "reference": "c9a162592dda7ae4ad3b49305da41e82155f9b9a", "shasum": "" }, "require": { @@ -2659,7 +2728,7 @@ "thumbnail", "watermark" ], - "time": "2017-01-25 18:52:04" + "time": "2017-02-23 16:15:05" }, { "name": "ircmaxell/password-compat", @@ -2833,23 +2902,23 @@ }, { "name": "jaybizzle/crawler-detect", - "version": "v1.2.32", + "version": "v1.2.37", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "be0d602fdf9a1a6b3c9a1eea6fdb04b1654ae71a" + "reference": "850e5fc85506f9bee623fd29936ceb044d63ad2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/be0d602fdf9a1a6b3c9a1eea6fdb04b1654ae71a", - "reference": "be0d602fdf9a1a6b3c9a1eea6fdb04b1654ae71a", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/850e5fc85506f9bee623fd29936ceb044d63ad2b", + "reference": "850e5fc85506f9bee623fd29936ceb044d63ad2b", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "*" + "phpunit/phpunit": "4.*" }, "type": "library", "autoload": { @@ -2877,7 +2946,7 @@ "crawlerdetect", "php crawler detect" ], - "time": "2017-01-26 10:29:48" + "time": "2017-03-21 09:17:34" }, { "name": "jaybizzle/laravel-crawler-detect", @@ -3324,16 +3393,16 @@ }, { "name": "laravel/socialite", - "version": "v2.0.20", + "version": "v2.0.21", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "aca8de9a93a28a119714e289c8bc599bd81aa88d" + "reference": "c4e4337e5b70149fdbefbb95b2c9e93d0749c413" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/aca8de9a93a28a119714e289c8bc599bd81aa88d", - "reference": "aca8de9a93a28a119714e289c8bc599bd81aa88d", + "url": "https://api.github.com/repos/laravel/socialite/zipball/c4e4337e5b70149fdbefbb95b2c9e93d0749c413", + "reference": "c4e4337e5b70149fdbefbb95b2c9e93d0749c413", "shasum": "" }, "require": { @@ -3374,7 +3443,7 @@ "laravel", "oauth" ], - "time": "2016-11-01 18:49:10" + "time": "2017-03-27 21:32:28" }, { "name": "laravelcollective/bus", @@ -3477,16 +3546,16 @@ }, { "name": "league/flysystem", - "version": "1.0.33", + "version": "1.0.37", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5c7f98498b12d47f9de90ec9186a90000125777c" + "reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5c7f98498b12d47f9de90ec9186a90000125777c", - "reference": "5c7f98498b12d47f9de90ec9186a90000125777c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/78b5cc4feb61a882302df4fbaf63b7662e5e4ccd", + "reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd", "shasum": "" }, "require": { @@ -3556,7 +3625,7 @@ "sftp", "storage" ], - "time": "2017-01-23 10:32:09" + "time": "2017-03-22 15:43:14" }, { "name": "league/flysystem-aws-s3-v3", @@ -3884,16 +3953,16 @@ }, { "name": "maatwebsite/excel", - "version": "2.1.10", + "version": "2.1.16", "source": { "type": "git", "url": "https://github.com/Maatwebsite/Laravel-Excel.git", - "reference": "a544a9b45b971499fb3b0fb7499ba0ac3b430233" + "reference": "655def96b5e98d1fe0974d9c4e2ec5f2b591a322" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/a544a9b45b971499fb3b0fb7499ba0ac3b430233", - "reference": "a544a9b45b971499fb3b0fb7499ba0ac3b430233", + "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/655def96b5e98d1fe0974d9c4e2ec5f2b591a322", + "reference": "655def96b5e98d1fe0974d9c4e2ec5f2b591a322", "shasum": "" }, "require": { @@ -3901,6 +3970,7 @@ "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*", + "jeremeamia/superclosure": "^2.3", "nesbot/carbon": "~1.0", "php": ">=5.5", "phpoffice/phpexcel": "1.8.*", @@ -3947,7 +4017,7 @@ "import", "laravel" ], - "time": "2017-01-20 16:16:51" + "time": "2017-03-28 10:47:04" }, { "name": "maximebf/debugbar", @@ -4210,16 +4280,16 @@ }, { "name": "monolog/monolog", - "version": "1.22.0", + "version": "1.22.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", - "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", "shasum": "" }, "require": { @@ -4284,7 +4354,7 @@ "logging", "psr-3" ], - "time": "2016-11-26 00:15:39" + "time": "2017-03-13 07:08:03" }, { "name": "mtdowling/cron-expression", @@ -4491,16 +4561,16 @@ }, { "name": "nwidart/laravel-modules", - "version": "1.16.0", + "version": "1.19.0", "source": { "type": "git", "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "2c46885be186fb51189dc8ee8a856d35835f6237" + "reference": "694640a499452f1bff8f424b250260343d81acba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/2c46885be186fb51189dc8ee8a856d35835f6237", - "reference": "2c46885be186fb51189dc8ee8a856d35835f6237", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/694640a499452f1bff8f424b250260343d81acba", + "reference": "694640a499452f1bff8f424b250260343d81acba", "shasum": "" }, "require": { @@ -4512,7 +4582,7 @@ "mockery/mockery": "~0.9", "orchestra/testbench": "^3.1|^3.2|^3.3|^3.4", "phpro/grumphp": "^0.9.1", - "phpunit/phpunit": "~4" + "phpunit/phpunit": "~5.7" }, "type": "library", "autoload": { @@ -4540,7 +4610,7 @@ "nwidart", "rad" ], - "time": "2017-01-24 18:35:40" + "time": "2017-03-16 10:56:06" }, { "name": "omnipay/2checkout", @@ -4603,16 +4673,16 @@ }, { "name": "omnipay/authorizenet", - "version": "2.4.2", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-authorizenet.git", - "reference": "6e1990f5d22f0f8e4dfe363b89c9776d0d803c34" + "reference": "229bdb205384f2e3bf844cdf258927895b4858d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-authorizenet/zipball/6e1990f5d22f0f8e4dfe363b89c9776d0d803c34", - "reference": "6e1990f5d22f0f8e4dfe363b89c9776d0d803c34", + "url": "https://api.github.com/repos/thephpleague/omnipay-authorizenet/zipball/229bdb205384f2e3bf844cdf258927895b4858d5", + "reference": "229bdb205384f2e3bf844cdf258927895b4858d5", "shasum": "" }, "require": { @@ -4658,7 +4728,7 @@ "pay", "payment" ], - "time": "2016-07-16 21:00:00" + "time": "2017-03-07 14:00:14" }, { "name": "omnipay/bitpay", @@ -5473,16 +5543,16 @@ }, { "name": "omnipay/multisafepay", - "version": "v2.3.1", + "version": "v2.3.4", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-multisafepay.git", - "reference": "01cfa7115ab7b3c79633e8137f802891c02640f2" + "reference": "9163f9b41c4bc658161bdd140b690d186ca535a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-multisafepay/zipball/01cfa7115ab7b3c79633e8137f802891c02640f2", - "reference": "01cfa7115ab7b3c79633e8137f802891c02640f2", + "url": "https://api.github.com/repos/thephpleague/omnipay-multisafepay/zipball/9163f9b41c4bc658161bdd140b690d186ca535a1", + "reference": "9163f9b41c4bc658161bdd140b690d186ca535a1", "shasum": "" }, "require": { @@ -5527,7 +5597,7 @@ "pay", "payment" ], - "time": "2016-03-21 02:10:38" + "time": "2017-02-22 06:42:13" }, { "name": "omnipay/netaxept", @@ -6336,16 +6406,16 @@ }, { "name": "paragonie/random_compat", - "version": "v1.4.1", + "version": "v1.4.2", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "c7e26a21ba357863de030f0b9e701c7d04593774" + "reference": "965cdeb01fdcab7653253aa81d40441d261f1e66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774", - "reference": "c7e26a21ba357863de030f0b9e701c7d04593774", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/965cdeb01fdcab7653253aa81d40441d261f1e66", + "reference": "965cdeb01fdcab7653253aa81d40441d261f1e66", "shasum": "" }, "require": { @@ -6380,7 +6450,7 @@ "pseudorandom", "random" ], - "time": "2016-03-18 20:34:03" + "time": "2017-03-13 16:22:52" }, { "name": "patricktalmadge/bootstrapper", @@ -6548,6 +6618,55 @@ ], "time": "2016-06-16 16:22:20" }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14 16:28:37" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -6780,12 +6899,12 @@ "source": { "type": "git", "url": "https://github.com/simshaun/recurr.git", - "reference": "699a55524db1a086ba2c607bd891ecf03dbd572a" + "reference": "6cb19ee64c41f6e580670b9168035d37a1cab8fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/simshaun/recurr/zipball/699a55524db1a086ba2c607bd891ecf03dbd572a", - "reference": "699a55524db1a086ba2c607bd891ecf03dbd572a", + "url": "https://api.github.com/repos/simshaun/recurr/zipball/6cb19ee64c41f6e580670b9168035d37a1cab8fd", + "reference": "6cb19ee64c41f6e580670b9168035d37a1cab8fd", "shasum": "" }, "require": { @@ -6826,7 +6945,7 @@ "recurring", "rrule" ], - "time": "2016-12-19 21:12:08" + "time": "2017-03-02 04:31:32" }, { "name": "softcommerce/omnipay-paytrace", @@ -6929,16 +7048,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.5", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "cd142238a339459b10da3d8234220963f392540c" + "reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/cd142238a339459b10da3d8234220963f392540c", - "reference": "cd142238a339459b10da3d8234220963f392540c", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e", + "reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e", "shasum": "" }, "require": { @@ -6979,20 +7098,20 @@ "mail", "mailer" ], - "time": "2016-12-29 10:02:40" + "time": "2017-02-13 07:52:53" }, { "name": "symfony/class-loader", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "0152f7a47acd564ca62c652975c2b32ac6d613a6" + "reference": "c29a5bc6ca14cfff1f5e3d7781ed74b6e898d2b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/0152f7a47acd564ca62c652975c2b32ac6d613a6", - "reference": "0152f7a47acd564ca62c652975c2b32ac6d613a6", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/c29a5bc6ca14cfff1f5e3d7781ed74b6e898d2b9", + "reference": "c29a5bc6ca14cfff1f5e3d7781ed74b6e898d2b9", "shasum": "" }, "require": { @@ -7035,20 +7154,20 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2017-01-10 14:14:38" + "time": "2017-02-18 17:28:00" }, { "name": "symfony/config", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "c5ea878b5a7f6a01b9a2f182f905831711b9ff3f" + "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/c5ea878b5a7f6a01b9a2f182f905831711b9ff3f", - "reference": "c5ea878b5a7f6a01b9a2f182f905831711b9ff3f", + "url": "https://api.github.com/repos/symfony/config/zipball/741d6d4cd1414d67d48eb71aba6072b46ba740c2", + "reference": "741d6d4cd1414d67d48eb71aba6072b46ba740c2", "shasum": "" }, "require": { @@ -7091,7 +7210,7 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-01-02 20:32:22" + "time": "2017-03-01 18:18:25" }, { "name": "symfony/console", @@ -7155,16 +7274,16 @@ }, { "name": "symfony/css-selector", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa" + "reference": "a48f13dc83c168f1253a5d2a5a4fb46c36244c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/f0e628f04fc055c934b3211cfabdb1c59eefbfaa", - "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/a48f13dc83c168f1253a5d2a5a4fb46c36244c4c", + "reference": "a48f13dc83c168f1253a5d2a5a4fb46c36244c4c", "shasum": "" }, "require": { @@ -7204,7 +7323,7 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2017-01-02 20:32:22" + "time": "2017-02-21 09:12:04" }, { "name": "symfony/debug", @@ -7265,16 +7384,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "22b2c97cffc6a612db82084f9e7823b095958751" + "reference": "74e0935e414ad33d5e82074212c0eedb4681a691" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/22b2c97cffc6a612db82084f9e7823b095958751", - "reference": "22b2c97cffc6a612db82084f9e7823b095958751", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/74e0935e414ad33d5e82074212c0eedb4681a691", + "reference": "74e0935e414ad33d5e82074212c0eedb4681a691", "shasum": "" }, "require": { @@ -7324,20 +7443,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-01-10 14:21:25" + "time": "2017-03-05 00:06:55" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.17", + "version": "v2.8.18", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "74877977f90fb9c3e46378d5764217c55f32df34" + "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74877977f90fb9c3e46378d5764217c55f32df34", - "reference": "74877977f90fb9c3e46378d5764217c55f32df34", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bb4ec47e8e109c1c1172145732d0aa468d967cd0", + "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0", "shasum": "" }, "require": { @@ -7345,7 +7464,7 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/config": "^2.0.5|~3.0.0", "symfony/dependency-injection": "~2.6|~3.0.0", "symfony/expression-language": "~2.6|~3.0.0", "symfony/stopwatch": "~2.3|~3.0.0" @@ -7384,20 +7503,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-01-02 20:30:24" + "time": "2017-02-21 08:33:48" }, { "name": "symfony/filesystem", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4" + "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/a0c6ef2dc78d33b58d91d3a49f49797a184d06f4", - "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/bc0f17bed914df2cceb989972c3b996043c4da4a", + "reference": "bc0f17bed914df2cceb989972c3b996043c4da4a", "shasum": "" }, "require": { @@ -7433,7 +7552,7 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-01-08 20:47:33" + "time": "2017-03-06 19:30:27" }, { "name": "symfony/finder", @@ -7486,16 +7605,16 @@ }, { "name": "symfony/http-foundation", - "version": "v2.8.17", + "version": "v2.8.18", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "37658106408cbde9617b41ddca633a22823ca45e" + "reference": "88af747e7af17d8d7d439ad4639dc3e23ddd3edd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/37658106408cbde9617b41ddca633a22823ca45e", - "reference": "37658106408cbde9617b41ddca633a22823ca45e", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/88af747e7af17d8d7d439ad4639dc3e23ddd3edd", + "reference": "88af747e7af17d8d7d439ad4639dc3e23ddd3edd", "shasum": "" }, "require": { @@ -7537,7 +7656,7 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-02-02 13:38:20" + "time": "2017-03-04 12:20:59" }, { "name": "symfony/http-kernel", @@ -7623,16 +7742,16 @@ }, { "name": "symfony/options-resolver", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "855429e3e9014b9dafee2a667de304c3aaa86fe6" + "reference": "56e3d0a41313f8a54326851f10690d591e62a24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/855429e3e9014b9dafee2a667de304c3aaa86fe6", - "reference": "855429e3e9014b9dafee2a667de304c3aaa86fe6", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/56e3d0a41313f8a54326851f10690d591e62a24c", + "reference": "56e3d0a41313f8a54326851f10690d591e62a24c", "shasum": "" }, "require": { @@ -7673,7 +7792,7 @@ "configuration", "options" ], - "time": "2017-01-02 20:32:22" + "time": "2017-02-21 09:12:04" }, { "name": "symfony/polyfill-mbstring", @@ -8209,16 +8328,16 @@ }, { "name": "symfony/yaml", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "50eadbd7926e31842893c957eca362b21592a97d" + "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/50eadbd7926e31842893c957eca362b21592a97d", - "reference": "50eadbd7926e31842893c957eca362b21592a97d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a", + "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a", "shasum": "" }, "require": { @@ -8260,7 +8379,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-01-03 13:51:32" + "time": "2017-03-07 16:47:02" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8514,29 +8633,30 @@ }, { "name": "twig/twig", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "ddc9e3e20ee9c0b6908f401ac8353635b750eca7" + "reference": "05cf49921b13f6f01d3cfdf9018cfa7a8086fd5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/ddc9e3e20ee9c0b6908f401ac8353635b750eca7", - "reference": "ddc9e3e20ee9c0b6908f401ac8353635b750eca7", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/05cf49921b13f6f01d3cfdf9018cfa7a8086fd5a", + "reference": "05cf49921b13f6f01d3cfdf9018cfa7a8086fd5a", "shasum": "" }, "require": { "php": ">=5.2.7" }, "require-dev": { + "psr/container": "^1.0", "symfony/debug": "~2.7", - "symfony/phpunit-bridge": "~3.2" + "symfony/phpunit-bridge": "~3.3@dev" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.31-dev" + "dev-master": "1.33-dev" } }, "autoload": { @@ -8571,7 +8691,7 @@ "keywords": [ "templating" ], - "time": "2017-01-11 19:36:15" + "time": "2017-03-22 15:40:09" }, { "name": "vink/omnipay-komoju", @@ -8933,16 +9053,16 @@ }, { "name": "zendframework/zend-http", - "version": "2.5.5", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "98b1cac0bc7a91497c5898184281abcd0e24c8d6" + "reference": "09f4d279f46d86be63171ff62ee0f79eca878678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/98b1cac0bc7a91497c5898184281abcd0e24c8d6", - "reference": "98b1cac0bc7a91497c5898184281abcd0e24c8d6", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/09f4d279f46d86be63171ff62ee0f79eca878678", + "reference": "09f4d279f46d86be63171ff62ee0f79eca878678", "shasum": "" }, "require": { @@ -8953,15 +9073,15 @@ "zendframework/zend-validator": "^2.5" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "^4.0", + "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev", - "dev-develop": "2.6-dev" + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" } }, "autoload": { @@ -8979,7 +9099,7 @@ "http", "zf2" ], - "time": "2016-08-08 15:01:54" + "time": "2017-01-31 14:41:02" }, { "name": "zendframework/zend-json", @@ -9174,27 +9294,27 @@ }, { "name": "zendframework/zend-validator", - "version": "2.8.1", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "8ec9f57a717dd37340308aa632f148a2c2be1cfc" + "reference": "b71641582297eab52753b72cd4eb45a5ded4485c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/8ec9f57a717dd37340308aa632f148a2c2be1cfc", - "reference": "8ec9f57a717dd37340308aa632f148a2c2be1cfc", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/b71641582297eab52753b72cd4eb45a5ded4485c", + "reference": "b71641582297eab52753b72cd4eb45a5ded4485c", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", - "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^2.7.6 || ^3.1" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "^4.0", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", "zendframework/zend-cache": "^2.6.1", + "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6", "zendframework/zend-db": "^2.7", "zendframework/zend-filter": "^2.6", @@ -9206,20 +9326,20 @@ "zendframework/zend-uri": "^2.5" }, "suggest": { - "zendframework/zend-db": "Zend\\Db component", + "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages as well as to use the various Date validators", + "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", "zendframework/zend-i18n-resources": "Translations of validator messages", - "zendframework/zend-math": "Zend\\Math component", + "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component", + "zendframework/zend-session": "Zend\\Session component, required by the Csrf validator", "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev", - "dev-develop": "2.9-dev" + "dev-master": "2.9-dev", + "dev-develop": "2.10-dev" }, "zf": { "component": "Zend\\Validator", @@ -9241,7 +9361,7 @@ "validator", "zf2" ], - "time": "2016-06-23 13:44:31" + "time": "2017-03-17 10:15:50" }, { "name": "zendframework/zendservice-apple-apns", @@ -9332,25 +9452,25 @@ }, { "name": "zircote/swagger-php", - "version": "2.0.8", + "version": "2.0.9", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "39f4c30692a4925597e7d4280fc58794fb4f3730" + "reference": "b0b136e3c51c8b05d78a0ea3f1b60cd331cc3544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/39f4c30692a4925597e7d4280fc58794fb4f3730", - "reference": "39f4c30692a4925597e7d4280fc58794fb4f3730", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/b0b136e3c51c8b05d78a0ea3f1b60cd331cc3544", + "reference": "b0b136e3c51c8b05d78a0ea3f1b60cd331cc3544", "shasum": "" }, "require": { "doctrine/annotations": "*", - "php": ">=5.4.0", + "php": ">=5.6", "symfony/finder": "*" }, "require-dev": { - "phpunit/phpunit": ">=4.8", + "phpunit/phpunit": ">=4.8 <=5.6", "squizlabs/php_codesniffer": ">=2.7", "zendframework/zend-form": "<2.8" }, @@ -9390,7 +9510,7 @@ "rest", "service discovery" ], - "time": "2016-12-16 12:39:03" + "time": "2017-02-22 23:16:25" } ], "packages-dev": [ @@ -9455,16 +9575,16 @@ }, { "name": "codeception/c3", - "version": "2.0.9", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/Codeception/c3.git", - "reference": "85c8e405a03c1d5055f1977673b20377aaa0bd22" + "reference": "47842d23030638237fd3306657c5fe998273fc18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/c3/zipball/85c8e405a03c1d5055f1977673b20377aaa0bd22", - "reference": "85c8e405a03c1d5055f1977673b20377aaa0bd22", + "url": "https://api.github.com/repos/Codeception/c3/zipball/47842d23030638237fd3306657c5fe998273fc18", + "reference": "47842d23030638237fd3306657c5fe998273fc18", "shasum": "" }, "require": { @@ -9501,20 +9621,20 @@ "code coverage", "codecoverage" ], - "time": "2017-01-21 00:34:49" + "time": "2017-02-04 01:52:32" }, { "name": "codeception/codeception", - "version": "2.2.8", + "version": "2.2.10", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "0a87d4b19070a24636125993450a9d3f698a6663" + "reference": "c32a3f92834db08ceedb4666ea2265c3aa43396e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/0a87d4b19070a24636125993450a9d3f698a6663", - "reference": "0a87d4b19070a24636125993450a9d3f698a6663", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c32a3f92834db08ceedb4666ea2265c3aa43396e", + "reference": "c32a3f92834db08ceedb4666ea2265c3aa43396e", "shasum": "" }, "require": { @@ -9525,10 +9645,11 @@ "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", "php": ">=5.4.0 <8.0", - "phpunit/php-code-coverage": ">=2.1.3 <5.0", + "phpunit/php-code-coverage": ">=2.2.4 <5.0", "phpunit/phpunit": ">4.8.20 <6.0", "sebastian/comparator": "~1.1", "sebastian/diff": "^1.4", + "stecman/symfony-console-completion": "^0.7.0", "symfony/browser-kit": ">=2.7 <4.0", "symfony/console": ">=2.7 <4.0", "symfony/css-selector": ">=2.7 <4.0", @@ -9593,7 +9714,7 @@ "functional testing", "unit testing" ], - "time": "2017-01-20 00:54:15" + "time": "2017-03-25 03:19:52" }, { "name": "doctrine/instantiator", @@ -9956,27 +10077,27 @@ }, { "name": "phpspec/prophecy", - "version": "v1.6.2", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", - "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", + "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0|^2.0" + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.0", + "phpspec/phpspec": "^2.5|^3.2", "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", @@ -10015,7 +10136,7 @@ "spy", "stub" ], - "time": "2016-11-21 14:58:47" + "time": "2017-03-02 20:05:34" }, { "name": "phpunit/php-code-coverage", @@ -10169,25 +10290,30 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.8", + "version": "1.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4|~5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -10209,20 +10335,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12 18:03:57" + "time": "2017-02-26 11:10:40" }, { "name": "phpunit/php-token-stream", - "version": "1.4.9", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", - "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", "shasum": "" }, "require": { @@ -10258,20 +10384,20 @@ "keywords": [ "tokenizer" ], - "time": "2016-11-15 14:06:22" + "time": "2017-02-27 10:12:30" }, { "name": "phpunit/phpunit", - "version": "4.8.34", + "version": "4.8.35", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7eb45205d27edd94bd2b3614085ea158bd1e2bca" + "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7eb45205d27edd94bd2b3614085ea158bd1e2bca", - "reference": "7eb45205d27edd94bd2b3614085ea158bd1e2bca", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87", + "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87", "shasum": "" }, "require": { @@ -10330,7 +10456,7 @@ "testing", "xunit" ], - "time": "2017-01-26 16:15:36" + "time": "2017-02-06 05:18:07" }, { "name": "phpunit/phpunit-mock-objects", @@ -10390,16 +10516,16 @@ }, { "name": "sebastian/comparator", - "version": "1.2.2", + "version": "1.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", - "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", "shasum": "" }, "require": { @@ -10450,7 +10576,7 @@ "compare", "equality" ], - "time": "2016-11-19 09:18:40" + "time": "2017-01-29 09:50:25" }, { "name": "sebastian/diff", @@ -10674,16 +10800,16 @@ }, { "name": "sebastian/recursion-context", - "version": "1.0.2", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", "shasum": "" }, "require": { @@ -10723,7 +10849,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" + "time": "2016-10-03 07:41:43" }, { "name": "sebastian/version", @@ -10761,17 +10887,62 @@ "time": "2015-06-21 13:59:46" }, { - "name": "symfony/browser-kit", - "version": "v3.2.2", + "name": "stecman/symfony-console-completion", + "version": "0.7.0", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "548f8230bad9f77463b20b15993a008f03e96db5" + "url": "https://github.com/stecman/symfony-console-completion.git", + "reference": "5461d43e53092b3d3b9dbd9d999f2054730f4bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/548f8230bad9f77463b20b15993a008f03e96db5", - "reference": "548f8230bad9f77463b20b15993a008f03e96db5", + "url": "https://api.github.com/repos/stecman/symfony-console-completion/zipball/5461d43e53092b3d3b9dbd9d999f2054730f4bbb", + "reference": "5461d43e53092b3d3b9dbd9d999f2054730f4bbb", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/console": "~2.3 || ~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stecman\\Component\\Symfony\\Console\\BashCompletion\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Holdaway", + "email": "stephen@stecman.co.nz" + } + ], + "description": "Automatic BASH completion for Symfony Console Component based applications.", + "time": "2016-02-24 05:08:54" + }, + { + "name": "symfony/browser-kit", + "version": "v3.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "2fe0caa60c1a1dfeefd0425741182687a9b382b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/2fe0caa60c1a1dfeefd0425741182687a9b382b8", + "reference": "2fe0caa60c1a1dfeefd0425741182687a9b382b8", "shasum": "" }, "require": { @@ -10815,20 +10986,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2017-01-02 20:32:22" + "time": "2017-02-21 09:12:04" }, { "name": "symfony/dom-crawler", - "version": "v3.2.2", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "27d9790840a4efd3b7bb8f5f4f9efc27b36b7024" + "reference": "403944e294cf4ceb3b8447f54cbad88ea7b99cee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/27d9790840a4efd3b7bb8f5f4f9efc27b36b7024", - "reference": "27d9790840a4efd3b7bb8f5f4f9efc27b36b7024", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/403944e294cf4ceb3b8447f54cbad88ea7b99cee", + "reference": "403944e294cf4ceb3b8447f54cbad88ea7b99cee", "shasum": "" }, "require": { @@ -10871,7 +11042,7 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2017-01-02 20:32:22" + "time": "2017-02-21 09:12:04" }, { "name": "webmozart/assert", diff --git a/config/app.php b/config/app.php index 66fb04581..5d11d2331 100644 --- a/config/app.php +++ b/config/app.php @@ -157,6 +157,7 @@ return [ 'Jaybizzle\LaravelCrawlerDetect\LaravelCrawlerDetectServiceProvider', Codedge\Updater\UpdaterServiceProvider::class, Nwidart\Modules\LaravelModulesServiceProvider::class, + Barryvdh\Cors\ServiceProvider::class, /* * Application Service Providers... diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 000000000..99ccbfc88 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,20 @@ + false, + 'allowedOrigins' => ['*'], + 'allowedHeaders' => ['*'], + 'allowedMethods' => ['*'], + 'exposedHeaders' => [], + 'maxAge' => 0, +]; + diff --git a/config/database.php b/config/database.php index a184796c2..e019d61d6 100644 --- a/config/database.php +++ b/config/database.php @@ -62,6 +62,7 @@ return [ 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => env('DB_STRICT', false), + 'engine' => 'InnoDB', ], 'pgsql' => [ diff --git a/database/migrations/2017_03_16_085702_add_gateway_fee_location.php b/database/migrations/2017_03_16_085702_add_gateway_fee_location.php new file mode 100644 index 000000000..7752f7086 --- /dev/null +++ b/database/migrations/2017_03_16_085702_add_gateway_fee_location.php @@ -0,0 +1,140 @@ +integer('invoice_number_counter')->default(1)->nullable(); + $table->integer('quote_number_counter')->default(1)->nullable(); + }); + + Schema::table('credits', function ($table) { + $table->text('public_notes')->nullable(); + }); + + // update invoice_item_type_id for task invoice items + DB::statement('update invoice_items + left join invoices on invoices.id = invoice_items.invoice_id + set invoice_item_type_id = 2 + where invoices.has_tasks = 1'); + + Schema::create('account_email_settings', function ($table) { + $table->increments('id'); + $table->unsignedInteger('account_id')->index(); + $table->timestamps(); + + $table->string('reply_to_email')->nullable(); + $table->string('bcc_email')->nullable(); + + $table->string('email_subject_invoice'); + $table->string('email_subject_quote'); + $table->string('email_subject_payment'); + $table->text('email_template_invoice'); + $table->text('email_template_quote'); + $table->text('email_template_payment'); + + $table->string('email_subject_reminder1'); + $table->string('email_subject_reminder2'); + $table->string('email_subject_reminder3'); + $table->text('email_template_reminder1'); + $table->text('email_template_reminder2'); + $table->text('email_template_reminder3'); + + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + }); + + DB::statement('insert into account_email_settings (account_id, + bcc_email, + email_subject_invoice, + email_subject_quote, + email_subject_payment, + email_template_invoice, + email_template_quote, + email_template_payment, + email_subject_reminder1, + email_subject_reminder2, + email_subject_reminder3, + email_template_reminder1, + email_template_reminder2, + email_template_reminder3 + ) + select id, + bcc_email, + email_subject_invoice, + email_subject_quote, + email_subject_payment, + email_template_invoice, + email_template_quote, + email_template_payment, + email_subject_reminder1, + email_subject_reminder2, + email_subject_reminder3, + email_template_reminder1, + email_template_reminder2, + email_template_reminder3 + from accounts;'); + + Schema::table('accounts', function ($table) { + $table->dropColumn('bcc_email'); + $table->dropColumn('email_subject_invoice'); + $table->dropColumn('email_subject_quote'); + $table->dropColumn('email_subject_payment'); + $table->dropColumn('email_template_invoice'); + $table->dropColumn('email_template_quote'); + $table->dropColumn('email_template_payment'); + $table->dropColumn('email_subject_reminder1'); + $table->dropColumn('email_subject_reminder2'); + $table->dropColumn('email_subject_reminder3'); + $table->dropColumn('email_template_reminder1'); + $table->dropColumn('email_template_reminder2'); + $table->dropColumn('email_template_reminder3'); + + if (Schema::hasColumn('accounts', 'auto_wrap')) { + $table->dropColumn('auto_wrap'); + } + if (Schema::hasColumn('accounts', 'utf8_invoices')) { + $table->dropColumn('utf8_invoices'); + } + if (Schema::hasColumn('accounts', 'dark_mode')) { + $table->dropColumn('dark_mode'); + } + }); + + Schema::table('accounts', function ($table) { + $table->boolean('gateway_fee_enabled')->default(0); + $table->date('reset_counter_date')->nullable(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function ($table) { + $table->dropColumn('gateway_fee_enabled'); + $table->dropColumn('reset_counter_date'); + }); + + Schema::table('clients', function ($table) { + $table->dropColumn('invoice_number_counter'); + $table->dropColumn('quote_number_counter'); + }); + + Schema::table('credits', function ($table) { + $table->dropColumn('public_notes'); + }); + } +} diff --git a/database/seeds/CurrenciesSeeder.php b/database/seeds/CurrenciesSeeder.php index 4cc3e14d0..5e6ed9039 100644 --- a/database/seeds/CurrenciesSeeder.php +++ b/database/seeds/CurrenciesSeeder.php @@ -67,6 +67,7 @@ class CurrenciesSeeder extends Seeder ['name' => 'Russian Ruble', 'code' => 'RUB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['name' => 'Mozambican Metical', 'code' => 'MZN', 'symbol' => 'MT', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ',', 'swap_currency_symbol' => true], ['name' => 'Omani Rial', 'code' => 'OMR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Ukrainian Hryvnia', 'code' => 'UAH', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ]; foreach ($currencies as $currency) { diff --git a/database/seeds/UserTableSeeder.php b/database/seeds/UserTableSeeder.php index 05c418a46..d03f0eccc 100644 --- a/database/seeds/UserTableSeeder.php +++ b/database/seeds/UserTableSeeder.php @@ -1,6 +1,7 @@ $faker->state, 'postal_code' => $faker->postcode, 'country_id' => Country::all()->random()->id, - 'account_key' => str_random(RANDOM_KEY_LENGTH), + 'account_key' => strtolower(str_random(RANDOM_KEY_LENGTH)), 'invoice_terms' => $faker->text($faker->numberBetween(50, 300)), 'work_phone' => $faker->phoneNumber, 'work_email' => $faker->safeEmail, @@ -44,6 +45,10 @@ class UserTableSeeder extends Seeder 'pdf_email_attachment' => true, ]); + $emailSettings = AccountEmailSettings::create([ + 'account_id' => $account->id + ]); + $user = User::create([ 'first_name' => $faker->firstName, 'last_name' => $faker->lastName, diff --git a/database/setup.sql b/database/setup.sql index 879bca607..cf63b132b 100644 --- a/database/setup.sql +++ b/database/setup.sql @@ -15,6 +15,47 @@ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +-- +-- Table structure for table `account_email_settings` +-- + +DROP TABLE IF EXISTS `account_email_settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `account_email_settings` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `account_id` int(10) unsigned NOT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `reply_to_email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `bcc_email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `email_subject_invoice` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email_subject_quote` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email_subject_payment` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email_template_invoice` text COLLATE utf8_unicode_ci NOT NULL, + `email_template_quote` text COLLATE utf8_unicode_ci NOT NULL, + `email_template_payment` text COLLATE utf8_unicode_ci NOT NULL, + `email_subject_reminder1` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email_subject_reminder2` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email_subject_reminder3` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `email_template_reminder1` text COLLATE utf8_unicode_ci NOT NULL, + `email_template_reminder2` text COLLATE utf8_unicode_ci NOT NULL, + `email_template_reminder3` text COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `account_email_settings_account_id_index` (`account_id`), + CONSTRAINT `account_email_settings_account_id_foreign` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `account_email_settings` +-- + +LOCK TABLES `account_email_settings` WRITE; +/*!40000 ALTER TABLE `account_email_settings` DISABLE KEYS */; +/*!40000 ALTER TABLE `account_email_settings` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `account_gateway_settings` -- @@ -232,14 +273,9 @@ CREATE TABLE `accounts` ( `quote_number_counter` int(11) DEFAULT '1', `share_counter` tinyint(1) NOT NULL DEFAULT '1', `id_number` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_template_invoice` text COLLATE utf8_unicode_ci, - `email_template_quote` text COLLATE utf8_unicode_ci, - `email_template_payment` text COLLATE utf8_unicode_ci, `token_billing_type_id` smallint(6) NOT NULL DEFAULT '4', `invoice_footer` text COLLATE utf8_unicode_ci, `pdf_email_attachment` smallint(6) NOT NULL DEFAULT '0', - `utf8_invoices` tinyint(1) NOT NULL DEFAULT '1', - `auto_wrap` tinyint(1) NOT NULL DEFAULT '0', `subdomain` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `font_size` smallint(6) NOT NULL DEFAULT '9', `invoice_labels` text COLLATE utf8_unicode_ci, @@ -248,15 +284,6 @@ CREATE TABLE `accounts` ( `iframe_url` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `military_time` tinyint(1) NOT NULL DEFAULT '0', `referral_user_id` int(10) unsigned DEFAULT NULL, - `email_subject_invoice` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_subject_quote` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_subject_payment` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_subject_reminder1` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_subject_reminder2` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_subject_reminder3` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `email_template_reminder1` text COLLATE utf8_unicode_ci, - `email_template_reminder2` text COLLATE utf8_unicode_ci, - `email_template_reminder3` text COLLATE utf8_unicode_ci, `enable_reminder1` tinyint(1) NOT NULL DEFAULT '0', `enable_reminder2` tinyint(1) NOT NULL DEFAULT '0', `enable_reminder3` tinyint(1) NOT NULL DEFAULT '0', @@ -317,7 +344,6 @@ CREATE TABLE `accounts` ( `show_accept_quote_terms` tinyint(1) NOT NULL DEFAULT '0', `require_invoice_signature` tinyint(1) NOT NULL DEFAULT '0', `require_quote_signature` tinyint(1) NOT NULL DEFAULT '0', - `bcc_email` text COLLATE utf8_unicode_ci, `client_number_prefix` text COLLATE utf8_unicode_ci, `client_number_counter` int(11) DEFAULT '0', `client_number_pattern` text COLLATE utf8_unicode_ci, @@ -325,6 +351,8 @@ CREATE TABLE `accounts` ( `payment_terms` tinyint(4) DEFAULT NULL, `reset_counter_frequency_id` smallint(6) DEFAULT NULL, `payment_type_id` smallint(6) DEFAULT NULL, + `gateway_fee_enabled` tinyint(1) NOT NULL DEFAULT '0', + `reset_counter_date` date DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `accounts_account_key_unique` (`account_key`), KEY `accounts_timezone_id_foreign` (`timezone_id`), @@ -574,6 +602,8 @@ CREATE TABLE `clients` ( `vat_number` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `id_number` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `language_id` int(10) unsigned DEFAULT NULL, + `invoice_number_counter` int(11) DEFAULT '1', + `quote_number_counter` int(11) DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `clients_account_id_public_id_unique` (`account_id`,`public_id`), KEY `clients_user_id_foreign` (`user_id`), @@ -762,6 +792,7 @@ CREATE TABLE `credits` ( `credit_number` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `private_notes` text COLLATE utf8_unicode_ci NOT NULL, `public_id` int(10) unsigned NOT NULL, + `public_notes` text COLLATE utf8_unicode_ci, PRIMARY KEY (`id`), UNIQUE KEY `credits_account_id_public_id_unique` (`account_id`,`public_id`), KEY `credits_user_id_foreign` (`user_id`), @@ -800,7 +831,7 @@ CREATE TABLE `currencies` ( `code` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `swap_currency_symbol` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -809,7 +840,7 @@ CREATE TABLE `currencies` ( LOCK TABLES `currencies` WRITE; /*!40000 ALTER TABLE `currencies` DISABLE KEYS */; -INSERT INTO `currencies` VALUES (1,'US Dollar','$','2',',','.','USD',0),(2,'British Pound','£','2',',','.','GBP',0),(3,'Euro','€','2','.',',','EUR',0),(4,'South African Rand','R','2','.',',','ZAR',0),(5,'Danish Krone','kr','2','.',',','DKK',1),(6,'Israeli Shekel','NIS ','2',',','.','ILS',0),(7,'Swedish Krona','kr','2','.',',','SEK',1),(8,'Kenyan Shilling','KSh ','2',',','.','KES',0),(9,'Canadian Dollar','C$','2',',','.','CAD',0),(10,'Philippine Peso','P ','2',',','.','PHP',0),(11,'Indian Rupee','Rs. ','2',',','.','INR',0),(12,'Australian Dollar','$','2',',','.','AUD',0),(13,'Singapore Dollar','','2',',','.','SGD',0),(14,'Norske Kroner','kr','2','.',',','NOK',1),(15,'New Zealand Dollar','$','2',',','.','NZD',0),(16,'Vietnamese Dong','','0','.',',','VND',0),(17,'Swiss Franc','','2','\'','.','CHF',0),(18,'Guatemalan Quetzal','Q','2',',','.','GTQ',0),(19,'Malaysian Ringgit','RM','2',',','.','MYR',0),(20,'Brazilian Real','R$','2','.',',','BRL',0),(21,'Thai Baht','','2',',','.','THB',0),(22,'Nigerian Naira','','2',',','.','NGN',0),(23,'Argentine Peso','$','2','.',',','ARS',0),(24,'Bangladeshi Taka','Tk','2',',','.','BDT',0),(25,'United Arab Emirates Dirham','DH ','2',',','.','AED',0),(26,'Hong Kong Dollar','','2',',','.','HKD',0),(27,'Indonesian Rupiah','Rp','2',',','.','IDR',0),(28,'Mexican Peso','$','2',',','.','MXN',0),(29,'Egyptian Pound','E£','2',',','.','EGP',0),(30,'Colombian Peso','$','2','.',',','COP',0),(31,'West African Franc','CFA ','2',',','.','XOF',0),(32,'Chinese Renminbi','RMB ','2',',','.','CNY',0),(33,'Rwandan Franc','RF ','2',',','.','RWF',0),(34,'Tanzanian Shilling','TSh ','2',',','.','TZS',0),(35,'Netherlands Antillean Guilder','','2','.',',','ANG',0),(36,'Trinidad and Tobago Dollar','TT$','2',',','.','TTD',0),(37,'East Caribbean Dollar','EC$','2',',','.','XCD',0),(38,'Ghanaian Cedi','','2',',','.','GHS',0),(39,'Bulgarian Lev','','2',' ','.','BGN',0),(40,'Aruban Florin','Afl. ','2',' ','.','AWG',0),(41,'Turkish Lira','TL ','2','.',',','TRY',0),(42,'Romanian New Leu','','2',',','.','RON',0),(43,'Croatian Kuna','kn','2','.',',','HRK',0),(44,'Saudi Riyal','','2',',','.','SAR',0),(45,'Japanese Yen','¥','0',',','.','JPY',0),(46,'Maldivian Rufiyaa','','2',',','.','MVR',0),(47,'Costa Rican Colón','','2',',','.','CRC',0),(48,'Pakistani Rupee','Rs ','0',',','.','PKR',0),(49,'Polish Zloty','zł','2',' ',',','PLN',1),(50,'Sri Lankan Rupee','LKR','2',',','.','LKR',1),(51,'Czech Koruna','Kč','2',' ',',','CZK',1),(52,'Uruguayan Peso','$','2','.',',','UYU',0),(53,'Namibian Dollar','$','2',',','.','NAD',0),(54,'Tunisian Dinar','','2',',','.','TND',0),(55,'Russian Ruble','','2',',','.','RUB',0),(56,'Mozambican Metical','MT','2','.',',','MZN',1),(57,'Omani Rial','','2',',','.','OMR',0); +INSERT INTO `currencies` VALUES (1,'US Dollar','$','2',',','.','USD',0),(2,'British Pound','£','2',',','.','GBP',0),(3,'Euro','€','2','.',',','EUR',0),(4,'South African Rand','R','2','.',',','ZAR',0),(5,'Danish Krone','kr','2','.',',','DKK',1),(6,'Israeli Shekel','NIS ','2',',','.','ILS',0),(7,'Swedish Krona','kr','2','.',',','SEK',1),(8,'Kenyan Shilling','KSh ','2',',','.','KES',0),(9,'Canadian Dollar','C$','2',',','.','CAD',0),(10,'Philippine Peso','P ','2',',','.','PHP',0),(11,'Indian Rupee','Rs. ','2',',','.','INR',0),(12,'Australian Dollar','$','2',',','.','AUD',0),(13,'Singapore Dollar','','2',',','.','SGD',0),(14,'Norske Kroner','kr','2','.',',','NOK',1),(15,'New Zealand Dollar','$','2',',','.','NZD',0),(16,'Vietnamese Dong','','0','.',',','VND',0),(17,'Swiss Franc','','2','\'','.','CHF',0),(18,'Guatemalan Quetzal','Q','2',',','.','GTQ',0),(19,'Malaysian Ringgit','RM','2',',','.','MYR',0),(20,'Brazilian Real','R$','2','.',',','BRL',0),(21,'Thai Baht','','2',',','.','THB',0),(22,'Nigerian Naira','','2',',','.','NGN',0),(23,'Argentine Peso','$','2','.',',','ARS',0),(24,'Bangladeshi Taka','Tk','2',',','.','BDT',0),(25,'United Arab Emirates Dirham','DH ','2',',','.','AED',0),(26,'Hong Kong Dollar','','2',',','.','HKD',0),(27,'Indonesian Rupiah','Rp','2',',','.','IDR',0),(28,'Mexican Peso','$','2',',','.','MXN',0),(29,'Egyptian Pound','E£','2',',','.','EGP',0),(30,'Colombian Peso','$','2','.',',','COP',0),(31,'West African Franc','CFA ','2',',','.','XOF',0),(32,'Chinese Renminbi','RMB ','2',',','.','CNY',0),(33,'Rwandan Franc','RF ','2',',','.','RWF',0),(34,'Tanzanian Shilling','TSh ','2',',','.','TZS',0),(35,'Netherlands Antillean Guilder','','2','.',',','ANG',0),(36,'Trinidad and Tobago Dollar','TT$','2',',','.','TTD',0),(37,'East Caribbean Dollar','EC$','2',',','.','XCD',0),(38,'Ghanaian Cedi','','2',',','.','GHS',0),(39,'Bulgarian Lev','','2',' ','.','BGN',0),(40,'Aruban Florin','Afl. ','2',' ','.','AWG',0),(41,'Turkish Lira','TL ','2','.',',','TRY',0),(42,'Romanian New Leu','','2',',','.','RON',0),(43,'Croatian Kuna','kn','2','.',',','HRK',0),(44,'Saudi Riyal','','2',',','.','SAR',0),(45,'Japanese Yen','¥','0',',','.','JPY',0),(46,'Maldivian Rufiyaa','','2',',','.','MVR',0),(47,'Costa Rican Colón','','2',',','.','CRC',0),(48,'Pakistani Rupee','Rs ','0',',','.','PKR',0),(49,'Polish Zloty','zł','2',' ',',','PLN',1),(50,'Sri Lankan Rupee','LKR','2',',','.','LKR',1),(51,'Czech Koruna','Kč','2',' ',',','CZK',1),(52,'Uruguayan Peso','$','2','.',',','UYU',0),(53,'Namibian Dollar','$','2',',','.','NAD',0),(54,'Tunisian Dinar','','2',',','.','TND',0),(55,'Russian Ruble','','2',',','.','RUB',0),(56,'Mozambican Metical','MT','2','.',',','MZN',1),(57,'Omani Rial','','2',',','.','OMR',0),(58,'Ukrainian Hryvnia','','2',',','.','UAH',0); /*!40000 ALTER TABLE `currencies` ENABLE KEYS */; UNLOCK TABLES; @@ -1145,7 +1176,7 @@ CREATE TABLE `gateways` ( LOCK TABLES `gateways` WRITE; /*!40000 ALTER TABLE `gateways` DISABLE KEYS */; -INSERT INTO `gateways` VALUES (1,'2017-02-27 13:59:53','2017-02-27 13:59:53','Authorize.Net AIM','AuthorizeNet_AIM',1,1,4,0,NULL,0,0),(2,'2017-02-27 13:59:53','2017-02-27 13:59:53','Authorize.Net SIM','AuthorizeNet_SIM',1,2,10000,0,NULL,0,0),(3,'2017-02-27 13:59:53','2017-02-27 13:59:53','CardSave','CardSave',1,1,10000,0,NULL,0,0),(4,'2017-02-27 13:59:53','2017-02-27 13:59:53','Eway Rapid','Eway_RapidShared',1,1,10000,0,NULL,1,0),(5,'2017-02-27 13:59:53','2017-02-27 13:59:53','FirstData Connect','FirstData_Connect',1,1,10000,0,NULL,0,0),(6,'2017-02-27 13:59:53','2017-02-27 13:59:53','GoCardless','GoCardless',1,1,10000,0,NULL,1,0),(7,'2017-02-27 13:59:53','2017-02-27 13:59:53','Migs ThreeParty','Migs_ThreeParty',1,1,10000,0,NULL,0,0),(8,'2017-02-27 13:59:53','2017-02-27 13:59:53','Migs TwoParty','Migs_TwoParty',1,1,10000,0,NULL,0,0),(9,'2017-02-27 13:59:53','2017-02-27 13:59:53','Mollie','Mollie',1,1,7,0,NULL,1,0),(10,'2017-02-27 13:59:53','2017-02-27 13:59:53','MultiSafepay','MultiSafepay',1,1,10000,0,NULL,0,0),(11,'2017-02-27 13:59:53','2017-02-27 13:59:53','Netaxept','Netaxept',1,1,10000,0,NULL,0,0),(12,'2017-02-27 13:59:53','2017-02-27 13:59:53','NetBanx','NetBanx',1,1,10000,0,NULL,0,0),(13,'2017-02-27 13:59:53','2017-02-27 13:59:53','PayFast','PayFast',1,1,10000,0,NULL,1,0),(14,'2017-02-27 13:59:53','2017-02-27 13:59:53','Payflow Pro','Payflow_Pro',1,1,10000,0,NULL,0,0),(15,'2017-02-27 13:59:53','2017-02-27 13:59:53','PaymentExpress PxPay','PaymentExpress_PxPay',1,1,10000,0,NULL,0,0),(16,'2017-02-27 13:59:53','2017-02-27 13:59:53','PaymentExpress PxPost','PaymentExpress_PxPost',1,1,10000,0,NULL,0,0),(17,'2017-02-27 13:59:53','2017-02-27 13:59:53','PayPal Express','PayPal_Express',1,1,3,0,NULL,1,0),(18,'2017-02-27 13:59:53','2017-02-27 13:59:53','PayPal Pro','PayPal_Pro',1,1,10000,0,NULL,0,0),(19,'2017-02-27 13:59:53','2017-02-27 13:59:53','Pin','Pin',1,1,10000,0,NULL,0,0),(20,'2017-02-27 13:59:53','2017-02-27 13:59:53','SagePay Direct','SagePay_Direct',1,1,10000,0,NULL,0,0),(21,'2017-02-27 13:59:53','2017-02-27 13:59:53','SagePay Server','SagePay_Server',1,1,10000,0,NULL,0,0),(22,'2017-02-27 13:59:53','2017-02-27 13:59:53','SecurePay DirectPost','SecurePay_DirectPost',1,1,10000,0,NULL,0,0),(23,'2017-02-27 13:59:53','2017-02-27 13:59:53','Stripe','Stripe',1,1,1,0,NULL,0,0),(24,'2017-02-27 13:59:53','2017-02-27 13:59:53','TargetPay Direct eBanking','TargetPay_Directebanking',1,1,10000,0,NULL,0,0),(25,'2017-02-27 13:59:53','2017-02-27 13:59:53','TargetPay Ideal','TargetPay_Ideal',1,1,10000,0,NULL,0,0),(26,'2017-02-27 13:59:53','2017-02-27 13:59:53','TargetPay Mr Cash','TargetPay_Mrcash',1,1,10000,0,NULL,0,0),(27,'2017-02-27 13:59:53','2017-02-27 13:59:53','TwoCheckout','TwoCheckout',1,1,10000,0,NULL,1,0),(28,'2017-02-27 13:59:53','2017-02-27 13:59:53','WorldPay','WorldPay',1,1,10000,0,NULL,0,0),(29,'2017-02-27 13:59:53','2017-02-27 13:59:53','BeanStream','BeanStream',1,2,10000,0,NULL,0,0),(30,'2017-02-27 13:59:53','2017-02-27 13:59:53','Psigate','Psigate',1,2,10000,0,NULL,0,0),(31,'2017-02-27 13:59:53','2017-02-27 13:59:53','moolah','AuthorizeNet_AIM',1,1,10000,0,NULL,0,0),(32,'2017-02-27 13:59:53','2017-02-27 13:59:53','Alipay','Alipay_Express',1,1,10000,0,NULL,0,0),(33,'2017-02-27 13:59:53','2017-02-27 13:59:53','Buckaroo','Buckaroo_CreditCard',1,1,10000,0,NULL,0,0),(34,'2017-02-27 13:59:53','2017-02-27 13:59:53','Coinbase','Coinbase',1,1,10000,0,NULL,0,0),(35,'2017-02-27 13:59:53','2017-02-27 13:59:53','DataCash','DataCash',1,1,10000,0,NULL,0,0),(36,'2017-02-27 13:59:53','2017-02-27 13:59:53','Neteller','Neteller',1,2,10000,0,NULL,0,0),(37,'2017-02-27 13:59:53','2017-02-27 13:59:53','Pacnet','Pacnet',1,1,10000,0,NULL,0,0),(38,'2017-02-27 13:59:53','2017-02-27 13:59:53','PaymentSense','PaymentSense',1,2,10000,0,NULL,0,0),(39,'2017-02-27 13:59:53','2017-02-27 13:59:53','Realex','Realex_Remote',1,1,10000,0,NULL,0,0),(40,'2017-02-27 13:59:53','2017-02-27 13:59:53','Sisow','Sisow',1,1,10000,0,NULL,0,0),(41,'2017-02-27 13:59:53','2017-02-27 13:59:53','Skrill','Skrill',1,1,10000,0,NULL,1,0),(42,'2017-02-27 13:59:53','2017-02-27 13:59:53','BitPay','BitPay',1,1,6,0,NULL,1,0),(43,'2017-02-27 13:59:53','2017-02-27 13:59:53','Dwolla','Dwolla',1,1,5,0,NULL,1,0),(44,'2017-02-27 13:59:53','2017-02-27 13:59:53','AGMS','Agms',1,1,10000,0,NULL,0,0),(45,'2017-02-27 13:59:53','2017-02-27 13:59:53','Barclays','BarclaysEpdq\\Essential',1,1,10000,0,NULL,0,0),(46,'2017-02-27 13:59:53','2017-02-27 13:59:53','Cardgate','Cardgate',1,1,10000,0,NULL,0,0),(47,'2017-02-27 13:59:53','2017-02-27 13:59:53','Checkout.com','CheckoutCom',1,1,10000,0,NULL,0,0),(48,'2017-02-27 13:59:53','2017-02-27 13:59:53','Creditcall','Creditcall',1,1,10000,0,NULL,0,0),(49,'2017-02-27 13:59:53','2017-02-27 13:59:53','Cybersource','Cybersource',1,1,10000,0,NULL,0,0),(50,'2017-02-27 13:59:53','2017-02-27 13:59:53','ecoPayz','Ecopayz',1,1,10000,0,NULL,0,0),(51,'2017-02-27 13:59:53','2017-02-27 13:59:53','Fasapay','Fasapay',1,1,10000,0,NULL,0,0),(52,'2017-02-27 13:59:53','2017-02-27 13:59:53','Komoju','Komoju',1,1,10000,0,NULL,0,0),(53,'2017-02-27 13:59:53','2017-02-27 13:59:53','Multicards','Multicards',1,1,10000,0,NULL,0,0),(54,'2017-02-27 13:59:53','2017-02-27 13:59:53','Pagar.Me','Pagarme',1,2,10000,0,NULL,0,0),(55,'2017-02-27 13:59:53','2017-02-27 13:59:53','Paysafecard','Paysafecard',1,1,10000,0,NULL,0,0),(56,'2017-02-27 13:59:53','2017-02-27 13:59:53','Paytrace','Paytrace_CreditCard',1,1,10000,0,NULL,0,0),(57,'2017-02-27 13:59:53','2017-02-27 13:59:53','Secure Trading','SecureTrading',1,1,10000,0,NULL,0,0),(58,'2017-02-27 13:59:53','2017-02-27 13:59:53','SecPay','SecPay',1,1,10000,0,NULL,0,0),(59,'2017-02-27 13:59:53','2017-02-27 13:59:53','WeChat Express','WeChat_Express',1,2,10000,0,NULL,0,0),(60,'2017-02-27 13:59:53','2017-02-27 13:59:53','WePay','WePay',1,1,10000,0,NULL,0,0),(61,'2017-02-27 13:59:53','2017-02-27 13:59:53','Braintree','Braintree',1,1,2,0,NULL,0,0),(62,'2017-02-27 13:59:53','2017-02-27 13:59:53','Custom','Custom',1,1,8,0,NULL,1,0); +INSERT INTO `gateways` VALUES (1,'2017-04-02 16:31:14','2017-04-02 16:31:14','Authorize.Net AIM','AuthorizeNet_AIM',1,1,4,0,NULL,0,0),(2,'2017-04-02 16:31:14','2017-04-02 16:31:14','Authorize.Net SIM','AuthorizeNet_SIM',1,2,10000,0,NULL,0,0),(3,'2017-04-02 16:31:14','2017-04-02 16:31:14','CardSave','CardSave',1,1,10000,0,NULL,0,0),(4,'2017-04-02 16:31:14','2017-04-02 16:31:14','Eway Rapid','Eway_RapidShared',1,1,10000,0,NULL,1,0),(5,'2017-04-02 16:31:14','2017-04-02 16:31:14','FirstData Connect','FirstData_Connect',1,1,10000,0,NULL,0,0),(6,'2017-04-02 16:31:14','2017-04-02 16:31:14','GoCardless','GoCardless',1,1,10000,0,NULL,1,0),(7,'2017-04-02 16:31:14','2017-04-02 16:31:14','Migs ThreeParty','Migs_ThreeParty',1,1,10000,0,NULL,0,0),(8,'2017-04-02 16:31:14','2017-04-02 16:31:14','Migs TwoParty','Migs_TwoParty',1,1,10000,0,NULL,0,0),(9,'2017-04-02 16:31:14','2017-04-02 16:31:14','Mollie','Mollie',1,1,7,0,NULL,1,0),(10,'2017-04-02 16:31:14','2017-04-02 16:31:14','MultiSafepay','MultiSafepay',1,1,10000,0,NULL,0,0),(11,'2017-04-02 16:31:14','2017-04-02 16:31:14','Netaxept','Netaxept',1,1,10000,0,NULL,0,0),(12,'2017-04-02 16:31:14','2017-04-02 16:31:14','NetBanx','NetBanx',1,1,10000,0,NULL,0,0),(13,'2017-04-02 16:31:14','2017-04-02 16:31:14','PayFast','PayFast',1,1,10000,0,NULL,1,0),(14,'2017-04-02 16:31:14','2017-04-02 16:31:14','Payflow Pro','Payflow_Pro',1,1,10000,0,NULL,0,0),(15,'2017-04-02 16:31:14','2017-04-02 16:31:14','PaymentExpress PxPay','PaymentExpress_PxPay',1,1,10000,0,NULL,0,0),(16,'2017-04-02 16:31:14','2017-04-02 16:31:14','PaymentExpress PxPost','PaymentExpress_PxPost',1,1,10000,0,NULL,0,0),(17,'2017-04-02 16:31:14','2017-04-02 16:31:14','PayPal Express','PayPal_Express',1,1,3,0,NULL,1,0),(18,'2017-04-02 16:31:14','2017-04-02 16:31:14','PayPal Pro','PayPal_Pro',1,1,10000,0,NULL,0,0),(19,'2017-04-02 16:31:14','2017-04-02 16:31:14','Pin','Pin',1,1,10000,0,NULL,0,0),(20,'2017-04-02 16:31:14','2017-04-02 16:31:14','SagePay Direct','SagePay_Direct',1,1,10000,0,NULL,0,0),(21,'2017-04-02 16:31:14','2017-04-02 16:31:14','SagePay Server','SagePay_Server',1,1,10000,0,NULL,0,0),(22,'2017-04-02 16:31:14','2017-04-02 16:31:14','SecurePay DirectPost','SecurePay_DirectPost',1,1,10000,0,NULL,0,0),(23,'2017-04-02 16:31:14','2017-04-02 16:31:14','Stripe','Stripe',1,1,1,0,NULL,0,0),(24,'2017-04-02 16:31:14','2017-04-02 16:31:14','TargetPay Direct eBanking','TargetPay_Directebanking',1,1,10000,0,NULL,0,0),(25,'2017-04-02 16:31:14','2017-04-02 16:31:14','TargetPay Ideal','TargetPay_Ideal',1,1,10000,0,NULL,0,0),(26,'2017-04-02 16:31:14','2017-04-02 16:31:14','TargetPay Mr Cash','TargetPay_Mrcash',1,1,10000,0,NULL,0,0),(27,'2017-04-02 16:31:14','2017-04-02 16:31:14','TwoCheckout','TwoCheckout',1,1,10000,0,NULL,1,0),(28,'2017-04-02 16:31:14','2017-04-02 16:31:14','WorldPay','WorldPay',1,1,10000,0,NULL,0,0),(29,'2017-04-02 16:31:14','2017-04-02 16:31:14','BeanStream','BeanStream',1,2,10000,0,NULL,0,0),(30,'2017-04-02 16:31:14','2017-04-02 16:31:14','Psigate','Psigate',1,2,10000,0,NULL,0,0),(31,'2017-04-02 16:31:14','2017-04-02 16:31:14','moolah','AuthorizeNet_AIM',1,1,10000,0,NULL,0,0),(32,'2017-04-02 16:31:14','2017-04-02 16:31:14','Alipay','Alipay_Express',1,1,10000,0,NULL,0,0),(33,'2017-04-02 16:31:14','2017-04-02 16:31:14','Buckaroo','Buckaroo_CreditCard',1,1,10000,0,NULL,0,0),(34,'2017-04-02 16:31:14','2017-04-02 16:31:14','Coinbase','Coinbase',1,1,10000,0,NULL,0,0),(35,'2017-04-02 16:31:14','2017-04-02 16:31:14','DataCash','DataCash',1,1,10000,0,NULL,0,0),(36,'2017-04-02 16:31:14','2017-04-02 16:31:14','Neteller','Neteller',1,2,10000,0,NULL,0,0),(37,'2017-04-02 16:31:14','2017-04-02 16:31:14','Pacnet','Pacnet',1,1,10000,0,NULL,0,0),(38,'2017-04-02 16:31:14','2017-04-02 16:31:14','PaymentSense','PaymentSense',1,2,10000,0,NULL,0,0),(39,'2017-04-02 16:31:14','2017-04-02 16:31:14','Realex','Realex_Remote',1,1,10000,0,NULL,0,0),(40,'2017-04-02 16:31:14','2017-04-02 16:31:14','Sisow','Sisow',1,1,10000,0,NULL,0,0),(41,'2017-04-02 16:31:14','2017-04-02 16:31:14','Skrill','Skrill',1,1,10000,0,NULL,1,0),(42,'2017-04-02 16:31:14','2017-04-02 16:31:14','BitPay','BitPay',1,1,6,0,NULL,1,0),(43,'2017-04-02 16:31:14','2017-04-02 16:31:14','Dwolla','Dwolla',1,1,5,0,NULL,1,0),(44,'2017-04-02 16:31:14','2017-04-02 16:31:14','AGMS','Agms',1,1,10000,0,NULL,0,0),(45,'2017-04-02 16:31:14','2017-04-02 16:31:14','Barclays','BarclaysEpdq\\Essential',1,1,10000,0,NULL,0,0),(46,'2017-04-02 16:31:14','2017-04-02 16:31:14','Cardgate','Cardgate',1,1,10000,0,NULL,0,0),(47,'2017-04-02 16:31:14','2017-04-02 16:31:14','Checkout.com','CheckoutCom',1,1,10000,0,NULL,0,0),(48,'2017-04-02 16:31:14','2017-04-02 16:31:14','Creditcall','Creditcall',1,1,10000,0,NULL,0,0),(49,'2017-04-02 16:31:14','2017-04-02 16:31:14','Cybersource','Cybersource',1,1,10000,0,NULL,0,0),(50,'2017-04-02 16:31:14','2017-04-02 16:31:14','ecoPayz','Ecopayz',1,1,10000,0,NULL,0,0),(51,'2017-04-02 16:31:14','2017-04-02 16:31:14','Fasapay','Fasapay',1,1,10000,0,NULL,0,0),(52,'2017-04-02 16:31:14','2017-04-02 16:31:14','Komoju','Komoju',1,1,10000,0,NULL,0,0),(53,'2017-04-02 16:31:14','2017-04-02 16:31:14','Multicards','Multicards',1,1,10000,0,NULL,0,0),(54,'2017-04-02 16:31:14','2017-04-02 16:31:14','Pagar.Me','Pagarme',1,2,10000,0,NULL,0,0),(55,'2017-04-02 16:31:14','2017-04-02 16:31:14','Paysafecard','Paysafecard',1,1,10000,0,NULL,0,0),(56,'2017-04-02 16:31:14','2017-04-02 16:31:14','Paytrace','Paytrace_CreditCard',1,1,10000,0,NULL,0,0),(57,'2017-04-02 16:31:14','2017-04-02 16:31:14','Secure Trading','SecureTrading',1,1,10000,0,NULL,0,0),(58,'2017-04-02 16:31:14','2017-04-02 16:31:14','SecPay','SecPay',1,1,10000,0,NULL,0,0),(59,'2017-04-02 16:31:14','2017-04-02 16:31:14','WeChat Express','WeChat_Express',1,2,10000,0,NULL,0,0),(60,'2017-04-02 16:31:14','2017-04-02 16:31:14','WePay','WePay',1,1,10000,0,NULL,0,0),(61,'2017-04-02 16:31:14','2017-04-02 16:31:14','Braintree','Braintree',1,1,2,0,NULL,0,0),(62,'2017-04-02 16:31:14','2017-04-02 16:31:14','Custom','Custom',1,1,8,0,NULL,1,0); /*!40000 ALTER TABLE `gateways` ENABLE KEYS */; UNLOCK TABLES; @@ -1512,7 +1543,7 @@ CREATE TABLE `migrations` ( LOCK TABLES `migrations` WRITE; /*!40000 ALTER TABLE `migrations` DISABLE KEYS */; -INSERT INTO `migrations` VALUES ('2013_11_05_180133_confide_setup_users_table',1),('2013_11_28_195703_setup_countries_table',1),('2014_02_13_151500_add_cascase_drops',1),('2014_02_19_151817_add_support_for_invoice_designs',1),('2014_03_03_155556_add_phone_to_account',1),('2014_03_19_201454_add_language_support',1),('2014_03_20_200300_create_payment_libraries',1),('2014_03_23_051736_enable_forcing_jspdf',1),('2014_03_25_102200_add_sort_and_recommended_to_gateways',1),('2014_04_03_191105_add_pro_plan',1),('2014_04_17_100523_add_remember_token',1),('2014_04_17_145108_add_custom_fields',1),('2014_04_23_170909_add_products_settings',1),('2014_04_29_174315_add_advanced_settings',1),('2014_05_17_175626_add_quotes',1),('2014_06_17_131940_add_accepted_credit_cards_to_account_gateways',1),('2014_07_13_142654_one_click_install',1),('2014_07_17_205900_support_hiding_quantity',1),('2014_07_24_171214_add_zapier_support',1),('2014_10_01_141248_add_company_vat_number',1),('2014_10_05_141856_track_last_seen_message',1),('2014_10_06_103529_add_timesheets',1),('2014_10_06_195330_add_invoice_design_table',1),('2014_10_13_054100_add_invoice_number_settings',1),('2014_10_14_225227_add_danish_translation',1),('2014_10_22_174452_add_affiliate_price',1),('2014_10_30_184126_add_company_id_number',1),('2014_11_04_200406_allow_null_client_currency',1),('2014_12_03_154119_add_discount_type',1),('2015_02_12_102940_add_email_templates',1),('2015_02_17_131714_support_token_billing',1),('2015_02_27_081836_add_invoice_footer',1),('2015_03_03_140259_add_tokens',1),('2015_03_09_151011_add_ip_to_activity',1),('2015_03_15_174122_add_pdf_email_attachment_option',1),('2015_03_30_100000_create_password_resets_table',1),('2015_04_12_093447_add_sv_language',1),('2015_04_13_100333_add_notify_approved',1),('2015_04_16_122647_add_partial_amount_to_invoices',1),('2015_05_21_184104_add_font_size',1),('2015_05_27_121828_add_tasks',1),('2015_05_27_170808_add_custom_invoice_labels',1),('2015_06_09_134208_add_has_tasks_to_invoices',1),('2015_06_14_093410_enable_resuming_tasks',1),('2015_06_14_173025_multi_company_support',1),('2015_07_07_160257_support_locking_account',1),('2015_07_08_114333_simplify_tasks',1),('2015_07_19_081332_add_custom_design',1),('2015_07_27_183830_add_pdfmake_support',1),('2015_08_13_084041_add_formats_to_datetime_formats_table',1),('2015_09_04_080604_add_swap_postal_code',1),('2015_09_07_135935_add_account_domain',1),('2015_09_10_185135_add_reminder_emails',1),('2015_10_07_135651_add_social_login',1),('2015_10_21_075058_add_default_tax_rates',1),('2015_10_21_185724_add_invoice_number_pattern',1),('2015_10_27_180214_add_is_system_to_activities',1),('2015_10_29_133747_add_default_quote_terms',1),('2015_11_01_080417_encrypt_tokens',1),('2015_11_03_181318_improve_currency_localization',1),('2015_11_30_133206_add_email_designs',1),('2015_12_27_154513_add_reminder_settings',1),('2015_12_30_042035_add_client_view_css',1),('2016_01_04_175228_create_vendors_table',1),('2016_01_06_153144_add_invoice_font_support',1),('2016_01_17_155725_add_quote_to_invoice_option',1),('2016_01_18_195351_add_bank_accounts',1),('2016_01_24_112646_add_bank_subaccounts',1),('2016_01_27_173015_add_header_footer_option',1),('2016_02_01_135956_add_source_currency_to_expenses',1),('2016_02_25_152948_add_client_password',1),('2016_02_28_081424_add_custom_invoice_fields',1),('2016_03_14_066181_add_user_permissions',1),('2016_03_14_214710_add_support_three_decimal_taxes',1),('2016_03_22_168362_add_documents',1),('2016_03_23_215049_support_multiple_tax_rates',1),('2016_04_16_103943_enterprise_plan',1),('2016_04_18_174135_add_page_size',1),('2016_04_23_182223_payments_changes',1),('2016_05_16_102925_add_swap_currency_symbol_to_currency',1),('2016_05_18_085739_add_invoice_type_support',1),('2016_05_24_164847_wepay_ach',1),('2016_07_08_083802_support_new_pricing',1),('2016_07_13_083821_add_buy_now_buttons',1),('2016_08_10_184027_add_support_for_bots',1),('2016_09_05_150625_create_gateway_types',1),('2016_10_20_191150_add_expense_to_activities',1),('2016_11_03_113316_add_invoice_signature',1),('2016_11_03_161149_add_bluevine_fields',1),('2016_11_28_092904_add_task_projects',1),('2016_12_13_113955_add_pro_plan_discount',1),('2017_01_01_214241_add_inclusive_taxes',1),('2017_02_23_095934_add_custom_product_fields',1); +INSERT INTO `migrations` VALUES ('2013_11_05_180133_confide_setup_users_table',1),('2013_11_28_195703_setup_countries_table',1),('2014_02_13_151500_add_cascase_drops',1),('2014_02_19_151817_add_support_for_invoice_designs',1),('2014_03_03_155556_add_phone_to_account',1),('2014_03_19_201454_add_language_support',1),('2014_03_20_200300_create_payment_libraries',1),('2014_03_23_051736_enable_forcing_jspdf',1),('2014_03_25_102200_add_sort_and_recommended_to_gateways',1),('2014_04_03_191105_add_pro_plan',1),('2014_04_17_100523_add_remember_token',1),('2014_04_17_145108_add_custom_fields',1),('2014_04_23_170909_add_products_settings',1),('2014_04_29_174315_add_advanced_settings',1),('2014_05_17_175626_add_quotes',1),('2014_06_17_131940_add_accepted_credit_cards_to_account_gateways',1),('2014_07_13_142654_one_click_install',1),('2014_07_17_205900_support_hiding_quantity',1),('2014_07_24_171214_add_zapier_support',1),('2014_10_01_141248_add_company_vat_number',1),('2014_10_05_141856_track_last_seen_message',1),('2014_10_06_103529_add_timesheets',1),('2014_10_06_195330_add_invoice_design_table',1),('2014_10_13_054100_add_invoice_number_settings',1),('2014_10_14_225227_add_danish_translation',1),('2014_10_22_174452_add_affiliate_price',1),('2014_10_30_184126_add_company_id_number',1),('2014_11_04_200406_allow_null_client_currency',1),('2014_12_03_154119_add_discount_type',1),('2015_02_12_102940_add_email_templates',1),('2015_02_17_131714_support_token_billing',1),('2015_02_27_081836_add_invoice_footer',1),('2015_03_03_140259_add_tokens',1),('2015_03_09_151011_add_ip_to_activity',1),('2015_03_15_174122_add_pdf_email_attachment_option',1),('2015_03_30_100000_create_password_resets_table',1),('2015_04_12_093447_add_sv_language',1),('2015_04_13_100333_add_notify_approved',1),('2015_04_16_122647_add_partial_amount_to_invoices',1),('2015_05_21_184104_add_font_size',1),('2015_05_27_121828_add_tasks',1),('2015_05_27_170808_add_custom_invoice_labels',1),('2015_06_09_134208_add_has_tasks_to_invoices',1),('2015_06_14_093410_enable_resuming_tasks',1),('2015_06_14_173025_multi_company_support',1),('2015_07_07_160257_support_locking_account',1),('2015_07_08_114333_simplify_tasks',1),('2015_07_19_081332_add_custom_design',1),('2015_07_27_183830_add_pdfmake_support',1),('2015_08_13_084041_add_formats_to_datetime_formats_table',1),('2015_09_04_080604_add_swap_postal_code',1),('2015_09_07_135935_add_account_domain',1),('2015_09_10_185135_add_reminder_emails',1),('2015_10_07_135651_add_social_login',1),('2015_10_21_075058_add_default_tax_rates',1),('2015_10_21_185724_add_invoice_number_pattern',1),('2015_10_27_180214_add_is_system_to_activities',1),('2015_10_29_133747_add_default_quote_terms',1),('2015_11_01_080417_encrypt_tokens',1),('2015_11_03_181318_improve_currency_localization',1),('2015_11_30_133206_add_email_designs',1),('2015_12_27_154513_add_reminder_settings',1),('2015_12_30_042035_add_client_view_css',1),('2016_01_04_175228_create_vendors_table',1),('2016_01_06_153144_add_invoice_font_support',1),('2016_01_17_155725_add_quote_to_invoice_option',1),('2016_01_18_195351_add_bank_accounts',1),('2016_01_24_112646_add_bank_subaccounts',1),('2016_01_27_173015_add_header_footer_option',1),('2016_02_01_135956_add_source_currency_to_expenses',1),('2016_02_25_152948_add_client_password',1),('2016_02_28_081424_add_custom_invoice_fields',1),('2016_03_14_066181_add_user_permissions',1),('2016_03_14_214710_add_support_three_decimal_taxes',1),('2016_03_22_168362_add_documents',1),('2016_03_23_215049_support_multiple_tax_rates',1),('2016_04_16_103943_enterprise_plan',1),('2016_04_18_174135_add_page_size',1),('2016_04_23_182223_payments_changes',1),('2016_05_16_102925_add_swap_currency_symbol_to_currency',1),('2016_05_18_085739_add_invoice_type_support',1),('2016_05_24_164847_wepay_ach',1),('2016_07_08_083802_support_new_pricing',1),('2016_07_13_083821_add_buy_now_buttons',1),('2016_08_10_184027_add_support_for_bots',1),('2016_09_05_150625_create_gateway_types',1),('2016_10_20_191150_add_expense_to_activities',1),('2016_11_03_113316_add_invoice_signature',1),('2016_11_03_161149_add_bluevine_fields',1),('2016_11_28_092904_add_task_projects',1),('2016_12_13_113955_add_pro_plan_discount',1),('2017_01_01_214241_add_inclusive_taxes',1),('2017_02_23_095934_add_custom_product_fields',1),('2017_03_16_085702_add_gateway_fee_location',1); /*!40000 ALTER TABLE `migrations` ENABLE KEYS */; UNLOCK TABLES; @@ -1563,7 +1594,7 @@ CREATE TABLE `payment_libraries` ( LOCK TABLES `payment_libraries` WRITE; /*!40000 ALTER TABLE `payment_libraries` DISABLE KEYS */; -INSERT INTO `payment_libraries` VALUES (1,'2017-02-27 13:59:51','2017-02-27 13:59:51','Omnipay',1),(2,'2017-02-27 13:59:51','2017-02-27 13:59:51','PHP-Payments [Deprecated]',1); +INSERT INTO `payment_libraries` VALUES (1,'2017-04-02 16:31:13','2017-04-02 16:31:13','Omnipay',1),(2,'2017-04-02 16:31:13','2017-04-02 16:31:13','PHP-Payments [Deprecated]',1); /*!40000 ALTER TABLE `payment_libraries` ENABLE KEYS */; UNLOCK TABLES; @@ -1579,7 +1610,7 @@ CREATE TABLE `payment_methods` ( `account_id` int(10) unsigned NOT NULL, `user_id` int(10) unsigned NOT NULL, `contact_id` int(10) unsigned DEFAULT NULL, - `account_gateway_token_id` int(10) unsigned NOT NULL, + `account_gateway_token_id` int(10) unsigned DEFAULT NULL, `payment_type_id` int(10) unsigned NOT NULL, `source_reference` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `routing_number` int(10) unsigned DEFAULT NULL, @@ -1599,10 +1630,10 @@ CREATE TABLE `payment_methods` ( KEY `payment_methods_public_id_index` (`public_id`), KEY `payment_methods_user_id_foreign` (`user_id`), KEY `payment_methods_contact_id_foreign` (`contact_id`), - KEY `payment_methods_account_gateway_token_id_foreign` (`account_gateway_token_id`), KEY `payment_methods_payment_type_id_foreign` (`payment_type_id`), KEY `payment_methods_currency_id_foreign` (`currency_id`), - CONSTRAINT `payment_methods_account_gateway_token_id_foreign` FOREIGN KEY (`account_gateway_token_id`) REFERENCES `account_gateway_tokens` (`id`), + KEY `payment_methods_account_gateway_token_id_foreign` (`account_gateway_token_id`), + CONSTRAINT `payment_methods_account_gateway_token_id_foreign` FOREIGN KEY (`account_gateway_token_id`) REFERENCES `account_gateway_tokens` (`id`) ON DELETE CASCADE, CONSTRAINT `payment_methods_account_id_foreign` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, CONSTRAINT `payment_methods_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE CASCADE, CONSTRAINT `payment_methods_currency_id_foreign` FOREIGN KEY (`currency_id`) REFERENCES `currencies` (`id`), @@ -1673,7 +1704,7 @@ CREATE TABLE `payment_terms` ( LOCK TABLES `payment_terms` WRITE; /*!40000 ALTER TABLE `payment_terms` DISABLE KEYS */; -INSERT INTO `payment_terms` VALUES (1,7,'Net 7','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,1),(2,10,'Net 10','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,2),(3,14,'Net 14','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,3),(4,15,'Net 15','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,4),(5,30,'Net 30','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,5),(6,60,'Net 60','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,6),(7,90,'Net 90','2017-02-27 13:59:51','2017-02-27 13:59:51',NULL,0,0,7),(8,-1,'Net 0','2017-02-27 13:59:55','2017-02-27 13:59:55',NULL,0,0,0); +INSERT INTO `payment_terms` VALUES (1,7,'Net 7','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,1),(2,10,'Net 10','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,2),(3,14,'Net 14','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,3),(4,15,'Net 15','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,4),(5,30,'Net 30','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,5),(6,60,'Net 60','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,6),(7,90,'Net 90','2017-04-02 16:31:13','2017-04-02 16:31:13',NULL,0,0,7),(8,-1,'Net 0','2017-04-02 16:31:16','2017-04-02 16:31:16',NULL,0,0,0); /*!40000 ALTER TABLE `payment_terms` ENABLE KEYS */; UNLOCK TABLES; @@ -1758,7 +1789,7 @@ CREATE TABLE `payments` ( CONSTRAINT `payments_client_id_foreign` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE, CONSTRAINT `payments_contact_id_foreign` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE CASCADE, CONSTRAINT `payments_invoice_id_foreign` FOREIGN KEY (`invoice_id`) REFERENCES `invoices` (`id`) ON DELETE CASCADE, - CONSTRAINT `payments_payment_method_id_foreign` FOREIGN KEY (`payment_method_id`) REFERENCES `payment_methods` (`id`), + CONSTRAINT `payments_payment_method_id_foreign` FOREIGN KEY (`payment_method_id`) REFERENCES `payment_methods` (`id`) ON DELETE CASCADE, CONSTRAINT `payments_payment_status_id_foreign` FOREIGN KEY (`payment_status_id`) REFERENCES `payment_statuses` (`id`), CONSTRAINT `payments_payment_type_id_foreign` FOREIGN KEY (`payment_type_id`) REFERENCES `payment_types` (`id`), CONSTRAINT `payments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE @@ -2268,4 +2299,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2017-02-27 17:59:55 +-- Dump completed on 2017-04-02 22:31:16 diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 000000000..ea6b34b62 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,107 @@ +API +=== + +Invoice Ninja provides a REST based API, `click here `_ to see the full list of methods available. + +To access the API you first need to create a token using the "Tokens” page under "Advanced Settings”. + +- **Zapier** [hosted or self-host]: https://zapier.com/zapbook/invoice-ninja/ +- **PHP SDK**: https://github.com/invoiceninja/sdk-php + +.. NOTE:: Replace ninja.dev with https://app.invoiceninja.com to access a hosted account. + +Reading Data +"""""""""""" + +Here’s an example of reading the list of clients using cURL from the command line. + +.. code-block:: shell + + curl -X GET ninja.dev/api/v1/clients -H "X-Ninja-Token: TOKEN" + +For invoices, quotes, tasks and payments simply change the object type. + +.. code-block:: shell + + curl -X GET ninja.dev/api/v1/invoices -H "X-Ninja-Token: TOKEN" + +You can search clients by their email address or id number and invoices by their invoice number. + +.. code-block:: shell + + curl -X GET ninja.dev/api/v1/clients?email= -H "X-Ninja-Token: TOKEN" + curl -X GET ninja.dev/api/v1/clients?id_number= -H "X-Ninja-Token: TOKEN" + curl -X GET ninja.dev/api/v1/invoices?invoice_number= -H "X-Ninja-Token: TOKEN" + +To load a single record specify the Id in the URL. + +.. code-block:: shell + + curl -X GET ninja.dev/api/v1/invoices/1 -H "X-Ninja-Token: TOKEN" + +You can specify additional relationships to load using the ``include`` parameter. + +.. code-block:: shell + + curl -X GET ninja.dev/api/v1/clients/1?include=invoices.invitations -H "X-Ninja-Token: TOKEN" + +You can download a PDF using the following URL + +.. code-block:: shell + + curl -X GET ninja.dev/api/v1/download/1 -H "X-Ninja-Token: TOKEN" + +Optional Settings +""""""""""""""""" + +The following are optional query parameter settings: + +- ``serializer``: Either array (the default) or `JSON `_. +- ``include``: A comma-separated list of nested relationships to include. +- ``client_id``: If set the results will be filtered by the client. +- ``page``: The page number of results to return when the results are paginated. +- ``per_page``: The number of results to return per page. +- ``updated_at``: Timestamp used as a filter to only show recently updated records. + +Creating Data +""""""""""""" + +.. TIP:: Add ``-H "X-Requested-With: XMLHttpRequest"`` to see validation errors in the response. + +Here’s an example of creating a client. Note that email address is a property of the client’s contact not the client itself. + +.. code-block:: shell + + curl -X POST ninja.dev/api/v1/clients -H "Content-Type:application/json" + -d '{"name":"Client","contact":{"email":"test@gmail.com"}}' -H "X-Ninja-Token: TOKEN" + +You can also update a client by specifying a value for ‘id’. Next, here’s an example of creating an invoice. + +.. code-block:: shell + + curl -X POST ninja.dev/api/v1/invoices -H "Content-Type:application/json" + -d '{"client_id":"1", "invoice_items":[{"product_key": "ITEM", "notes":"Test", "cost":10, "qty":1}]}' + -H "X-Ninja-Token: TOKEN" + +If the product_key is set and matches an existing record the product fields will be auto-populated. If the email field is set then we’ll search for a matching client. If no matches are found a new client will be created. + +Options +^^^^^^^ +- ``email_invoice``: Email the invoice to the client. +- ``auto_bill``: Attempt to auto-bill the invoice using stored payment methods or credits. +- ``paid``: Create a payment for the defined amount. + +Emailing Invoices +""""""""""""""""" + +To email an invoice use the email_invoice command passing the id of the invoice. + +.. code-block:: shell + + curl -X POST ninja.dev/api/v1/email_invoice -d '{"id":1}' + -H "Content-Type:application/json" -H "X-Ninja-Token: TOKEN" + +Subscriptions +""""""""""""" + +You can use subscriptions to have Invoice Ninja POST newly created records to a third-party application. To enable this feature you need to manually add a record to the subscriptions table. To determine the event_id find the associated EVENT_CREATE_ value from app/Constants.php. diff --git a/docs/conf.py b/docs/conf.py index 4e8e07cd7..556af9b28 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'3.1' +version = u'3.2' # The full version, including alpha/beta/rc tags. -release = u'3.1.3' +release = u'3.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/configure.rst b/docs/configure.rst index b9c16327e..31fbc989a 100644 --- a/docs/configure.rst +++ b/docs/configure.rst @@ -59,12 +59,14 @@ If you require contacts to enter a password to see their invoice you'll need to You can install PhantomJS to generate the PDF locally, to enable it add ``PHANTOMJS_BIN_PATH=/usr/local/bin/phantomjs``. +We suggest using version >= 2.1.1, users have reported seeing 'Error: 0' with older versions. + .. TIP:: To determine the path you can run ``which phantomjs`` from the command line. Custom Fonts """""""""""" -Follow these steps to add custom ttf fonts: ie, Google fonts +Follow these steps to add custom ttf fonts: ie, `Google fonts `_ - Create a new folder in ``public/fonts/invoice-fonts/`` and copy over the ttf files - Run ``grunt dump_dir`` @@ -72,11 +74,26 @@ Follow these steps to add custom ttf fonts: ie, Google fonts - Run ``php artisan db:seed --class=FontsSeeder`` - Clear the cache by adding ``?clear_cache=true`` to the end of the URL +Omnipay +""""""" + +We use `Omnipay `_ to support our payment gateway integrations. + +Follow these steps to add a driver. + +- Add the package to composer.json and then run ``composer install`` +- Add a row to the gateways table. ``name`` is used in the gateway select, ``provider`` needs to match the Omnipay driver name +- Clear the cache by adding ``?clear_cache=true`` to the end of the URL + +.. NOTE:: Most drivers also require `code changes `_ to work correctly. + Google Map """""""""" You need to create a Google Maps API key for the Javascript, Geocoding and Embed APIs and then add ``GOOGLE_MAPS_API_KEY=your_key`` to the .env file. +You can disable the feature by adding ``GOOGLE_MAPS_ENABLED=false`` to the .env file. + Using a Proxy """"""""""""" diff --git a/docs/developer_guide.rst b/docs/developer_guide.rst new file mode 100644 index 000000000..78b9fbf3c --- /dev/null +++ b/docs/developer_guide.rst @@ -0,0 +1,48 @@ +Developer Guide +=============== + +This guide will provide an overview of Invoice Ninja. If anything’s unclear please send us an email, we’re always working to improve it. + +The application is written in PHP using the `Laravel `_ framework, the full list of libraries can be found on our `GitHub `_ page. + +If you’re running the app for your own use you can white label the client portal and emails by purchasing an annual white label license from within the application. If you’d like to white label the admin pages to re-sell the application please send us an email to learn about our `affiliate program `_. + +We try to follow the `PSR-2 `_ style guidelines and are using the `Git-Flow `_ model of branching and releasing, please create pull requests against the develop branch. + +Code +"""" + +When setting up the app you can choose to either use the self hosted zip or checkout the code from GitHub. The zip includes all third party libraries, whereas checking out the code from GitHub requires using Composer and Bower. + +We use Gulp to concatenate the JavasScript and CSS files. After making any changes you need to run gulp to re-generate the files. + +Most of the system tables are cached (ie, currencies, languages, etc). If you make any changes you need to clear the cache either by loading any page with ?clear_cache=true added at the end of the URL. + +Database +"""""""" + +The following are the main entities, you can browse the `app/Models `_ folder for the complete list. + +- Accounts +users +- Clients +contacts +- Invoices +invoice_items +- Payments +- Credits + +The best places to start when reviewing the code are `app/Http/routes.php `_ and `app/Providers/EventServiceProvider.php `_. + +To enable each account to have it’s own incrementing Ids (ie, /clients/1) all account entity classes extend the custom EntityModel.php class. This gives each entity a public_id field. You can read more about it in `this post `_. + +All actions are tracked in the activities table. Example of actions are creating a client, viewing an invoice or entering a payment. This is implemented using Laravel model events. An example can be seen at the bottom of `app/Models/Invoice.php `_. + +Laravel supplies `soft delete `_ functionality, however in order to ensure referential integrity records are only deleted when a user cancels their account. To support this we’ve added an is_deleted field. When the deleted_at field is set the entity has been archived, when is_deleted is true the entity has been deleted. + +Automated Tests +""""""""""""""" + +To run the `Codeception `_ tests you’ll need to install `PhantomJS `_. + +- Create config file: ``cp tests/_bootstrap.php.default tests/_bootstrap.php`` +- Create test user: ``php artisan db:seed --class=UserTableSeeder`` +- Start the PhantomJS web server: ``phantomjs --webdriver=4444`` +- Run the tests: ``sudo ./vendor/codeception/codeception/codecept run --debug`` diff --git a/docs/index.rst b/docs/index.rst index d10d97e9b..b5e5c910c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,4 +47,6 @@ Want to find out everything there is to know about how to use your Invoice Ninja configure update iphone_app + api + developer_guide custom_modules diff --git a/docs/install.rst b/docs/install.rst index b7e8f093a..d8708539e 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -29,7 +29,7 @@ Step 1: Download the code You can either download the zip file below or checkout the code from our GitHub repository. The zip includes all third party libraries whereas using GitHub requires you to use Composer to install the dependencies. -https://download.invoiceninja.com/ninja-v3.0.5.zip +https://download.invoiceninja.com/ninja-v3.1.3.zip .. Note:: All Pro and Enterprise features from our hosted app are included in both the zip file and the GitHub repository. We offer a $20 per year white-label license to remove our branding. @@ -66,7 +66,7 @@ Please see these guides for detailed information on configuring Apache or Nginx. Once you can access the site the initial setup screen will enable you to configure the database and email settings as well as create the initial admin user. -.. Tip:: The best practice to remove public/ from the URL is to map the webroot to the /public folder, alternatively you can uncomment ``RewriteRule ^(.*)$ public/$1 [L]`` in the .htaccess file. +.. Tip:: To remove public/ from the URL map the webroot to the /public folder, alternatively you can uncomment ``RewriteRule ^(.*)$ public/$1 [L]`` in the .htaccess file. Troubleshooting ^^^^^^^^^^^^^^^ diff --git a/docs/update.rst b/docs/update.rst index a50e8bca8..bd2bc333a 100644 --- a/docs/update.rst +++ b/docs/update.rst @@ -1,7 +1,7 @@ Update ====== -.. Note:: We recommend backing up your database before updating the app. +.. NOTE:: We recommend backing up your database before updating the app. To update the app you just need to copy over the latest code. The app tracks the current version in a file called version.txt, if it notices a change it loads ``/update`` to run the database migrations. @@ -14,7 +14,7 @@ If the auto-update fails you can manually run the update with the following comm php artisan migrate php artisan db:seed --class=UpdateSeeder -We’ve seen some updates fail when moving the app to a new server because the MySQL default storage engine has changed with MySQL 5.7. If you see ``SQLSTATE[HY000]: General error: 1215`` error you may be able to fix it by running this `SQL query `_ +.. NOTE:: If you've downloaded the code from GitHub you also need to run ``composer install`` Version 2.6 """"""""""" diff --git a/public/apple-touch-icon-152x152-precomposed.png b/public/apple-touch-icon-152x152-precomposed.png new file mode 100644 index 000000000..cf6386b18 Binary files /dev/null and b/public/apple-touch-icon-152x152-precomposed.png differ diff --git a/public/built.js b/public/built.js index 60c15ee91..0a55fe609 100644 --- a/public/built.js +++ b/public/built.js @@ -1,27 +1,27 @@ -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=r.split("+")[1]:r.split("-").length>1&&(c=parseInt(r.split("-")[1])*-1),t=t.replace(r,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?$.trim((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={},a=$("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,""),a=o.length,s=e?Math.ceil((3*a+1>>2)/e)*e:3*a+1>>2,r=new Uint8Array(s),c=0,l=0,u=0;u>>(16>>>n&24)&255;c=0}return r}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,a=0;a0&&4*a/3%76===0&&(n+="\r\n"),o|=t[a]<<(16>>>e&24),2!==e&&t.length-a!==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,a=Math.ceil(o),s={h:e,m:i,s:a};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 a;if(n){var s={keys:[e]},r=new Fuse(t,s);a=r.search(i)}else a=[],substrRegex=new RegExp(escapeRegExp(i),"i"),$.each(t,function(t,n){substrRegex.test(n[e])&&a.push(n)});o(a)}}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 a=JSON.parse(e,i);t.invoice_design_id;if(!t.features.remove_created_by&&!isEdge){var s="function"==typeof a.footer?a.footer():a.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 r,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 a(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",r,!1),t.removeEventListener("load",r,!1)):(ft.detachEvent("onreadystatechange",r),t.detachEvent("onload",r))}function r(){(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(wt,"-$1").toLowerCase();if(n=t.getAttribute(i),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:Tt.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,a,s=ot.expando,r=t.nodeType,c=r?ot.cache:t,l=r?t[s]:t[s]&&s;if(l&&c[l]&&(i||c[l].data)||void 0!==n||"string"!=typeof e)return l||(l=r?t[s]=Y.pop()||ot.guid++:s),c[l]||(c[l]=r?{}:{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)),a=c[l],i||(a.data||(a.data={}),a=a.data),void 0!==n&&(a[ot.camelCase(e)]=n),"string"==typeof e?(o=a[e],null==o&&(o=a[ot.camelCase(e)])):o=a,o}}function h(t,e,n){if(ot.acceptData(t)){var i,o,a=t.nodeType,s=a?ot.cache:t,r=a?t[ot.expando]:ot.expando;if(s[r]){if(e&&(i=n?s[r]:s[r].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[r].data,l(s[r])))&&(a?ot.cleanData([t],!0):nt.deleteExpando||s!=s.window?delete s[r]:s[r]=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,a=typeof t.getElementsByTagName!==_t?t.getElementsByTagName(e||"*"):typeof t.querySelectorAll!==_t?t.querySelectorAll(e||"*"):void 0;if(!a)for(a=[],n=t.childNodes||t;null!=(i=n[o]);o++)!e||ot.nodeName(i,e)?a.push(i):ot.merge(a,g(i,e));return void 0===e||e&&ot.nodeName(t,e)?ot.merge([t],a):a}function b(t){xt.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 z(t,e){if(1===e.nodeType&&ot.hasData(t)){var n,i,o,a=ot._data(t),s=ot._data(e,a),r=a.events;if(r){delete s.handle,s.events={};for(n in r)for(i=0,o=r[n].length;i")).appendTo(e.documentElement),e=(Qt[0].contentWindow||Qt[0].contentDocument).document,e.write(),e.close(),n=T(t,e),Qt.detach()),Zt[t]=n),n}function C(t,e){return{get:function(){var n=t();if(null!=n)return n?void delete this.get:(this.get=e).apply(this,arguments)}}}function O(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 N(t,e){for(var n,i,o,a=[],s=0,r=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(rt,ct)},nodeName:function(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()},each:function(t,e,i){var o,a=0,s=t.length,r=n(t);if(i){if(r)for(;az.cacheLength&&delete t[e.shift()],t[n+" "]=i}var e=[];return t}function i(t){return t[X]=!0,t}function o(t){var e=D.createElement("div");try{return!!t(e)}catch(n){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function a(t,e){for(var n=t.split("|"),i=t.length;i--;)z.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 r(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,a=t([],n.length,e),s=a.length;s--;)n[o=a[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,a=n.length;o-1&&(i[l]=!(s[l]=h))}}else M=g(M===s?M.splice(f,M.length):M),a?a(null,s,M,c):Q.apply(s,M)})}function v(t){for(var e,n,i,o=t.length,a=z.relative[t[0].type],s=a||z.relative[" "],r=a?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=!a&&(i||n!==N)||((e=n).nodeType?c(t,n,i):l(t,n,i));return e=null,o}];r1&&f(u),r>1&&d(t.slice(0,r-1).concat({value:" "===t[r-2].type?"*":""})).replace(ct,"$1"),n,r0,a=t.length>0,s=function(i,s,r,c,l){var u,h,d,p=0,f="0",m=i&&[],b=[],v=N,M=i||a&&z.find.TAG("*",l),y=R+=null==v?1:Math.random()||.1,A=M.length;for(l&&(N=s!==D&&s);f!==A&&null!=(u=M[f]);f++){if(a&&u){for(h=0;d=t[h++];)if(d(u,s,r)){c.push(u);break}l&&(R=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,r);if(i){if(p>0)for(;f--;)m[f]||b[f]||(b[f]=K.call(c));b=g(b)}Q.apply(c,b),l&&!i&&b.length>0&&p+n.length>1&&e.uniqueSort(c)}return l&&(R=y,N=v),m};return o?i(s):s}var y,A,z,_,T,w,C,O,N,S,x,L,D,k,q,W,E,B,I,X="sizzle"+1*new Date,P=t.document,R=0,F=0,H=n(),j=n(),U=n(),$=function(t,e){return t===e&&(x=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],K=J.pop,G=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("^"+at),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"),zt=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)},_t=function(){L()};try{Q.apply(J=Z.call(P.childNodes),P.childNodes),J[P.childNodes.length].nodeType}catch(Tt){Q={apply:J.length?function(t,e){G.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={},T=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:P;return i!==D&&9===i.nodeType&&i.documentElement?(D=i,k=i.documentElement,n=i.defaultView,n&&n!==n.top&&(n.addEventListener?n.addEventListener("unload",_t,!1):n.attachEvent&&n.attachEvent("onunload",_t)),q=!T(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 k.appendChild(t).id=X,!i.getElementsByName||!i.getElementsByName(X).length}),A.getById?(z.find.ID=function(t,e){if("undefined"!=typeof e.getElementById&&q){var n=e.getElementById(t);return n&&n.parentNode?[n]:[]}},z.filter.ID=function(t){var e=t.replace(At,zt);return function(t){return t.getAttribute("id")===e}}):(delete z.find.ID,z.filter.ID=function(t){var e=t.replace(At,zt);return function(t){var n="undefined"!=typeof t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}}),z.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,a=e.getElementsByTagName(t);if("*"===t){for(;n=a[o++];)1===n.nodeType&&i.push(n);return i}return a},z.find.CLASS=A.getElementsByClassName&&function(t,e){if(q)return e.getElementsByClassName(t)},E=[],W=[],(A.qsa=bt.test(i.querySelectorAll))&&(o(function(t){k.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&W.push("[*^$]="+nt+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||W.push("\\["+nt+"*(?:value|"+et+")"),t.querySelectorAll("[id~="+X+"-]").length||W.push("~="),t.querySelectorAll(":checked").length||W.push(":checked"),t.querySelectorAll("a#"+X+"+*").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=k.matches||k.webkitMatchesSelector||k.mozMatchesSelector||k.oMatchesSelector||k.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(k.compareDocumentPosition),I=e||bt.test(k.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 x=!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===P&&I(P,t)?-1:e===i||e.ownerDocument===P&&I(P,e)?1:S?tt(S,t)-tt(S,e):0:4&n?-1:1)}:function(t,e){if(t===e)return x=!0,0;var n,o=0,a=t.parentNode,r=e.parentNode,c=[t],l=[e];if(!a||!r)return t===i?-1:e===i?1:a?-1:r?1:S?tt(S,t)-tt(S,e):0;if(a===r)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]===P?-1:l[o]===P?1:0},i):D},e.matches=function(t,n){return e(t,null,null,n)},e.matchesSelector=function(t,n){if((t.ownerDocument||t)!==D&&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,D,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==D&&L(t),I(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==D&&L(t);var n=z.attrHandle[e.toLowerCase()],i=n&&Y.call(z.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(x=!A.detectDuplicates,S=!A.sortStable&&t.slice(0),t.sort($),x){for(;e=t[o++];)e===t[o]&&(i=n.push(o));for(;i--;)t.splice(n[i],1)}return S=null,t},_=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+=_(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[i++];)n+=_(e);return n},z=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,zt),t[3]=(t[3]||t[4]||t[5]||"").replace(At,zt),"~="===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=w(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,zt).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 a=e.attr(o,t);return null==a?"!="===n:!n||(a+="","="===n?a===i:"!="===n?a!==i:"^="===n?i&&0===a.indexOf(i):"*="===n?i&&a.indexOf(i)>-1:"$="===n?i&&a.slice(-i.length)===i:"~="===n?(" "+a.replace(rt," ")+" ").indexOf(i)>-1:"|="===n&&(a===i||a.slice(0,i.length+1)===i+"-"))}},CHILD:function(t,e,n,i,o){var a="nth"!==t.slice(0,3),s="last"!==t.slice(-4),r="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=a!==s?"nextSibling":"previousSibling",g=e.parentNode,b=r&&e.nodeName.toLowerCase(),v=!c&&!r;if(g){if(a){for(;m;){for(h=e;h=h[m];)if(r?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[X]||(g[X]={}),l=u[t]||[],p=l[0]===R&&l[1],d=l[0]===R&&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]=[R,p,d];break}}else if(v&&(l=(e[X]||(e[X]={}))[t])&&l[0]===R)d=l[1];else for(;(h=++p&&h&&h[m]||(d=p=0)||f.pop())&&((r?h.nodeName.toLowerCase()!==b:1!==h.nodeType)||!++d||(v&&((h[X]||(h[X]={}))[t]=[R,d]),h!==e)););return d-=o,d===i||d%i===0&&d/i>=0}}},PSEUDO:function(t,n){var o,a=z.pseudos[t]||z.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return a[X]?a(n):a.length>1?(o=[t,t,"",n],z.setFilters.hasOwnProperty(t.toLowerCase())?i(function(t,e){for(var i,o=a(t,n),s=o.length;s--;)i=tt(t,o[s]),t[i]=!(e[i]=o[s])}):function(t){return a(t,0,o)}):a}},pseudos:{not:i(function(t){var e=[],n=[],o=C(t.replace(ct,"$1"));return o[X]?i(function(t,e,n,i){for(var a,s=o(t,null,i,[]),r=t.length;r--;)(a=s[r])&&(t[r]=!(e[r]=a))}):function(t,i,a){return e[0]=t,o(e,null,a,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,zt),function(e){return(e.textContent||e.innerText||_(e)).indexOf(t)>-1}}),lang:i(function(t){return pt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(At,zt).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===k},focus:function(t){return t===D.activeElement&&(!D.hasFocus||D.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!z.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=a[0]).type&&A.getById&&9===e.nodeType&&q&&z.relative[a[1].type]){if(e=(z.find.ID(s.matches[0].replace(At,zt),e)||[])[0],!e)return n;l&&(e=e.parentNode),t=t.slice(a.shift().value.length)}for(o=ft.needsContext.test(t)?0:a.length;o--&&(s=a[o],!z.relative[r=s.type]);)if((c=z.find[r])&&(i=c(s.matches[0].replace(At,zt),Mt.test(a[0].type)&&u(e.parentNode)||e))){if(a.splice(o,1),t=i.length&&d(a),!t)return Q.apply(n,i),n;break}}return(l||C(t,h))(i,e,!q,n,Mt.test(t)&&u(e.parentNode)||e),n},A.sortStable=X.split("").sort($).join("")===X,A.detectDuplicates=!!x,L(),A.sortDetached=o(function(t){return 1&t.compareDocumentPosition(D.createElement("div"))}),o(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||a("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")})||a("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),o(function(t){return null==t.getAttribute("disabled")})||a(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))){a.push(n);break}return this.pushStack(a.length>1?ot.unique(a):a)},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]||a(t):ot.extend({},t);var e,n,i,o,s,r,c=[],l=!t.once&&[],u=function(a){for(n=t.memory&&a,i=!0,s=r||0,r=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,a){var s=ot.isFunction(t[e])&&t[e];o[a[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[a[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,a){var s=a[2],r=a[3];i[a[1]]=s.add,r&&s.add(function(){n=r},e[1^t][2].disable,e[2][2].lock),o[a[0]]=function(){return o[a[0]+"With"](this===o?i:this,arguments),this},o[a[0]+"With"]=s.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(t){var e,n,i,o=0,a=J.call(arguments),s=a.length,r=1!==s||t&&ot.isFunction(t.promise)?s:0,c=1===r?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):--r||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",r,!1),t.addEventListener("load",r,!1);else{ft.attachEvent("onreadystatechange",r),t.attachEvent("onload",r);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 zt,_t="undefined";for(zt in ot(nt))break;nt.ownLast="0"!==zt,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!==_t&&(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 Tt=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,wt=/([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,a=this[0],s=a&&a.attributes;if(void 0===t){if(this.length&&(o=ot.data(a),1===a.nodeType&&!ot._data(a,"parsedAttrs"))){for(n=s.length;n--;)s[n]&&(i=s[n].name,0===i.indexOf("data-")&&(i=ot.camelCase(i.slice(5)),c(a,i,o[i])));ot._data(a,"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)}):a?c(a,t,ot.data(a,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(),a=ot._queueHooks(t,e),s=function(){ot.dequeue(t,e)};"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete a.stop,o.call(t,s,a)),!i&&a&&a.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,Dt=/^key/,kt=/^(?:mouse|pointer|contextmenu)|click/,qt=/^(?:focusinfocus|focusoutblur)$/,Wt=/^([^.]*)(?:\.(.+)|)$/;ot.event={global:{},add:function(t,e,n,i,o){var a,s,r,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===_t||t&&ot.event.triggered===t.type?void 0:ot.event.dispatch.apply(u.elem,arguments)},u.elem=t),e=(e||"").match(Mt)||[""],r=e.length;r--;)a=Wt.exec(e[r])||[],p=m=a[1],f=(a[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 a,s,r,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(r=Wt.exec(e[l])||[],p=m=r[1],f=(r[2]||"").split(".").sort(),p){for(h=ot.event.special[p]||{},p=(i?h.delegateType:h.bindType)||p,d=u[p]||[],r=r[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),c=a=d.length;a--;)s=d[a],!o&&m!==s.origType||n&&n.guid!==s.guid||r&&!r.test(s.namespace)||i&&i!==s.selector&&("**"!==i||!s.selector)||(d.splice(a,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 a,s,r,c,l,u,h,d=[i||ft],p=et.call(e,"type")?e.type:e,f=et.call(e,"namespace")?e.namespace.split("."):[];if(r=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)||(r=r.parentNode);r;r=r.parentNode)d.push(r),u=r;u===(i.ownerDocument||ft)&&d.push(u.defaultView||u.parentWindow||t)}for(h=0;(r=d[h++])&&!e.isPropagationStopped();)e.type=h>1?c:l.bindType||p,a=(ot._data(r,"events")||{})[e.type]&&ot._data(r,"handle"),a&&a.apply(r,n),a=s&&r[s],a&&a.apply&&ot.acceptData(r)&&(e.result=a.apply(r,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,a,s=[],r=J.call(arguments),c=(ot._data(this,"events")||{})[t.type]||[],l=ot.event.special[t.type]||{};if(r[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,a=0;(i=o.handlers[a++])&&!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,r),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,a,s=[],r=e.delegateCount,c=t.target;if(r&&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=[],a=0;a=0:ot.find(n,this,null,[c]).length),o[n]&&o.push(i);o.length&&s.push({elem:c,handlers:o})}return r]","i"),Xt=/^\s+/,Pt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Rt=/<([\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
","
"]},Kt=m(ft),Gt=Kt.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,a,s,r,c=ot.contains(t.ownerDocument,t);if(nt.html5Clone||ot.isXMLDoc(t)||!It.test("<"+t.nodeName+">")?a=t.cloneNode(!0):(Gt.innerHTML=t.outerHTML,Gt.removeChild(a=Gt.firstChild)),!(nt.noCloneEvent&&nt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||ot.isXMLDoc(t)))for(i=g(a),r=g(t),s=0;null!=(o=r[s]);++s)i[s]&&_(o,i[s]);if(e)if(n)for(r=r||g(t),i=i||g(a),s=0;null!=(o=r[s]);s++)z(o,i[s]);else z(t,a);return i=g(a,"script"),i.length>0&&A(i,!c&&g(t,"script")),i=r=o=null,a},buildFragment:function(t,e,n,i){for(var o,a,s,r,c,l,u,h=t.length,d=m(e),p=[],f=0;f")+u[2],o=u[0];o--;)r=r.lastChild;if(!nt.leadingWhitespace&&Xt.test(a)&&p.push(e.createTextNode(Xt.exec(a)[0])),!nt.tbody)for(a="table"!==c||Ft.test(a)?""!==u[1]||Ft.test(a)?0:r:r.firstChild,o=a&&a.childNodes.length;o--;)ot.nodeName(l=a.childNodes[o],"tbody")&&!l.childNodes.length&&a.removeChild(l);for(ot.merge(p,r.childNodes),r.textContent="";r.firstChild;)r.removeChild(r.firstChild);r=d.lastChild}else p.push(e.createTextNode(a));for(r&&d.removeChild(r),nt.appendChecked||ot.grep(g(p,"input"),b),f=0;a=p[f++];)if((!i||ot.inArray(a,i)===-1)&&(s=ot.contains(a.ownerDocument,a),r=g(d.appendChild(a),"script"),s&&A(r),n))for(o=0;a=r[o++];)$t.test(a.type||"")&&n.push(a);return r=null,d},cleanData:function(t,e){for(var n,i,o,a,s=0,r=ot.expando,c=ot.cache,l=nt.deleteExpando,u=ot.event.special;null!=(n=t[s]);s++)if((e||ot.acceptData(n))&&(o=n[r],a=o&&c[o])){if(a.events)for(i in a.events)u[i]?ot.event.remove(n,i):ot.removeEvent(n,i,a.handle);c[o]&&(delete c[o],l?delete n[r]:typeof n.removeAttribute!==_t?n.removeAttribute(r):n[r]=null,Y.push(o))}}}),ot.fn.extend({text:function(t){return St(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 St(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||!Xt.test(t))&&!Jt[(Rt.exec(t)||["",""])[1].toLowerCase()]){t=t.replace(Pt,"<$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&&(r=ot.buildFragment(t,this[0].ownerDocument,!1,this),n=r.firstChild,1===r.childNodes.length&&(r=n),n)){for(a=ot.map(g(r,"script"),M),o=a.length;c
t
",o=e.getElementsByTagName("td"),o[0].style.cssText="margin:0;border:0;padding:0;display:none",r=0===o[0].offsetHeight,r&&(o[0].style.display="",o[1].style.display="none",r=0===o[0].offsetHeight),n.removeChild(i))}var n,i,o,a,s,r,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==r&&e(),r},boxSizingReliable:function(){return null==s&&e(),s},pixelPosition:function(){return null==a&&e(),a},reliableMarginRight:function(){return null==c&&e(),c}}))}(),ot.swap=function(t,e,n,i){var o,a,s={};for(a in e)s[a]=t.style[a],t.style[a]=e[a];o=n.apply(t,i||[]);for(a in e)t.style[a]=s[a];return o};var ae=/alpha\([^)]*\)/i,se=/opacity\s*=\s*([^)]*)/,re=/^(none|table(?!-c[ea]).+)/,ce=new RegExp("^("+Ct+")(.*)$","i"),le=new RegExp("^([+-])=("+Ct+")","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,a,s,r=ot.camelCase(e),c=t.style;if(e=ot.cssProps[r]||(ot.cssProps[r]=O(c,r)),s=ot.cssHooks[e]||ot.cssHooks[r],void 0===n)return s&&"get"in s&&void 0!==(o=s.get(t,!1,i))?o:c[e];if(a=typeof n,"string"===a&&(o=le.exec(n))&&(n=(o[1]+1)*o[2]+parseFloat(ot.css(t,e)),a="number"),null!=n&&n===n&&("number"!==a||ot.cssNumber[r]||(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,a,s,r=ot.camelCase(e);return e=ot.cssProps[r]||(ot.cssProps[r]=O(t.style,r)),s=ot.cssHooks[e]||ot.cssHooks[r],s&&"get"in s&&(a=s.get(t,!0,n)),void 0===a&&(a=ee(t,e,i)),"normal"===a&&e in he&&(a=he[e]),""===n||n?(o=parseFloat(a),n===!0||ot.isNumeric(o)?o||0:a):a}}),ot.each(["height","width"],function(t,e){ot.cssHooks[e]={get:function(t,n,i){if(n)return re.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 S(t,n,i?x(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+")":"",a=i&&i.filter||n.filter||"";n.zoom=1,(e>=1||""===e)&&""===ot.trim(a.replace(ae,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===e||i&&!i.filter)||(n.filter=ae.test(a)?a.replace(ae,o):a+" "+o)}}),ot.cssHooks.marginRight=C(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={},a="string"==typeof n?n.split(" "):[n];i<4;i++)o[t+Ot[i]+e]=a[i]||a[i-2]||a[0];return o}},ne.test(t)||(ot.cssHooks[t+e].set=S)}),ot.fn.extend({css:function(t,e){return St(this,function(t,e,n){var i,o,a={},s=0;if(ot.isArray(e)){for(i=te(t),o=e.length;s1)},show:function(){return N(this,!0)},hide:function(){return N(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){Nt(this)?ot(this).show():ot(this).hide()})}}),ot.Tween=D,D.prototype={constructor:D,init:function(t,e,n,i,o,a){this.elem=t,this.prop=n,this.easing=o||"swing",this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=a||(ot.cssNumber[n]?"":"px")},cur:function(){var t=D.propHooks[this.prop];return t&&t.get?t.get(this):D.propHooks._default.get(this)},run:function(t){var e,n=D.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):D.propHooks._default.set(this),this}},D.prototype.init.prototype=D.prototype,D.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}}},D.propHooks.scrollTop=D.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=D.prototype.init,ot.fx.step={};var pe,fe,me=/^(?:toggle|show|hide)$/,ge=new RegExp("^(?:([+-])=|)("+Ct+")([a-z%]*)$","i"),be=/queueHooks$/,ve=[E],Me={"*":[function(t,e){var n=this.createTween(t,e),i=n.cur(),o=ge.exec(e),a=o&&o[3]||(ot.cssNumber[t]?"":"px"),s=(ot.cssNumber[t]||"px"!==a&&+i)&&ge.exec(ot.css(n.elem,t)),r=1,c=20;if(s&&s[3]!==a){a=a||s[3],o=o||[],s=+i||1;do r=r||".5",s/=r,ot.style(n.elem,t,s+a);while(r!==(r=n.cur()/i)&&1!==r&&--c)}return o&&(s=n.start=+s||+i||0,n.unit=a,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,a="select-one"===t.type||o<0,s=a?null:[],r=a?o+1:i.length,c=o<0?r:a?o:0;c=0)try{i.selected=n=!0}catch(r){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,ze,_e=ot.expr.attrHandle,Te=/^(?:checked|selected)$/i,we=nt.getSetAttribute,Ce=nt.input;ot.fn.extend({attr:function(t,e){return St(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,a=t.nodeType;if(t&&3!==a&&8!==a&&2!==a)return typeof t.getAttribute===_t?ot.prop(t,e,n):(1===a&&ot.isXMLDoc(t)||(e=e.toLowerCase(),i=ot.attrHooks[e]||(ot.expr.match.bool.test(e)?ze: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,a=e&&e.match(Mt);if(a&&1===t.nodeType)for(;n=a[o++];)i=ot.propFix[n]||n,ot.expr.match.bool.test(n)?Ce&&we||!Te.test(n)?t[i]=!1:t[ot.camelCase("default-"+n)]=t[i]=!1:ot.attr(t,n,""),t.removeAttribute(we?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}}}}}),ze={set:function(t,e,n){return e===!1?ot.removeAttr(t,n):Ce&&we||!Te.test(n)?t.setAttribute(!we&&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=_e[e]||ot.find.attr;_e[e]=Ce&&we||!Te.test(e)?function(t,e,i){var o,a;return i||(a=_e[e],_e[e]=o,o=null!=n(t,e,i)?e.toLowerCase():null,_e[e]=a),o}:function(t,e,n){if(!n)return t[ot.camelCase("default-"+e)]?e.toLowerCase():null}}),Ce&&we||(ot.attrHooks.value={set:function(t,e,n){return ot.nodeName(t,"input")?void(t.defaultValue=e):Ae&&Ae.set(t,e,n)}}),we||(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}},_e.id=_e.name=_e.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 Oe=/^(?:input|select|textarea|button|object)$/i,Ne=/^(?:a|area)$/i;ot.fn.extend({prop:function(t,e){return St(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,a,s=t.nodeType;if(t&&3!==s&&8!==s&&2!==s)return a=1!==s||!ot.isXMLDoc(t),a&&(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):Oe.test(t.nodeName)||Ne.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 Se=/[\t\r\n\f]/g;ot.fn.extend({addClass:function(t){var e,n,i,o,a,s,r=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)||[];r=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),a=t.match(Mt)||[];e=a[i++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else n!==_t&&"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 xe=ot.now(),Le=/\?/,De=/(,)|(\[|{)|(}|])|"(?:[^"\\\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(De,function(t,e,o,a){return n&&e&&(i=0),0===i?t:(n=o||e,i+=!a-!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 ke,qe,We=/#.*$/,Ee=/([?&])_=[^&]*/,Be=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Ie=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Xe=/^(?:GET|HEAD)$/,Pe=/^\/\//,Re=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Fe={},He={},je="*/".concat("*");try{qe=location.href}catch(Ue){qe=ft.createElement("a"),qe.href="",qe=qe.href}ke=Re.exec(qe.toLowerCase())||[],ot.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qe,type:"GET",isLocal:Ie.test(ke[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?R(R(t,ot.ajaxSettings),e):R(ot.ajaxSettings,t)},ajaxPrefilter:X(Fe),ajaxTransport:X(He),ajax:function(t,e){function n(t,e,n,i){var o,u,b,v,y,z=e;2!==M&&(M=2,r&&clearTimeout(r),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[a]=y),y=A.getResponseHeader("etag"),y&&(ot.etag[a]=y)),204===t||"HEAD"===h.type?z="nocontent":304===t?z="notmodified":(z=v.state,u=v.data,b=v.error,o=!b)):(b=z,!t&&z||(z="error",t<0&&(t=0))),A.status=t,A.statusText=(e||z)+"",o?f.resolveWith(d,[u,z,A]):f.rejectWith(d,[A,z,b]),A.statusCode(g),g=void 0,c&&p.trigger(o?"ajaxSuccess":"ajaxError",[A,h,o?u:b]),m.fireWith(d,[A,z]),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,a,s,r,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(Pe,ke[1]+"//"),h.type=e.method||e.type||h.method||h.type,h.dataTypes=ot.trim(h.dataType||"*").toLowerCase().match(Mt)||[""],null==h.crossDomain&&(i=Re.exec(h.url.toLowerCase()),h.crossDomain=!(!i||i[1]===ke[1]&&i[2]===ke[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(ke[3]||("http:"===ke[1]?"80":"443")))),h.data&&h.processData&&"string"!=typeof h.data&&(h.data=ot.param(h.data,h.traditional)),P(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=!Xe.test(h.type),a=h.url,h.hasContent||(h.data&&(a=h.url+=(Le.test(a)?"&":"?")+h.data,delete h.data),h.cache===!1&&(h.url=Ee.test(a)?a.replace(Ee,"$1_="+xe++):a+(Le.test(a)?"&":"?")+"_="+xe++)),h.ifModified&&(ot.lastModified[a]&&A.setRequestHeader("If-Modified-Since",ot.lastModified[a]),ot.etag[a]&&A.setRequestHeader("If-None-Match",ot.etag[a])),(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=P(He,h,e,A)){A.readyState=1,c&&p.trigger("ajaxSend",[A,h]),h.async&&h.timeout>0&&(r=setTimeout(function(){A.abort("timeout")},h.timeout));try{M=1,l.send(b,n)}catch(z){if(!(M<2))throw z;n(-1,z)}}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,Ke=/^(?: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")&&Ke.test(this.nodeName)&&!Je.test(t)&&(this.checked||!xt.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 Ge=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,a=t.xhr(),s=++Ge;if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)a[o]=t.xhrFields[o];t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(o in n)void 0!==n[o]&&a.setRequestHeader(o,n[o]+"");a.send(t.hasContent&&t.data||null),e=function(n,o){var r,c,l;if(e&&(o||4===a.readyState))if(delete Qe[s],e=void 0,a.onreadystatechange=ot.noop,o)4!==a.readyState&&a.abort();else{l={},r=a.status,"string"==typeof a.responseText&&(l.text=a.responseText);try{c=a.statusText}catch(u){c=""}r||!t.isLocal||t.crossDomain?1223===r&&(r=204):r=l.text?200:404}l&&i(r,c,l,a.getAllResponseHeaders())},t.async?4===a.readyState?setTimeout(e):a.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+"_"+xe++;return this[t]=!0,t}}),ot.ajaxPrefilter("json jsonp",function(e,n,i){var o,a,s,r=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(r||"jsonp"===e.dataTypes[0])return o=e.jsonpCallback=ot.isFunction(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,r?e[r]=e[r].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",a=t[o],t[o]=function(){s=arguments},i.always(function(){t[o]=a,e[o]&&(e.jsonpCallback=n.jsonpCallback,tn.push(o)),s&&ot.isFunction(a)&&a(s[0]),s=a=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,a,s=this,r=t.indexOf(" ");return r>=0&&(i=ot.trim(t.slice(r,t.length)),t=t.slice(0,r)),ot.isFunction(e)?(n=e,e=void 0):e&&"object"==typeof e&&(a="POST"),s.length>0&&ot.ajax({url:t,type:a,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,a,s,r,c,l,u=ot.css(t,"position"),h=ot(t),d={};"static"===u&&(t.style.position="relative"),r=h.offset(),a=ot.css(t,"top"),c=ot.css(t,"left"),l=("absolute"===u||"fixed"===u)&&ot.inArray("auto",[a,c])>-1,l?(i=h.position(),s=i.top,o=i.left):(s=parseFloat(a)||0,o=parseFloat(c)||0),ot.isFunction(e)&&(e=e.call(t,n,r)),null!=e.top&&(d.top=e.top-r.top+s),null!=e.left&&(d.left=e.left-r.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],a=o&&o.ownerDocument;if(a)return e=a.documentElement,ot.contains(e,o)?(typeof o.getBoundingClientRect!==_t&&(i=o.getBoundingClientRect()),n=V(a),{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 St(this,function(t,i,o){var a=V(t);return void 0===o?a?e in a?a[e]:a.document.documentElement[i]:t[i]:void(a?a.scrollTo(n?ot(a).scrollLeft():o,n?o:ot(a).scrollTop()):t[i]=o)},t,i,arguments.length,null)}}),ot.each(["top","left"],function(t,e){ot.cssHooks[e]=C(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 a=arguments.length&&(n||"boolean"!=typeof i),s=n||(i===!0||o===!0?"margin":"border");return St(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,a?i:void 0,a,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 an=t.jQuery,sn=t.$;return ot.noConflict=function(e){return t.$===ot&&(t.$=sn),e&&t.jQuery===ot&&(t.jQuery=an),ot},typeof e===_t&&(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,a,s,r=e.nodeName.toLowerCase();return"area"===r?(o=e.parentNode,a=o.name,!(!e.href||!a||"map"!==o.nodeName.toLowerCase())&&(s=t("img[usemap='#"+a+"']")[0],!!s&&n(s))):(/input|select|textarea|button|object/.test(r)?!e.disabled:"a"===r?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=a(t("
"))}function a(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 r(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)/,a=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&&a.length?a: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,a){return t.each(o,function(){n-=parseFloat(t.css(e,"padding"+this))||0,i&&(n-=parseFloat(t.css(e,"border"+this+"Width"))||0),a&&(n-=parseFloat(t.css(e,"margin"+this))||0)}),n}var o="Width"===n?["Left","Right"]:["Top","Bottom"],a=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(a,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(a,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,a=t.ui[e].prototype;for(o in i)a.plugins[o]=a.plugins[o]||[],a.plugins[o].push([n,i[o]])},call:function(t,e,n,i){var o,a=t.plugins[e];if(a&&(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,a,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]),a=0;a=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,a,s=Math.max,r=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("
"),a=i.children()[0];return t("body").append(i),e=a.offsetWidth,i.css("overflow","scroll"),n=a.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:a<0?"top":i>0?"bottom":"middle"};ms(r(i),r(a))?c.important="horizontal":c.important="vertical",o.using.call(this,t,c)}),u.offset(t.extend(O,{using:l}))})},t.ui.position={fit:{left:function(t,e){var n,i=e.within,o=i.isWindow?i.scrollLeft:i.offset.left,a=i.width,r=t.left-e.collisionPosition.marginLeft,c=o-r,l=r+e.collisionWidth-a-o;e.collisionWidth>a?c>0&&l<=0?(n=t.left+c+e.collisionWidth-a-o,t.left+=c-n):l>0&&c<=0?t.left=o:c>l?t.left=o+a-e.collisionWidth:t.left=o:c>0?t.left+=c:l>0?t.left-=l:t.left=s(t.left-r,t.left)},top:function(t,e){var n,i=e.within,o=i.isWindow?i.scrollTop:i.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,c=o-r,l=r+e.collisionHeight-a-o;e.collisionHeight>a?c>0&&l<=0?(n=t.top+c+e.collisionHeight-a-o,t.top+=c-n):l>0&&c<=0?t.top=o:c>l?t.top=o+a-e.collisionHeight:t.top=o:c>0?t.top+=c:l>0?t.top-=l:t.top=s(t.top-r,t.top)}},flip:{left:function(t,e){var n,i,o=e.within,a=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-a,(n<0||n0&&(i=t.left-e.collisionPosition.marginLeft+d+p+f-c,(i>0||r(i)u&&(i<0||i0&&(n=t.top-e.collisionPosition.marginTop+p+f+m-c,t.top+p+f+m>h&&(n>0||r(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),a=!1;switch(e.keyCode){case n.RIGHT:case n.DOWN:a=this.headers[(o+1)%i];break;case n.LEFT:case n.UP:a=this.headers[(o-1+i)%i];break;case n.SPACE:case n.ENTER:this._eventHandler(e);break;case n.HOME:a=this.headers[0];break;case n.END:a=this.headers[i-1]}a&&(t(e.target).attr("tabIndex",-1),t(a).attr("tabIndex",0),a.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),a=o[0]===i[0],s=a&&n.collapsible,r=s?t():o.next(),c=i.next(),l={oldHeader:i,oldPanel:c,newHeader:s?t():o,newPanel:r};e.preventDefault(),a&&!n.collapsible||this._trigger("beforeActivate",e,l)===!1||(n.active=!s&&this.headers.index(o),this.active=a?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),a||(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,a,s=this,r=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,a,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),a=!1,clearTimeout(this.filterTimer),o===i?a=!0:o=i+o,n=this._filterMenuItems(o),n=a&&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,a=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),a.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=a.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,a,s,r;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,a=this.activeMenu.scrollTop(),s=this.activeMenu.height(),r=e.outerHeight(),o<0?this.activeMenu.scrollTop(a+o):o+r>s&&this.activeMenu.scrollTop(a+o-s+r))},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(),a="textarea"===o,s="input"===o;this.isMultiLine=!!a||!s&&this.element.prop("isContentEditable"),this.valueMethod=this.element[a||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 a=t.ui.keyCode;switch(o.keyCode){case a.PAGE_UP:e=!0,this._move("previousPage",o);break;case a.PAGE_DOWN:e=!0,this._move("nextPage",o);break;case a.UP:e=!0,this._keyEvent("previous",o);break;case a.DOWN:e=!0,this._keyEvent("next",o);break;case a.ENTER:this.menu.active&&(e=!0,o.preventDefault(),this.menu.select(o));break;case a.TAB:this.menu.active&&this.menu.select(o);break;case a.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 r=t("a",n),c=r[0],l=r[1],u=r[2],h=r[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,a,s,r,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),a=i;a<=o;a++)p+=d!==a?'
  • '+e.fnFormatNumber(a)+"
  • ":'
  • '+e.fnFormatNumber(a)+"
  • ";for(a=0,s=f.length;a",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:r,ra:c,sa:l?c:r,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,a=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 a=function(e){n.call(t,e)},s="on"+e;t.attachEvent(s,a),h.a.u.ja(t,function(){t.detachEvent(s,a)})}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 a;if("input"===h.a.B(t)&&t.type&&"click"==i.toLowerCase()?(a=t.type,a="checkbox"==a||"radio"==a):a=!1,o&&!a)o(t).trigger(i);else if("function"==typeof n.createEvent){if("function"!=typeof t.dispatchEvent)throw Error("The supplied element doesn't support dispatchEvent");a=n.createEvent(d[i]||"HTMLEvents"),a.initEvent(i,!0,!0,e,0,0,0,0,0,!1,!1,!1,!1,0,t),t.dispatchEvent(a)}else if(a&&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",""]||!a.indexOf("",""]||(!a.indexOf("",""]||[0,"",""],t="ignored
    "+a[1]+t+a[2]+"
    ","function"==typeof e.innerShiv?i.appendChild(e.innerShiv(t)):i.innerHTML=t;a[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),a=0;a"},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,a=i.length;oa[0]?c+a[0]:a[0]),c);for(var c=1===l?c:Math.min(e+(a[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,a=-1,s=0,r=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=a?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,r=a?/^\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 a(){var t=e.checked,a=d?o():t;if(!h.ca.pa()&&(!c||t)){var s=h.k.t(n);l?u!==a?(t&&(h.a.Y(s,a,!0),h.a.Y(s,u,!1)),u=a):h.a.Y(s,a,t):h.g.va(s,i,"checked",a,!0)}}function s(){var t=h.a.c(n());e.checked=l?0<=h.a.l(t,o()):r?t:o()===t}var r="checkbox"==e.type,c="radio"==e.type;if(r||c){var l=r&&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(a,null,{G:e}),h.a.q(e,"click",a),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 a=e()||{};h.a.A(a,function(a){"string"==typeof a&&h.a.q(t,a,function(t){var s,r=e()[a];if(r){try{var c=h.a.R(arguments);i=o.$data,c.unshift(i),s=r.apply(i,c)}finally{!0!==s&&(t.preventDefault?t.preventDefault():t.returnValue=!1)}!1===n.get(a+"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 a;try{a=o.activeElement}catch(s){a=o.body}i=a===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),a=i.bind(null,!1);h.a.q(t,"focus",o),h.a.q(t,"focusin",o),h.a.q(t,"blur",a),h.a.q(t,"focusout",a)},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],a=i.parentNode,s=h.J.instance,r=s.preprocessNode;if(r){if(e(i,o,function(t,e){var n=t.previousSibling,a=r.call(s,t);a&&(t===i&&(i=a[0]||e),t===o&&(o=a[a.length-1]||n))}),t.length=0,!i)return;i===o?t.push(i):(t.push(i,o),h.a.ea(t,a))}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,a)}}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,a){if(a=a||{},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},a.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(),T.afterMove&&T.afterMove.call(this,b,s,r)}y&&y.apply(this,arguments)},connectWith:!!T.connectClass&&"."+T.connectClass})),void 0!==T.isEnabled&&t.computed({read:function(){A.sortable(r(T.isEnabled)?"enable":"disable")},disposeWhenNodeIsRemoved:u})},0);return t.utils.domNodeDisposal.addDisposeCallback(u,function(){(A.data("ui-sortable")||A.data("sortable"))&&A.sortable("destroy"),clearTimeout(w)}),{controlsDescendantBindings:!0}},update:function(e,n,i,a,s){var r=p(n,"foreach");l(e,o,r.foreach),t.bindingHandlers.template.update(e,function(){return r},i,a,s)},connectClass:"ko_container",allowDrop:!0,afterMove:null,beforeMove:null,options:{}},t.bindingHandlers.draggable={init:function(n,i,o,a,c){var u=r(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(r(g)?"enable":"disable")},disposeWhenNodeIsRemoved:n}),t.bindingHandlers.template.init(n,function(){return f},o,a,c)},update:function(e,n,i,o,a){var s=p(n,"data");return t.bindingHandlers.template.update(e,function(){return s},i,o,a)},connectClass:t.bindingHandlers.sortable.connectClass,options:{helper:"clone"}}}),function(){var t=this,e=t._,n=Array.prototype,i=Object.prototype,o=Function.prototype,a=n.push,s=n.slice,r=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,a){return t.call(e,n,i,o,a)}}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,a=-(1/0),s=-(1/0);if(null==e&&null!=t){t=t.length===+t.length?t:p.values(t);for(var r=0,c=t.length;ra&&(a=i)}else e=p.iteratee(e,n),p.each(t,function(t,n,i){o=e(t,n,i),(o>s||o===-(1/0)&&a===-(1/0))&&(a=t,s=o)});return a},p.min=function(t,e,n){var i,o,a=1/0,s=1/0;if(null==e&&null!=t){t=t.length===+t.length?t:p.values(t);for(var r=0,c=t.length;ri||void 0===n)return 1;if(n>>1;n(t[r])=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),a=0;ae?(clearTimeout(s),s=null,r=l,a=t.apply(i,o),s||(i=o=null)):s||n.trailing===!1||(s=setTimeout(c,u)),a}},p.debounce=function(t,e,n){var i,o,a,s,r,c=function(){var l=p.now()-s;l0?i=setTimeout(c,e-l):(i=null,n||(r=t.apply(a,o),i||(a=o=null)))};return function(){a=this,o=arguments,s=p.now();var l=n&&!i;return i||(i=setTimeout(c,e)),l&&(r=t.apply(a,o),a=o=null),r}},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),z=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=z(y),p.unescape=z(A),p.result=function(t,e){if(null!=t){var n=t[e];return p.isFunction(n)?t[e]():n}};var _=0;p.uniqueId=function(t){var e=++_+"";return t?t+e:e},p.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,w={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},C=/\\|'|\r|\n|\u2028|\u2029/g,O=function(t){return"\\"+w[t]};p.template=function(t,e,n){!e&&n&&(e=n),e=p.defaults({},e,p.templateSettings);var i=RegExp([(e.escape||T).source,(e.interpolate||T).source,(e.evaluate||T).source].join("|")+"|$","g"),o=0,a="__p+='";t.replace(i,function(e,n,i,s,r){return a+=t.slice(o,r).replace(C,O),o=r+e.length,n?a+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":i?a+="'+\n((__t=("+i+"))==null?'':__t)+\n'":s&&(a+="';\n"+s+"\n__p+='"),e}),a+="';\n",e.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{var s=new Function(e.variable||"obj","_",a)}catch(r){throw r.source=a,r}var c=function(t){return s.call(this,t,p)},l=e.variable||"obj";return c.source="function("+l+"){\n"+a+"}",c},p.chain=function(t){var e=p(t);return e._chain=!0,e};var N=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 a.apply(t,arguments),N.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],N.call(this,n)}}),p.each(["concat","join","slice"],function(t){var e=n[t];p.prototype[t]=function(){return N.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 a(t){return function(){return this[t].apply(this,arguments)}}function s(e,n){function i(t,e){return e.toLowerCase()}var o,a=t(e).data(),s={},r=new RegExp("^"+n.toLowerCase()+"([A-Z])");n=new RegExp("^"+n.toLowerCase());for(var c in a)n.test(c)&&(o=c.replace(r,i),s[o]=a[c]);return s}function r(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+a-(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(),a=this.viewDate.getUTCMonth(),s=new Date;return e.getUTCFullYear()i||e.getUTCFullYear()===i&&e.getUTCMonth()>a)&&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),z=new Date(Number(z=n(A.getUTCFullYear(),0,1))+(11-z.getUTCDay())%7*864e5),_=(A-z)/864e5/7+1;M.push(''+_+"")}if(v=this.getClassNames(p),v.push("day"),this.o.beforeShowDay!==t.noop){var T=this.o.beforeShowDay(this._utc_to_local(p));T===e?T={}:"boolean"==typeof T?T={enabled:T}:"string"==typeof T&&(T={classes:T}),T.enabled===!1&&v.push("disabled"),T.classes&&(v=v.concat(T.classes.split(/\s+/))),T.tooltip&&(i=T.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 w=this.picker.find(".datepicker-months").find("th:eq(1)").text(a).end().find("span").removeClass("active");if(t.each(this.dates,function(t,e){e.getUTCFullYear()===a&&w.eq(e.getUTCMonth()).addClass("active")}),(al)&&w.addClass("disabled"),a===r&&w.slice(0,c).addClass("disabled"),a===l&&w.slice(u+1).addClass("disabled"),this.o.beforeShowMonth!==t.noop){var C=this;t.each(w,function(e,n){if(!t(n).hasClass("disabled")){var i=new Date(a,e,1),o=C.o.beforeShowMonth(i);o===!1&&t(n).addClass("disabled")}})}M="",a=10*parseInt(a/10,10);var O=this.picker.find(".datepicker-years").find("th:eq(1)").text(a+"-"+(a+9)).end().find("td");a-=1;for(var N,S=t.map(this.dates,function(t){return t.getUTCFullYear()}),x=-1;x<11;x++)N=["year"],x===-1?N.push("old"):10===x&&N.push("new"),t.inArray(a,S)!==-1&&N.push("active"),(al)&&N.push("disabled"),M+=''+a+"",a+=1;O.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,a,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 r=g.modes[this.viewMode].navStep*("prev"===s[0].className?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,r),this._trigger("changeMonth",this.viewDate);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,r),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")?(a=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,a))):(a=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,a))),this.showMode(-1),this.fill());break;case"td":s.hasClass("day")&&!s.hasClass("disabled")&&(a=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,a)))}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,a=new Date(t.valueOf()),s=a.getUTCDate(),r=a.getUTCMonth(),c=Math.abs(n);if(n=n>0?1:-1,1===c)o=n===-1?function(){return a.getUTCMonth()===r}:function(){return a.getUTCMonth()!==i},i=r+n,a.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,a=!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),a=!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(a){this.dates.length?this._trigger("changeDate"):this._trigger("clearDate");var r;this.isInput?r=this.element:this.component&&(r=this.element.find("input")),r&&r.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),a=o-1,s=o+1,r=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 a=t(this),c=a.data("datepicker"),h="object"==typeof n&&n;if(!c){var d=s(this,"date"),f=t.extend({},p,d,h),m=r(f.language),g=t.extend({},p,m,d,h);if(a.hasClass("input-daterange")||g.inputs){var b={inputs:g.inputs||a.find("input").toArray()};a.data("datepicker",c=new u(this,t.extend(g,b)))}else a.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,a){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 r,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,a,s,r,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,a;if(i=arguments[0],t=2<=arguments.length?c.call(arguments,1):[],this._callbacks=this._callbacks||{},n=this._callbacks[i])for(o=0,a=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,a,s,r,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]"),a=0,c=h.length;a'+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=[],r=0,u=p.length;r\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,a,s;for(i=arguments[0],n=2<=arguments.length?c.call(arguments,1):[],a=0,s=n.length;a'+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,a;if(n=t.hiddenFileInput.files,n.length)for(o=0,a=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,a;for(e=function(t){var e,n,i;for(n=0,i=t.length;n0){for(s=["TB","GB","MB","KB","b"],n=r=0,c=s.length;r=e){i=t/Math.pow(this.options.filesizeBase,4-n),o=a;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(a=0,s=n.length;a1024*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)a.append(this._getParamName(u),t[u],this._renameFilename(t[u].name));return this.submitRequest(z,a,t)},n.prototype.submitRequest=function(t,e,n){return t.send(e)},n.prototype._finished=function(t,e,i){var o,a,s;for(a=0,s=t.length;au;)e=o[4*(c-1)+3],0===e?a=c:u=c,c=a+u>>1;return l=c/s,0===l?1:l},a=function(t,e,n,i,a,s,r,c,l,u){var h;return h=o(e),t.drawImage(e,n,i,a,s,r,c,l,u/h)},i=function(t,e){var n,i,o,a,s,r,c,l,u;if(o=!1,u=!0,i=t.document,l=i.documentElement,n=i.addEventListener?"addEventListener":"attachEvent",c=i.addEventListener?"removeEventListener":"detachEvent",r=i.addEventListener?"":"on",a=function(n){if("readystatechange"!==n.type||"complete"===i.readyState)return("load"===n.type?t:i)[c](r+n.type,a,!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 a("poll")},"complete"!==i.readyState){if(i.createEventObject&&l.doScroll){try{u=!t.frameElement}catch(h){}u&&s()}return i[n](r+"DOMContentLoaded",a,!1),i[n](r+"readystatechange",a,!1),t[n](r+"load",a,!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 a,s,r=this,c=arguments;return a=function(){i=null,n||(o=t.apply(r,c))},s=n&&!i,clearTimeout(i),i=setTimeout(a,e),s&&(o=t.apply(r,c)),o}},throttle:function(t,e){var n,i,o,a,s,r;return s=0,r=function(){s=new Date,o=null,a=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,a=t.apply(n,i)):o||(o=setTimeout(r,l)),a}},stringify:function(t){return e.isString(t)?t:JSON.stringify(t)},noop:function(){}}}(),n=function(){"use strict";function t(t){var s,r;return r=e.mixin({},a,t),s={css:o(),classes:r,html:n(r),selectors:i(r)},{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 a={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?r(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=a(n.sync,this,[e].concat(i)),s=a(n.async,this,[e].concat(i)),o()&&l(s);return this}function a(t,e,n){function i(){for(var i,o=0,a=t.length;!i&&o