diff --git a/.env.example b/.env.example index 1ed1ec049cbb..ec281e283601 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 92bf4064b384..067ed7c1530e 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 0c2d0bbb7327..0fad62197099 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 dca1327f40ac..de02d0d47094 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 7e339643e182..1606e5eec5ec 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 88b317df5a69..0a62ca626fbf 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 8a047352ca8d..bd7a2bc30ead 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 27636445b238..e5dc615bb6f6 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 1a568596fc9f..75634ad9da1e 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 f5567383c745..9ed644515c34 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 cceb58a2502f..cc20a725c211 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 c3d65cf0a50a..e7e04b15921d 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 56f2dc5d746a..44bbd40006ba 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 c87b333fe20c..13e96ae7443b 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 c5fbccb78790..efe471557917 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 0a07f8b0b5bd..ef7460b6480c 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 90e85c312f78..55ebaeb559db 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 928fe68b8c7c..d32968eacbf9 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 40a46176fc59..c583835fd49d 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 000000000000..a74658077f59 --- /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 fb85331eb1e2..df3691599535 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 07bbcb995fce..1b67c1e8d1f1 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 cbdcb3f5e1a4..1a2dea026ca4 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 7f26529fe06d..7948d210e27b 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 6fde46cd1613..49618a7abc72 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 d310f6f9ee67..b948e28960e0 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 640a6503f27c..18d562dfbedc 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 ab53284bce2f..399e73cdeb6a 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 207177e1747c..9710fdc4fdc8 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 3597c3f47734..5037a3063ee4 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 9dc5915d90de..d45eb47fc534 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 daba96174e55..0661fb3750f1 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 959bc8281b35..5eadb2a473f5 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 69d85eecc216..6e2f43a59306 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 ca24c03941d7..208d176eb9d3 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 412aeb67cec6..860fee6e9a61 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 cebab115f806..6b24acf36584 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 414151fd3ee2..db0c0d715f52 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 64365c910ec5..f505517c58c8 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 70f1780d4f18..c6429521a875 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 f7227a97af37..573f4fea4ca5 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 995fc8298d40..e508f8d0c551 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 9a59d953b007..fef40c5cfdab 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 000000000000..9c8750ad504d --- /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 6f5cda03823a..04fe56320ba5 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 f2e1d468419a..d9e187a6e159 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 03ee478ebb65..17388a6f3af5 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 94d6fba71a5d..7912c0df45da 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 25696c6fff5b..1c7d91a3b89b 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 141bf89f08eb..b5bd2cd3e311 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 000000000000..e6a40d18bb56 --- /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 000000000000..48f6d0e9ba05 --- /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 000000000000..9ffe334138be --- /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 000000000000..c1f289e2cae3 --- /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 a638b2b049c0..f37abac24e44 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 2434480642bd..2dfb3366a67b 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 73053ef106e4..eb59cc9b6214 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 014cbe64bcd3..3888ccbda063 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 6d5c3ca90346..03ada0d395df 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 000000000000..752b2aea1e19 --- /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 b3c72fd2d37d..5a42983d38ba 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 95c6b614ceef..afe117a5dffe 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 c19ba4bda6cb..087b1a356ac9 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 e1b4243b8e13..1205d981bbc3 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 6bc0c5b290fc..3cb8a052cc19 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 c4619d64e02d..1765686ad043 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 37c89b89529d..0e493bf204ea 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 0ab2a8faff7b..7e5b3b66c67f 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 aecb9cc51f71..ba12d00aeb74 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 782f1668dc04..139627f46a24 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 000000000000..710f84215f69 --- /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 352293419bc8..c94e50e3edba 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 ca415b1e6012..353506979ac2 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 2510373de0c5..ecd7cd323e18 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 1688fd4085de..240234d878f5 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 d67644931c89..e427ed5e17b1 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 6913274f977c..ad20e15bc67e 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 3bbf4430fa72..2fd96e09155e 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 1073bef63017..d29408ab31e8 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 655b924c3b8e..f76144b3faeb 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 f536b454ef41..b4593bd92426 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 3f0f159dfb81..b40378310200 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 98ba3c66febd..d226a78079bd 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 12939f5ecce8..bc2cbaa4cd55 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 c509bd66afcb..302e7d6b8ed2 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 000000000000..487cd6172fd9 --- /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 000000000000..0425b3f1d6aa --- /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 000000000000..689130f38c67 --- /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 7acb3cb79448..6a53c8d29240 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 ac3d0c847b24..ffec414c59b7 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 1ebdc69e79e9..7398844c1404 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 8dce6bdcd1b1..187e9f16f245 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 6ee92f1c2c8c..473423aad520 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 c1983b3e47b3..4b6ffa080c6e 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 000000000000..3e843b25b3f9 --- /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 a7d1213d9c96..78799255090b 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 b0f12f63ea90..befde9b3bc67 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 880845f07555..10a4220e1ce8 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 908e01e96a55..b9cb66d0f560 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 5df24a1d1659..d448646ab989 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 19bb5c1fcc81..a0124a825b48 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 919c57153792..4cd0a47c9bfd 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 000000000000..e53f6e0fb8fe --- /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 803f799a2a6f..29d83e1486f3 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 4930393412d6..c964a7f4dcd6 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 f338ccb316cf..e047bf162c68 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 fc1446a6a612..9ff3d67beed4 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 f2c3d19a1fcc..e30a458735cb 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 51a30bec46d4..7a72fda3e2d3 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 adf89a709a38..032cf365cf52 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 e1fce0310cf3..1a1c6366bc63 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 20077584e4a2..6a322e153d79 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 7210e8e96261..7bed6377013a 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 84b0c440b380..6d472485a07a 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 0b5364841753..765e9b9f3cd4 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 000000000000..6239c8452939 --- /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 2dcecfe002c7..7c11d68b834c 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 2986eb73c1fb..4f12d9c0d4b9 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 4fd72ffde690..4e6ad4a633aa 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 752e3dbec0dc..f36bef9573ce 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 99ee24a6d1f2..f48a646773b6 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 16fd8d4e82c0..a2cea573ba61 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 0937f25f45f6..5b62755831a5 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 c4d8b44d164a..a23ba7843ee8 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 446e6492acb6..7935fce8991c 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 81e014de4b66..d5278cc193b1 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 fd7e47c039c5..241e3e992f0d 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 ae2a1acb92f6..6e6dbcb2bb84 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 d7c695588e36..823b338648ec 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 8e3bf57e69c0..ba50a4a53169 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 af2de116f801..2bcc8791056c 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 ed44cd4c42cc..8c4a4d38be6a 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 000000000000..2aab428a006f --- /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 000000000000..3b8bbf97919c --- /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 f2ade844dd03..f06241c95928 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 b5feb742bd6b..db84008750f0 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 627381edc7cb..c6e2829d298f 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 9670fe30b198..846d4be87ff0 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 40ef6d8fe008..b0ae7b213193 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 fe938d6cc724..e31cefa78a8d 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 66fb04581fc5..5d11d2331ea8 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 000000000000..99ccbfc88ac0 --- /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 a184796c2ba4..e019d61d63cd 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 000000000000..7752f70867dc --- /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 4cc3e14d00c5..5e6ed9039691 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 05c418a46d72..d03f0ecccace 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 879bca6072f6..cf63b132b74d 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 000000000000..ea6b34b62d86 --- /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 4e8e07cd7b73..556af9b289ae 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 b9c16327ec42..31fbc989a36f 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 000000000000..78b9fbf3c555 --- /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 d10d97e9b630..b5e5c910ce1e 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 b7e8f093a99c..d8708539ee71 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 a50e8bca80b7..bd2bc333a9ce 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 000000000000..cf6386b18471 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 60c15ee9172c..0a55fe609b7e 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