diff --git a/.env.example b/.env.example index 2d1db72861cc..18053683d7ba 100644 --- a/.env.example +++ b/.env.example @@ -26,9 +26,14 @@ MAILGUN_SECRET= #POSTMARK_API_TOKEN= PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' +#PHANTOMJS_BIN_PATH=/usr/local/bin/phantomjs + LOG=single REQUIRE_HTTPS=false API_SECRET=password +IOS_DEVICE= +ANDROID_DEVICE= +FCM_API_TOKEN= #TRUSTED_PROXIES= @@ -46,6 +51,7 @@ API_SECRET=password #GOOGLE_CLIENT_SECRET= #GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google +GOOGLE_MAPS_ENABLED=true #GOOGLE_MAPS_API_KEY= #S3_KEY= diff --git a/.travis.yml b/.travis.yml index 727f351d7d1b..5eaa04417349 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,8 @@ php: # - 5.5.9 # - 5.6 # - 5.6 - - 7.0 +# - 7.0 + - 7.1 # - hhvm addons: diff --git a/app/Console/Commands/ChargeRenewalInvoices.php b/app/Console/Commands/ChargeRenewalInvoices.php index 0e9ef0e4c071..df3ee079a9d5 100644 --- a/app/Console/Commands/ChargeRenewalInvoices.php +++ b/app/Console/Commands/ChargeRenewalInvoices.php @@ -6,7 +6,6 @@ use App\Ninja\Repositories\AccountRepository; use App\Services\PaymentService; use App\Models\Invoice; use App\Models\Account; -use Exception; /** * Class ChargeRenewalInvoices @@ -81,11 +80,10 @@ class ChargeRenewalInvoices extends Command continue; } - try { - $this->info("Charging invoice {$invoice->invoice_number}"); - $this->paymentService->autoBillInvoice($invoice); - } catch (Exception $exception) { - $this->info('Error: ' . $exception->getMessage()); + $this->info("Charging invoice {$invoice->invoice_number}"); + if ( ! $this->paymentService->autoBillInvoice($invoice)) { + $this->info('Failed to auto-bill, emailing invoice'); + $this->mailer->sendInvoice($invoice); } } diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index a39681a38456..c5f2d8e2aed3 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -2,6 +2,7 @@ use DB; use Mail; +use Utils; use Carbon; use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; @@ -139,20 +140,26 @@ class CheckData extends Command { ENTITY_VENDOR, ENTITY_INVOICE, ENTITY_USER + ], + 'products' => [ + ENTITY_USER, + ], + 'expense_categories' => [ + ENTITY_USER, + ], + 'projects' => [ + ENTITY_USER, + ENTITY_CLIENT, ] ]; foreach ($tables as $table => $entityTypes) { foreach ($entityTypes as $entityType) { + $tableName = Utils::pluralizeEntityType($entityType); $records = DB::table($table) - ->join("{$entityType}s", "{$entityType}s.id", '=', "{$table}.{$entityType}_id"); - - if ($entityType != ENTITY_CLIENT) { - $records = $records->join('clients', 'clients.id', '=', "{$table}.client_id"); - } - - $records = $records->where("{$table}.account_id", '!=', DB::raw("{$entityType}s.account_id")) - ->get(["{$table}.id", 'clients.account_id', 'clients.user_id']); + ->join($tableName, "{$tableName}.id", '=', "{$table}.{$entityType}_id") + ->where("{$table}.account_id", '!=', DB::raw("{$tableName}.account_id")) + ->get(["{$table}.id"]); if (count($records)) { $this->isValid = false; diff --git a/app/Constants.php b/app/Constants.php new file mode 100644 index 000000000000..92a49a54bfc6 --- /dev/null +++ b/app/Constants.php @@ -0,0 +1,582 @@ + ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], + 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], + 4 => ['card' => 'images/credit_cards/Test-AmericanExpress-Icon.png', 'text' => 'American Express'], + 8 => ['card' => 'images/credit_cards/Test-Diners-Icon.png', 'text' => 'Diners'], + 16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'] + ]; + define('CREDIT_CARDS', serialize($creditCards)); + + $cachedTables = [ + 'currencies' => 'App\Models\Currency', + 'sizes' => 'App\Models\Size', + 'industries' => 'App\Models\Industry', + 'timezones' => 'App\Models\Timezone', + 'dateFormats' => 'App\Models\DateFormat', + 'datetimeFormats' => 'App\Models\DatetimeFormat', + 'languages' => 'App\Models\Language', + 'paymentTerms' => 'App\Models\PaymentTerm', + 'paymentTypes' => 'App\Models\PaymentType', + 'countries' => 'App\Models\Country', + 'invoiceDesigns' => 'App\Models\InvoiceDesign', + 'invoiceStatus' => 'App\Models\InvoiceStatus', + 'frequencies' => 'App\Models\Frequency', + 'gateways' => 'App\Models\Gateway', + 'gatewayTypes' => 'App\Models\GatewayType', + 'fonts' => 'App\Models\Font', + 'banks' => 'App\Models\Bank', + ]; + define('CACHED_TABLES', serialize($cachedTables)); + + + // TODO remove these translation functions + function uctrans($text) + { + return ucwords(trans($text)); + } + + // optional trans: only return the string if it's translated + function otrans($text) + { + $locale = Session::get(SESSION_LOCALE); + + if ($locale == 'en') { + return trans($text); + } else { + $string = trans($text); + $english = trans($text, [], 'en'); + return $string != $english ? $string : ''; + } + } + + // include modules in translations + function mtrans($entityType, $text = false) + { + if ( ! $text) { + $text = $entityType; + } + + // check if this has been translated in a module language file + if ( ! Utils::isNinjaProd() && $module = Module::find($entityType)) { + $key = "{$module->getLowerName()}::texts.{$text}"; + $value = trans($key); + if ($key != $value) { + return $value; + } + } + + return trans("texts.{$text}"); + } +} diff --git a/app/Constants/Domain.php b/app/Constants/Domain.php new file mode 100644 index 000000000000..afdcbf90c52e --- /dev/null +++ b/app/Constants/Domain.php @@ -0,0 +1,29 @@ +invitation = $invitation; + $this->notes = $notes; } } diff --git a/app/Events/QuoteInvitationWasEmailed.php b/app/Events/QuoteInvitationWasEmailed.php index 54481ab9e763..a8d8d58ebeb7 100644 --- a/app/Events/QuoteInvitationWasEmailed.php +++ b/app/Events/QuoteInvitationWasEmailed.php @@ -15,14 +15,20 @@ class QuoteInvitationWasEmailed extends Event */ public $invitation; + /** + * @var String + */ + public $notes; + /** * Create a new event instance. * * @param Invitation $invitation */ - public function __construct(Invitation $invitation) + public function __construct(Invitation $invitation, $notes) { $this->invitation = $invitation; + $this->notes = $notes; } } diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index 1920978c0e77..f81e50510928 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -27,11 +27,14 @@ class AccountApiController extends BaseAPIController $this->accountRepo = $accountRepo; } - public function ping() + public function ping(Request $request) { $headers = Utils::getApiHeaders(); - return Response::make(RESULT_SUCCESS, 200, $headers); + if(hash_equals(env(API_SECRET),$request->api_secret)) + return Response::make(RESULT_SUCCESS, 200, $headers); + else + return $this->errorResponse(['message'=>'API Secret does not match .env variable'], 400); } public function register(RegisterRequest $request) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index e486d3e63254..83f90f0869e5 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -39,6 +39,9 @@ use App\Services\AuthService; use App\Services\PaymentService; use App\Http\Requests\UpdateAccountRequest; +use App\Http\Requests\SaveClientPortalSettings; +use App\Http\Requests\SaveEmailSettings; + /** * Class AccountController */ @@ -233,6 +236,7 @@ class AccountController extends BaseController $company->plan_expires = date_create()->modify($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d'); } + $company->trial_plan = null; $company->plan = $plan; $company->save(); @@ -521,7 +525,7 @@ class AccountController extends BaseController $data = [ 'account' => Auth::user()->account, 'title' => trans('texts.tax_rates'), - 'taxRates' => TaxRate::scope()->get(['id', 'name', 'rate']), + 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']), ]; return View::make('accounts.tax_rates', $data); @@ -555,12 +559,12 @@ class AccountController extends BaseController $document = new stdClass(); $client->name = 'Sample Client'; - $client->address1 = trans('texts.address1'); - $client->city = trans('texts.city'); - $client->state = trans('texts.state'); - $client->postal_code = trans('texts.postal_code'); - $client->work_phone = trans('texts.work_phone'); - $client->work_email = trans('texts.work_id'); + $client->address1 = '10 Main St.'; + $client->city = 'New York'; + $client->state = 'NY'; + $client->postal_code = '10000'; + $client->work_phone = '(212) 555-0000'; + $client->work_email = 'sample@example.com'; $invoice->invoice_number = '0000'; $invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d')); @@ -713,14 +717,10 @@ class AccountController extends BaseController return AccountController::export(); } elseif ($section === ACCOUNT_INVOICE_SETTINGS) { return AccountController::saveInvoiceSettings(); - } elseif ($section === ACCOUNT_EMAIL_SETTINGS) { - return AccountController::saveEmailSettings(); } elseif ($section === ACCOUNT_INVOICE_DESIGN) { return AccountController::saveInvoiceDesign(); } elseif ($section === ACCOUNT_CUSTOMIZE_DESIGN) { return AccountController::saveCustomizeDesign(); - } elseif ($section === ACCOUNT_CLIENT_PORTAL) { - return AccountController::saveClientPortal(); } elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) { return AccountController::saveEmailTemplates(); } elseif ($section === ACCOUNT_PRODUCTS) { @@ -788,53 +788,30 @@ class AccountController extends BaseController /** * @return \Illuminate\Http\RedirectResponse */ - private function saveClientPortal() + public function saveClientPortalSettings(SaveClientPortalSettings $request) { - $account = Auth::user()->account; - $account->fill(Input::all()); - - // Only allowed for pro Invoice Ninja users or white labeled self-hosted users - if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { - $input_css = Input::get('client_view_css'); - if (Utils::isNinja()) { - // Allow referencing the body element - $input_css = preg_replace('/(? - // - - // Create a new configuration object - $config = \HTMLPurifier_Config::createDefault(); - $config->set('Filter.ExtractStyleBlocks', true); - $config->set('CSS.AllowImportant', true); - $config->set('CSS.AllowTricky', true); - $config->set('CSS.Trusted', true); - - // Create a new purifier instance - $purifier = new \HTMLPurifier($config); - - // Wrap our CSS in style tags and pass to purifier. - // we're not actually interested in the html response though - $html = $purifier->purify(''); - - // The "style" blocks are stored seperately - $output_css = $purifier->context->get('StyleBlocks'); - - // Get the first style block - $sanitized_css = count($output_css) ? $output_css[0] : ''; - } else { - $sanitized_css = $input_css; - } - - $account->client_view_css = $sanitized_css; - } - + $account = $request->user()->account; + $account->fill($request->all()); + $account->subdomain = $request->subdomain; + $account->iframe_url = $request->iframe_url; $account->save(); - Session::flash('message', trans('texts.updated_settings')); + return redirect('settings/' . ACCOUNT_CLIENT_PORTAL) + ->with('message', trans('texts.updated_settings')); + } - return Redirect::to('settings/'.ACCOUNT_CLIENT_PORTAL); + /** + * @return $this|\Illuminate\Http\RedirectResponse + */ + public function saveEmailSettings(SaveEmailSettings $request) + { + $account = $request->user()->account; + $account->fill($request->all()); + $account->bcc_email = $request->bcc_email; + $account->save(); + + return redirect('settings/' . ACCOUNT_EMAIL_SETTINGS) + ->with('message', trans('texts.updated_settings')); } /** @@ -904,83 +881,18 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_PRODUCTS); } - /** - * @return $this|\Illuminate\Http\RedirectResponse - */ - private function saveEmailSettings() - { - if (Auth::user()->account->hasFeature(FEATURE_CUSTOM_EMAILS)) { - $user = Auth::user(); - $subdomain = null; - $iframeURL = null; - $rules = []; - - if (Input::get('custom_link') == 'subdomain') { - $subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH)); - if (Utils::isNinja()) { - $exclude = [ - 'www', - 'app', - 'mail', - 'admin', - 'blog', - 'user', - 'contact', - 'payment', - 'payments', - 'billing', - 'invoice', - 'business', - 'owner', - 'info', - 'ninja', - 'docs', - 'doc', - 'documents' - ]; - $rules['subdomain'] = "unique:accounts,subdomain,{$user->account_id},id|not_in:" . implode(',', $exclude); - } - } else { - $iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH)); - $iframeURL = rtrim($iframeURL, '/'); - } - - $validator = Validator::make(Input::all(), $rules); - - if ($validator->fails()) { - return Redirect::to('settings/'.ACCOUNT_EMAIL_SETTINGS) - ->withErrors($validator) - ->withInput(); - } else { - $account = Auth::user()->account; - $account->subdomain = $subdomain; - $account->iframe_url = $iframeURL; - $account->pdf_email_attachment = Input::get('pdf_email_attachment') ? true : false; - $account->document_email_attachment = Input::get('document_email_attachment') ? true : false; - $account->email_design_id = Input::get('email_design_id'); - - if (Utils::isNinja()) { - $account->enable_email_markup = Input::get('enable_email_markup') ? true : false; - } - - $account->save(); - Session::flash('message', trans('texts.updated_settings')); - } - } - - return Redirect::to('settings/'.ACCOUNT_EMAIL_SETTINGS); - } - /** * @return $this|\Illuminate\Http\RedirectResponse */ private function saveInvoiceSettings() { if (Auth::user()->account->hasFeature(FEATURE_INVOICE_SETTINGS)) { - $rules = [ - 'invoice_number_pattern' => 'has_counter', - 'quote_number_pattern' => 'has_counter', - ]; + $rules = []; + foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_CLIENT] as $entityType) { + if (Input::get("{$entityType}_number_type") == 'pattern') { + $rules["{$entityType}_number_pattern"] = 'has_counter'; + } + } $validator = Validator::make(Input::all(), $rules); @@ -1015,6 +927,10 @@ class AccountController extends BaseController $account->auto_convert_quote = Input::get('auto_convert_quote'); $account->recurring_invoice_number_prefix = Input::get('recurring_invoice_number_prefix'); + $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'); + if (Input::has('recurring_hour')) { $account->recurring_hour = Input::get('recurring_hour'); } @@ -1023,20 +939,14 @@ class AccountController extends BaseController $account->quote_number_counter = Input::get('quote_number_counter'); } - if (Input::get('invoice_number_type') == 'prefix') { - $account->invoice_number_prefix = trim(Input::get('invoice_number_prefix')); - $account->invoice_number_pattern = null; - } else { - $account->invoice_number_pattern = trim(Input::get('invoice_number_pattern')); - $account->invoice_number_prefix = null; - } - - if (Input::get('quote_number_type') == 'prefix') { - $account->quote_number_prefix = trim(Input::get('quote_number_prefix')); - $account->quote_number_pattern = null; - } else { - $account->quote_number_pattern = trim(Input::get('quote_number_pattern')); - $account->quote_number_prefix = null; + foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_CLIENT] as $entityType) { + if (Input::get("{$entityType}_number_type") == 'prefix') { + $account->{"{$entityType}_number_prefix"} = trim(Input::get("{$entityType}_number_prefix")); + $account->{"{$entityType}_number_pattern"} = null; + } else { + $account->{"{$entityType}_number_pattern"} = trim(Input::get("{$entityType}_number_pattern")); + $account->{"{$entityType}_number_prefix"} = null; + } } if (!$account->share_counter @@ -1218,6 +1128,13 @@ class AccountController extends BaseController $user->email = trim(strtolower(Input::get('email'))); $user->phone = trim(Input::get('phone')); + if ( ! Auth::user()->is_admin) { + $user->notify_sent = Input::get('notify_sent'); + $user->notify_viewed = Input::get('notify_viewed'); + $user->notify_paid = Input::get('notify_paid'); + $user->notify_approved = Input::get('notify_approved'); + } + if (Utils::isNinja()) { if (Input::get('referral_code') && !$user->referral_code) { $user->referral_code = $this->accountRepo->getReferralCode(); @@ -1461,22 +1378,6 @@ class AccountController extends BaseController return Redirect::to('/settings/'.ACCOUNT_USER_DETAILS)->with('message', trans('texts.confirmation_resent')); } - /** - * @param $plan - * @return \Illuminate\Http\RedirectResponse - */ - public function startTrial($plan) - { - /** @var \App\Models\User $user */ - $user = Auth::user(); - - if ($user->isEligibleForTrial($plan)) { - $user->account->startTrial($plan); - } - - return Redirect::back()->with('message', trans('texts.trial_success')); - } - /** * @param $section * @param bool $subSection diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index dd35fc0ed032..7d2d97e294e1 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -266,21 +266,21 @@ class AccountGatewayController extends BaseController $config->publishableKey = $oldConfig->publishableKey; } - $plaidClientId = Input::get('plaid_client_id'); + $plaidClientId = trim(Input::get('plaid_client_id')); if ($plaidClientId = str_replace('*', '', $plaidClientId)) { $config->plaidClientId = $plaidClientId; } elseif ($oldConfig && property_exists($oldConfig, 'plaidClientId')) { $config->plaidClientId = $oldConfig->plaidClientId; } - $plaidSecret = Input::get('plaid_secret'); + $plaidSecret = trim(Input::get('plaid_secret')); if ($plaidSecret = str_replace('*', '', $plaidSecret)) { $config->plaidSecret = $plaidSecret; } elseif ($oldConfig && property_exists($oldConfig, 'plaidSecret')) { $config->plaidSecret = $oldConfig->plaidSecret; } - $plaidPublicKey = Input::get('plaid_public_key'); + $plaidPublicKey = trim(Input::get('plaid_public_key')); if ($plaidPublicKey = str_replace('*', '', $plaidPublicKey)) { $config->plaidPublicKey = $plaidPublicKey; } elseif ($oldConfig && property_exists($oldConfig, 'plaidPublicKey')) { diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 647ea7dd2eec..9a6a32ea78eb 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -56,6 +56,7 @@ class AppController extends BaseController $app = Input::get('app'); $app['key'] = env('APP_KEY') ?: str_random(RANDOM_KEY_LENGTH); $app['debug'] = Input::get('debug') ? 'true' : 'false'; + $app['https'] = Input::get('https') ? 'true' : 'false'; $database = Input::get('database'); $dbType = 'mysql'; // $database['default']; @@ -80,6 +81,7 @@ class AppController extends BaseController $_ENV['APP_ENV'] = 'production'; $_ENV['APP_DEBUG'] = $app['debug']; + $_ENV['REQUIRE_HTTPS'] = $app['https']; $_ENV['APP_URL'] = $app['url']; $_ENV['APP_KEY'] = $app['key']; $_ENV['APP_CIPHER'] = env('APP_CIPHER', 'AES-256-CBC'); @@ -157,6 +159,7 @@ class AppController extends BaseController $_ENV['APP_URL'] = $app['url']; $_ENV['APP_DEBUG'] = Input::get('debug') ? 'true' : 'false'; + $_ENV['REQUIRE_HTTPS'] = Input::get('https') ? 'true' : 'false'; $_ENV['DB_TYPE'] = 'mysql'; // $db['default']; $_ENV['DB_HOST'] = $db['type']['host']; @@ -279,7 +282,7 @@ class AppController extends BaseController // legacy fix: check cipher is in .env file if ( ! env('APP_CIPHER')) { $fp = fopen(base_path().'/.env', 'a'); - fwrite($fp, "\nAPP_CIPHER=rijndael-128"); + fwrite($fp, "\nAPP_CIPHER=AES-256-CBC"); fclose($fp); } diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 0824f4d44e4d..c016c6ba97ee 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -141,6 +141,7 @@ class AuthController extends Controller if (Auth::check()) { Event::fire(new UserLoggedIn()); + /* $users = false; // we're linking a new account if ($request->link_accounts && $userId && Auth::user()->id != $userId) { @@ -150,6 +151,9 @@ class AuthController extends Controller } else { $users = $this->accountRepo->loadAccounts(Auth::user()->id); } + */ + + $users = $this->accountRepo->loadAccounts(Auth::user()->id); Session::put(SESSION_USER_ACCOUNTS, $users); } elseif ($user) { diff --git a/app/Http/Controllers/BlueVineController.php b/app/Http/Controllers/BlueVineController.php index d6cd3bfa26f6..aeab0508c22e 100644 --- a/app/Http/Controllers/BlueVineController.php +++ b/app/Http/Controllers/BlueVineController.php @@ -61,9 +61,9 @@ class BlueVineController extends BaseController { } } - $account = $user->primaryAccount(); - $account->bluevine_status = 'signed_up'; - $account->save(); + $company = $user->account->company; + $company->bluevine_status = 'signed_up'; + $company->save(); $quote_data = json_decode( $response->getBody() ); @@ -74,9 +74,9 @@ class BlueVineController extends BaseController { $user = Auth::user(); if ( $user ) { - $account = $user->primaryAccount(); - $account->bluevine_status = 'ignored'; - $account->save(); + $company = $user->account->company; + $company->bluevine_status = 'ignored'; + $company->save(); } return 'success'; diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 321692f7e3d4..c2fd0a93d614 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -84,7 +84,10 @@ class ClientController extends BaseController $user = Auth::user(); $actionLinks = []; - if($user->can('create', ENTITY_TASK)){ + if ($user->can('create', ENTITY_INVOICE)){ + $actionLinks[] = ['label' => trans('texts.new_invoice'), 'url' => URL::to('/invoices/create/'.$client->public_id)]; + } + if ($user->can('create', ENTITY_TASK)){ $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)]; } if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_QUOTE)) { @@ -215,4 +218,28 @@ class ClientController extends BaseController return $this->returnBulk(ENTITY_CLIENT, $action, $ids); } + + public function statement() + { + $account = Auth::user()->account; + $client = Client::scope(request()->client_id)->with('contacts')->firstOrFail(); + $invoice = $account->createInvoice(ENTITY_INVOICE); + $invoice->client = $client; + $invoice->date_format = $account->date_format ? $account->date_format->format_moment : 'MMM D, YYYY'; + $invoice->invoice_items = Invoice::scope() + ->with(['client']) + ->whereClientId($client->id) + ->invoices() + ->whereIsPublic(true) + ->where('balance', '>', 0) + ->get(); + + $data = [ + 'showBreadcrumbs' => false, + 'client' => $client, + 'invoice' => $invoice, + ]; + + return view('clients.statement', $data); + } } diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php index 9ee46c7b9ec4..7cefa6e1e1bf 100644 --- a/app/Http/Controllers/ClientPortalController.php +++ b/app/Http/Controllers/ClientPortalController.php @@ -63,6 +63,8 @@ class ClientPortalController extends BaseController ]); } + $account->loadLocalizationSettings($client); + if (!Input::has('phantomjs') && !Input::has('silent') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { if ($invoice->isType(INVOICE_TYPE_QUOTE)) { @@ -75,8 +77,6 @@ class ClientPortalController extends BaseController Session::put($invitationKey, true); // track this invitation has been seen Session::put('contact_key', $invitation->contact->contact_key);// track current contact - $account->loadLocalizationSettings($client); - $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date); $invoice->features = [ @@ -99,6 +99,11 @@ class ClientPortalController extends BaseController 'phone', ]); + // translate the client country name + if ($invoice->client->country) { + $invoice->client->country->name = trans('texts.country_' . $invoice->client->country->name); + } + $data = []; $paymentTypes = $this->getPaymentTypes($account, $client, $invitation); $paymentURL = ''; @@ -204,9 +209,13 @@ class ClientPortalController extends BaseController return RESULT_FAILURE; } - $invitation->signature_base64 = Input::get('signature'); - $invitation->signature_date = date_create(); - $invitation->save(); + if ($signature = Input::get('signature')) { + $invitation->signature_base64 = $signature; + $invitation->signature_date = date_create(); + $invitation->save(); + } + + session(['authorized:' . $invitation->invitation_key => true]); return RESULT_SUCCESS; } diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 764d78111517..02c0988a5368 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -43,11 +43,13 @@ class DashboardController extends BaseController $expenses = $dashboardRepo->expenses($accountId, $userId, $viewAll); $tasks = $dashboardRepo->tasks($accountId, $userId, $viewAll); - $showBlueVinePromo = $user->is_admin + $showBlueVinePromo = $user->is_admin && env('BLUEVINE_PARTNER_UNIQUE_ID') && ! $account->company->bluevine_status && $account->created_at <= date( 'Y-m-d', strtotime( '-1 month' )); + $showWhiteLabelExpired = Utils::isSelfHost() && $account->company->hasExpiredPlan(PLAN_WHITE_LABEL); + // check if the account has quotes $hasQuotes = false; foreach ([$upcoming, $pastDue] as $data) { @@ -97,6 +99,7 @@ class DashboardController extends BaseController 'expenses' => $expenses, 'tasks' => $tasks, 'showBlueVinePromo' => $showBlueVinePromo, + 'showWhiteLabelExpired' => $showWhiteLabelExpired, ]; if ($showBlueVinePromo) { diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php index 3dd225d1445d..b639bb7dda65 100644 --- a/app/Http/Controllers/DocumentAPIController.php +++ b/app/Http/Controllers/DocumentAPIController.php @@ -33,7 +33,20 @@ class DocumentAPIController extends BaseAPIController } /** - * @return \Illuminate\Http\Response + * @SWG\Get( + * path="/documents", + * summary="List of document", + * tags={"document"}, + * @SWG\Response( + * response=200, + * description="A list with documents", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Document")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function index() { @@ -59,13 +72,29 @@ class DocumentAPIController extends BaseAPIController } /** - * @param CreateDocumentRequest $request - * - * @return \Illuminate\Http\Response + * @SWG\Post( + * path="/documents", + * tags={"document"}, + * summary="Create a document", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/Document") + * ), + * @SWG\Response( + * response=200, + * description="New document", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Document")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function store(CreateDocumentRequest $request) { - + $document = $this->documentRepo->upload($request->all()); return $this->itemResponse($document); diff --git a/app/Http/Controllers/ExpenseCategoryApiController.php b/app/Http/Controllers/ExpenseCategoryApiController.php index 5a18735dee62..1d7515ceed40 100644 --- a/app/Http/Controllers/ExpenseCategoryApiController.php +++ b/app/Http/Controllers/ExpenseCategoryApiController.php @@ -9,7 +9,6 @@ use App\Http\Requests\CreateExpenseCategoryRequest; use App\Http\Requests\UpdateExpenseCategoryRequest; use App\Ninja\Repositories\ExpenseCategoryRepository; - class ExpenseCategoryApiController extends BaseAPIController { protected $categoryRepo; @@ -19,23 +18,65 @@ class ExpenseCategoryApiController extends BaseAPIController public function __construct(ExpenseCategoryRepository $categoryRepo, ExpenseCategoryService $categoryService) { parent::__construct(); - + $this->categoryRepo = $categoryRepo; $this->categoryService = $categoryService; } + /** + * @SWG\Post( + * path="/expense_categories", + * tags={"expense_category"}, + * summary="Create an expense category", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/ExpenseCategory") + * ), + * @SWG\Response( + * response=200, + * description="New expense category", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function store(CreateExpenseCategoryRequest $request) + { + $category = $this->categoryRepo->save($request->input()); + + return $this->itemResponse($category); + } + + + /** + * @SWG\Put( + * path="/expense_categories/{expense_category_id}", + * tags={"expense_category"}, + * summary="Update an expense category", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/ExpenseCategory") + * ), + * @SWG\Response( + * response=200, + * description="Update expense category", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ public function update(UpdateExpenseCategoryRequest $request) { $category = $this->categoryRepo->save($request->input(), $request->entity()); return $this->itemResponse($category); } - - public function store(CreateExpenseCategoryRequest $request) - { - $category = $this->categoryRepo->save($request->input()); - - return $this->itemResponse($category); - - } } diff --git a/app/Http/Controllers/ExpenseCategoryController.php b/app/Http/Controllers/ExpenseCategoryController.php index fdea57cbe8e0..ebee16df7c89 100644 --- a/app/Http/Controllers/ExpenseCategoryController.php +++ b/app/Http/Controllers/ExpenseCategoryController.php @@ -74,7 +74,7 @@ class ExpenseCategoryController extends BaseController Session::flash('message', trans('texts.created_expense_category')); - return redirect()->to('/expense_categories'); + return redirect()->to($category->getRoute()); } public function update(UpdateExpenseCategoryRequest $request) diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index cc850cbf3272..e159b268f22c 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -253,7 +253,7 @@ class ExpenseController extends BaseController 'customLabel1' => Auth::user()->account->custom_vendor_label1, 'customLabel2' => Auth::user()->account->custom_vendor_label2, 'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(), - 'taxRates' => TaxRate::scope()->orderBy('name')->get(), + 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(), ]; } diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index f1e74bd2a924..ed0d1480e12e 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -32,7 +32,21 @@ class ExportController extends BaseController { $format = $request->input('format'); $date = date('Y-m-d'); - $fileName = "invoice-ninja-{$date}"; + + // set the filename based on the entity types selected + if ($request->include == 'all') { + $fileName = "invoice-ninja-{$date}"; + } else { + $fields = $request->all(); + $fields = array_filter(array_map(function ($key) { + if ( ! in_array($key, ['format', 'include', '_token'])) { + return $key; + } else { + return null; + } + }, array_keys($fields), $fields)); + $fileName = "invoice-ninja-" . join('-', $fields) . "-{$date}"; + } if ($format === 'JSON') { return $this->returnJSON($request, $fileName); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 4f153f78ef2b..5c5a7b485446 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -1,10 +1,12 @@ true]); - } - /** * @return \Illuminate\Contracts\View\View */ @@ -134,4 +128,24 @@ class HomeController extends BaseController { return RESULT_SUCCESS; } + + /** + * @return mixed + */ + public function contactUs() + { + Mail::raw(request()->message, function ($message) { + $subject = 'Customer Message'; + if ( ! Utils::isNinja()) { + $subject .= ': v' . NINJA_VERSION; + } + $message->to(CONTACT_EMAIL) + ->from(CONTACT_EMAIL, Auth::user()->present()->fullName) + ->replyTo(Auth::user()->email, Auth::user()->present()->fullName) + ->subject($subject); + }); + + return redirect(Request::server('HTTP_REFERER')) + ->with('message', trans('texts.contact_us_response')); + } } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index 7ea84d3484cf..ce38734ff3a8 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -12,12 +12,13 @@ use App\Models\Product; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\InvoiceRepository; -use App\Ninja\Mailers\ContactMailer as Mailer; use App\Http\Requests\InvoiceRequest; use App\Http\Requests\CreateInvoiceAPIRequest; use App\Http\Requests\UpdateInvoiceAPIRequest; use App\Services\InvoiceService; use App\Services\PaymentService; +use App\Jobs\SendInvoiceEmail; +use App\Jobs\SendPaymentEmail; class InvoiceApiController extends BaseAPIController { @@ -25,7 +26,7 @@ class InvoiceApiController extends BaseAPIController protected $entityType = ENTITY_INVOICE; - public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer, PaymentService $paymentService) + public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, PaymentService $paymentService) { parent::__construct(); @@ -33,7 +34,6 @@ class InvoiceApiController extends BaseAPIController $this->clientRepo = $clientRepo; $this->paymentRepo = $paymentRepo; $this->invoiceService = $invoiceService; - $this->mailer = $mailer; $this->paymentService = $paymentService; } @@ -185,9 +185,9 @@ class InvoiceApiController extends BaseAPIController if ($isEmailInvoice) { if ($payment) { - $this->mailer->sendPaymentConfirmation($payment); + $this->dispatch(new SendPaymentEmail($payment)); } elseif ( ! $invoice->is_recurring) { - $this->mailer->sendInvoice($invoice); + $this->dispatch(new SendInvoiceEmail($invoice)); } } @@ -229,7 +229,7 @@ class InvoiceApiController extends BaseAPIController } if (!isset($data['invoice_date'])) { - $fields['invoice_date_sql'] = date_create()->format('Y-m-d'); + $fields['invoice_date_sql'] = Utils::today(); } if (!isset($data['due_date'])) { $fields['due_date_sql'] = false; @@ -293,7 +293,7 @@ class InvoiceApiController extends BaseAPIController { $invoice = $request->entity(); - $this->mailer->sendInvoice($invoice); + $this->dispatch(new SendInvoiceEmail($invoice)); $response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT); $headers = Utils::getApiHeaders(); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index dde8f27a3e39..702e47df3859 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -9,16 +9,16 @@ use Cache; use Redirect; use DB; use URL; -use DropdownButton; use App\Models\Invoice; use App\Models\Client; use App\Models\Account; use App\Models\Product; use App\Models\Expense; +use App\Models\Payment; use App\Models\TaxRate; use App\Models\InvoiceDesign; use App\Models\Activity; -use App\Ninja\Mailers\ContactMailer as Mailer; +use App\Jobs\SendInvoiceEmail; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\DocumentRepository; @@ -32,7 +32,6 @@ use App\Http\Requests\UpdateInvoiceRequest; class InvoiceController extends BaseController { - protected $mailer; protected $invoiceRepo; protected $clientRepo; protected $documentRepo; @@ -41,11 +40,10 @@ class InvoiceController extends BaseController protected $recurringInvoiceService; protected $entityType = ENTITY_INVOICE; - public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService, PaymentService $paymentService) + public function __construct(InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService, PaymentService $paymentService) { // parent::__construct(); - $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; $this->clientRepo = $clientRepo; $this->invoiceService = $invoiceService; @@ -100,10 +98,10 @@ class InvoiceController extends BaseController if ($clone) { $invoice->id = $invoice->public_id = null; $invoice->is_public = false; - $invoice->invoice_number = $account->getNextInvoiceNumber($invoice); + $invoice->invoice_number = $account->getNextNumber($invoice); $invoice->balance = $invoice->amount; $invoice->invoice_status_id = 0; - $invoice->invoice_date = date_create()->format('Y-m-d'); + $invoice->invoice_date = Utils::today(); $method = 'POST'; $url = "{$entityType}s"; } else { @@ -124,44 +122,6 @@ class InvoiceController extends BaseController 'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS), ]; - $actions = [ - ['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")], - ['url' => URL::to("{$entityType}s/{$entityType}_history/{$invoice->public_id}"), 'label' => trans('texts.view_history')], - DropdownButton::DIVIDER - ]; - - if ($entityType == ENTITY_QUOTE) { - if ($invoice->quote_invoice_id) { - $actions[] = ['url' => URL::to("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans('texts.view_invoice')]; - } else { - $actions[] = ['url' => 'javascript:onConvertClick()', 'label' => trans('texts.convert_to_invoice')]; - } - } elseif ($entityType == ENTITY_INVOICE) { - if ($invoice->quote_id) { - $actions[] = ['url' => URL::to("quotes/{$invoice->quote_id}/edit"), 'label' => trans('texts.view_quote')]; - } - - if (!$invoice->is_recurring && $invoice->balance > 0 && $invoice->is_public) { - $actions[] = ['url' => 'javascript:submitBulkAction("markPaid")', 'label' => trans('texts.mark_paid')]; - $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; - } - - foreach ($invoice->payments as $payment) { - $label = trans('texts.view_payment'); - if (count($invoice->payments) > 1) { - $label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client); - } - $actions[] = ['url' => $payment->present()->url, 'label' => $label]; - } - } - - if (count($actions) > 3) { - $actions[] = DropdownButton::DIVIDER; - } - - $actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$entityType}")]; - $actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans("texts.delete_{$entityType}")]; - $lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null; if(!Auth::user()->hasPermission('view_all')){ @@ -179,7 +139,6 @@ class InvoiceController extends BaseController 'title' => trans("texts.edit_{$entityType}"), 'client' => $invoice->client, 'isRecurring' => $invoice->is_recurring, - 'actions' => $actions, 'lastSent' => $lastSent]; $data = array_merge($data, self::getViewModel($invoice)); @@ -326,7 +285,11 @@ class InvoiceController extends BaseController $defaultTax = false; foreach ($rates as $rate) { - $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; + $name = $rate->name . ' ' . ($rate->rate+0) . '%'; + if ($rate->is_inclusive) { + $name .= ' - ' . trans('texts.inclusive'); + } + $options[($rate->is_inclusive ? '1 ' : '0 ') . $rate->rate . ' ' . $rate->name] = $name; // load default invoice tax if ($rate->id == $account->default_tax_rate_id) { @@ -340,7 +303,7 @@ class InvoiceController extends BaseController if (isset($options[$key])) { continue; } - $options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%'; + $options['0 ' . $key] = $rate['name'] . ' ' . $rate['rate'] . '%'; } } @@ -452,7 +415,8 @@ class InvoiceController extends BaseController if ($invoice->is_recurring) { $response = $this->emailRecurringInvoice($invoice); } else { - $response = $this->mailer->sendInvoice($invoice, false, $pdfUpload); + $this->dispatch(new SendInvoiceEmail($invoice, false, $pdfUpload)); + return true; } if ($response === true) { @@ -482,7 +446,8 @@ class InvoiceController extends BaseController if ($invoice->isPaid()) { return true; } else { - return $this->mailer->sendInvoice($invoice); + $this->dispatch(new SendInvoiceEmail($invoice)); + return true; } } @@ -514,6 +479,8 @@ class InvoiceController extends BaseController if ($count > 0) { if ($action == 'markSent') { $key = 'marked_sent_invoice'; + } elseif ($action == 'emailInvoice') { + $key = 'emailed_' . $entityType; } elseif ($action == 'markPaid') { $key = 'created_payment'; } else { @@ -543,6 +510,8 @@ class InvoiceController extends BaseController public function invoiceHistory(InvoiceRequest $request) { $invoice = $request->entity(); + $paymentId = $request->payment_id ? Payment::getPrivateId($request->payment_id) : false; + $invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country'); $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date); @@ -553,17 +522,21 @@ class InvoiceController extends BaseController ]; $invoice->invoice_type_id = intval($invoice->invoice_type_id); - $activityTypeId = $invoice->isType(INVOICE_TYPE_QUOTE) ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE; - $activities = Activity::scope(false, $invoice->account_id) - ->where('activity_type_id', '=', $activityTypeId) - ->where('invoice_id', '=', $invoice->id) - ->orderBy('id', 'desc') - ->get(['id', 'created_at', 'user_id', 'json_backup']); + $activities = Activity::scope(false, $invoice->account_id); + if ($paymentId) { + $activities->whereIn('activity_type_id', [ACTIVITY_TYPE_CREATE_PAYMENT]) + ->where('payment_id', '=', $paymentId); + } else { + $activities->whereIn('activity_type_id', [ACTIVITY_TYPE_UPDATE_INVOICE, ACTIVITY_TYPE_UPDATE_QUOTE]) + ->where('invoice_id', '=', $invoice->id); + } + $activities = $activities->orderBy('id', 'desc') + ->get(['id', 'created_at', 'user_id', 'json_backup', 'activity_type_id', 'payment_id']); $versionsJson = []; $versionsSelect = []; $lastId = false; - + //dd($activities->toArray()); foreach ($activities as $activity) { if ($backup = json_decode($activity->json_backup)) { $backup->invoice_date = Utils::fromSqlDate($backup->invoice_date); @@ -576,16 +549,17 @@ class InvoiceController extends BaseController $backup->invoice_type_id = isset($backup->invoice_type_id) && intval($backup->invoice_type_id) == INVOICE_TYPE_QUOTE; $backup->account = $invoice->account->toArray(); - $versionsJson[$activity->id] = $backup; + $versionsJson[$paymentId ? 0 : $activity->id] = $backup; $key = Utils::timestampToDateTimeString(strtotime($activity->created_at)) . ' - ' . $activity->user->getDisplayName(); - $versionsSelect[$lastId ? $lastId : 0] = $key; + $versionsSelect[$lastId ?: 0] = $key; $lastId = $activity->id; } else { Utils::logError('Failed to parse invoice backup'); } } - if ($lastId) { + // Show the current version as the last in the history + if ( ! $paymentId) { $versionsSelect[$lastId] = Utils::timestampToDateTimeString(strtotime($invoice->created_at)) . ' - ' . $invoice->user->getDisplayName(); } @@ -595,6 +569,7 @@ class InvoiceController extends BaseController 'versionsSelect' => $versionsSelect, 'invoiceDesigns' => InvoiceDesign::getDesigns(), 'invoiceFonts' => Cache::get('fonts'), + 'paymentId' => $paymentId, ]; return View::make('invoices.history', $data); diff --git a/app/Http/Controllers/NinjaController.php b/app/Http/Controllers/NinjaController.php index 2a0725239eec..cab57f9da2b5 100644 --- a/app/Http/Controllers/NinjaController.php +++ b/app/Http/Controllers/NinjaController.php @@ -6,6 +6,7 @@ use Input; use Utils; use View; use Validator; +use Auth; use URL; use Cache; use Omnipay; @@ -274,4 +275,15 @@ class NinjaController extends BaseController Session::flash('error', $message); Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true); } + + public function hideWhiteLabelMessage() + { + $user = Auth::user(); + $company = $user->account->company; + + $company->plan = null; + $company->save(); + + return RESULT_SUCCESS; + } } diff --git a/app/Http/Controllers/OnlinePaymentController.php b/app/Http/Controllers/OnlinePaymentController.php index b6c9a280cecf..ae596b89eaa7 100644 --- a/app/Http/Controllers/OnlinePaymentController.php +++ b/app/Http/Controllers/OnlinePaymentController.php @@ -11,6 +11,7 @@ use Exception; use Validator; use App\Models\Invitation; use App\Models\Account; +use App\Models\Client; use App\Models\Payment; use App\Models\Product; use App\Models\PaymentMethod; @@ -76,6 +77,11 @@ class OnlinePaymentController extends BaseController $invitation = $invitation->load('invoice.client.account.account_gateways.gateway'); $account = $invitation->account; + + if ($account->requiresAuthorization($invitation->invoice) && ! session('authorized:' . $invitation->invitation_key)) { + return redirect()->to('view/' . $invitation->invitation_key); + } + $account->loadLocalizationSettings($invitation->invoice->client); if ( ! $gatewayTypeAlias) { @@ -283,22 +289,31 @@ class OnlinePaymentController extends BaseController return redirect()->to("{$failureUrl}/?error=invalid product"); } - $rules = [ - 'first_name' => 'string|max:100', - 'last_name' => 'string|max:100', - 'email' => 'email|string|max:100', - ]; - - $validator = Validator::make(Input::all(), $rules); - if ($validator->fails()) { - return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first()); + // check for existing client using contact_key + $client = false; + if ($contactKey = Input::get('contact_key')) { + $client = Client::scope()->whereHas('contacts', function ($query) use ($contactKey) { + $query->where('contact_key', $contactKey); + })->first(); } + if ( ! $client) { + $rules = [ + 'first_name' => 'string|max:100', + 'last_name' => 'string|max:100', + 'email' => 'email|string|max:100', + ]; - $data = [ - 'currency_id' => $account->currency_id, - 'contact' => Input::all() - ]; - $client = $clientRepo->save($data); + $validator = Validator::make(Input::all(), $rules); + if ($validator->fails()) { + return redirect()->to("{$failureUrl}/?error=" . $validator->errors()->first()); + } + + $data = [ + 'currency_id' => $account->currency_id, + 'contact' => Input::all() + ]; + $client = $clientRepo->save($data); + } $data = [ 'client_id' => $client->id, diff --git a/app/Http/Controllers/PaymentApiController.php b/app/Http/Controllers/PaymentApiController.php index b311b1caf4d9..4daad26d0d88 100644 --- a/app/Http/Controllers/PaymentApiController.php +++ b/app/Http/Controllers/PaymentApiController.php @@ -108,6 +108,9 @@ class PaymentApiController extends BaseAPIController */ public function store(CreatePaymentAPIRequest $request) { + // check payment has been marked sent + $request->invoice->markSentIfUnsent(); + $payment = $this->paymentRepo->save($request->input()); if (Input::get('email_receipt')) { @@ -142,7 +145,7 @@ class PaymentApiController extends BaseAPIController public function destroy(UpdatePaymentRequest $request) { $payment = $request->entity(); - + $this->clientRepo->delete($payment); return $this->itemResponse($payment); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 2b38ba25d76b..bcb244f1e21d 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -5,6 +5,7 @@ use Session; use Utils; use View; use Cache; +use DropdownButton; use App\Models\Invoice; use App\Models\Client; use App\Ninja\Repositories\PaymentRepository; @@ -84,7 +85,6 @@ class PaymentController extends BaseController { $invoices = Invoice::scope() ->invoices() - ->whereIsPublic(true) ->where('invoices.balance', '>', 0) ->with('client', 'invoice_status') ->orderBy('invoice_number')->get(); @@ -122,9 +122,21 @@ class PaymentController extends BaseController public function edit(PaymentRequest $request) { $payment = $request->entity(); - $payment->payment_date = Utils::fromSqlDate($payment->payment_date); + $actions = []; + if ($payment->invoiceJsonBackup()) { + $actions[] = ['url' => url("/invoices/invoice_history/{$payment->invoice->public_id}?payment_id={$payment->public_id}"), 'label' => trans('texts.view_invoice')]; + } + $actions[] = ['url' => url("/invoices/{$payment->invoice->public_id}/edit"), 'label' => trans('texts.edit_invoice')]; + $actions[] = DropdownButton::DIVIDER; + if ( ! $payment->trashed()) { + $actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_payment')]; + $actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans('texts.delete_payment')]; + } else { + $actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_expense')]; + } + $data = [ 'client' => null, 'invoice' => null, @@ -138,6 +150,7 @@ class PaymentController extends BaseController 'method' => 'PUT', 'url' => 'payments/'.$payment->public_id, 'title' => trans('texts.edit_payment'), + 'actions' => $actions, 'paymentTypes' => Cache::get('paymentTypes'), 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ]; @@ -151,8 +164,10 @@ class PaymentController extends BaseController */ public function store(CreatePaymentRequest $request) { - $input = $request->input(); + // 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); @@ -173,6 +188,10 @@ class PaymentController extends BaseController */ public function update(UpdatePaymentRequest $request) { + if (in_array($request->action, ['archive', 'delete', 'restore'])) { + return self::bulk(); + } + $payment = $this->paymentRepo->save($request->input(), $request->entity()); Session::flash('message', trans('texts.updated_payment')); @@ -191,7 +210,7 @@ class PaymentController extends BaseController $count = $this->paymentService->bulk($ids, $action, ['amount'=>$amount]); if ($count > 0) { - $message = Utils::pluralize($action=='refund'?'refunded_payment':$action.'d_payment', $count); + $message = Utils::pluralize($action=='refund' ? 'refunded_payment':$action.'d_payment', $count); Session::flash('message', $message); } diff --git a/app/Http/Controllers/ProductApiController.php b/app/Http/Controllers/ProductApiController.php index 0143007d84dd..f0a0b8af43e4 100644 --- a/app/Http/Controllers/ProductApiController.php +++ b/app/Http/Controllers/ProductApiController.php @@ -33,7 +33,20 @@ class ProductApiController extends BaseAPIController } /** - * @return \Illuminate\Http\Response + * @SWG\Get( + * path="/products", + * summary="List of products", + * tags={"product"}, + * @SWG\Response( + * response=200, + * description="A list with products", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Product")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function index() { @@ -45,8 +58,25 @@ class ProductApiController extends BaseAPIController } /** - * @param CreateProductRequest $request - * @return \Illuminate\Http\Response + * @SWG\Post( + * path="/products", + * tags={"product"}, + * summary="Create a product", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/Product") + * ), + * @SWG\Response( + * response=200, + * description="New product", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function store(CreateProductRequest $request) { @@ -56,16 +86,32 @@ class ProductApiController extends BaseAPIController } /** - * @param UpdateProductRequest $request - * @param $publicId - * @return \Illuminate\Http\Response + * @SWG\Put( + * path="/products/{product_id}", + * tags={"product"}, + * summary="Update a product", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/Product") + * ), + * @SWG\Response( + * response=200, + * description="Update product", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Product")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function update(UpdateProductRequest $request, $publicId) { if ($request->action) { return $this->handleAction($request); } - + $data = $request->input(); $data['public_id'] = $publicId; $product = $this->productRepo->save($data, $request->entity()); diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index e9ab8dd005a8..3dd650d5a5fd 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -74,7 +74,7 @@ class ProductController extends BaseController $data = [ 'account' => $account, - 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null, + 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null, 'product' => $product, 'entity' => $product, 'method' => 'PUT', @@ -94,7 +94,7 @@ class ProductController extends BaseController $data = [ 'account' => $account, - 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null, + 'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->whereIsInclusive(false)->get(['id', 'name', 'rate']) : null, 'product' => null, 'method' => 'POST', 'url' => 'products', diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 3431d5c18323..f23da88bd489 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -6,12 +6,9 @@ use Input; use Utils; use DB; use Session; +use Str; use View; use App\Models\Account; -use App\Models\Client; -use App\Models\Payment; -use App\Models\Expense; -use App\Models\Task; /** * Class ReportController @@ -67,19 +64,22 @@ class ReportController extends BaseController } $reportTypes = [ - ENTITY_CLIENT => trans('texts.client'), - ENTITY_INVOICE => trans('texts.invoice'), - ENTITY_PRODUCT => trans('texts.product'), - ENTITY_PAYMENT => trans('texts.payment'), - ENTITY_EXPENSE => trans('texts.expense'), - ENTITY_TASK => trans('texts.task'), - ENTITY_TAX_RATE => trans('texts.tax'), + 'client', + 'product', + 'invoice', + 'invoice_details', + 'aging', + 'profit_and_loss', + 'payment', + 'expense', + 'task', + 'tax_rate', ]; $params = [ 'startDate' => $startDate->format('Y-m-d'), 'endDate' => $endDate->format('Y-m-d'), - 'reportTypes' => $reportTypes, + 'reportTypes' => array_combine($reportTypes, Utils::trans($reportTypes)), 'reportType' => $reportType, 'title' => trans('texts.charts_and_reports'), 'account' => Auth::user()->account, @@ -87,8 +87,18 @@ class ReportController extends BaseController if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) { $isExport = $action == 'export'; - $params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport)); - + $reportClass = '\\App\\Ninja\\Reports\\' . Str::studly($reportType) . 'Report'; + $options = [ + 'date_field' => $dateField, + 'invoice_status' => request()->invoice_status, + 'group_dates_by' => request()->group_dates_by, + ]; + $report = new $reportClass($startDate, $endDate, $isExport, $options); + if (Input::get('report_type')) { + $report->run(); + } + $params['report'] = $report; + $params = array_merge($params, $report->results()); if ($isExport) { self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']); } @@ -96,427 +106,12 @@ class ReportController extends BaseController $params['columns'] = []; $params['displayData'] = []; $params['reportTotals'] = []; + $params['report'] = false; } return View::make('reports.chart_builder', $params); } - /** - * @param $reportType - * @param $startDate - * @param $endDate - * @param $dateField - * @param $isExport - * @return array - */ - private function generateReport($reportType, $startDate, $endDate, $dateField, $isExport) - { - if ($reportType == ENTITY_CLIENT) { - return $this->generateClientReport($startDate, $endDate, $isExport); - } elseif ($reportType == ENTITY_INVOICE) { - return $this->generateInvoiceReport($startDate, $endDate, $isExport); - } elseif ($reportType == ENTITY_PRODUCT) { - return $this->generateProductReport($startDate, $endDate, $isExport); - } elseif ($reportType == ENTITY_PAYMENT) { - return $this->generatePaymentReport($startDate, $endDate, $isExport); - } elseif ($reportType == ENTITY_TAX_RATE) { - return $this->generateTaxRateReport($startDate, $endDate, $dateField, $isExport); - } elseif ($reportType == ENTITY_EXPENSE) { - return $this->generateExpenseReport($startDate, $endDate, $isExport); - } elseif ($reportType == ENTITY_TASK) { - return $this->generateTaskReport($startDate, $endDate, $isExport); - } - } - - private function generateTaskReport($startDate, $endDate, $isExport) - { - $columns = ['client', 'date', 'description', 'duration']; - $displayData = []; - - $tasks = Task::scope() - ->with('client.contacts') - ->withArchived() - ->dateRange($startDate, $endDate); - - foreach ($tasks->get() as $task) { - $displayData[] = [ - $task->client ? ($isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'), - link_to($task->present()->url, $task->getStartTime()), - $task->present()->description, - Utils::formatTime($task->getDuration()), - ]; - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => [], - ]; - } - - /** - * @param $startDate - * @param $endDate - * @param $dateField - * @param $isExport - * @return array - */ - private function generateTaxRateReport($startDate, $endDate, $dateField, $isExport) - { - $columns = ['tax_name', 'tax_rate', 'amount', 'paid']; - - $account = Auth::user()->account; - $displayData = []; - $reportTotals = []; - - $clients = Client::scope() - ->withArchived() - ->with('contacts') - ->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) { - $query->with('invoice_items')->withArchived(); - if ($dateField == FILTER_INVOICE_DATE) { - $query->where('invoice_date', '>=', $startDate) - ->where('invoice_date', '<=', $endDate) - ->with('payments'); - } else { - $query->whereHas('payments', function($query) use ($startDate, $endDate) { - $query->where('payment_date', '>=', $startDate) - ->where('payment_date', '<=', $endDate) - ->withArchived(); - }) - ->with(['payments' => function($query) use ($startDate, $endDate) { - $query->where('payment_date', '>=', $startDate) - ->where('payment_date', '<=', $endDate) - ->withArchived(); - }]); - } - }]); - - foreach ($clients->get() as $client) { - $currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId(); - $amount = 0; - $paid = 0; - $taxTotals = []; - - foreach ($client->invoices as $invoice) { - foreach ($invoice->getTaxes(true) as $key => $tax) { - if ( ! isset($taxTotals[$currencyId])) { - $taxTotals[$currencyId] = []; - } - if (isset($taxTotals[$currencyId][$key])) { - $taxTotals[$currencyId][$key]['amount'] += $tax['amount']; - $taxTotals[$currencyId][$key]['paid'] += $tax['paid']; - } else { - $taxTotals[$currencyId][$key] = $tax; - } - } - - $amount += $invoice->amount; - $paid += $invoice->getAmountPaid(); - } - - foreach ($taxTotals as $currencyId => $taxes) { - foreach ($taxes as $tax) { - $displayData[] = [ - $tax['name'], - $tax['rate'] . '%', - $account->formatMoney($tax['amount'], $client), - $account->formatMoney($tax['paid'], $client) - ]; - } - - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']); - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']); - } - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => $reportTotals, - ]; - - } - - /** - * @param $startDate - * @param $endDate - * @param $isExport - * @return array - */ - private function generatePaymentReport($startDate, $endDate, $isExport) - { - $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; - - $account = Auth::user()->account; - $displayData = []; - $reportTotals = []; - - $payments = Payment::scope() - ->withArchived() - ->excludeFailed() - ->whereHas('client', function($query) { - $query->where('is_deleted', '=', false); - }) - ->whereHas('invoice', function($query) { - $query->where('is_deleted', '=', false); - }) - ->with('client.contacts', 'invoice', 'payment_type', 'account_gateway.gateway') - ->where('payment_date', '>=', $startDate) - ->where('payment_date', '<=', $endDate); - - foreach ($payments->get() as $payment) { - $invoice = $payment->invoice; - $client = $payment->client; - $displayData[] = [ - $isExport ? $client->getDisplayName() : $client->present()->link, - $isExport ? $invoice->invoice_number : $invoice->present()->link, - $invoice->present()->invoice_date, - $account->formatMoney($invoice->amount, $client), - $payment->present()->payment_date, - $account->formatMoney($payment->getCompletedAmount(), $client), - $payment->present()->method, - ]; - - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->getCompletedAmount()); - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => $reportTotals, - ]; - } - - /** - * @param $startDate - * @param $endDate - * @param $isExport - * @return array - */ - private function generateInvoiceReport($startDate, $endDate, $isExport) - { - $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; - - $account = Auth::user()->account; - $displayData = []; - $reportTotals = []; - - $clients = Client::scope() - ->withTrashed() - ->with('contacts') - ->where('is_deleted', '=', false) - ->with(['invoices' => function($query) use ($startDate, $endDate) { - $query->invoices() - ->withArchived() - ->where('invoice_date', '>=', $startDate) - ->where('invoice_date', '<=', $endDate) - ->with(['payments' => function($query) { - $query->withArchived() - ->excludeFailed() - ->with('payment_type', 'account_gateway.gateway'); - }, 'invoice_items']) - ->withTrashed(); - }]); - - foreach ($clients->get() as $client) { - foreach ($client->invoices as $invoice) { - - $payments = count($invoice->payments) ? $invoice->payments : [false]; - foreach ($payments as $payment) { - $displayData[] = [ - $isExport ? $client->getDisplayName() : $client->present()->link, - $isExport ? $invoice->invoice_number : $invoice->present()->link, - $invoice->present()->invoice_date, - $account->formatMoney($invoice->amount, $client), - $payment ? $payment->present()->payment_date : '', - $payment ? $account->formatMoney($payment->getCompletedAmount(), $client) : '', - $payment ? $payment->present()->method : '', - ]; - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->getCompletedAmount() : 0); - } - - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance); - } - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => $reportTotals, - ]; - } - - /** - * @param $startDate - * @param $endDate - * @param $isExport - * @return array - */ - private function generateProductReport($startDate, $endDate, $isExport) - { - $columns = ['client', 'invoice_number', 'invoice_date', 'quantity', 'product']; - - $account = Auth::user()->account; - $displayData = []; - $reportTotals = []; - - $clients = Client::scope() - ->withTrashed() - ->with('contacts') - ->where('is_deleted', '=', false) - ->with(['invoices' => function($query) use ($startDate, $endDate) { - $query->where('invoice_date', '>=', $startDate) - ->where('invoice_date', '<=', $endDate) - ->where('is_deleted', '=', false) - ->where('is_recurring', '=', false) - ->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) - ->with(['invoice_items']) - ->withTrashed(); - }]); - - foreach ($clients->get() as $client) { - foreach ($client->invoices as $invoice) { - - foreach ($invoice->invoice_items as $invoiceItem) { - $displayData[] = [ - $isExport ? $client->getDisplayName() : $client->present()->link, - $isExport ? $invoice->invoice_number : $invoice->present()->link, - $invoice->present()->invoice_date, - round($invoiceItem->qty, 2), - $invoiceItem->product_key, - ]; - //$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->amount : 0); - } - - //$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); - //$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance); - } - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => [], - ]; - } - - /** - * @param $startDate - * @param $endDate - * @param $isExport - * @return array - */ - private function generateClientReport($startDate, $endDate, $isExport) - { - $columns = ['client', 'amount', 'paid', 'balance']; - - $account = Auth::user()->account; - $displayData = []; - $reportTotals = []; - - $clients = Client::scope() - ->withArchived() - ->with('contacts') - ->with(['invoices' => function($query) use ($startDate, $endDate) { - $query->where('invoice_date', '>=', $startDate) - ->where('invoice_date', '<=', $endDate) - ->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) - ->where('is_recurring', '=', false) - ->withArchived(); - }]); - - foreach ($clients->get() as $client) { - $amount = 0; - $paid = 0; - - foreach ($client->invoices as $invoice) { - $amount += $invoice->amount; - $paid += $invoice->getAmountPaid(); - } - - $displayData[] = [ - $isExport ? $client->getDisplayName() : $client->present()->link, - $account->formatMoney($amount, $client), - $account->formatMoney($paid, $client), - $account->formatMoney($amount - $paid, $client) - ]; - - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $amount); - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $paid); - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $amount - $paid); - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => $reportTotals, - ]; - } - - /** - * @param $startDate - * @param $endDate - * @param $isExport - * @return array - */ - private function generateExpenseReport($startDate, $endDate, $isExport) - { - $columns = ['vendor', 'client', 'date', 'expense_amount']; - - $account = Auth::user()->account; - $displayData = []; - $reportTotals = []; - - $expenses = Expense::scope() - ->withArchived() - ->with('client.contacts', 'vendor') - ->where('expense_date', '>=', $startDate) - ->where('expense_date', '<=', $endDate); - - - foreach ($expenses->get() as $expense) { - $amount = $expense->amountWithTax(); - - $displayData[] = [ - $expense->vendor ? ($isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '', - $expense->client ? ($isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '', - $expense->present()->expense_date, - Utils::formatMoney($amount, $expense->currency_id), - ]; - - $reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount); - $reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0); - } - - return [ - 'columns' => $columns, - 'displayData' => $displayData, - 'reportTotals' => $reportTotals, - ]; - } - - /** - * @param $data - * @param $currencyId - * @param $field - * @param $value - * @return mixed - */ - private function addToTotals($data, $currencyId, $field, $value) { - $currencyId = $currencyId ?: Auth::user()->account->getCurrencyId(); - - if (!isset($data[$currencyId][$field])) { - $data[$currencyId][$field] = 0; - } - - $data[$currencyId][$field] += $value; - - return $data; - } - /** * @param $reportType * @param $data @@ -534,6 +129,7 @@ class ReportController extends BaseController Utils::exportData($output, $data, Utils::trans($columns)); + /* fwrite($output, trans('texts.totals')); foreach ($totals as $currencyId => $fields) { foreach ($fields as $key => $value) { @@ -550,6 +146,7 @@ class ReportController extends BaseController } fwrite($output, $csv."\n"); } + */ fclose($output); exit; diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 9de256c98976..452ce50ebaa6 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -253,10 +253,11 @@ class TaskController extends BaseController Session::flash('message', trans('texts.stopped_task')); return Redirect::to('tasks'); } else if ($action == 'invoice' || $action == 'add_to_invoice') { - $tasks = Task::scope($ids)->with('client')->get(); + $tasks = Task::scope($ids)->with('client')->orderBy('project_id', 'id')->get(); $clientPublicId = false; $data = []; + $lastProjectId = false; foreach ($tasks as $task) { if ($task->client) { if (!$clientPublicId) { @@ -276,11 +277,13 @@ class TaskController extends BaseController } $account = Auth::user()->account; + $showProject = $lastProjectId != $task->project_id; $data[] = [ 'publicId' => $task->public_id, - 'description' => $task->description . "\n\n" . $task->present()->times($account), + 'description' => $task->present()->invoiceDescription($account, $showProject), 'duration' => $task->getHours(), ]; + $lastProjectId = $task->project_id; } if ($action == 'invoice') { diff --git a/app/Http/Controllers/TaxRateApiController.php b/app/Http/Controllers/TaxRateApiController.php index e5b0003d6e30..2fa442af49b2 100644 --- a/app/Http/Controllers/TaxRateApiController.php +++ b/app/Http/Controllers/TaxRateApiController.php @@ -28,6 +28,22 @@ class TaxRateApiController extends BaseAPIController $this->taxRateRepo = $taxRateRepo; } + /** + * @SWG\Get( + * path="/tax_rates", + * summary="List of tax rates", + * tags={"tax_rate"}, + * @SWG\Response( + * response=200, + * description="A list with tax rates", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/TaxRate")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ public function index() { $taxRates = TaxRate::scope() @@ -37,6 +53,27 @@ class TaxRateApiController extends BaseAPIController return $this->listResponse($taxRates); } + /** + * @SWG\Post( + * path="/tax_rates", + * tags={"tax_rate"}, + * summary="Create a tax rate", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/TaxRate") + * ), + * @SWG\Response( + * response=200, + * description="New tax rate", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ public function store(CreateTaxRateRequest $request) { $taxRate = $this->taxRateRepo->save($request->input()); @@ -45,16 +82,32 @@ class TaxRateApiController extends BaseAPIController } /** - * @param UpdateTaxRateRequest $request - * @param $publicId - * @return \Illuminate\Http\Response + * @SWG\Put( + * path="/tax_rates/{tax_rate_id}", + * tags={"tax_rate"}, + * summary="Update a tax rate", + * @SWG\Parameter( + * in="body", + * name="body", + * @SWG\Schema(ref="#/definitions/TaxRate") + * ), + * @SWG\Response( + * response=200, + * description="Update tax rate", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/TaxRate")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) */ public function update(UpdateTaxRateRequest $request, $publicId) { if ($request->action) { return $this->handleAction($request); } - + $data = $request->input(); $data['public_id'] = $publicId; $taxRate = $this->taxRateRepo->save($data, $request->entity()); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index e18c8148bb66..1646c539f8c8 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -240,7 +240,7 @@ class UserController extends BaseController $user = User::where('confirmation_code', '=', $code)->get()->first(); if ($user) { - $notice_msg = trans('texts.security.confirmation'); + $notice_msg = trans('texts.security_confirmation'); $user->confirmed = true; $user->confirmation_code = ''; @@ -356,7 +356,7 @@ class UserController extends BaseController Session::put(SESSION_USER_ACCOUNTS, $users); Session::flash('message', trans('texts.unlinked_account')); - return Redirect::to('/dashboard'); + return Redirect::to('/manage_companies'); } public function manageCompanies() diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index effda8c55283..bc16ee9e407c 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -82,7 +82,6 @@ class VendorController extends BaseController 'actionLinks' => $actionLinks, 'showBreadcrumbs' => false, 'vendor' => $vendor, - 'totalexpense' => $vendor->getTotalExpense(), 'title' => trans('texts.view_vendor'), 'hasRecurringInvoices' => false, 'hasQuotes' => false, diff --git a/app/Http/Middleware/ApiCheck.php b/app/Http/Middleware/ApiCheck.php index 6e7e73223d20..93de1b0c433c 100644 --- a/app/Http/Middleware/ApiCheck.php +++ b/app/Http/Middleware/ApiCheck.php @@ -25,7 +25,9 @@ class ApiCheck { { $loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register') - || $request->is('api/v1/oauth_login'); + || $request->is('api/v1/oauth_login') + || $request->is('api/v1/ping'); + $headers = Utils::getApiHeaders(); $hasApiSecret = false; @@ -38,7 +40,8 @@ class ApiCheck { // check API secret if ( ! $hasApiSecret) { sleep(ERROR_DELAY); - return Response::json('Invalid value for API_SECRET', 403, $headers); + $error['error'] = ['message'=>'Invalid value for API_SECRET']; + return Response::json($error, 403, $headers); } } else { // check for a valid token @@ -50,7 +53,8 @@ class ApiCheck { Session::set('token_id', $token->id); } else { sleep(ERROR_DELAY); - return Response::json('Invalid token', 403, $headers); + $error['error'] = ['message'=>'Invalid token']; + return Response::json($error, 403, $headers); } } @@ -59,7 +63,8 @@ class ApiCheck { } if (!Utils::hasFeature(FEATURE_API) && !$hasApiSecret) { - return Response::json('API requires pro plan', 403, $headers); + $error['error'] = ['message'=>'API requires pro plan']; + return Response::json($error, 403, $headers); } else { $key = Auth::check() ? Auth::user()->account->id : $request->getClientIp(); diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index 776974a8946c..c18eb306cc9c 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -149,7 +149,8 @@ class StartupCheck $company = Auth::user()->account->company; $company->plan_term = PLAN_TERM_YEARLY; $company->plan_paid = $data; - $company->plan_expires = date_create($data)->modify('+1 year')->format('Y-m-d'); + $date = max(date_create($data), date_create($company->plan_expires)); + $company->plan_expires = $date->modify('+1 year')->format('Y-m-d'); $company->plan = PLAN_WHITE_LABEL; $company->save(); diff --git a/app/Http/Requests/CreateClientRequest.php b/app/Http/Requests/CreateClientRequest.php index 10d5e2496271..4bd296fea662 100644 --- a/app/Http/Requests/CreateClientRequest.php +++ b/app/Http/Requests/CreateClientRequest.php @@ -19,8 +19,14 @@ class CreateClientRequest extends ClientRequest */ public function rules() { - return [ + $rules = [ 'contacts' => 'valid_contacts', ]; + + if ($this->user()->account->client_number_counter) { + $rules['id_number'] = 'unique:clients,id_number,,id,account_id,' . $this->user()->account_id; + } + + return $rules; } } diff --git a/app/Http/Requests/CreateInvoiceAPIRequest.php b/app/Http/Requests/CreateInvoiceAPIRequest.php index 26eeb521d7f9..f5a8d1ac85f6 100644 --- a/app/Http/Requests/CreateInvoiceAPIRequest.php +++ b/app/Http/Requests/CreateInvoiceAPIRequest.php @@ -1,5 +1,7 @@ 'date', ]; + if ($this->user()->account->client_number_counter) { + $clientId = Client::getPrivateId(request()->input('client')['public_id']); + $rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id; + } + return $rules; } } diff --git a/app/Http/Requests/CreateInvoiceRequest.php b/app/Http/Requests/CreateInvoiceRequest.php index 3fc6f66f7f30..68edaa54efb8 100644 --- a/app/Http/Requests/CreateInvoiceRequest.php +++ b/app/Http/Requests/CreateInvoiceRequest.php @@ -1,5 +1,7 @@ 'date', ]; + if ($this->user()->account->client_number_counter) { + $clientId = Client::getPrivateId(request()->input('client')['public_id']); + $rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id; + } + /* There's a problem parsing the dates if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) { $rules['end_date'] = 'after' . Request::get('start_date'); diff --git a/app/Http/Requests/CreatePaymentAPIRequest.php b/app/Http/Requests/CreatePaymentAPIRequest.php index 4a43bf368b2b..d809c4d3d044 100644 --- a/app/Http/Requests/CreatePaymentAPIRequest.php +++ b/app/Http/Requests/CreatePaymentAPIRequest.php @@ -33,9 +33,8 @@ class CreatePaymentAPIRequest extends PaymentRequest ]; } - $invoice = Invoice::scope($this->invoice_id) + $this->invoice = $invoice = Invoice::scope($this->invoice_id) ->invoices() - ->whereIsPublic(true) ->firstOrFail(); $this->merge([ diff --git a/app/Http/Requests/CreatePaymentRequest.php b/app/Http/Requests/CreatePaymentRequest.php index ff30f0da9d8e..9b45b2486dd7 100644 --- a/app/Http/Requests/CreatePaymentRequest.php +++ b/app/Http/Requests/CreatePaymentRequest.php @@ -22,9 +22,8 @@ class CreatePaymentRequest extends PaymentRequest public function rules() { $input = $this->input(); - $invoice = Invoice::scope($input['invoice']) + $this->invoice = $invoice = Invoice::scope($input['invoice']) ->invoices() - ->whereIsPublic(true) ->firstOrFail(); $rules = [ diff --git a/app/Http/Requests/SaveClientPortalSettings.php b/app/Http/Requests/SaveClientPortalSettings.php new file mode 100644 index 000000000000..3c2485e7d7ef --- /dev/null +++ b/app/Http/Requests/SaveClientPortalSettings.php @@ -0,0 +1,58 @@ +user()->is_admin && $this->user()->isPro(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = []; + + if ($this->custom_link == 'subdomain' && Utils::isNinja()) { + $rules['subdomain'] = "unique:accounts,subdomain,{$this->user()->account_id},id|valid_subdomain"; + } + + return $rules; + } + + public function sanitize() + { + $input = $this->all(); + + if ($this->client_view_css && Utils::isNinja()) { + $input['client_view_css'] = HTMLUtils::sanitize($this->client_view_css); + } + + if ($this->custom_link == 'subdomain') { + $subdomain = substr(strtolower($input['subdomain']), 0, MAX_SUBDOMAIN_LENGTH); + $input['subdomain'] = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $subdomain); + $input['iframe_url'] = null; + } else { + $iframeURL = substr(strtolower($input['iframe_url']), 0, MAX_IFRAME_URL_LENGTH); + $iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', $iframeURL); + $input['iframe_url'] = rtrim($iframeURL, '/'); + $input['subdomain'] = null; + } + + $this->replace($input); + + return $this->all(); + } + +} diff --git a/app/Http/Requests/SaveEmailSettings.php b/app/Http/Requests/SaveEmailSettings.php new file mode 100644 index 000000000000..84f8c5baf8d0 --- /dev/null +++ b/app/Http/Requests/SaveEmailSettings.php @@ -0,0 +1,27 @@ +user()->is_admin && $this->user()->isPro(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'bcc_email' => 'email', + ]; + } + +} diff --git a/app/Http/Requests/UpdateClientRequest.php b/app/Http/Requests/UpdateClientRequest.php index c1cdebe1891e..07e9bc665b5c 100644 --- a/app/Http/Requests/UpdateClientRequest.php +++ b/app/Http/Requests/UpdateClientRequest.php @@ -19,8 +19,14 @@ class UpdateClientRequest extends ClientRequest */ public function rules() { - return [ + $rules = [ 'contacts' => 'valid_contacts', ]; + + if ($this->user()->account->client_number_counter) { + $rules['id_number'] = 'unique:clients,id_number,'.$this->entity()->id.',id,account_id,' . $this->user()->account_id; + } + + return $rules; } } diff --git a/app/Http/Requests/UpdateInvoiceAPIRequest.php b/app/Http/Requests/UpdateInvoiceAPIRequest.php index f0432d94e008..fb3f1b42a82b 100644 --- a/app/Http/Requests/UpdateInvoiceAPIRequest.php +++ b/app/Http/Requests/UpdateInvoiceAPIRequest.php @@ -1,5 +1,7 @@ 'date', ]; + if ($this->user()->account->client_number_counter) { + $clientId = Client::getPrivateId(request()->input('client')['public_id']); + $rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id; + } + return $rules; } } diff --git a/app/Http/Requests/UpdateInvoiceRequest.php b/app/Http/Requests/UpdateInvoiceRequest.php index 92e5c236dde2..476fc74c9009 100644 --- a/app/Http/Requests/UpdateInvoiceRequest.php +++ b/app/Http/Requests/UpdateInvoiceRequest.php @@ -1,5 +1,7 @@ 'date', ]; + if ($this->user()->account->client_number_counter) { + $clientId = Client::getPrivateId(request()->input('client')['public_id']); + $rules['client.id_number'] = 'unique:clients,id_number,'.$clientId.',id,account_id,' . $this->user()->account_id; + } + /* There's a problem parsing the dates if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) { $rules['end_date'] = 'after' . Request::get('start_date'); diff --git a/app/Http/routes.php b/app/Http/routes.php index 3acbbc9e0d5a..cb9acd590c18 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -11,14 +11,6 @@ | */ -//Crypt::decrypt(); -//apc_clear_cache(); -//dd(DB::getQueryLog()); -//dd(Client::getPrivateId(1)); -//dd(new DateTime()); -//dd(App::environment()); -//dd(gethostname()); -//Log::error('test'); // Application setup Route::get('/setup', 'AppController@showSetup'); @@ -28,7 +20,6 @@ Route::get('/update', 'AppController@update'); // Public pages Route::get('/', 'HomeController@showIndex'); -Route::get('/terms', 'HomeController@showTerms'); Route::get('/log_error', 'HomeController@logError'); Route::get('/invoice_now', 'HomeController@invoiceNow'); Route::get('/keep_alive', 'HomeController@keepAlive'); @@ -131,6 +122,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('account/get_search_data', ['as' => 'get_search_data', 'uses' => 'AccountController@getSearchData']); Route::get('check_invoice_number/{invoice_id?}', 'InvoiceController@checkInvoiceNumber'); Route::post('save_sidebar_state', 'UserController@saveSidebarState'); + Route::post('contact_us', 'HomeController@contactUs'); Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); @@ -141,10 +133,11 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('api/clients', 'ClientController@getDatatable'); Route::get('api/activities/{client_id?}', 'ActivityController@getDatatable'); Route::post('clients/bulk', 'ClientController@bulk'); + Route::get('clients/statement/{client_id}', 'ClientController@statement'); Route::resource('tasks', 'TaskController'); Route::get('api/tasks/{client_id?}', 'TaskController@getDatatable'); - Route::get('tasks/create/{client_id?}', 'TaskController@create'); + Route::get('tasks/create/{client_id?}/{project_id?}', 'TaskController@create'); Route::post('tasks/bulk', 'TaskController@bulk'); Route::get('projects', 'ProjectController@index'); Route::get('api/projects', 'ProjectController@getDatatable'); @@ -211,7 +204,7 @@ Route::group(['middleware' => 'auth:user'], function() { // Expense Route::resource('expenses', 'ExpenseController'); - Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create'); + Route::get('expenses/create/{vendor_id?}/{client_id?}/{category_id?}', 'ExpenseController@create'); Route::get('api/expenses', 'ExpenseController@getDatatable'); Route::get('api/expenses/{id}', 'ExpenseController@getDatatableVendor'); Route::post('expenses/bulk', 'ExpenseController@bulk'); @@ -227,6 +220,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::post('bluevine/signup', 'BlueVineController@signup'); Route::get('bluevine/hide_message', 'BlueVineController@hideMessage'); Route::get('bluevine/completed', 'BlueVineController@handleCompleted'); + Route::get('white_label/hide_message', 'NinjaController@hideWhiteLabelMessage'); }); Route::group([ @@ -237,8 +231,6 @@ Route::group([ Route::resource('users', 'UserController'); Route::post('users/bulk', 'UserController@bulk'); Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation'); - Route::get('start_trial/{plan}', 'AccountController@startTrial') - ->where(['plan'=>'pro']); Route::get('/switch_account/{user_id}', 'UserController@switchAccount'); Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount'); Route::get('/manage_companies', 'UserController@manageCompanies'); @@ -252,10 +244,12 @@ Route::group([ Route::post('tax_rates/bulk', 'TaxRateController@bulk'); Route::get('settings/email_preview', 'AccountController@previewEmail'); + Route::post('settings/client_portal', 'AccountController@saveClientPortalSettings'); + Route::post('settings/email_settings', 'AccountController@saveEmailSettings'); Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy'); Route::get('settings/data_visualizations', 'ReportController@d3'); - Route::get('settings/reports', 'ReportController@showReports'); - Route::post('settings/reports', 'ReportController@showReports'); + Route::get('reports', 'ReportController@showReports'); + Route::post('reports', 'ReportController@showReports'); Route::post('settings/change_plan', 'AccountController@changePlan'); Route::post('settings/cancel_account', 'AccountController@cancelAccount'); @@ -359,573 +353,6 @@ Route::get('/comments/feed', function() { return Redirect::to(NINJA_WEB_URL.'/comments/feed', 301); }); -if (!defined('CONTACT_EMAIL')) { - define('CONTACT_EMAIL', config('mail.from.address')); - define('CONTACT_NAME', config('mail.from.name')); - define('SITE_URL', config('app.url')); - - define('ENV_DEVELOPMENT', 'local'); - define('ENV_STAGING', 'staging'); - - define('RECENTLY_VIEWED', 'recent_history'); - - define('ENTITY_CLIENT', 'client'); - define('ENTITY_CONTACT', 'contact'); - define('ENTITY_INVOICE', 'invoice'); - define('ENTITY_DOCUMENT', 'document'); - define('ENTITY_INVOICE_ITEM', 'invoice_item'); - define('ENTITY_INVITATION', 'invitation'); - define('ENTITY_RECURRING_INVOICE', 'recurring_invoice'); - define('ENTITY_PAYMENT', 'payment'); - define('ENTITY_CREDIT', 'credit'); - define('ENTITY_QUOTE', 'quote'); - define('ENTITY_TASK', 'task'); - define('ENTITY_ACCOUNT_GATEWAY', 'account_gateway'); - define('ENTITY_USER', 'user'); - define('ENTITY_TOKEN', 'token'); - define('ENTITY_TAX_RATE', 'tax_rate'); - define('ENTITY_PRODUCT', 'product'); - define('ENTITY_ACTIVITY', 'activity'); - define('ENTITY_VENDOR', 'vendor'); - define('ENTITY_VENDOR_ACTIVITY', 'vendor_activity'); - define('ENTITY_EXPENSE', 'expense'); - define('ENTITY_PAYMENT_TERM', 'payment_term'); - define('ENTITY_EXPENSE_ACTIVITY', 'expense_activity'); - define('ENTITY_BANK_ACCOUNT', 'bank_account'); - define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount'); - define('ENTITY_EXPENSE_CATEGORY', 'expense_category'); - define('ENTITY_PROJECT', 'project'); - - define('INVOICE_TYPE_STANDARD', 1); - define('INVOICE_TYPE_QUOTE', 2); - - define('PERSON_CONTACT', 'contact'); - define('PERSON_USER', 'user'); - define('PERSON_VENDOR_CONTACT','vendorcontact'); - - define('BASIC_SETTINGS', 'basic_settings'); - define('ADVANCED_SETTINGS', 'advanced_settings'); - - define('ACCOUNT_COMPANY_DETAILS', 'company_details'); - define('ACCOUNT_USER_DETAILS', 'user_details'); - define('ACCOUNT_LOCALIZATION', 'localization'); - define('ACCOUNT_NOTIFICATIONS', 'notifications'); - define('ACCOUNT_IMPORT_EXPORT', 'import_export'); - define('ACCOUNT_MANAGEMENT', 'account_management'); - define('ACCOUNT_PAYMENTS', 'online_payments'); - define('ACCOUNT_BANKS', 'bank_accounts'); - define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses'); - define('ACCOUNT_MAP', 'import_map'); - define('ACCOUNT_EXPORT', 'export'); - define('ACCOUNT_TAX_RATES', 'tax_rates'); - define('ACCOUNT_PRODUCTS', 'products'); - define('ACCOUNT_ADVANCED_SETTINGS', 'advanced_settings'); - define('ACCOUNT_INVOICE_SETTINGS', 'invoice_settings'); - define('ACCOUNT_INVOICE_DESIGN', 'invoice_design'); - define('ACCOUNT_CLIENT_PORTAL', 'client_portal'); - define('ACCOUNT_EMAIL_SETTINGS', 'email_settings'); - define('ACCOUNT_REPORTS', 'reports'); - define('ACCOUNT_USER_MANAGEMENT', 'user_management'); - define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations'); - define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders'); - define('ACCOUNT_API_TOKENS', 'api_tokens'); - define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design'); - define('ACCOUNT_SYSTEM_SETTINGS', 'system_settings'); - define('ACCOUNT_PAYMENT_TERMS','payment_terms'); - - define('ACTION_RESTORE', 'restore'); - define('ACTION_ARCHIVE', 'archive'); - define('ACTION_CLONE', 'clone'); - define('ACTION_CONVERT', 'convert'); - define('ACTION_DELETE', 'delete'); - - define('ACTIVITY_TYPE_CREATE_CLIENT', 1); - define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2); - define('ACTIVITY_TYPE_DELETE_CLIENT', 3); - define('ACTIVITY_TYPE_CREATE_INVOICE', 4); - define('ACTIVITY_TYPE_UPDATE_INVOICE', 5); - define('ACTIVITY_TYPE_EMAIL_INVOICE', 6); - define('ACTIVITY_TYPE_VIEW_INVOICE', 7); - define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8); - define('ACTIVITY_TYPE_DELETE_INVOICE', 9); - define('ACTIVITY_TYPE_CREATE_PAYMENT', 10); - //define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11); - define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12); - define('ACTIVITY_TYPE_DELETE_PAYMENT', 13); - define('ACTIVITY_TYPE_CREATE_CREDIT', 14); - //define('ACTIVITY_TYPE_UPDATE_CREDIT', 15); - define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16); - define('ACTIVITY_TYPE_DELETE_CREDIT', 17); - define('ACTIVITY_TYPE_CREATE_QUOTE', 18); - define('ACTIVITY_TYPE_UPDATE_QUOTE', 19); - define('ACTIVITY_TYPE_EMAIL_QUOTE', 20); - define('ACTIVITY_TYPE_VIEW_QUOTE', 21); - define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22); - define('ACTIVITY_TYPE_DELETE_QUOTE', 23); - define('ACTIVITY_TYPE_RESTORE_QUOTE', 24); - define('ACTIVITY_TYPE_RESTORE_INVOICE', 25); - define('ACTIVITY_TYPE_RESTORE_CLIENT', 26); - define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27); - define('ACTIVITY_TYPE_RESTORE_CREDIT', 28); - define('ACTIVITY_TYPE_APPROVE_QUOTE', 29); - define('ACTIVITY_TYPE_CREATE_VENDOR', 30); - define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31); - define('ACTIVITY_TYPE_DELETE_VENDOR', 32); - define('ACTIVITY_TYPE_RESTORE_VENDOR', 33); - define('ACTIVITY_TYPE_CREATE_EXPENSE', 34); - define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35); - define('ACTIVITY_TYPE_DELETE_EXPENSE', 36); - define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37); - define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39); - define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40); - define('ACTIVITY_TYPE_FAILED_PAYMENT', 41); - define('ACTIVITY_TYPE_CREATE_TASK', 42); - define('ACTIVITY_TYPE_UPDATE_TASK', 43); - define('ACTIVITY_TYPE_ARCHIVE_TASK', 44); - define('ACTIVITY_TYPE_DELETE_TASK', 45); - define('ACTIVITY_TYPE_RESTORE_TASK', 46); - define('ACTIVITY_TYPE_UPDATE_EXPENSE', 47); - - - define('DEFAULT_INVOICE_NUMBER', '0001'); - define('RECENTLY_VIEWED_LIMIT', 20); - define('LOGGED_ERROR_LIMIT', 100); - define('RANDOM_KEY_LENGTH', 32); - define('MAX_NUM_USERS', 20); - define('MAX_IMPORT_ROWS', 500); - define('MAX_SUBDOMAIN_LENGTH', 30); - define('MAX_IFRAME_URL_LENGTH', 250); - define('MAX_LOGO_FILE_SIZE', 200); // KB - define('MAX_FAILED_LOGINS', 10); - define('MAX_INVOICE_ITEMS', env('MAX_INVOICE_ITEMS', 100)); - define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB - define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB - define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed) - define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels - define('DEFAULT_FONT_SIZE', 9); - define('DEFAULT_HEADER_FONT', 1);// Roboto - define('DEFAULT_BODY_FONT', 1);// Roboto - define('DEFAULT_SEND_RECURRING_HOUR', 8); - - define('IMPORT_CSV', 'CSV'); - define('IMPORT_JSON', 'JSON'); - define('IMPORT_FRESHBOOKS', 'FreshBooks'); - define('IMPORT_WAVE', 'Wave'); - define('IMPORT_RONIN', 'Ronin'); - define('IMPORT_HIVEAGE', 'Hiveage'); - define('IMPORT_ZOHO', 'Zoho'); - define('IMPORT_NUTCACHE', 'Nutcache'); - define('IMPORT_INVOICEABLE', 'Invoiceable'); - define('IMPORT_HARVEST', 'Harvest'); - - define('MAX_NUM_CLIENTS', 100); - define('MAX_NUM_CLIENTS_PRO', 20000); - define('MAX_NUM_CLIENTS_LEGACY', 500); - define('MAX_INVOICE_AMOUNT', 1000000000); - define('LEGACY_CUTOFF', 57800); - define('ERROR_DELAY', 3); - - define('MAX_NUM_VENDORS', 100); - define('MAX_NUM_VENDORS_PRO', 20000); - - define('STATUS_ACTIVE', 'active'); - define('STATUS_ARCHIVED', 'archived'); - define('STATUS_DELETED', 'deleted'); - - define('INVOICE_STATUS_DRAFT', 1); - define('INVOICE_STATUS_SENT', 2); - define('INVOICE_STATUS_VIEWED', 3); - define('INVOICE_STATUS_APPROVED', 4); - define('INVOICE_STATUS_PARTIAL', 5); - define('INVOICE_STATUS_PAID', 6); - define('INVOICE_STATUS_OVERDUE', 7); - - define('PAYMENT_STATUS_PENDING', 1); - define('PAYMENT_STATUS_VOIDED', 2); - define('PAYMENT_STATUS_FAILED', 3); - define('PAYMENT_STATUS_COMPLETED', 4); - define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5); - define('PAYMENT_STATUS_REFUNDED', 6); - - define('TASK_STATUS_LOGGED', 1); - define('TASK_STATUS_RUNNING', 2); - define('TASK_STATUS_INVOICED', 3); - define('TASK_STATUS_PAID', 4); - - define('EXPENSE_STATUS_LOGGED', 1); - define('EXPENSE_STATUS_INVOICED', 2); - define('EXPENSE_STATUS_PAID', 3); - - define('CUSTOM_DESIGN', 11); - - define('FREQUENCY_WEEKLY', 1); - define('FREQUENCY_TWO_WEEKS', 2); - define('FREQUENCY_FOUR_WEEKS', 3); - define('FREQUENCY_MONTHLY', 4); - define('FREQUENCY_THREE_MONTHS', 5); - define('FREQUENCY_SIX_MONTHS', 6); - define('FREQUENCY_ANNUALLY', 7); - - define('SESSION_TIMEZONE', 'timezone'); - define('SESSION_CURRENCY', 'currency'); - define('SESSION_CURRENCY_DECORATOR', 'currency_decorator'); - define('SESSION_DATE_FORMAT', 'dateFormat'); - define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat'); - define('SESSION_DATETIME_FORMAT', 'datetimeFormat'); - define('SESSION_COUNTER', 'sessionCounter'); - define('SESSION_LOCALE', 'sessionLocale'); - define('SESSION_USER_ACCOUNTS', 'userAccounts'); - define('SESSION_REFERRAL_CODE', 'referralCode'); - define('SESSION_LEFT_SIDEBAR', 'showLeftSidebar'); - define('SESSION_RIGHT_SIDEBAR', 'showRightSidebar'); - - define('SESSION_LAST_REQUEST_PAGE', 'SESSION_LAST_REQUEST_PAGE'); - define('SESSION_LAST_REQUEST_TIME', 'SESSION_LAST_REQUEST_TIME'); - - define('CURRENCY_DOLLAR', 1); - define('CURRENCY_EURO', 3); - - define('DEFAULT_TIMEZONE', 'US/Eastern'); - define('DEFAULT_COUNTRY', 840); // United Stated - define('DEFAULT_CURRENCY', CURRENCY_DOLLAR); - define('DEFAULT_LANGUAGE', 1); // English - define('DEFAULT_DATE_FORMAT', 'M j, Y'); - define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy'); - define('DEFAULT_DATETIME_FORMAT', 'F j, Y g:i a'); - define('DEFAULT_DATETIME_MOMENT_FORMAT', 'MMM D, YYYY h:mm:ss a'); - define('DEFAULT_LOCALE', 'en'); - define('DEFAULT_MAP_ZOOM', 10); - - define('RESULT_SUCCESS', 'success'); - define('RESULT_FAILURE', 'failure'); - - - define('PAYMENT_LIBRARY_OMNIPAY', 1); - define('PAYMENT_LIBRARY_PHP_PAYMENTS', 2); - - define('GATEWAY_AUTHORIZE_NET', 1); - define('GATEWAY_EWAY', 4); - define('GATEWAY_MOLLIE', 9); - define('GATEWAY_PAYFAST', 13); - define('GATEWAY_PAYPAL_EXPRESS', 17); - define('GATEWAY_PAYPAL_PRO', 18); - define('GATEWAY_SAGE_PAY_DIRECT', 20); - define('GATEWAY_SAGE_PAY_SERVER', 21); - define('GATEWAY_STRIPE', 23); - define('GATEWAY_GOCARDLESS', 6); - define('GATEWAY_TWO_CHECKOUT', 27); - define('GATEWAY_BEANSTREAM', 29); - define('GATEWAY_PSIGATE', 30); - define('GATEWAY_MOOLAH', 31); - define('GATEWAY_BITPAY', 42); - define('GATEWAY_DWOLLA', 43); - define('GATEWAY_CHECKOUT_COM', 47); - define('GATEWAY_CYBERSOURCE', 49); - define('GATEWAY_WEPAY', 60); - define('GATEWAY_BRAINTREE', 61); - define('GATEWAY_CUSTOM', 62); - - // The customer exists, but only as a local concept - // The remote gateway doesn't understand the concept of customers - define('CUSTOMER_REFERENCE_LOCAL', 'local'); - - define('EVENT_CREATE_CLIENT', 1); - define('EVENT_CREATE_INVOICE', 2); - define('EVENT_CREATE_QUOTE', 3); - define('EVENT_CREATE_PAYMENT', 4); - define('EVENT_CREATE_VENDOR',5); - - 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_GATEWAY_ID', GATEWAY_STRIPE); - define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); - define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.invoiceninja.com')); - define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); - define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest')); - define('NINJA_DATE', '2000-01-01'); - define('NINJA_VERSION', '2.9.5' . env('NINJA_VERSION_SUFFIX')); - - define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); - define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); - define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/')); - - define('NINJA_FORUM_URL', env('NINJA_FORUM_URL', 'https://www.invoiceninja.com/forums/forum/support/')); - define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/')); - define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com')); - define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja')); - define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja')); - define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/')); - define('PDFMAKE_DOCS', env('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html')); - define('PHANTOMJS_CLOUD', env('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/')); - define('PHP_DATE_FORMATS', env('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php')); - define('REFERRAL_PROGRAM_URL', env('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/')); - define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup')); - define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all')); - define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect')); - define('TRANSIFEX_URL', env('TRANSIFEX_URL', 'https://www.transifex.com/invoice-ninja/invoice-ninja')); - define('CHROME_PDF_HELP_URL', 'https://support.google.com/chrome/answer/6213030?hl=en'); - define('FIREFOX_PDF_HELP_URL', 'https://support.mozilla.org/en-US/kb/view-pdf-files-firefox'); - - define('MSBOT_LOGIN_URL', 'https://login.microsoftonline.com/common/oauth2/v2.0/token'); - define('MSBOT_LUIS_URL', 'https://api.projectoxford.ai/luis/v1/application'); - define('SKYPE_API_URL', 'https://apis.skype.com/v3'); - define('MSBOT_STATE_URL', 'https://state.botframework.com/v3'); - - define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='); - - define('COUNT_FREE_DESIGNS', 4); - define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design - define('PRODUCT_ONE_CLICK_INSTALL', 1); - define('PRODUCT_INVOICE_DESIGNS', 2); - define('PRODUCT_WHITE_LABEL', 3); - define('PRODUCT_SELF_HOST', 4); - define('WHITE_LABEL_AFFILIATE_KEY', '92D2J5'); - define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74'); - define('SELF_HOST_AFFILIATE_KEY', '8S69AD'); - - define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 8)); - define('PLAN_PRICE_ENTERPRISE_MONTHLY_2', env('PLAN_PRICE_ENTERPRISE_MONTHLY_2', 12)); - define('PLAN_PRICE_ENTERPRISE_MONTHLY_5', env('PLAN_PRICE_ENTERPRISE_MONTHLY_5', 18)); - define('PLAN_PRICE_ENTERPRISE_MONTHLY_10', env('PLAN_PRICE_ENTERPRISE_MONTHLY_10', 24)); - define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 20)); - define('INVOICE_DESIGNS_PRICE', env('INVOICE_DESIGNS_PRICE', 10)); - - define('USER_TYPE_SELF_HOST', 'SELF_HOST'); - define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST'); - define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE'); - - define('TEST_USERNAME', 'user@example.com'); - define('TEST_PASSWORD', 'password'); - define('API_SECRET', 'API_SECRET'); - define('DEFAULT_API_PAGE_SIZE', 15); - define('MAX_API_PAGE_SIZE', 500); - - define('IOS_PUSH_CERTIFICATE', env('IOS_PUSH_CERTIFICATE', '')); - - define('TOKEN_BILLING_DISABLED', 1); - define('TOKEN_BILLING_OPT_IN', 2); - define('TOKEN_BILLING_OPT_OUT', 3); - define('TOKEN_BILLING_ALWAYS', 4); - - define('PAYMENT_TYPE_CREDIT', 1); - define('PAYMENT_TYPE_ACH', 5); - define('PAYMENT_TYPE_VISA', 6); - define('PAYMENT_TYPE_MASTERCARD', 7); - define('PAYMENT_TYPE_AMERICAN_EXPRESS', 8); - define('PAYMENT_TYPE_DISCOVER', 9); - define('PAYMENT_TYPE_DINERS', 10); - define('PAYMENT_TYPE_EUROCARD', 11); - define('PAYMENT_TYPE_NOVA', 12); - define('PAYMENT_TYPE_CREDIT_CARD_OTHER', 13); - define('PAYMENT_TYPE_PAYPAL', 14); - define('PAYMENT_TYPE_CARTE_BLANCHE', 17); - define('PAYMENT_TYPE_UNIONPAY', 18); - define('PAYMENT_TYPE_JCB', 19); - define('PAYMENT_TYPE_LASER', 20); - define('PAYMENT_TYPE_MAESTRO', 21); - define('PAYMENT_TYPE_SOLO', 22); - define('PAYMENT_TYPE_SWITCH', 23); - - define('PAYMENT_METHOD_STATUS_NEW', 'new'); - define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed'); - define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified'); - - define('GATEWAY_TYPE_CREDIT_CARD', 1); - define('GATEWAY_TYPE_BANK_TRANSFER', 2); - define('GATEWAY_TYPE_PAYPAL', 3); - define('GATEWAY_TYPE_BITCOIN', 4); - define('GATEWAY_TYPE_DWOLLA', 5); - define('GATEWAY_TYPE_CUSTOM', 6); - define('GATEWAY_TYPE_TOKEN', 'token'); - - define('REMINDER1', 'reminder1'); - define('REMINDER2', 'reminder2'); - define('REMINDER3', 'reminder3'); - - define('REMINDER_DIRECTION_AFTER', 1); - define('REMINDER_DIRECTION_BEFORE', 2); - - define('REMINDER_FIELD_DUE_DATE', 1); - define('REMINDER_FIELD_INVOICE_DATE', 2); - - define('FILTER_INVOICE_DATE', 'invoice_date'); - define('FILTER_PAYMENT_DATE', 'payment_date'); - - define('SOCIAL_GOOGLE', 'Google'); - define('SOCIAL_FACEBOOK', 'Facebook'); - define('SOCIAL_GITHUB', 'GitHub'); - define('SOCIAL_LINKEDIN', 'LinkedIn'); - - define('USER_STATE_ACTIVE', 'active'); - define('USER_STATE_PENDING', 'pending'); - define('USER_STATE_DISABLED', 'disabled'); - define('USER_STATE_ADMIN', 'admin'); - define('USER_STATE_OWNER', 'owner'); - - define('API_SERIALIZER_ARRAY', 'array'); - define('API_SERIALIZER_JSON', 'json'); - - define('EMAIL_DESIGN_PLAIN', 1); - define('EMAIL_DESIGN_LIGHT', 2); - define('EMAIL_DESIGN_DARK', 3); - - define('BANK_LIBRARY_OFX', 1); - - define('CURRENCY_DECORATOR_CODE', 'code'); - define('CURRENCY_DECORATOR_SYMBOL', 'symbol'); - define('CURRENCY_DECORATOR_NONE', 'none'); - - define('RESELLER_REVENUE_SHARE', 'A'); - define('RESELLER_LIMITED_USERS', 'B'); - - define('AUTO_BILL_OFF', 1); - define('AUTO_BILL_OPT_IN', 2); - define('AUTO_BILL_OPT_OUT', 3); - define('AUTO_BILL_ALWAYS', 4); - - // These must be lowercase - define('PLAN_FREE', 'free'); - define('PLAN_PRO', 'pro'); - define('PLAN_ENTERPRISE', 'enterprise'); - define('PLAN_WHITE_LABEL', 'white_label'); - define('PLAN_TERM_MONTHLY', 'month'); - define('PLAN_TERM_YEARLY', 'year'); - - // Pro - define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design'); - define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by'); - define('FEATURE_DIFFERENT_DESIGNS', 'different_designs'); - define('FEATURE_EMAIL_TEMPLATES_REMINDERS', 'email_templates_reminders'); - define('FEATURE_INVOICE_SETTINGS', 'invoice_settings'); - define('FEATURE_CUSTOM_EMAILS', 'custom_emails'); - define('FEATURE_PDF_ATTACHMENT', 'pdf_attachment'); - define('FEATURE_MORE_INVOICE_DESIGNS', 'more_invoice_designs'); - define('FEATURE_QUOTES', 'quotes'); - define('FEATURE_TASKS', 'tasks'); - define('FEATURE_EXPENSES', 'expenses'); - define('FEATURE_REPORTS', 'reports'); - define('FEATURE_BUY_NOW_BUTTONS', 'buy_now_buttons'); - define('FEATURE_API', 'api'); - define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password'); - define('FEATURE_CUSTOM_URL', 'custom_url'); - - define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed - - // Whitelabel - define('FEATURE_CLIENT_PORTAL_CSS', 'client_portal_css'); - define('FEATURE_WHITE_LABEL', 'feature_white_label'); - - // Enterprise - define('FEATURE_DOCUMENTS', 'documents'); - - // No Trial allowed - define('FEATURE_USERS', 'users');// Grandfathered for old Pro users - define('FEATURE_USER_PERMISSIONS', 'user_permissions'); - - // Pro users who started paying on or before this date will be able to manage users - define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-06-04'); - define('EXTRAS_GRANDFATHER_COMPANY_ID', 35089); - - // WePay - define('WEPAY_PRODUCTION', 'production'); - define('WEPAY_STAGE', 'stage'); - define('WEPAY_CLIENT_ID', env('WEPAY_CLIENT_ID')); - define('WEPAY_CLIENT_SECRET', env('WEPAY_CLIENT_SECRET')); - define('WEPAY_AUTO_UPDATE', env('WEPAY_AUTO_UPDATE', false)); - define('WEPAY_ENVIRONMENT', env('WEPAY_ENVIRONMENT', WEPAY_PRODUCTION)); - define('WEPAY_ENABLE_CANADA', env('WEPAY_ENABLE_CANADA', false)); - define('WEPAY_THEME', env('WEPAY_THEME','{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}')); - - define('SKYPE_CARD_RECEIPT', 'message/card.receipt'); - define('SKYPE_CARD_CAROUSEL', 'message/card.carousel'); - define('SKYPE_CARD_HERO', ''); - - define('BOT_STATE_GET_EMAIL', 'get_email'); - define('BOT_STATE_GET_CODE', 'get_code'); - define('BOT_STATE_READY', 'ready'); - define('SIMILAR_MIN_THRESHOLD', 50); - - // https://docs.botframework.com/en-us/csharp/builder/sdkreference/attachments.html - define('SKYPE_BUTTON_OPEN_URL', 'openUrl'); - define('SKYPE_BUTTON_IM_BACK', 'imBack'); - define('SKYPE_BUTTON_POST_BACK', 'postBack'); - define('SKYPE_BUTTON_CALL', 'call'); // "tel:123123123123" - define('SKYPE_BUTTON_PLAY_AUDIO', 'playAudio'); - define('SKYPE_BUTTON_PLAY_VIDEO', 'playVideo'); - define('SKYPE_BUTTON_SHOW_IMAGE', 'showImage'); - define('SKYPE_BUTTON_DOWNLOAD_FILE', 'downloadFile'); - - define('INVOICE_FIELDS_CLIENT', 'client_fields'); - define('INVOICE_FIELDS_INVOICE', 'invoice_fields'); - define('INVOICE_FIELDS_ACCOUNT', 'account_fields'); - - $creditCards = [ - 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], - 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], - 4 => ['card' => 'images/credit_cards/Test-AmericanExpress-Icon.png', 'text' => 'American Express'], - 8 => ['card' => 'images/credit_cards/Test-Diners-Icon.png', 'text' => 'Diners'], - 16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'] - ]; - define('CREDIT_CARDS', serialize($creditCards)); - - $cachedTables = [ - 'currencies' => 'App\Models\Currency', - 'sizes' => 'App\Models\Size', - 'industries' => 'App\Models\Industry', - 'timezones' => 'App\Models\Timezone', - 'dateFormats' => 'App\Models\DateFormat', - 'datetimeFormats' => 'App\Models\DatetimeFormat', - 'languages' => 'App\Models\Language', - 'paymentTerms' => 'App\Models\PaymentTerm', - 'paymentTypes' => 'App\Models\PaymentType', - 'countries' => 'App\Models\Country', - 'invoiceDesigns' => 'App\Models\InvoiceDesign', - 'invoiceStatus' => 'App\Models\InvoiceStatus', - 'frequencies' => 'App\Models\Frequency', - 'gateways' => 'App\Models\Gateway', - 'gatewayTypes' => 'App\Models\GatewayType', - 'fonts' => 'App\Models\Font', - 'banks' => 'App\Models\Bank', - ]; - define('CACHED_TABLES', serialize($cachedTables)); - - function uctrans($text) - { - return ucwords(trans($text)); - } - - // optional trans: only return the string if it's translated - function otrans($text) - { - $locale = Session::get(SESSION_LOCALE); - - if ($locale == 'en') { - return trans($text); - } else { - $string = trans($text); - $english = trans($text, [], 'en'); - return $string != $english ? $string : ''; - } - } - - // include modules in translations - function mtrans($entityType, $text = false) - { - if ( ! $text) { - $text = $entityType; - } - - if ( ! Utils::isNinjaProd() && $module = Module::find($entityType)) { - return trans("{$module->getLowerName()}::texts.{$text}"); - } else { - return trans("texts.{$text}"); - } - } -} - - /* if (Utils::isNinjaDev()) { @@ -934,3 +361,6 @@ if (Utils::isNinjaDev()) Auth::loginUsingId(1); } */ + +// Include static app constants +require_once app_path() . '/Constants.php'; diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php new file mode 100644 index 000000000000..cd1bae1ceb3d --- /dev/null +++ b/app/Jobs/Job.php @@ -0,0 +1,47 @@ +sendTo( + config('queue.failed.notify_email'), + config('mail.from.address'), + config('mail.from.name'), + config('queue.failed.notify_subject', trans('texts.job_failed', ['name'=>$this->jobName])), + 'job_failed', + [ + 'name' => $this->jobName, + ] + ); + } + + $logger->error( + trans('texts.job_failed', ['name' => $this->jobName]) + ); + } + */ +} diff --git a/app/Jobs/SendInvoiceEmail.php b/app/Jobs/SendInvoiceEmail.php new file mode 100644 index 000000000000..8194a1c95684 --- /dev/null +++ b/app/Jobs/SendInvoiceEmail.php @@ -0,0 +1,73 @@ +invoice = $invoice; + $this->reminder = $reminder; + $this->pdfString = $pdfString; + } + + /** + * Execute the job. + * + * @param ContactMailer $mailer + */ + public function handle(ContactMailer $mailer) + { + $mailer->sendInvoice($this->invoice, $this->reminder, $this->pdfString); + } + + /** + * Handle a job failure. + * + * @param ContactMailer $mailer + * @param Logger $logger + */ + /* + public function failed(ContactMailer $mailer, Logger $logger) + { + $this->jobName = $this->job->getName(); + + parent::failed($mailer, $logger); + } + */ +} diff --git a/app/Jobs/SendNotificationEmail.php b/app/Jobs/SendNotificationEmail.php new file mode 100644 index 000000000000..32eaa6c42de0 --- /dev/null +++ b/app/Jobs/SendNotificationEmail.php @@ -0,0 +1,66 @@ +user = $user; + $this->invoice = $invoice; + $this->type = $type; + $this->payment = $payment; + } + + /** + * Execute the job. + * + * @param ContactMailer $mailer + */ + public function handle(UserMailer $userMailer) + { + $userMailer->sendNotification($this->user, $this->invoice, $this->type, $this->payment); + } + +} diff --git a/app/Jobs/SendPaymentEmail.php b/app/Jobs/SendPaymentEmail.php new file mode 100644 index 000000000000..c27c0f5f44ad --- /dev/null +++ b/app/Jobs/SendPaymentEmail.php @@ -0,0 +1,47 @@ +payment = $payment; + } + + /** + * Execute the job. + * + * @param ContactMailer $mailer + */ + public function handle(ContactMailer $contactMailer) + { + $contactMailer->sendPaymentConfirmation($this->payment); + } + + +} diff --git a/app/Jobs/SendPushNotification.php b/app/Jobs/SendPushNotification.php new file mode 100644 index 000000000000..a9774e1f084c --- /dev/null +++ b/app/Jobs/SendPushNotification.php @@ -0,0 +1,52 @@ +invoice = $invoice; + $this->type = $type; + } + + /** + * Execute the job. + * + * @param PushService $pushService + */ + public function handle(PushService $pushService) + { + $pushService->sendNotification($this->invoice, $this->type); + } + +} diff --git a/app/Libraries/CurlUtils.php b/app/Libraries/CurlUtils.php index d6ce9d0e37c0..4a11a90939dd 100644 --- a/app/Libraries/CurlUtils.php +++ b/app/Libraries/CurlUtils.php @@ -1,5 +1,7 @@ getEngine()->setPath($path); + + $request = $client->getMessageFactory()->createRequest($url, $method); + $response = $client->getMessageFactory()->createResponse(); + + // Send the request + $client->send($request, $response); + + if ($response->getStatus() === 200) { + return $response->getContent(); + } else { + //$response->getStatus(); + return false; + } + + } } diff --git a/app/Libraries/HTMLUtils.php b/app/Libraries/HTMLUtils.php new file mode 100644 index 000000000000..cb0ce92c02ec --- /dev/null +++ b/app/Libraries/HTMLUtils.php @@ -0,0 +1,37 @@ + + // + + // Create a new configuration object + $config = HTMLPurifier_Config::createDefault(); + $config->set('Filter.ExtractStyleBlocks', true); + $config->set('CSS.AllowImportant', true); + $config->set('CSS.AllowTricky', true); + $config->set('CSS.Trusted', true); + + // Create a new purifier instance + $purifier = new HTMLPurifier($config); + + // Wrap our CSS in style tags and pass to purifier. + // we're not actually interested in the html response though + $purifier->purify(''); + + // The "style" blocks are stored seperately + $css = $purifier->context->get('StyleBlocks'); + + // Get the first style block + return count($css) ? $css[0] : ''; + } +} diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index c869cce10d3f..12028c660eb1 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -67,6 +67,11 @@ class Utils return self::isNinjaProd() || self::isNinjaDev(); } + public static function isSelfHost() + { + return ! static::isNinjaProd(); + } + public static function isNinjaProd() { if (Utils::isReseller()) { @@ -97,13 +102,21 @@ class Utils public static function isWhiteLabel() { - if (Utils::isNinjaProd()) { - return false; + $account = false; + + if (Utils::isNinja()) { + if (Auth::check()) { + $account = Auth::user()->account; + } elseif ($contactKey = session('contact_key')) { + if ($contact = \App\Models\Contact::whereContactKey($contactKey)->first()) { + $account = $contact->account; + } + } + } else { + $account = \App\Models\Account::first(); } - $account = \App\Models\Account::first(); - - return $account && $account->hasFeature(FEATURE_WHITE_LABEL); + return $account ? $account->hasFeature(FEATURE_WHITE_LABEL) : false; } public static function getResllerType() @@ -111,6 +124,11 @@ class Utils return isset($_ENV['RESELLER_TYPE']) ? $_ENV['RESELLER_TYPE'] : false; } + public static function getTermsLink() + { + return static::isNinja() ? NINJA_WEB_URL.'/terms' : NINJA_WEB_URL.'/self-hosting-the-invoice-ninja-platform'; + } + public static function isOAuthEnabled() { $providers = [ @@ -238,6 +256,8 @@ class Utils $price = PLAN_PRICE_ENTERPRISE_MONTHLY_5; } elseif ($numUsers <= 10) { $price = PLAN_PRICE_ENTERPRISE_MONTHLY_10; + } elseif ($numUsers <= 20) { + $price = PLAN_PRICE_ENTERPRISE_MONTHLY_20; } else { static::fatalError('Invalid number of users: ' . $numUsers); } @@ -256,8 +276,10 @@ class Utils return 1; } elseif ($max <= 5) { return 3; - } else { + } elseif ($max <= 10) { return 6; + } else { + return 11; } } @@ -266,7 +288,7 @@ class Utils return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1); } - public static function trans($input) + public static function trans($input, $module = false) { $data = []; @@ -274,7 +296,11 @@ class Utils if ($field == 'checkbox') { $data[] = $field; } elseif ($field) { - $data[] = trans("texts.$field"); + if ($module) { + $data[] = mtrans($module, $field); + } else { + $data[] = trans("texts.$field"); + } } else { $data[] = ''; } diff --git a/app/Listeners/ActivityListener.php b/app/Listeners/ActivityListener.php index ee016cbff39b..2a3985f54ff6 100644 --- a/app/Listeners/ActivityListener.php +++ b/app/Listeners/ActivityListener.php @@ -132,7 +132,7 @@ class ActivityListener } $backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts') - ->withArchived() + ->withTrashed() ->find($event->invoice->id); $activity = $this->activityRepo->create( @@ -200,7 +200,8 @@ class ActivityListener ACTIVITY_TYPE_EMAIL_INVOICE, false, false, - $event->invitation + $event->invitation, + $event->notes ); } @@ -238,7 +239,9 @@ class ActivityListener return; } - $backupQuote = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($event->quote->id); + $backupQuote = Invoice::with('invoice_items', 'client.account', 'client.contacts') + ->withTrashed() + ->find($event->quote->id); $activity = $this->activityRepo->create( $event->quote, @@ -296,7 +299,8 @@ class ActivityListener ACTIVITY_TYPE_EMAIL_QUOTE, false, false, - $event->invitation + $event->invitation, + $event->notes ); } diff --git a/app/Listeners/HandleUserLoggedIn.php b/app/Listeners/HandleUserLoggedIn.php index b8a9e3f1ad70..f8b3bb8cfb4e 100644 --- a/app/Listeners/HandleUserLoggedIn.php +++ b/app/Listeners/HandleUserLoggedIn.php @@ -52,6 +52,10 @@ class HandleUserLoggedIn { $account->loadLocalizationSettings(); + if (strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') || strpos($_SERVER['HTTP_USER_AGENT'], 'iPad')) { + Session::flash('warning', trans('texts.iphone_app_message', ['link' => link_to(NINJA_IOS_APP_URL, trans('texts.iphone_app'))])); + } + // if they're using Stripe make sure they're using Stripe.js $accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE); if ($accountGateway && ! $accountGateway->getPublishableStripeKey()) { diff --git a/app/Listeners/InvoiceListener.php b/app/Listeners/InvoiceListener.php index 566d9b4994c2..c7353c768ad5 100644 --- a/app/Listeners/InvoiceListener.php +++ b/app/Listeners/InvoiceListener.php @@ -2,6 +2,7 @@ use Utils; use Auth; +use App\Models\Activity; use App\Events\InvoiceWasUpdated; use App\Events\InvoiceWasCreated; use App\Events\PaymentWasCreated; @@ -69,6 +70,13 @@ class InvoiceListener $invoice->updateBalances($adjustment, $partial); $invoice->updatePaidStatus(); + + // store a backup of the invoice + $activity = Activity::wherePaymentId($payment->id) + ->whereActivityTypeId(ACTIVITY_TYPE_CREATE_PAYMENT) + ->first(); + $activity->json_backup = $invoice->hidePrivateFields()->toJSON(); + $activity->save(); } /** diff --git a/app/Listeners/NotificationListener.php b/app/Listeners/NotificationListener.php index ad94e5767a61..8772ef02e0ff 100644 --- a/app/Listeners/NotificationListener.php +++ b/app/Listeners/NotificationListener.php @@ -1,46 +1,20 @@ userMailer = $userMailer; - $this->contactMailer = $contactMailer; - $this->pushService = $pushService; - } - /** * @param $invoice * @param $type @@ -52,7 +26,7 @@ class NotificationListener { if ($user->{"notify_{$type}"}) { - $this->userMailer->sendNotification($user, $invoice, $type, $payment); + dispatch(new SendNotificationEmail($user, $invoice, $type, $payment)); } } } @@ -63,7 +37,7 @@ class NotificationListener public function emailedInvoice(InvoiceWasEmailed $event) { $this->sendEmails($event->invoice, 'sent'); - $this->pushService->sendNotification($event->invoice, 'sent'); + dispatch(new SendPushNotification($event->invoice, 'sent')); } /** @@ -72,7 +46,7 @@ class NotificationListener public function emailedQuote(QuoteWasEmailed $event) { $this->sendEmails($event->quote, 'sent'); - $this->pushService->sendNotification($event->quote, 'sent'); + dispatch(new SendPushNotification($event->quote, 'sent')); } /** @@ -85,7 +59,7 @@ class NotificationListener } $this->sendEmails($event->invoice, 'viewed'); - $this->pushService->sendNotification($event->invoice, 'viewed'); + dispatch(new SendPushNotification($event->invoice, 'viewed')); } /** @@ -98,7 +72,7 @@ class NotificationListener } $this->sendEmails($event->quote, 'viewed'); - $this->pushService->sendNotification($event->quote, 'viewed'); + dispatch(new SendPushNotification($event->quote, 'viewed')); } /** @@ -107,7 +81,7 @@ class NotificationListener public function approvedQuote(QuoteInvitationWasApproved $event) { $this->sendEmails($event->quote, 'approved'); - $this->pushService->sendNotification($event->quote, 'approved'); + dispatch(new SendPushNotification($event->quote, 'approved')); } /** @@ -120,10 +94,9 @@ class NotificationListener return; } - $this->contactMailer->sendPaymentConfirmation($event->payment); $this->sendEmails($event->payment->invoice, 'paid', $event->payment); - - $this->pushService->sendNotification($event->payment->invoice, 'paid'); + dispatch(new SendPaymentEmail($event->payment)); + dispatch(new SendPushNotification($event->payment->invoice, 'paid')); } } diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php index 5b84413cb25a..595bc868bb12 100644 --- a/app/Listeners/SubscriptionListener.php +++ b/app/Listeners/SubscriptionListener.php @@ -1,5 +1,9 @@ invoice->account); + $this->checkSubscriptions(EVENT_UPDATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); + } + + /** + * @param InvoiceWasDeleted $event + */ + public function deletedInvoice(InvoiceWasDeleted $event) + { + $transformer = new InvoiceTransformer($event->invoice->account); + $this->checkSubscriptions(EVENT_DELETE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); + } + + /** + * @param QuoteWasUpdated $event + */ + public function updatedQuote(QuoteWasUpdated $event) + { + $transformer = new InvoiceTransformer($event->quote->account); + $this->checkSubscriptions(EVENT_UPDATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); + } + + /** + * @param InvoiceWasDeleted $event + */ + public function deletedQuote(QuoteWasDeleted $event) + { + $transformer = new InvoiceTransformer($event->quote->account); + $this->checkSubscriptions(EVENT_DELETE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); } /** diff --git a/app/Models/Account.php b/app/Models/Account.php index a27074edbd03..b1df65189390 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -13,6 +13,8 @@ use Illuminate\Support\Facades\Storage; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; use App\Models\Traits\PresentsInvoice; +use App\Models\Traits\GeneratesNumbers; +use App\Models\Traits\SendsEmails; /** * Class Account @@ -22,6 +24,8 @@ class Account extends Eloquent use PresentableTrait; use SoftDeletes; use PresentsInvoice; + use GeneratesNumbers; + use SendsEmails; /** * @var string @@ -80,6 +84,12 @@ class Account extends Eloquent 'show_accept_quote_terms', 'require_invoice_signature', 'require_quote_signature', + 'pdf_email_attachment', + 'document_email_attachment', + 'email_design_id', + 'enable_email_markup', + 'domain_id', + 'payment_terms', ]; /** @@ -103,11 +113,11 @@ class Account extends Eloquent public static $advancedSettings = [ ACCOUNT_INVOICE_SETTINGS, ACCOUNT_INVOICE_DESIGN, + ACCOUNT_CLIENT_PORTAL, ACCOUNT_EMAIL_SETTINGS, ACCOUNT_TEMPLATES_AND_REMINDERS, ACCOUNT_BANKS, - ACCOUNT_CLIENT_PORTAL, - ACCOUNT_REPORTS, + //ACCOUNT_REPORTS, ACCOUNT_DATA_VISUALIZATIONS, ACCOUNT_API_TOKENS, ACCOUNT_USER_MANAGEMENT, @@ -466,6 +476,17 @@ class Account extends Eloquent return $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT; } + + public function getSampleLink() + { + $invitation = new Invitation(); + $invitation->account = $this; + $invitation->invitation_key = '...'; + + return $invitation->getLink(); + } + + /** * @param $amount * @param null $client @@ -862,7 +883,7 @@ class Account extends Eloquent if ($this->hasClientNumberPattern($invoice) && !$clientId) { // do nothing, we don't yet know the value } elseif ( ! $invoice->invoice_number) { - $invoice->invoice_number = $this->getNextInvoiceNumber($invoice); + $invoice->invoice_number = $this->getNextNumber($invoice); } } @@ -874,191 +895,6 @@ class Account extends Eloquent return $invoice; } - /** - * @param $invoice_type_id - * @return string - */ - public function getNumberPrefix($invoice_type_id) - { - if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { - return ''; - } - - return ($invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_prefix : $this->invoice_number_prefix) ?: ''; - } - - /** - * @param $invoice_type_id - * @return bool - */ - public function hasNumberPattern($invoice_type_id) - { - if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { - return false; - } - - return $invoice_type_id == INVOICE_TYPE_QUOTE ? ($this->quote_number_pattern ? true : false) : ($this->invoice_number_pattern ? true : false); - } - - /** - * @param $invoice - * @return string - */ - public function hasClientNumberPattern($invoice) - { - $pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern; - - return strstr($pattern, '$custom'); - } - - /** - * @param $invoice - * @return bool|mixed - */ - public function getNumberPattern($invoice) - { - $pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern; - - if (!$pattern) { - return false; - } - - $search = ['{$year}']; - $replace = [date('Y')]; - - $search[] = '{$counter}'; - $replace[] = str_pad($this->getCounter($invoice->invoice_type_id), $this->invoice_number_padding, '0', STR_PAD_LEFT); - - if (strstr($pattern, '{$userId}')) { - $search[] = '{$userId}'; - $replace[] = str_pad(($invoice->user->public_id + 1), 2, '0', STR_PAD_LEFT); - } - - $matches = false; - preg_match('/{\$date:(.*?)}/', $pattern, $matches); - if (count($matches) > 1) { - $format = $matches[1]; - $search[] = $matches[0]; - $replace[] = str_replace($format, date($format), $matches[1]); - } - - $pattern = str_replace($search, $replace, $pattern); - - if ($invoice->client_id) { - $pattern = $this->getClientInvoiceNumber($pattern, $invoice); - } - - return $pattern; - } - - /** - * @param $pattern - * @param $invoice - * @return mixed - */ - private function getClientInvoiceNumber($pattern, $invoice) - { - if (!$invoice->client) { - return $pattern; - } - - $search = [ - '{$custom1}', - '{$custom2}', - ]; - - $replace = [ - $invoice->client->custom_value1, - $invoice->client->custom_value2, - ]; - - return str_replace($search, $replace, $pattern); - } - - /** - * @param $invoice_type_id - * @return mixed - */ - public function getCounter($invoice_type_id) - { - return $invoice_type_id == INVOICE_TYPE_QUOTE && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter; - } - - /** - * @param $entityType - * @return mixed|string - */ - public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE) - { - $invoice = $this->createInvoice($entityType); - return $this->getNextInvoiceNumber($invoice); - } - - /** - * @param $invoice - * @param bool $validateUnique - * @return mixed|string - */ - public function getNextInvoiceNumber($invoice, $validateUnique = true) - { - if ($this->hasNumberPattern($invoice->invoice_type_id)) { - $number = $this->getNumberPattern($invoice); - } else { - $counter = $this->getCounter($invoice->invoice_type_id); - $prefix = $this->getNumberPrefix($invoice->invoice_type_id); - $counterOffset = 0; - $check = false; - - // confirm the invoice number isn't already taken - do { - $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); - if ($validateUnique) { - $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); - $counter++; - $counterOffset++; - } - } while ($check); - - // update the invoice counter to be caught up - if ($counterOffset > 1) { - if ($invoice->isType(INVOICE_TYPE_QUOTE)) { - if ( ! $this->share_counter) { - $this->quote_number_counter += $counterOffset - 1; - } - } else { - $this->invoice_number_counter += $counterOffset - 1; - } - - $this->save(); - } - } - - if ($invoice->recurring_invoice_id) { - $number = $this->recurring_invoice_number_prefix . $number; - } - - return $number; - } - - /** - * @param $invoice - */ - public function incrementCounter($invoice) - { - // if they didn't use the counter don't increment it - if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice, false)) { - return; - } - - if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { - $this->quote_number_counter += 1; - } else { - $this->invoice_number_counter += 1; - } - - $this->save(); - } - /** * @param bool $client */ @@ -1107,6 +943,10 @@ class Account extends Eloquent return; } + if ($this->company->trial_started && $this->company->trial_started != '0000-00-00') { + return; + } + $this->company->trial_plan = $plan; $this->company->trial_started = date_create()->format('Y-m-d'); $this->company->save(); @@ -1166,7 +1006,6 @@ class Account extends Eloquent return false; } // Fallthrough - case FEATURE_CLIENT_PORTAL_CSS: case FEATURE_REMOVE_CREATED_BY: return !empty($planDetails);// A plan is required even for self-hosted users @@ -1342,31 +1181,6 @@ class Account extends Eloquent return $plan_details && $plan_details['trial']; } - /** - * @param null $plan - * @return array|bool - */ - public function isEligibleForTrial($plan = null) - { - if (!$this->company->trial_plan) { - if ($plan) { - return $plan == PLAN_PRO || $plan == PLAN_ENTERPRISE; - } else { - return [PLAN_PRO, PLAN_ENTERPRISE]; - } - } - - if ($this->company->trial_plan == PLAN_PRO) { - if ($plan) { - return $plan != PLAN_PRO; - } else { - return [PLAN_ENTERPRISE]; - } - } - - return false; - } - /** * @return int */ @@ -1709,8 +1523,8 @@ class Account extends Eloquent */ public function showCustomField($field, $entity = false) { - if ($this->hasFeature(FEATURE_INVOICE_SETTINGS)) { - return $this->$field ? true : false; + if ($this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->$field) { + return true; } if (!$entity) { @@ -1753,9 +1567,7 @@ class Account extends Eloquent if ($headerFont != $bodyFont) { $css .= 'h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{'.$headerFont.'}'; } - } - if ($this->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { - // For self-hosted users, a white-label license is required for custom CSS + $css .= $this->client_view_css; } @@ -1882,7 +1694,7 @@ class Account extends Eloquent return $this->enabled_modules & static::$modules[$entityType]; } - public function showAuthenticatePanel($invoice) + public function requiresAuthorization($invoice) { return $this->showAcceptTerms($invoice) || $this->showSignature($invoice); } @@ -1904,6 +1716,22 @@ class Account extends Eloquent return $invoice->isQuote() ? $this->require_quote_signature : $this->require_invoice_signature; } + + public function emailMarkupEnabled() + { + if ( ! Utils::isNinja()) { + return false; + } + + return $this->enable_email_markup; + } + + public function defaultDueDate() + { + $numDays = $this->payment_terms == -1 ? 0 : $this->payment_terms; + + return Carbon::now($this->getTimezone())->addDays($numDays)->format('Y-m-d'); + } } Account::updated(function ($account) diff --git a/app/Models/Client.php b/app/Models/Client.php index 02da4dc912a5..d88f5986c0b8 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -275,6 +275,12 @@ class Client extends EntityModel } else { $contact = Contact::createNew(); $contact->send_invoice = true; + + 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); + } } if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->account->enable_portal_password){ @@ -379,6 +385,14 @@ class Client extends EntityModel return ENTITY_CLIENT; } + /** + * @return bool + */ + public function showMap() + { + return $this->hasAddress() && env('GOOGLE_MAPS_ENABLED') !== false; + } + /** * @return bool */ @@ -521,6 +535,7 @@ class Client extends EntityModel Client::creating(function ($client) { $client->setNullValues(); + $client->account->incrementCounter($client); }); Client::updating(function ($client) { diff --git a/app/Models/Company.php b/app/Models/Company.php index 94111e446556..f1bfd81b9a2b 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -56,13 +56,17 @@ class Company extends Eloquent // handle promos and discounts public function hasActiveDiscount(Carbon $date = null) { - if ( ! $this->discount) { + if ( ! $this->discount || ! $this->discount_expires) { return false; } $date = $date ?: Carbon::today(); - return $this->discount_expires && $this->discount_expires->gt($date); + if ($this->plan_term == PLAN_TERM_MONTHLY) { + return $this->discount_expires->gt($date); + } else { + return $this->discount_expires->subMonths(11)->gt($date); + } } public function discountedPrice($price) @@ -74,6 +78,29 @@ class Company extends Eloquent return $price - ($price * $this->discount); } + public function daysUntilPlanExpires() + { + if ( ! $this->hasActivePlan()) { + return 0; + } + + return Carbon::parse($this->plan_expires)->diffInDays(Carbon::today()); + } + + public function hasActivePlan() + { + return Carbon::parse($this->plan_expires) >= Carbon::today(); + } + + public function hasExpiredPlan($plan) + { + if ($this->plan != $plan) { + return false; + } + + return Carbon::parse($this->plan_expires) < Carbon::today(); + } + public function hasEarnedPromo() { if ( ! Utils::isNinjaProd() || Utils::isPro()) { diff --git a/app/Models/Contact.php b/app/Models/Contact.php index feece59de005..94f6c388a1ad 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -128,7 +128,7 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa public function getFullName() { if ($this->first_name || $this->last_name) { - return $this->first_name.' '.$this->last_name; + return trim($this->first_name.' '.$this->last_name); } else { return ''; } diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index e272b4350857..05cf083bcb4f 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -289,6 +289,7 @@ class EntityModel extends Eloquent 'vendors' => 'building', 'settings' => 'cog', 'self-update' => 'download', + 'reports' => 'th-list', ]; return array_get($icons, $entityType); @@ -335,4 +336,15 @@ class EntityModel extends Eloquent return $class::getStatuses($entityType); } + + public function statusClass() + { + return ''; + } + + public function statusLabel() + { + return ''; + } + } diff --git a/app/Models/Expense.php b/app/Models/Expense.php index 1829fed8d186..3a747c0d5c29 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -221,6 +221,53 @@ class Expense extends EntityModel return $statuses; } + + public static function calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance) + { + if ($invoiceId) { + if (floatval($balance) > 0) { + $label = 'invoiced'; + } else { + $label = 'paid'; + } + } elseif ($shouldBeInvoiced) { + $label = 'pending'; + } else { + $label = 'logged'; + } + + return trans("texts.{$label}"); + } + + public static function calcStatusClass($shouldBeInvoiced, $invoiceId, $balance) + { + if ($invoiceId) { + if (floatval($balance) > 0) { + return 'default'; + } else { + return 'success'; + } + } elseif ($shouldBeInvoiced) { + return 'warning'; + } else { + return 'primary'; + } + } + + public function statusClass() + { + $balance = $this->invoice ? $this->invoice->balance : 0; + + return static::calcStatusClass($this->should_be_invoiced, $this->invoice_id, $balance); + } + + public function statusLabel() + { + $balance = $this->invoice ? $this->invoice->balance : 0; + + return static::calcStatusLabel($this->should_be_invoiced, $this->invoice_id, $balance); + } + } Expense::creating(function ($expense) { diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 370c521b7830..5b62b8218183 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -68,14 +68,19 @@ class Invitation extends EntityModel $this->load('account'); } + $account = $this->account; + $iframe_url = $account->iframe_url; $url = trim(SITE_URL, '/'); - $iframe_url = $this->account->iframe_url; - if ($this->account->hasFeature(FEATURE_CUSTOM_URL)) { + if ($account->hasFeature(FEATURE_CUSTOM_URL)) { + if (Utils::isNinjaProd()) { + $url = $account->present()->clientPortalLink(); + } + if ($iframe_url && !$forceOnsite) { return "{$iframe_url}?{$this->invitation_key}"; } elseif ($this->account->subdomain) { - $url = Utils::replaceSubdomain($url, $this->account->subdomain); + $url = Utils::replaceSubdomain($url, $account->subdomain); } } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 2c7fc0fcc555..4c16459a11bd 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -10,6 +10,7 @@ use App\Events\InvoiceWasCreated; use App\Events\InvoiceWasUpdated; use App\Events\InvoiceInvitationWasEmailed; use App\Events\QuoteInvitationWasEmailed; +use App\Libraries\CurlUtils; /** * Class Invoice @@ -64,10 +65,22 @@ class Invoice extends EntityModel implements BalanceAffecting 'date:', ]; + public static $statusClasses = [ + INVOICE_STATUS_SENT => 'info', + INVOICE_STATUS_VIEWED => 'warning', + INVOICE_STATUS_APPROVED => 'success', + INVOICE_STATUS_PARTIAL => 'primary', + INVOICE_STATUS_PAID => 'success', + ]; + /** * @var string */ public static $fieldInvoiceNumber = 'invoice_number'; + /** + * @var string + */ + public static $fieldPONumber = 'po_number'; /** * @var string */ @@ -101,6 +114,7 @@ class Invoice extends EntityModel implements BalanceAffecting return [ Client::$fieldName, Invoice::$fieldInvoiceNumber, + Invoice::$fieldPONumber, Invoice::$fieldInvoiceDate, Invoice::$fieldDueDate, Invoice::$fieldAmount, @@ -118,9 +132,11 @@ class Invoice extends EntityModel implements BalanceAffecting return [ 'number^po' => 'invoice_number', 'amount' => 'amount', - 'organization' => 'name', + 'client|organization' => 'name', 'paid^date' => 'paid', - 'invoice_date|create_date' => 'invoice_date', + 'invoice date|create date' => 'invoice_date', + 'po number' => 'po_number', + 'due date' => 'due_date', 'terms' => 'terms', 'notes' => 'notes', ]; @@ -181,31 +197,38 @@ class Invoice extends EntityModel implements BalanceAffecting return floatval($this->amount) - floatval($this->getOriginal('amount')); } - /** - * @return bool - */ public function isChanged() { - if ($this->getRawAdjustment() != 0) { - return true; - } - - foreach ([ - 'invoice_number', - 'po_number', - 'invoice_date', - 'due_date', - 'terms', - 'public_notes', - 'invoice_footer', - 'partial', - ] as $field) { - if ($this->$field != $this->getOriginal($field)) { + if (Utils::isNinja()) { + if ($this->getRawAdjustment() != 0) { return true; } - } - return false; + foreach ([ + 'invoice_number', + 'po_number', + 'invoice_date', + 'due_date', + 'terms', + 'public_notes', + 'invoice_footer', + 'partial', + ] as $field) { + if ($this->$field != $this->getOriginal($field)) { + return true; + } + } + + return false; + } else { + $dirty = $this->getDirty(); + + unset($dirty['invoice_status_id']); + unset($dirty['client_enable_auto_bill']); + unset($dirty['quote_invoice_id']); + + return count($dirty) > 0; + } } /** @@ -410,17 +433,36 @@ class Invoice extends EntityModel implements BalanceAffecting return $this->isType(INVOICE_TYPE_STANDARD) && ! $this->is_recurring; } + public function markSentIfUnsent() + { + if ( ! $this->isSent()) { + $this->markSent(); + } + } + + public function markSent() + { + if ( ! $this->isSent()) { + $this->invoice_status_id = INVOICE_STATUS_SENT; + } + + $this->is_public = true; + $this->save(); + + $this->markInvitationsSent(); + } + /** * @param bool $notify */ - public function markInvitationsSent($notify = false) + public function markInvitationsSent($notify = false, $reminder = false) { if ( ! $this->relationLoaded('invitations')) { $this->load('invitations'); } foreach ($this->invitations as $invitation) { - $this->markInvitationSent($invitation, false, $notify); + $this->markInvitationSent($invitation, false, $notify, $reminder); } } @@ -444,9 +486,9 @@ class Invoice extends EntityModel implements BalanceAffecting * @param bool $messageId * @param bool $notify */ - public function markInvitationSent($invitation, $messageId = false, $notify = true) + public function markInvitationSent($invitation, $messageId = false, $notify = true, $notes = false) { - if (!$this->isSent()) { + if ( ! $this->isSent()) { $this->invoice_status_id = INVOICE_STATUS_SENT; $this->save(); } @@ -460,9 +502,9 @@ class Invoice extends EntityModel implements BalanceAffecting } if ($this->isType(INVOICE_TYPE_QUOTE)) { - event(new QuoteInvitationWasEmailed($invitation)); + event(new QuoteInvitationWasEmailed($invitation, $notes)); } else { - event(new InvoiceInvitationWasEmailed($invitation)); + event(new InvoiceInvitationWasEmailed($invitation, $notes)); } } @@ -550,7 +592,58 @@ class Invoice extends EntityModel implements BalanceAffecting public function canBePaid() { - return floatval($this->balance) > 0 && ! $this->is_deleted && $this->isInvoice() && $this->is_public; + return floatval($this->balance) > 0 && ! $this->is_deleted && $this->isInvoice(); + } + + public static function calcStatusLabel($status, $class, $entityType, $quoteInvoiceId) + { + if ($quoteInvoiceId) { + $label = 'converted'; + } else if ($class == 'danger') { + $label = $entityType == ENTITY_INVOICE ? 'overdue' : 'expired'; + } else { + $label = 'status_' . strtolower($status); + } + + return trans("texts.{$label}"); + } + + public static function calcStatusClass($statusId, $balance, $dueDate) + { + if (static::calcIsOverdue($balance, $dueDate)) { + return 'danger'; + } + + if (isset(static::$statusClasses[$statusId])) { + return static::$statusClasses[$statusId]; + } + + return 'default'; + } + + public static function calcIsOverdue($balance, $dueDate) + { + if ( ! Utils::parseFloat($balance) > 0) { + return false; + } + + if ( ! $dueDate || $dueDate == '0000-00-00') { + return false; + } + + // it isn't considered overdue until the end of the day + return time() > (strtotime($dueDate) + (60*60*24)); + } + + public function statusClass() + { + return static::calcStatusClass($this->invoice_status_id, $this->balance, $this->due_date); + } + + public function statusLabel() + { + return static::calcStatusLabel($this->invoice_status->name, $this->statusClass(), $this->getEntityType(), $this->quote_invoice_id); +>>>>>>> release-3.0.0 } /** @@ -601,7 +694,7 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function isSent() { - return $this->invoice_status_id >= INVOICE_STATUS_SENT && $this->is_public; + return $this->invoice_status_id >= INVOICE_STATUS_SENT && $this->getOriginal('is_public'); } /** @@ -633,11 +726,7 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function isOverdue() { - if ( ! $this->due_date) { - return false; - } - - return time() > strtotime($this->due_date); + return static::calcIsOverdue($this->balance, $this->due_date); } /** @@ -1099,21 +1188,23 @@ class Invoice extends EntityModel implements BalanceAffecting */ public function getPDFString() { - if (!env('PHANTOMJS_CLOUD_KEY')) { + if ( ! env('PHANTOMJS_CLOUD_KEY') && ! env('PHANTOMJS_BIN_PATH')) { return false; } $invitation = $this->invitations[0]; $link = $invitation->getLink('view', true); - $key = env('PHANTOMJS_CLOUD_KEY'); - if (Utils::isNinjaDev()) { - $link = env('TEST_LINK'); + if (env('PHANTOMJS_BIN_PATH')) { + $pdfString = CurlUtils::phantom('GET', $link . '?phantomjs=true'); + } elseif ($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 = file_get_contents($url); $pdfString = strip_tags($pdfString); if ( ! $pdfString || strlen($pdfString) < 200) { diff --git a/app/Models/Payment.php b/app/Models/Payment.php index aa98e24d7960..4f080180603a 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -17,6 +17,15 @@ class Payment extends EntityModel use PresentableTrait; use SoftDeletes; + public static $statusClasses = [ + PAYMENT_STATUS_PENDING => 'info', + PAYMENT_STATUS_COMPLETED => 'success', + PAYMENT_STATUS_FAILED => 'danger', + PAYMENT_STATUS_PARTIALLY_REFUNDED => 'primary', + PAYMENT_STATUS_VOIDED => 'default', + PAYMENT_STATUS_REFUNDED => 'default', + ]; + /** * @var array */ @@ -302,6 +311,44 @@ class Payment extends EntityModel { return $value ? str_pad($value, 4, '0', STR_PAD_LEFT) : null; } + + public static function calcStatusLabel($statusId, $statusName, $amount) + { + if ($statusId == PAYMENT_STATUS_PARTIALLY_REFUNDED) { + return trans('texts.status_partially_refunded_amount', [ + 'amount' => $amount, + ]); + } else { + return trans('texts.status_' . strtolower($statusName)); + } + } + + public static function calcStatusClass($statusId) + { + return static::$statusClasses[$statusId]; + } + + + public function statusClass() + { + return static::calcStatusClass($this->payment_status_id); + } + + public function statusLabel() + { + $amount = $this->account->formatMoney($this->refunded, $this->client); + return static::calcStatusLabel($this->payment_status_id, $this->payment_status->name, $amount); + } + + public function invoiceJsonBackup() + { + $activity = Activity::wherePaymentId($this->id) + ->whereActivityTypeId(ACTIVITY_TYPE_CREATE_PAYMENT) + ->get(['json_backup']) + ->first(); + + return $activity->json_backup; + } } Payment::creating(function ($payment) { diff --git a/app/Models/Task.php b/app/Models/Task.php index 5a114a245ad0..73f0af286f5c 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -215,8 +215,64 @@ class Task extends EntityModel return $statuses; } -} + public static function calcStatusLabel($isRunning, $balance, $invoiceNumber) + { + if ($invoiceNumber) { + if (floatval($balance) > 0) { + $label = 'invoiced'; + } else { + $label = 'paid'; + } + } elseif ($isRunning) { + $label = 'running'; + } else { + $label = 'logged'; + } + return trans("texts.{$label}"); + } + + public static function calcStatusClass($isRunning, $balance, $invoiceNumber) + { + if ($invoiceNumber) { + if (floatval($balance)) { + return 'default'; + } else { + return 'success'; + } + } elseif ($isRunning) { + return 'primary'; + } else { + return 'warning'; + } + } + + public function statusClass() + { + if ($this->invoice) { + $balance = $this->invoice->balance; + $invoiceNumber = $this->invoice->invoice_number; + } else { + $balance = 0; + $invoiceNumber = false; + } + + return static::calcStatusClass($this->is_running, $balance, $invoiceNumber); + } + + public function statusLabel() + { + if ($this->invoice) { + $balance = $this->invoice->balance; + $invoiceNumber = $this->invoice->invoice_number; + } else { + $balance = 0; + $invoiceNumber = false; + } + + return static::calcStatusLabel($this->is_running, $balance, $invoiceNumber); + } +} Task::created(function ($task) { diff --git a/app/Models/TaxRate.php b/app/Models/TaxRate.php index f041c735d989..f9c25afc389c 100644 --- a/app/Models/TaxRate.php +++ b/app/Models/TaxRate.php @@ -18,7 +18,8 @@ class TaxRate extends EntityModel */ protected $fillable = [ 'name', - 'rate' + 'rate', + 'is_inclusive', ]; /** diff --git a/app/Models/Traits/GeneratesNumbers.php b/app/Models/Traits/GeneratesNumbers.php new file mode 100644 index 000000000000..0353c80f4be6 --- /dev/null +++ b/app/Models/Traits/GeneratesNumbers.php @@ -0,0 +1,236 @@ +getEntityType(); + + $counter = $this->getCounter($entityType); + $prefix = $this->getNumberPrefix($entityType); + $counterOffset = 0; + $check = false; + + if ($entityType == ENTITY_CLIENT && ! $this->clientNumbersEnabled()) { + return ''; + } + + // confirm the invoice number isn't already taken + do { + if ($this->hasNumberPattern($entityType)) { + $number = $this->applyNumberPattern($entity, $counter); + } else { + $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); + } + + if ($entity->isEntityType(ENTITY_CLIENT)) { + $check = Client::scope(false, $this->id)->whereIdNumber($number)->withTrashed()->first(); + } else { + $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); + } + $counter++; + $counterOffset++; + } while ($check); + + // update the counter to be caught up + if ($counterOffset > 1) { + if ($entity->isEntityType(ENTITY_CLIENT)) { + if ($this->clientNumbersEnabled()) { + $this->client_number_counter += $counterOffset - 1; + $this->save(); + } + } elseif ($entity->isType(INVOICE_TYPE_QUOTE)) { + if ( ! $this->share_counter) { + $this->quote_number_counter += $counterOffset - 1; + $this->save(); + } + } else { + $this->invoice_number_counter += $counterOffset - 1; + $this->save(); + } + } + + if ($entity->recurring_invoice_id) { + $number = $this->recurring_invoice_number_prefix . $number; + } + + return $number; + } + + /** + * @param $entityType + * @return string + */ + public function getNumberPrefix($entityType) + { + if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { + return ''; + } + + $field = "{$entityType}_number_prefix"; + return $this->$field ?: ''; + } + + /** + * @param $entityType + * @return bool + */ + public function getNumberPattern($entityType) + { + if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { + return false; + } + + $field = "{$entityType}_number_pattern"; + return $this->$field; + } + + /** + * @param $entityType + * @return bool + */ + public function hasNumberPattern($entityType) + { + return $this->getNumberPattern($entityType) ? true : false; + } + + /** + * @param $entityType + * @return string + */ + public function hasClientNumberPattern($invoice) + { + $pattern = $invoice->invoice_type_id == INVOICE_TYPE_QUOTE ? $this->quote_number_pattern : $this->invoice_number_pattern; + + return strstr($pattern, '$custom'); + } + + /** + * @param $entity + * @return bool|mixed + */ + public function applyNumberPattern($entity, $counter = 0) + { + $entityType = $entity->getEntityType(); + $counter = $counter ?: $this->getCounter($entityType); + $pattern = $this->getNumberPattern($entityType); + + if (!$pattern) { + return false; + } + + $search = ['{$year}']; + $replace = [date('Y')]; + + $search[] = '{$counter}'; + $replace[] = str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); + + if (strstr($pattern, '{$userId}')) { + $userId = $entity->user ? $entity->user->public_id : (Auth::check() ? Auth::user()->public_id : 0); + $search[] = '{$userId}'; + $replace[] = str_pad(($userId + 1), 2, '0', STR_PAD_LEFT); + } + + $matches = false; + preg_match('/{\$date:(.*?)}/', $pattern, $matches); + if (count($matches) > 1) { + $format = $matches[1]; + $search[] = $matches[0]; + $date = Carbon::now(session(SESSION_TIMEZONE, DEFAULT_TIMEZONE))->format($format); + $replace[] = str_replace($format, $date, $matches[1]); + } + + $pattern = str_replace($search, $replace, $pattern); + + if ($entity->client_id) { + $pattern = $this->getClientInvoiceNumber($pattern, $entity); + } + + return $pattern; + } + + /** + * @param $pattern + * @param $invoice + * @return mixed + */ + private function getClientInvoiceNumber($pattern, $invoice) + { + if (!$invoice->client) { + return $pattern; + } + + $search = [ + '{$custom1}', + '{$custom2}', + ]; + + $replace = [ + $invoice->client->custom_value1, + $invoice->client->custom_value2, + ]; + + return str_replace($search, $replace, $pattern); + } + + /** + * @param $entityType + * @return mixed + */ + public function getCounter($entityType) + { + if ($entityType == ENTITY_CLIENT) { + return $this->client_number_counter; + } elseif ($entityType == ENTITY_QUOTE && ! $this->share_counter) { + return $this->quote_number_counter; + } else { + return $this->invoice_number_counter; + } + } + + /** + * @param $entityType + * @return mixed|string + */ + public function previewNextInvoiceNumber($entityType = ENTITY_INVOICE) + { + $invoice = $this->createInvoice($entityType); + return $this->getNextNumber($invoice); + } + + /** + * @param $entity + */ + public function incrementCounter($entity) + { + if ($entity->isEntityType(ENTITY_CLIENT)) { + 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(); + } + + public function clientNumbersEnabled() + { + return $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $this->client_number_counter; + } +} diff --git a/app/Models/Traits/PresentsInvoice.php b/app/Models/Traits/PresentsInvoice.php index 493a4aecdbc5..4fd9e225ac76 100644 --- a/app/Models/Traits/PresentsInvoice.php +++ b/app/Models/Traits/PresentsInvoice.php @@ -96,6 +96,7 @@ trait PresentsInvoice 'client.address1', 'client.address2', 'client.city_state_postal', + 'client.postal_city_state', 'client.country', 'client.email', 'client.phone', @@ -114,6 +115,7 @@ trait PresentsInvoice 'account.address1', 'account.address2', 'account.city_state_postal', + 'account.postal_city_state', 'account.country', 'account.custom_value1', 'account.custom_value2', @@ -196,6 +198,7 @@ trait PresentsInvoice 'id_number', 'vat_number', 'city_state_postal', + 'postal_city_state', 'country', 'email', 'contact_name', @@ -203,6 +206,11 @@ trait PresentsInvoice 'website', 'phone', 'blank', + 'adjustment', + 'tax_invoice', + 'tax_quote', + 'statement', + 'statement_date', ]; foreach ($fields as $field) { diff --git a/app/Models/Traits/SendsEmails.php b/app/Models/Traits/SendsEmails.php new file mode 100644 index 000000000000..4843afa5e364 --- /dev/null +++ b/app/Models/Traits/SendsEmails.php @@ -0,0 +1,24 @@ +isPro() ? $this->bcc_email : false; + } + + public function getFromEmail() + { + if ( ! $this->isPro() || ! Utils::isNinjaProd() || Utils::isReseller()) { + return false; + } + + return Domain::getEmailFromId($this->domain_id); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 9ba3db5d2b77..d60a7c988943 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,12 +7,20 @@ use App\Events\UserSettingsChanged; use App\Events\UserSignedUp; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Database\Eloquent\SoftDeletes; +use Laracasts\Presenter\PresentableTrait; /** * Class User */ class User extends Authenticatable { + use PresentableTrait; + + /** + * @var string + */ + protected $presenter = 'App\Ninja\Presenters\UserPresenter'; + /** * @var array */ @@ -130,15 +138,6 @@ class User extends Authenticatable return $this->account->isTrial(); } - /** - * @param null $plan - * @return mixed - */ - public function isEligibleForTrial($plan = null) - { - return $this->account->isEligibleForTrial($plan); - } - /** * @return int */ diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index a68cf42e2ff3..466c6cc8fa47 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -267,6 +267,14 @@ class Vendor extends EntityModel return 'vendor'; } + /** + * @return bool + */ + public function showMap() + { + return $this->hasAddress() && env('GOOGLE_MAPS_ENABLED') !== false; + } + /** * @return bool */ @@ -321,12 +329,14 @@ class Vendor extends EntityModel /** * @return float|int */ - public function getTotalExpense() + public function getTotalExpenses() { return DB::table('expenses') - ->where('vendor_id', '=', $this->id) - ->whereNull('deleted_at') - ->sum('amount'); + ->select('expense_currency_id', DB::raw('SUM(amount) as amount')) + ->whereVendorId($this->id) + ->whereIsDeleted(false) + ->groupBy('expense_currency_id') + ->get(); } } diff --git a/app/Ninja/Datatables/ActivityDatatable.php b/app/Ninja/Datatables/ActivityDatatable.php index 62fdc646d839..0261bfc3bb1d 100644 --- a/app/Ninja/Datatables/ActivityDatatable.php +++ b/app/Ninja/Datatables/ActivityDatatable.php @@ -32,7 +32,13 @@ class ActivityDatatable extends EntityDatatable 'expense' => $model->expense_public_id ? link_to('/expenses/' . $model->expense_public_id, substr($model->expense_public_notes, 0, 30).'...') : null, ]; - return trans("texts.activity_{$model->activity_type_id}", $data); + $str = trans("texts.activity_{$model->activity_type_id}", $data); + + if ($model->notes) { + $str .= ' - ' . trans("texts.notes_{$model->notes}"); + } + + return $str; } ], [ diff --git a/app/Ninja/Datatables/EntityDatatable.php b/app/Ninja/Datatables/EntityDatatable.php index 99e2a414ad89..8d6249c1990c 100644 --- a/app/Ninja/Datatables/EntityDatatable.php +++ b/app/Ninja/Datatables/EntityDatatable.php @@ -64,4 +64,30 @@ class EntityDatatable return $data; } + + public function rightAlignIndices() + { + return $this->alignIndices(['amount', 'balance', 'cost']); + } + + public function centerAlignIndices() + { + return $this->alignIndices(['status']); + } + + public function alignIndices($fields) + { + $columns = $this->columnFields(); + $indices = []; + + foreach ($columns as $index => $column) { + if (in_array($column, $fields)) { + $indices[] = $index + 1; + } + } + + return $indices; + } + + } diff --git a/app/Ninja/Datatables/ExpenseDatatable.php b/app/Ninja/Datatables/ExpenseDatatable.php index 6bf432e6d02b..a818eb0b79d8 100644 --- a/app/Ninja/Datatables/ExpenseDatatable.php +++ b/app/Ninja/Datatables/ExpenseDatatable.php @@ -3,6 +3,7 @@ use Utils; use URL; use Auth; +use App\Models\Expense; class ExpenseDatatable extends EntityDatatable { @@ -123,24 +124,10 @@ class ExpenseDatatable extends EntityDatatable ]; } - private function getStatusLabel($invoiceId, $shouldBeInvoiced, $balance) { - if ($invoiceId) { - if (floatval($balance)) { - $label = trans('texts.invoiced'); - $class = 'default'; - } else { - $label = trans('texts.paid'); - $class = 'success'; - } - } elseif ($shouldBeInvoiced) { - $label = trans('texts.pending'); - $class = 'warning'; - } else { - $label = trans('texts.logged'); - $class = 'primary'; - } + $label = Expense::calcStatusLabel($shouldBeInvoiced, $invoiceId, $balance); + $class = Expense::calcStatusClass($shouldBeInvoiced, $invoiceId, $balance); return "

$label

"; } diff --git a/app/Ninja/Datatables/InvoiceDatatable.php b/app/Ninja/Datatables/InvoiceDatatable.php index 29771fad12a7..94a5bd9d87f0 100644 --- a/app/Ninja/Datatables/InvoiceDatatable.php +++ b/app/Ninja/Datatables/InvoiceDatatable.php @@ -3,6 +3,7 @@ use Utils; use URL; use Auth; +use App\Models\Invoice; class InvoiceDatatable extends EntityDatatable { @@ -167,35 +168,8 @@ class InvoiceDatatable extends EntityDatatable private function getStatusLabel($model) { - $entityType = $this->entityType; - - // check if invoice is overdue - if (Utils::parseFloat($model->balance) && $model->due_date && $model->due_date != '0000-00-00') { - if (\DateTime::createFromFormat('Y-m-d', $model->due_date) < new \DateTime('now')) { - $label = $entityType == ENTITY_INVOICE ? trans('texts.overdue') : trans('texts.expired'); - return '

' . $label . '

'; - } - } - - $label = trans('texts.status_' . strtolower($model->invoice_status_name)); - $class = 'default'; - switch ($model->invoice_status_id) { - case INVOICE_STATUS_SENT: - $class = 'info'; - break; - case INVOICE_STATUS_VIEWED: - $class = 'warning'; - break; - case INVOICE_STATUS_APPROVED: - $class = 'success'; - break; - case INVOICE_STATUS_PARTIAL: - $class = 'primary'; - break; - case INVOICE_STATUS_PAID: - $class = 'success'; - break; - } + $class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->due_date); + $label = Invoice::calcStatusLabel($model->invoice_status_name, $class, $this->entityType, $model->quote_invoice_id); return "

$label

"; } @@ -206,6 +180,10 @@ class InvoiceDatatable extends EntityDatatable if ($this->entityType == ENTITY_INVOICE || $this->entityType == ENTITY_QUOTE) { $actions[] = \DropdownButton::DIVIDER; + $actions[] = [ + 'label' => mtrans($this->entityType, 'email_' . $this->entityType), + 'url' => 'javascript:submitForm_'.$this->entityType.'("emailInvoice")', + ]; $actions[] = [ 'label' => mtrans($this->entityType, 'mark_sent'), 'url' => 'javascript:submitForm_'.$this->entityType.'("markSent")', diff --git a/app/Ninja/Datatables/PaymentDatatable.php b/app/Ninja/Datatables/PaymentDatatable.php index fb7dc175d5c7..ac24ed1dd3eb 100644 --- a/app/Ninja/Datatables/PaymentDatatable.php +++ b/app/Ninja/Datatables/PaymentDatatable.php @@ -3,6 +3,7 @@ use Utils; use URL; use Auth; +use App\Models\Payment; use App\Models\PaymentMethod; class PaymentDatatable extends EntityDatatable @@ -98,7 +99,7 @@ class PaymentDatatable extends EntityDatatable } ], [ - 'payment_status_name', + 'status', function ($model) { return self::getStatusLabel($model); } @@ -130,9 +131,7 @@ class PaymentDatatable extends EntityDatatable function ($model) { return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]) && $model->payment_status_id >= PAYMENT_STATUS_COMPLETED - && $model->refunded < $model->amount - && $model->transaction_reference - && in_array($model->gateway_id , static::$refundableGateways); + && $model->refunded < $model->amount; } ] ]; @@ -140,29 +139,10 @@ class PaymentDatatable extends EntityDatatable private function getStatusLabel($model) { - $label = trans('texts.status_' . strtolower($model->payment_status_name)); - $class = 'default'; - switch ($model->payment_status_id) { - case PAYMENT_STATUS_PENDING: - $class = 'info'; - break; - case PAYMENT_STATUS_COMPLETED: - $class = 'success'; - break; - case PAYMENT_STATUS_FAILED: - $class = 'danger'; - break; - case PAYMENT_STATUS_PARTIALLY_REFUNDED: - $label = trans('texts.status_partially_refunded_amount', [ - 'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id), - ]); - $class = 'primary'; - break; - case PAYMENT_STATUS_VOIDED: - case PAYMENT_STATUS_REFUNDED: - $class = 'default'; - break; - } + $amount = Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id); + $label = Payment::calcStatusLabel($model->payment_status_id, $model->status, $amount); + $class = Payment::calcStatusClass($model->payment_status_id); + return "

$label

"; } } diff --git a/app/Ninja/Datatables/RecurringInvoiceDatatable.php b/app/Ninja/Datatables/RecurringInvoiceDatatable.php index a04bcfe5918d..5fe1365817c7 100644 --- a/app/Ninja/Datatables/RecurringInvoiceDatatable.php +++ b/app/Ninja/Datatables/RecurringInvoiceDatatable.php @@ -32,6 +32,12 @@ class RecurringInvoiceDatatable extends EntityDatatable return Utils::fromSqlDate($model->start_date); } ], + [ + 'last_sent', + function ($model) { + return Utils::fromSqlDate($model->last_sent_date); + } + ], [ 'end_date', function ($model) { diff --git a/app/Ninja/Datatables/TaskDatatable.php b/app/Ninja/Datatables/TaskDatatable.php index 4d3956f2601e..bae69b85524a 100644 --- a/app/Ninja/Datatables/TaskDatatable.php +++ b/app/Ninja/Datatables/TaskDatatable.php @@ -108,21 +108,8 @@ class TaskDatatable extends EntityDatatable private function getStatusLabel($model) { - if ($model->invoice_number) { - if (floatval($model->balance)) { - $label = trans('texts.invoiced'); - $class = 'default'; - } else { - $class = 'success'; - $label = trans('texts.paid'); - } - } elseif ($model->is_running) { - $class = 'primary'; - $label = trans('texts.running'); - } else { - $class = 'warning'; - $label = trans('texts.logged'); - } + $label = Task::calcStatusLabel($model->is_running, $model->balance, $model->invoice_number); + $class = Task::calcStatusClass($model->is_running, $model->balance, $model->invoice_number); return "

$label

"; } diff --git a/app/Ninja/Datatables/TaxRateDatatable.php b/app/Ninja/Datatables/TaxRateDatatable.php index 3e16aba9ee19..4cc763d87ea4 100644 --- a/app/Ninja/Datatables/TaxRateDatatable.php +++ b/app/Ninja/Datatables/TaxRateDatatable.php @@ -20,6 +20,12 @@ class TaxRateDatatable extends EntityDatatable function ($model) { return $model->rate . '%'; } + ], + [ + 'type', + function ($model) { + return $model->is_inclusive ? trans('texts.inclusive') : trans('texts.exclusive'); + } ] ]; } diff --git a/app/Ninja/Import/BaseTransformer.php b/app/Ninja/Import/BaseTransformer.php index 2f0131c9dfa7..df9058a855e0 100644 --- a/app/Ninja/Import/BaseTransformer.php +++ b/app/Ninja/Import/BaseTransformer.php @@ -1,5 +1,6 @@ maps[ENTITY_CLIENT][$name]); @@ -37,7 +38,7 @@ class BaseTransformer extends TransformerAbstract * @param $key * @return bool */ - protected function hasProduct($key) + public function hasProduct($key) { $key = trim(strtolower($key)); return isset($this->maps[ENTITY_PRODUCT][$key]); @@ -48,7 +49,7 @@ class BaseTransformer extends TransformerAbstract * @param $field * @return string */ - protected function getString($data, $field) + public function getString($data, $field) { return (isset($data->$field) && $data->$field) ? $data->$field : ''; } @@ -58,18 +59,28 @@ class BaseTransformer extends TransformerAbstract * @param $field * @return int */ - protected function getNumber($data, $field) + public function getNumber($data, $field) { return (isset($data->$field) && $data->$field) ? $data->$field : 0; } + /** + * @param $data + * @param $field + * @return float + */ + public function getFloat($data, $field) + { + return (isset($data->$field) && $data->$field) ? Utils::parseFloat($data->$field) : 0; + } + /** * @param $name * @return null */ - protected function getClientId($name) + public function getClientId($name) { - $name = strtolower($name); + $name = strtolower(trim($name)); return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null; } @@ -77,9 +88,9 @@ class BaseTransformer extends TransformerAbstract * @param $name * @return null */ - protected function getProductId($name) + public function getProductId($name) { - $name = strtolower($name); + $name = strtolower(trim($name)); return isset($this->maps[ENTITY_PRODUCT][$name]) ? $this->maps[ENTITY_PRODUCT][$name] : null; } @@ -87,9 +98,9 @@ class BaseTransformer extends TransformerAbstract * @param $name * @return null */ - protected function getCountryId($name) + public function getCountryId($name) { - $name = strtolower($name); + $name = strtolower(trim($name)); return isset($this->maps['countries'][$name]) ? $this->maps['countries'][$name] : null; } @@ -97,9 +108,9 @@ class BaseTransformer extends TransformerAbstract * @param $name * @return null */ - protected function getCountryIdBy2($name) + public function getCountryIdBy2($name) { - $name = strtolower($name); + $name = strtolower(trim($name)); return isset($this->maps['countries2'][$name]) ? $this->maps['countries2'][$name] : null; } @@ -107,7 +118,7 @@ class BaseTransformer extends TransformerAbstract * @param $name * @return mixed */ - protected function getFirstName($name) + public function getFirstName($name) { $name = Utils::splitName($name); return $name[0]; @@ -118,10 +129,14 @@ class BaseTransformer extends TransformerAbstract * @param string $format * @return null */ - protected function getDate($date, $format = 'Y-m-d') + public function getDate($data, $field) { - if ( ! $date instanceof DateTime) { - $date = DateTime::createFromFormat($format, $date); + if ($date = data_get($data, $field)) { + try { + $date = new Carbon($date); + } catch (Exception $e) { + // if we fail to parse return blank + } } return $date ? $date->format('Y-m-d') : null; @@ -131,7 +146,7 @@ class BaseTransformer extends TransformerAbstract * @param $name * @return mixed */ - protected function getLastName($name) + public function getLastName($name) { $name = Utils::splitName($name); return $name[1]; @@ -141,7 +156,7 @@ class BaseTransformer extends TransformerAbstract * @param $number * @return string */ - protected function getInvoiceNumber($number) + public function getInvoiceNumber($number) { return str_pad(trim($number), 4, '0', STR_PAD_LEFT); } @@ -150,7 +165,7 @@ class BaseTransformer extends TransformerAbstract * @param $invoiceNumber * @return null */ - protected function getInvoiceId($invoiceNumber) + public function getInvoiceId($invoiceNumber) { $invoiceNumber = $this->getInvoiceNumber($invoiceNumber); $invoiceNumber = strtolower($invoiceNumber); @@ -161,7 +176,7 @@ class BaseTransformer extends TransformerAbstract * @param $invoiceNumber * @return bool */ - protected function hasInvoice($invoiceNumber) + public function hasInvoice($invoiceNumber) { $invoiceNumber = $this->getInvoiceNumber($invoiceNumber); $invoiceNumber = strtolower($invoiceNumber); @@ -172,7 +187,7 @@ class BaseTransformer extends TransformerAbstract * @param $invoiceNumber * @return null */ - protected function getInvoiceClientId($invoiceNumber) + public function getInvoiceClientId($invoiceNumber) { $invoiceNumber = $this->getInvoiceNumber($invoiceNumber); $invoiceNumber = strtolower($invoiceNumber); @@ -186,7 +201,7 @@ class BaseTransformer extends TransformerAbstract */ public function getVendorId($name) { - $name = strtolower($name); + $name = strtolower(trim($name)); return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null; } @@ -197,7 +212,7 @@ class BaseTransformer extends TransformerAbstract */ public function getExpenseCategoryId($name) { - $name = strtolower($name); + $name = strtolower(trim($name)); return isset($this->maps[ENTITY_EXPENSE_CATEGORY][$name]) ? $this->maps[ENTITY_EXPENSE_CATEGORY][$name] : null; } diff --git a/app/Ninja/Import/CSV/ExpenseTransformer.php b/app/Ninja/Import/CSV/ExpenseTransformer.php index cd566c0f1ab4..4b8a4cef64ff 100644 --- a/app/Ninja/Import/CSV/ExpenseTransformer.php +++ b/app/Ninja/Import/CSV/ExpenseTransformer.php @@ -1,5 +1,6 @@ isset($data->amount) ? (float) $data->amount : null, + 'amount' => $this->getFloat($data, 'amount'), 'vendor_id' => isset($data->vendor) ? $this->getVendorId($data->vendor) : null, 'client_id' => isset($data->client) ? $this->getClientId($data->client) : null, 'expense_date' => isset($data->expense_date) ? date('Y-m-d', strtotime($data->expense_date)) : null, diff --git a/app/Ninja/Import/CSV/InvoiceTransformer.php b/app/Ninja/Import/CSV/InvoiceTransformer.php index 6095388fdde0..2d151a45605e 100644 --- a/app/Ninja/Import/CSV/InvoiceTransformer.php +++ b/app/Ninja/Import/CSV/InvoiceTransformer.php @@ -26,20 +26,21 @@ class InvoiceTransformer extends BaseTransformer return [ 'client_id' => $this->getClientId($data->name), 'invoice_number' => isset($data->invoice_number) ? $this->getInvoiceNumber($data->invoice_number) : null, - 'paid' => isset($data->paid) ? (float) $data->paid : null, + 'paid' => $this->getFloat($data, 'paid'), 'po_number' => $this->getString($data, 'po_number'), 'terms' => $this->getString($data, 'terms'), 'public_notes' => $this->getString($data, 'public_notes'), - 'invoice_date_sql' => isset($data->invoice_date) ? $data->invoice_date : null, + 'invoice_date_sql' => $this->getDate($data, 'invoice_date'), + 'due_date_sql' => $this->getDate($data, 'due_date'), 'invoice_items' => [ [ 'product_key' => '', 'notes' => $this->getString($data, 'notes'), - 'cost' => isset($data->amount) ? (float) $data->amount : null, + 'cost' => $this->getFloat($data, 'amount'), 'qty' => 1, ] ], ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/CSV/PaymentTransformer.php b/app/Ninja/Import/CSV/PaymentTransformer.php index c8a1a82955e4..e06677fcb3ea 100644 --- a/app/Ninja/Import/CSV/PaymentTransformer.php +++ b/app/Ninja/Import/CSV/PaymentTransformer.php @@ -16,11 +16,11 @@ class PaymentTransformer extends BaseTransformer { return new Item($data, function ($data) { return [ - 'amount' => $data->paid, + 'amount' => $this->getFloat($data, 'paid'), 'payment_date_sql' => isset($data->invoice_date) ? $data->invoice_date : null, 'client_id' => $data->client_id, 'invoice_id' => $data->invoice_id, ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/CSV/ProductTransformer.php b/app/Ninja/Import/CSV/ProductTransformer.php index b8b877c29833..60663c495719 100644 --- a/app/Ninja/Import/CSV/ProductTransformer.php +++ b/app/Ninja/Import/CSV/ProductTransformer.php @@ -22,7 +22,7 @@ class ProductTransformer extends BaseTransformer return [ 'product_key' => $this->getString($data, 'product_key'), 'notes' => $this->getString($data, 'notes'), - 'cost' => $this->getNumber($data, 'cost'), + 'cost' => $this->getFloat($data, 'cost'), ]; }); } diff --git a/app/Ninja/Import/Harvest/InvoiceTransformer.php b/app/Ninja/Import/Harvest/InvoiceTransformer.php index ae47e7a7eb83..d6b2f619b0b9 100644 --- a/app/Ninja/Import/Harvest/InvoiceTransformer.php +++ b/app/Ninja/Import/Harvest/InvoiceTransformer.php @@ -28,7 +28,7 @@ class InvoiceTransformer extends BaseTransformer 'invoice_number' => $this->getInvoiceNumber($data->id), 'paid' => (float) $data->paid_amount, 'po_number' => $this->getString($data, 'po_number'), - 'invoice_date_sql' => $this->getDate($data->issue_date, 'm/d/Y'), + 'invoice_date_sql' => $this->getDate($data, 'issue_date'), 'invoice_items' => [ [ 'product_key' => '', @@ -40,4 +40,4 @@ class InvoiceTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Harvest/PaymentTransformer.php b/app/Ninja/Import/Harvest/PaymentTransformer.php index b8b98e0dc837..3391b9ace16e 100644 --- a/app/Ninja/Import/Harvest/PaymentTransformer.php +++ b/app/Ninja/Import/Harvest/PaymentTransformer.php @@ -17,10 +17,10 @@ class PaymentTransformer extends BaseTransformer return new Item($data, function ($data) { return [ 'amount' => $data->paid_amount, - 'payment_date_sql' => $this->getDate($data->last_payment_date, 'm/d/Y'), + 'payment_date_sql' => $this->getDate($data, 'last_payment_date'), 'client_id' => $data->client_id, 'invoice_id' => $data->invoice_id, ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Hiveage/InvoiceTransformer.php b/app/Ninja/Import/Hiveage/InvoiceTransformer.php index eab836eed2ac..a35e0a6d65aa 100644 --- a/app/Ninja/Import/Hiveage/InvoiceTransformer.php +++ b/app/Ninja/Import/Hiveage/InvoiceTransformer.php @@ -27,8 +27,8 @@ class InvoiceTransformer extends BaseTransformer 'client_id' => $this->getClientId($data->client), 'invoice_number' => $this->getInvoiceNumber($data->statement_no), 'paid' => (float) $data->paid_total, - 'invoice_date_sql' => $this->getDate($data->date), - 'due_date_sql' => $this->getDate($data->due_date), + 'invoice_date_sql' => $this->getDate($data, 'date'), + 'due_date_sql' => $this->getDate($data, 'due_date'), 'invoice_items' => [ [ 'product_key' => '', @@ -40,4 +40,4 @@ class InvoiceTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Hiveage/PaymentTransformer.php b/app/Ninja/Import/Hiveage/PaymentTransformer.php index fe091a5eff56..2057c32a94fc 100644 --- a/app/Ninja/Import/Hiveage/PaymentTransformer.php +++ b/app/Ninja/Import/Hiveage/PaymentTransformer.php @@ -17,7 +17,7 @@ class PaymentTransformer extends BaseTransformer return new Item($data, function ($data) { return [ 'amount' => $data->paid_total, - 'payment_date_sql' => $this->getDate($data->last_paid_on), + 'payment_date_sql' => $this->getDate($data, 'last_paid_on'), 'client_id' => $data->client_id, 'invoice_id' => $data->invoice_id, ]; diff --git a/app/Ninja/Import/Nutcache/InvoiceTransformer.php b/app/Ninja/Import/Nutcache/InvoiceTransformer.php index 227a4f587837..463585e89cc6 100644 --- a/app/Ninja/Import/Nutcache/InvoiceTransformer.php +++ b/app/Ninja/Import/Nutcache/InvoiceTransformer.php @@ -30,8 +30,8 @@ class InvoiceTransformer extends BaseTransformer 'po_number' => $this->getString($data, 'purchase_order'), 'terms' => $this->getString($data, 'terms'), 'public_notes' => $this->getString($data, 'notes'), - 'invoice_date_sql' => $this->getDate($data->date), - 'due_date_sql' => $this->getDate($data->due_date), + 'invoice_date_sql' => $this->getDate($data, 'date'), + 'due_date_sql' => $this->getDate($data, 'due_date'), 'invoice_items' => [ [ 'product_key' => '', @@ -43,4 +43,4 @@ class InvoiceTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Nutcache/PaymentTransformer.php b/app/Ninja/Import/Nutcache/PaymentTransformer.php index fcdf139e6d13..cd7737d44073 100644 --- a/app/Ninja/Import/Nutcache/PaymentTransformer.php +++ b/app/Ninja/Import/Nutcache/PaymentTransformer.php @@ -17,7 +17,7 @@ class PaymentTransformer extends BaseTransformer return new Item($data, function ($data) { return [ 'amount' => (float) $data->paid_to_date, - 'payment_date_sql' => $this->getDate($data->date), + 'payment_date_sql' => $this->getDate($data, 'date'), 'client_id' => $data->client_id, 'invoice_id' => $data->invoice_id, ]; diff --git a/app/Ninja/Import/Wave/InvoiceTransformer.php b/app/Ninja/Import/Wave/InvoiceTransformer.php index 0ae418ebaa1a..9dff9c39db6f 100644 --- a/app/Ninja/Import/Wave/InvoiceTransformer.php +++ b/app/Ninja/Import/Wave/InvoiceTransformer.php @@ -27,8 +27,8 @@ class InvoiceTransformer extends BaseTransformer 'client_id' => $this->getClientId($data->customer), 'invoice_number' => $this->getInvoiceNumber($data->invoice_num), 'po_number' => $this->getString($data, 'po_so'), - 'invoice_date_sql' => $this->getDate($data->invoice_date), - 'due_date_sql' => $this->getDate($data->due_date), + 'invoice_date_sql' => $this->getDate($data, 'invoice_date'), + 'due_date_sql' => $this->getDate($data, 'due_date'), 'paid' => 0, 'invoice_items' => [ [ @@ -41,4 +41,4 @@ class InvoiceTransformer extends BaseTransformer ]; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Import/Wave/PaymentTransformer.php b/app/Ninja/Import/Wave/PaymentTransformer.php index dbfbf6741e60..d08225b8e3c4 100644 --- a/app/Ninja/Import/Wave/PaymentTransformer.php +++ b/app/Ninja/Import/Wave/PaymentTransformer.php @@ -21,7 +21,7 @@ class PaymentTransformer extends BaseTransformer return new Item($data, function ($data) { return [ 'amount' => (float) $data->amount, - 'payment_date_sql' => $this->getDate($data->payment_date), + 'payment_date_sql' => $this->getDate($data, 'payment_date'), 'client_id' => $this->getInvoiceClientId($data->invoice_num), 'invoice_id' => $this->getInvoiceId($data->invoice_num), ]; diff --git a/app/Ninja/Import/Zoho/InvoiceTransformer.php b/app/Ninja/Import/Zoho/InvoiceTransformer.php index 9db0bc4ceb1a..268c675a9826 100644 --- a/app/Ninja/Import/Zoho/InvoiceTransformer.php +++ b/app/Ninja/Import/Zoho/InvoiceTransformer.php @@ -23,22 +23,45 @@ class InvoiceTransformer extends BaseTransformer } return new Item($data, function ($data) { - return [ + $invoice = [ 'client_id' => $this->getClientId($data->customer_name), 'invoice_number' => $this->getInvoiceNumber($data->invoice_number), 'paid' => (float) $data->total - (float) $data->balance, 'po_number' => $this->getString($data, 'purchaseorder'), 'due_date_sql' => $data->due_date, 'invoice_date_sql' => $data->invoice_date, + 'custom_value1' => (float) $data->latefee_amount + (float) $data->adjustment + (float) $data->shipping_charge, + 'custom_taxes1' => false, 'invoice_items' => [ [ - 'product_key' => '', + 'product_key' => $this->getString($data, 'item_name'), 'notes' => $this->getString($data, 'item_desc'), - 'cost' => (float) $data->total, - 'qty' => 1, + 'cost' => (float) $data->item_price, + 'qty' => (float) $data->quantity, + 'tax_name1' => (float) $data->item_tax1 ? trans('texts.tax') : '', + 'tax_rate1' => (float) $data->item_tax1, + 'tax_name2' => (float) $data->item_tax2 ? trans('texts.tax') : '', + 'tax_rate2' => (float) $data->item_tax2, ] ], ]; + + // we don't support line item discounts so we need to include + // the discount as a separate line item + if ((float) $data->discount_amount) { + $invoice['invoice_items'][] = [ + 'product_key' => '', + 'notes' => trans('texts.discount'), + 'cost' => (float) $data->discount_amount * -1, + 'qty' => 1, + 'tax_name1' => (float) $data->item_tax1 ? trans('texts.tax') : '', + 'tax_rate1' => (float) $data->item_tax1, + 'tax_name2' => (float) $data->item_tax2 ? trans('texts.tax') : '', + 'tax_rate2' => (float) $data->item_tax2, + ]; + } + + return $invoice; }); } -} \ No newline at end of file +} diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 1845582ae227..ddd8958bd65e 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -107,7 +107,7 @@ class ContactMailer extends Mailer } foreach ($invoice->invitations as $invitation) { - $response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString, $documentStrings); + $response = $this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString, $documentStrings, $reminder); if ($response === true) { $sent = true; } @@ -137,12 +137,13 @@ class ContactMailer extends Mailer * @throws \Laracasts\Presenter\Exceptions\PresenterException */ private function sendInvitation( - Invitation$invitation, + Invitation $invitation, Invoice $invoice, $body, $subject, $pdfString, - $documentStrings + $documentStrings, + $reminder ) { @@ -197,6 +198,9 @@ class ContactMailer extends Mailer 'client' => $client, 'invoice' => $invoice, 'documents' => $documentStrings, + 'notes' => $reminder, + 'bccEmail' => $account->getBccEmail(), + 'fromEmail' => $account->getFromEmail(), ]; if ($account->attachPDF()) { @@ -283,6 +287,8 @@ class ContactMailer extends Mailer 'account' => $account, 'payment' => $payment, 'entityType' => ENTITY_INVOICE, + 'bccEmail' => $account->getBccEmail(), + 'fromEmail' => $account->getFromEmail(), ]; if ($account->attachPDF()) { diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index c38a2c8704ad..5451653473de 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -21,11 +21,6 @@ class Mailer */ public function sendTo($toEmail, $fromEmail, $fromName, $subject, $view, $data = []) { - // check the username is set - if ( ! env('POSTMARK_API_TOKEN') && ! env('MAIL_USERNAME')) { - return trans('texts.invalid_mail_config'); - } - // don't send emails to dummy addresses if (stristr($toEmail, '@example.com')) { return true; @@ -47,18 +42,28 @@ class Mailer $replyEmail = $fromEmail; $fromEmail = CONTACT_EMAIL; + // Optionally send for alternate domain + if (!empty($data['fromEmail'])) { + $fromEmail = $data['fromEmail']; + } + $message->to($toEmail) ->from($fromEmail, $fromName) ->replyTo($replyEmail, $fromName) ->subject($subject); + // Optionally BCC the email + if (!empty($data['bccEmail'])) { + $message->bcc($data['bccEmail']); + } + // Attach the PDF to the email if (!empty($data['pdfString']) && !empty($data['pdfFileName'])) { $message->attachData($data['pdfString'], $data['pdfFileName']); } // Attach documents to the email - if(!empty($data['documents'])){ + if (!empty($data['documents'])){ foreach($data['documents'] as $document){ $message->attachData($document['data'], $document['name']); } @@ -89,7 +94,8 @@ class Mailer $messageId = $json->MessageID; } - $invoice->markInvitationSent($invitation, $messageId); + $notes = isset($data['notes']) ? $data['notes']: false; + $invoice->markInvitationSent($invitation, $messageId, true, $notes); } return true; @@ -107,6 +113,8 @@ class Mailer if (! $response) { $error = trans('texts.postmark_error', ['link' => link_to('https://status.postmarkapp.com/')]); Utils::logError($error); + + // TODO throw the exception once all emails are sent using the queue return $error; } @@ -121,7 +129,7 @@ class Mailer $invitation = $data['invitation']; $invitation->email_error = $emailError; $invitation->save(); - } elseif ( ! Utils::isNinja()) { + } elseif ( ! Utils::isNinjaProd()) { Utils::logError(Utils::getErrorString($exception)); } diff --git a/app/Ninja/Notifications/PushFactory.php b/app/Ninja/Notifications/PushFactory.php index e2f34c46be2d..6e69ac80bb7a 100644 --- a/app/Ninja/Notifications/PushFactory.php +++ b/app/Ninja/Notifications/PushFactory.php @@ -3,6 +3,7 @@ namespace App\Ninja\Notifications; use Davibennun\LaravelPushNotification\Facades\PushNotification; +use Illuminate\Support\Facades\Log; /** * Class PushFactory @@ -15,7 +16,6 @@ class PushFactory */ public function __construct() { - $this->certificate = IOS_PUSH_CERTIFICATE; } /** @@ -28,14 +28,15 @@ class PushFactory * @param $token * @param $message * @param $messageArray + * @param string $device - Type of device the message is being pushed to. * * @return void */ - public function customMessage($token, $message, $messageArray) + public function customMessage($token, $message, $messageArray, $device) { $customMessage = PushNotification::Message($message, $messageArray); - $this->message($token, $customMessage); + $this->message($token, $customMessage, $device); } /** @@ -51,11 +52,17 @@ class PushFactory * @return void */ - public function message($token, $message) + public function message($token, $message, $device) { - PushNotification::app($this->certificate) - ->to($token) - ->send($message); + try { + PushNotification::app($device) + ->to($token) + ->send($message); + } + catch(\Exception $e) { + Log::error($e->getMessage()); + } + } /** @@ -69,13 +76,13 @@ class PushFactory * * @param string $token - A valid token (can be any valid token) * @param string $message - Nil value for message - * + * @param string $device - Type of device the message is being pushed to. * @return array */ - public function getFeedback($token, $message = '') + public function getFeedback($token, $message = '', $device) { - $feedback = PushNotification::app($this->certificate) + $feedback = PushNotification::app($device) ->to($token) ->send($message); diff --git a/app/Ninja/PaymentDrivers/BasePaymentDriver.php b/app/Ninja/PaymentDrivers/BasePaymentDriver.php index 284badc9e101..ab8dd7c10b69 100644 --- a/app/Ninja/PaymentDrivers/BasePaymentDriver.php +++ b/app/Ninja/PaymentDrivers/BasePaymentDriver.php @@ -8,6 +8,7 @@ use Omnipay; use Exception; use CreditCard; use DateTime; +use App\Models\License; use App\Models\AccountGatewayToken; use App\Models\AccountGatewaySettings; use App\Models\Account; @@ -133,7 +134,12 @@ class BasePaymentDriver Session::reflash(); } else { $this->completeOnsitePurchase(); - Session::flash('message', trans('texts.applied_payment')); + if ($redirectUrl = session('redirect_url:' . $this->invitation->invitation_key)) { + $separator = strpos($redirectUrl, '?') === false ? '?' : '&'; + return redirect()->to($redirectUrl . $separator . 'invoice_id=' . $this->invoice()->public_id); + } else { + Session::flash('message', trans('texts.applied_payment')); + } } return redirect()->to('view/' . $this->invitation->invitation_key); @@ -596,6 +602,7 @@ class BasePaymentDriver { $invitation = $this->invitation; $invoice = $this->invoice(); + $invoice->markSentIfUnsent(); $payment = Payment::createNew($invitation); $payment->invitation_id = $invitation->id; @@ -605,7 +612,7 @@ class BasePaymentDriver $payment->client_id = $invoice->client_id; $payment->contact_id = $invitation->contact_id; $payment->transaction_reference = $ref; - $payment->payment_date = date_create()->format('Y-m-d'); + $payment->payment_date = Utils::today(); $payment->ip = Request::ip(); $payment = $this->creatingPayment($payment, $paymentMethod); @@ -622,9 +629,13 @@ class BasePaymentDriver $payment->save(); + $accountKey = $invoice->account->account_key; + + if ($accountKey == env('NINJA_LICENSE_ACCOUNT_KEY')) { + $this->createLicense($payment); // TODO move this code // enable pro plan for hosted users - if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) { + } elseif ($accountKey == NINJA_ACCOUNT_KEY) { foreach ($invoice->invoice_items as $invoice_item) { // Hacky, but invoices don't have meta fields to allow us to store this easily if (1 == preg_match('/^Plan - (.+) \((.+)\)$/', $invoice_item->product_key, $matches)) { @@ -688,6 +699,38 @@ class BasePaymentDriver return $payment; } + protected function createLicense($payment) + { + // TODO parse invoice to determine license + if ($payment->amount == 20) { + $affiliateId = 4; + $productId = PRODUCT_WHITE_LABEL; + } else { + $affiliateId = 1; + $productId = PRODUCT_ONE_CLICK_INSTALL; + } + + $license = new License(); + $license->first_name = $this->contact()->first_name; + $license->last_name = $this->contact()->last_name; + $license->email = $this->contact()->email; + $license->transaction_reference = $payment->transaction_reference; + $license->license_key = Utils::generateLicense(); + $license->affiliate_id = $affiliateId; + $license->product_id = $productId; + $license->save(); + + // Add the license key to the invoice content + $invoiceItem = $payment->invoice->invoice_items->first(); + $invoiceItem->notes .= "\n\n##{$license->license_key}"; + $invoiceItem->save(); + + // Add the license key to the redirect URL + $key = 'redirect_url:' . $payment->invitation->invitation_key; + $redirectUrl = session($key); + session([$key => "{$redirectUrl}?license_key={$license->license_key}&product_id={$productId}"]); + } + protected function creatingPayment($payment, $paymentMethod) { return $payment; diff --git a/app/Ninja/PaymentDrivers/SkrillPaymentDriver.php b/app/Ninja/PaymentDrivers/SkrillPaymentDriver.php new file mode 100644 index 000000000000..cab827d14cea --- /dev/null +++ b/app/Ninja/PaymentDrivers/SkrillPaymentDriver.php @@ -0,0 +1,26 @@ +invoice()->invoice_items as $item) { + $details[$item->product_key] = $item->notes; + } + + $data['language'] = $locale; + $data['details'] = $details; + + return $data; + } +} diff --git a/app/Ninja/Presenters/AccountPresenter.php b/app/Ninja/Presenters/AccountPresenter.php index 5408d4f1afd4..7c6c0d0fb32b 100644 --- a/app/Ninja/Presenters/AccountPresenter.php +++ b/app/Ninja/Presenters/AccountPresenter.php @@ -2,6 +2,7 @@ use stdClass; use Utils; +use Domain; use Laracasts\Presenter\Presenter; /** @@ -36,6 +37,11 @@ class AccountPresenter extends Presenter return $currency->code; } + public function clientPortalLink() + { + return Domain::getLinkFromId($this->entity->domain_id); + } + public function industry() { return $this->entity->industry ? $this->entity->industry->name : ''; @@ -46,6 +52,30 @@ class AccountPresenter extends Presenter return $this->entity->size ? $this->entity->size->name : ''; } + public function paymentTerms() + { + $terms = $this->entity->payment_terms; + + if ($terms == 0) { + return ''; + } elseif ($terms == -1) { + $terms = 0; + } + + return trans('texts.payment_terms_net') . ' ' . $terms; + } + + public function dueDatePlaceholder() + { + if ($this->entity->payment_terms == 0) { + return ' '; + } + + $date = $this->entity->defaultDueDate(); + + return $date ? Utils::fromSqlDate($date) : ' '; + } + private function createRBit($type, $source, $properties) { $data = new stdClass(); diff --git a/app/Ninja/Presenters/CompanyPresenter.php b/app/Ninja/Presenters/CompanyPresenter.php index 730b4f5cfd00..46ba7ab9f51d 100644 --- a/app/Ninja/Presenters/CompanyPresenter.php +++ b/app/Ninja/Presenters/CompanyPresenter.php @@ -10,7 +10,7 @@ class CompanyPresenter extends EntityPresenter return trans('texts.promo_message', [ 'expires' => $this->entity->promo_expires->format('M dS, Y'), - 'amount' => (int)($this->discount * 100) + 'amount' => (int)($this->discount * 100) . '%' ]); } @@ -22,7 +22,7 @@ class CompanyPresenter extends EntityPresenter return trans('texts.discount_message', [ 'expires' => $this->entity->discount_expires->format('M dS, Y'), - 'amount' => (int)($this->discount * 100) + 'amount' => (int)($this->discount * 100) . '%' ]); } diff --git a/app/Ninja/Presenters/EntityPresenter.php b/app/Ninja/Presenters/EntityPresenter.php index 5eec3d513cc0..cc069b5c5e45 100644 --- a/app/Ninja/Presenters/EntityPresenter.php +++ b/app/Ninja/Presenters/EntityPresenter.php @@ -29,16 +29,16 @@ class EntityPresenter extends Presenter if ($this->entity->is_deleted) { $class = 'danger'; - $text = trans('texts.deleted'); + $label = trans('texts.deleted'); } elseif ($this->entity->trashed()) { $class = 'warning'; - $text = trans('texts.archived'); + $label = trans('texts.archived'); } else { - //$class = 'success'; - //$text = trans('texts.active'); + $class = $this->entity->statusClass(); + $label = $this->entity->statusLabel(); } - return "{$text}"; + return "{$label}"; } /** diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php index 018ca273e76e..d014374a72f7 100644 --- a/app/Ninja/Presenters/ExpensePresenter.php +++ b/app/Ninja/Presenters/ExpensePresenter.php @@ -1,6 +1,7 @@ entity->expense_date); } + public function month() + { + return Carbon::parse($this->entity->payment_date)->format('Y m'); + } + public function amount() { return Utils::formatMoney($this->entity->amount, $this->entity->expense_currency_id); diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index 16554c3b168c..8eee2c48f504 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -1,7 +1,9 @@ entity->due_date || $this->entity->date_date == '0000-00-00') { + return 0; + } + + $date = Carbon::parse($this->entity->due_date); + + if ($date->isFuture()) { + return 0; + } + + return $date->diffInDays(); + } + + public function ageGroup() + { + $age = $this->age(); + + if ($age > 120) { + return 'age_group_120'; + } elseif ($age > 90) { + return 'age_group_90'; + } elseif ($age > 60) { + return 'age_group_60'; + } elseif ($age > 30) { + return 'age_group_30'; + } else { + return 'age_group_0'; + } + } + public function dueDateLabel() { if ($this->entity->isType(INVOICE_TYPE_STANDARD)) { @@ -162,4 +196,50 @@ class InvoicePresenter extends EntityPresenter { return [$data]; } + + public function moreActions() + { + $invoice = $this->entity; + $entityType = $invoice->getEntityType(); + + $actions = [ + ['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")], + ['url' => url("{$entityType}s/{$entityType}_history/{$invoice->public_id}"), 'label' => trans('texts.view_history')], + DropdownButton::DIVIDER + ]; + + if ($entityType == ENTITY_QUOTE) { + if ($invoice->quote_invoice_id) { + $actions[] = ['url' => url("invoices/{$invoice->quote_invoice_id}/edit"), 'label' => trans('texts.view_invoice')]; + } else { + $actions[] = ['url' => 'javascript:onConvertClick()', 'label' => trans('texts.convert_to_invoice')]; + } + } elseif ($entityType == ENTITY_INVOICE) { + if ($invoice->quote_id) { + $actions[] = ['url' => url("quotes/{$invoice->quote_id}/edit"), 'label' => trans('texts.view_quote')]; + } + + if (!$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')]; + } + + foreach ($invoice->payments as $payment) { + $label = trans('texts.view_payment'); + if (count($invoice->payments) > 1) { + $label .= ' - ' . $invoice->account->formatMoney($payment->amount, $invoice->client); + } + $actions[] = ['url' => $payment->present()->url, 'label' => $label]; + } + } + + if (count($actions) > 3) { + $actions[] = DropdownButton::DIVIDER; + } + + $actions[] = ['url' => 'javascript:onArchiveClick()', 'label' => trans("texts.archive_{$entityType}")]; + $actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans("texts.delete_{$entityType}")]; + + return $actions; + } } diff --git a/app/Ninja/Presenters/PaymentPresenter.php b/app/Ninja/Presenters/PaymentPresenter.php index 8d91a4827db8..29f15189f2b8 100644 --- a/app/Ninja/Presenters/PaymentPresenter.php +++ b/app/Ninja/Presenters/PaymentPresenter.php @@ -1,9 +1,15 @@ entity->amount, $this->entity->client->currency_id); + } + public function client() { return $this->entity->client ? $this->entity->client->getDisplayName() : ''; @@ -14,6 +20,11 @@ class PaymentPresenter extends EntityPresenter { return Utils::fromSqlDate($this->entity->payment_date); } + public function month() + { + return Carbon::parse($this->entity->payment_date)->format('Y m'); + } + public function method() { if ($this->entity->account_gateway) { @@ -22,5 +33,4 @@ class PaymentPresenter extends EntityPresenter { return $this->entity->payment_type->name; } } - } diff --git a/app/Ninja/Presenters/TaskPresenter.php b/app/Ninja/Presenters/TaskPresenter.php index 28dd05d81846..5ac73af531eb 100644 --- a/app/Ninja/Presenters/TaskPresenter.php +++ b/app/Ninja/Presenters/TaskPresenter.php @@ -26,12 +26,27 @@ class TaskPresenter extends EntityPresenter return substr($this->entity->description, 0, 40) . (strlen($this->entity->description) > 40 ? '...' : ''); } + public function project() + { + return $this->entity->project ? $this->entity->project->name : ''; + } + /** * @param $account * @return mixed */ - public function times($account) + public function invoiceDescription($account, $showProject) { + $str = ''; + + if ($showProject && $project = $this->project()) { + $str .= "## {$project}\n\n"; + } + + if ($description = trim($this->entity->description)) { + $str .= $description . "\n\n"; + } + $parts = json_decode($this->entity->time_log) ?: []; $times = []; @@ -49,27 +64,6 @@ class TaskPresenter extends EntityPresenter $times[] = "### {$start} - {$end}"; } - return implode("\n", $times); - } - - /** - * @return string - */ - public function status() - { - $class = $text = ''; - - if ($this->entity->is_deleted) { - $class = 'danger'; - $text = trans('texts.deleted'); - } elseif ($this->entity->trashed()) { - $class = 'warning'; - $text = trans('texts.archived'); - } else { - $class = 'success'; - $text = trans('texts.active'); - } - - return "{$text}"; + return $str . implode("\n", $times); } } diff --git a/app/Ninja/Presenters/UserPresenter.php b/app/Ninja/Presenters/UserPresenter.php new file mode 100644 index 000000000000..ce1098d41fbf --- /dev/null +++ b/app/Ninja/Presenters/UserPresenter.php @@ -0,0 +1,14 @@ +', $this->fullName(), $this->entity->email)); + } + + public function fullName() + { + return $this->entity->first_name . ' ' . $this->entity->last_name; + } +} diff --git a/app/Ninja/Reports/AbstractReport.php b/app/Ninja/Reports/AbstractReport.php new file mode 100644 index 000000000000..6af6a9d54cc5 --- /dev/null +++ b/app/Ninja/Reports/AbstractReport.php @@ -0,0 +1,84 @@ +startDate = $startDate; + $this->endDate = $endDate; + $this->isExport = $isExport; + $this->options = $options; + } + + public function run() + { + + } + + public function results() + { + return [ + 'columns' => $this->columns, + 'displayData' => $this->data, + 'reportTotals' => $this->totals, + ]; + } + + protected function addToTotals($currencyId, $field, $value, $dimension = false) + { + $currencyId = $currencyId ?: Auth::user()->account->getCurrencyId(); + + if ( ! isset($this->totals[$currencyId][$dimension])) { + $this->totals[$currencyId][$dimension] = []; + } + + if ( ! isset($this->totals[$currencyId][$dimension][$field])) { + $this->totals[$currencyId][$dimension][$field] = 0; + } + + $this->totals[$currencyId][$dimension][$field] += $value; + } + + public function tableHeader() + { + $str = ''; + + foreach ($this->columns as $key => $val) { + if (is_array($val)) { + $field = $key; + $class = $val; + } else { + $field = $val; + $class = []; + } + + if (strpos($field, 'date') !== false) { + //$class[] = 'group-date-monthyear'; + $class[] = 'group-date-' . (isset($this->options['group_dates_by']) ? $this->options['group_dates_by'] : 'monthyear'); + } elseif (in_array($field, ['client', 'method'])) { + $class[] = 'group-letter-100'; + } elseif (in_array($field, ['amount', 'paid', 'balance'])) { + $class[] = 'group-number-50'; + } + + $class = count($class) ? implode(' ', $class) : 'group-false'; + $label = trans("texts.{$field}"); + $str .= "{$label}"; + } + + return $str; + } +} diff --git a/app/Ninja/Reports/AgingReport.php b/app/Ninja/Reports/AgingReport.php new file mode 100644 index 000000000000..aaf87c47d07d --- /dev/null +++ b/app/Ninja/Reports/AgingReport.php @@ -0,0 +1,58 @@ + ['group-number-30'], + 'amount', + 'balance', + ]; + + public function run() + { + $account = Auth::user()->account; + + $clients = Client::scope() + ->withArchived() + ->with('contacts') + ->with(['invoices' => function($query) { + $query->invoices() + ->whereIsPublic(true) + ->withArchived() + ->where('balance', '>', 0) + ->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, + $invoice->present()->due_date, + $invoice->present()->age, + $account->formatMoney($invoice->amount, $client), + $account->formatMoney($invoice->balance, $client), + ]; + + $this->addToTotals($client->currency_id, $invoice->present()->ageGroup, $invoice->balance); + + //$this->addToTotals($client->currency_id, 'paid', $payment ? $payment->getCompletedAmount() : 0); + //$this->addToTotals($client->currency_id, 'amount', $invoice->amount); + //$this->addToTotals($client->currency_id, 'balance', $invoice->balance); + } + } + } +} diff --git a/app/Ninja/Reports/ClientReport.php b/app/Ninja/Reports/ClientReport.php new file mode 100644 index 000000000000..a511def1258e --- /dev/null +++ b/app/Ninja/Reports/ClientReport.php @@ -0,0 +1,53 @@ +account; + + $clients = Client::scope() + ->withArchived() + ->with('contacts') + ->with(['invoices' => function($query) { + $query->where('invoice_date', '>=', $this->startDate) + ->where('invoice_date', '<=', $this->endDate) + ->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) + ->where('is_recurring', '=', false) + ->withArchived(); + }]); + + foreach ($clients->get() as $client) { + $amount = 0; + $paid = 0; + + foreach ($client->invoices as $invoice) { + $amount += $invoice->amount; + $paid += $invoice->getAmountPaid(); + } + + $this->data[] = [ + $this->isExport ? $client->getDisplayName() : $client->present()->link, + $account->formatMoney($amount, $client), + $account->formatMoney($paid, $client), + $account->formatMoney($amount - $paid, $client) + ]; + + $this->addToTotals($client->currency_id, 'amount', $amount); + $this->addToTotals($client->currency_id, 'paid', $paid); + $this->addToTotals($client->currency_id, 'balance', $amount - $paid); + } + } +} diff --git a/app/Ninja/Reports/ExpenseReport.php b/app/Ninja/Reports/ExpenseReport.php new file mode 100644 index 000000000000..80e1dbf68d1f --- /dev/null +++ b/app/Ninja/Reports/ExpenseReport.php @@ -0,0 +1,44 @@ +account; + + $expenses = Expense::scope() + ->withArchived() + ->with('client.contacts', 'vendor') + ->where('expense_date', '>=', $this->startDate) + ->where('expense_date', '<=', $this->endDate); + + foreach ($expenses->get() as $expense) { + $amount = $expense->amountWithTax(); + + $this->data[] = [ + $expense->vendor ? ($this->isExport ? $expense->vendor->name : $expense->vendor->present()->link) : '', + $expense->client ? ($this->isExport ? $expense->client->getDisplayName() : $expense->client->present()->link) : '', + $expense->present()->expense_date, + $expense->present()->category, + Utils::formatMoney($amount, $expense->currency_id), + ]; + + $this->addToTotals($expense->expense_currency_id, 'amount', $amount); + $this->addToTotals($expense->invoice_currency_id, 'amount', 0); + } + } +} diff --git a/app/Ninja/Reports/InvoiceDetailsReport.php b/app/Ninja/Reports/InvoiceDetailsReport.php new file mode 100644 index 000000000000..4a100a16ca07 --- /dev/null +++ b/app/Ninja/Reports/InvoiceDetailsReport.php @@ -0,0 +1,61 @@ +account; + $status = $this->options['invoice_status']; + + $clients = Client::scope() + ->withArchived() + ->with('contacts') + ->with(['invoices' => function($query) use ($status) { + if ($status == 'draft') { + $query->whereIsPublic(false); + } elseif ($status == 'unpaid' || $status == 'paid') { + $query->whereIsPublic(true); + } + $query->invoices() + ->withArchived() + ->where('invoice_date', '>=', $this->startDate) + ->where('invoice_date', '<=', $this->endDate) + ->with(['invoice_items']); + }]); + + foreach ($clients->get() as $client) { + foreach ($client->invoices as $invoice) { + foreach ($invoice->invoice_items as $item) { + $this->data[] = [ + $this->isExport ? $client->getDisplayName() : $client->present()->link, + $this->isExport ? $invoice->invoice_number : $invoice->present()->link, + $invoice->present()->invoice_date, + $item->product_key, + $item->qty, + $account->formatMoney($item->cost, $client), + ]; + } + + //$this->addToTotals($client->currency_id, 'paid', $payment ? $payment->getCompletedAmount() : 0); + //$this->addToTotals($client->currency_id, 'amount', $invoice->amount); + //$this->addToTotals($client->currency_id, 'balance', $invoice->balance); + } + } + } +} diff --git a/app/Ninja/Reports/InvoiceReport.php b/app/Ninja/Reports/InvoiceReport.php new file mode 100644 index 000000000000..b9c1c1f0286e --- /dev/null +++ b/app/Ninja/Reports/InvoiceReport.php @@ -0,0 +1,73 @@ +account; + $status = $this->options['invoice_status']; + + $clients = Client::scope() + ->withArchived() + ->with('contacts') + ->with(['invoices' => function($query) use ($status) { + if ($status == 'draft') { + $query->whereIsPublic(false); + } elseif ($status == 'unpaid' || $status == 'paid') { + $query->whereIsPublic(true); + } + $query->invoices() + ->withArchived() + ->where('invoice_date', '>=', $this->startDate) + ->where('invoice_date', '<=', $this->endDate) + ->with(['payments' => function($query) { + $query->withArchived() + ->excludeFailed() + ->with('payment_type', 'account_gateway.gateway'); + }, 'invoice_items']); + }]); + + foreach ($clients->get() as $client) { + foreach ($client->invoices as $invoice) { + + $payments = count($invoice->payments) ? $invoice->payments : [false]; + foreach ($payments as $payment) { + if ( ! $payment && $status == 'paid') { + continue; + } elseif ($payment && $status == 'unpaid') { + continue; + } + $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), + $payment ? $payment->present()->payment_date : '', + $payment ? $account->formatMoney($payment->getCompletedAmount(), $client) : '', + $payment ? $payment->present()->method : '', + ]; + + $this->addToTotals($client->currency_id, 'paid', $payment ? $payment->getCompletedAmount() : 0); + } + + $this->addToTotals($client->currency_id, 'amount', $invoice->amount); + $this->addToTotals($client->currency_id, 'balance', $invoice->balance); + } + } + } +} diff --git a/app/Ninja/Reports/PaymentReport.php b/app/Ninja/Reports/PaymentReport.php new file mode 100644 index 000000000000..7521af2e3eb4 --- /dev/null +++ b/app/Ninja/Reports/PaymentReport.php @@ -0,0 +1,54 @@ +account; + + $payments = Payment::scope() + ->withArchived() + ->excludeFailed() + ->whereHas('client', function($query) { + $query->where('is_deleted', '=', false); + }) + ->whereHas('invoice', function($query) { + $query->where('is_deleted', '=', false); + }) + ->with('client.contacts', 'invoice', 'payment_type', 'account_gateway.gateway') + ->where('payment_date', '>=', $this->startDate) + ->where('payment_date', '<=', $this->endDate); + + foreach ($payments->get() as $payment) { + $invoice = $payment->invoice; + $client = $payment->client; + $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), + $payment->present()->payment_date, + $account->formatMoney($payment->getCompletedAmount(), $client), + $payment->present()->method, + ]; + + $this->addToTotals($client->currency_id, 'amount', $invoice->amount); + $this->addToTotals($client->currency_id, 'paid', $payment->getCompletedAmount()); + } + } +} diff --git a/app/Ninja/Reports/ProductReport.php b/app/Ninja/Reports/ProductReport.php new file mode 100644 index 000000000000..c611f9a20198 --- /dev/null +++ b/app/Ninja/Reports/ProductReport.php @@ -0,0 +1,55 @@ +account; + + $clients = Client::scope() + ->withTrashed() + ->with('contacts') + ->where('is_deleted', '=', false) + ->with(['invoices' => function($query) { + $query->where('invoice_date', '>=', $this->startDate) + ->where('invoice_date', '<=', $this->endDate) + ->where('is_deleted', '=', false) + ->where('is_recurring', '=', false) + ->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD) + ->with(['invoice_items']) + ->withTrashed(); + }]); + + foreach ($clients->get() as $client) { + foreach ($client->invoices as $invoice) { + + foreach ($invoice->invoice_items as $invoiceItem) { + $this->data[] = [ + $this->isExport ? $client->getDisplayName() : $client->present()->link, + $this->isExport ? $invoice->invoice_number : $invoice->present()->link, + $invoice->present()->invoice_date, + round($invoiceItem->qty, 2), + $invoiceItem->product_key, + ]; + //$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->amount : 0); + } + + //$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); + //$reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance); + } + } + } +} diff --git a/app/Ninja/Reports/ProfitAndLossReport.php b/app/Ninja/Reports/ProfitAndLossReport.php new file mode 100644 index 000000000000..d1108bf90a51 --- /dev/null +++ b/app/Ninja/Reports/ProfitAndLossReport.php @@ -0,0 +1,68 @@ +account; + + $payments = Payment::scope() + ->with('client.contacts') + ->withArchived() + ->excludeFailed(); + + foreach ($payments->get() as $payment) { + $client = $payment->client; + $this->data[] = [ + trans('texts.payment'), + $client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '', + $account->formatMoney($payment->getCompletedAmount(), $client), + $payment->present()->payment_date, + $payment->present()->method, + ]; + + $this->addToTotals($client->currency_id, 'revenue', $payment->getCompletedAmount(), $payment->present()->month); + $this->addToTotals($client->currency_id, 'expenses', 0, $payment->present()->month); + $this->addToTotals($client->currency_id, 'profit', $payment->getCompletedAmount(), $payment->present()->month); + } + + + $expenses = Expense::scope() + ->with('client.contacts') + ->withArchived(); + + foreach ($expenses->get() as $expense) { + $client = $expense->client; + $this->data[] = [ + trans('texts.expense'), + $client ? ($this->isExport ? $client->getDisplayName() : $client->present()->link) : '', + $expense->present()->amount, + $expense->present()->expense_date, + $expense->present()->category, + ]; + + $this->addToTotals($client->currency_id, 'revenue', 0, $expense->present()->month); + $this->addToTotals($client->currency_id, 'expenses', $expense->amount, $expense->present()->month); + $this->addToTotals($client->currency_id, 'profit', $expense->amount * -1, $expense->present()->month); + } + + + //$this->addToTotals($client->currency_id, 'paid', $payment ? $payment->getCompletedAmount() : 0); + //$this->addToTotals($client->currency_id, 'amount', $invoice->amount); + //$this->addToTotals($client->currency_id, 'balance', $invoice->balance); + } +} diff --git a/app/Ninja/Reports/TaskReport.php b/app/Ninja/Reports/TaskReport.php new file mode 100644 index 000000000000..4d7b7398b7ce --- /dev/null +++ b/app/Ninja/Reports/TaskReport.php @@ -0,0 +1,36 @@ +with('client.contacts') + ->withArchived() + ->dateRange($this->startDate, $this->endDate); + + foreach ($tasks->get() as $task) { + $this->data[] = [ + $task->client ? ($this->isExport ? $task->client->getDisplayName() : $task->client->present()->link) : trans('texts.unassigned'), + link_to($task->present()->url, $task->getStartTime()), + $task->present()->project, + $task->present()->description, + Utils::formatTime($task->getDuration()), + ]; + } + } +} diff --git a/app/Ninja/Reports/TaxRateReport.php b/app/Ninja/Reports/TaxRateReport.php new file mode 100644 index 000000000000..800914c841da --- /dev/null +++ b/app/Ninja/Reports/TaxRateReport.php @@ -0,0 +1,82 @@ +account; + + $clients = Client::scope() + ->withArchived() + ->with('contacts') + ->with(['invoices' => function($query) { + $query->with('invoice_items')->withArchived(); + if ($this->options['date_field'] == FILTER_INVOICE_DATE) { + $query->where('invoice_date', '>=', $this->startDate) + ->where('invoice_date', '<=', $this->endDate) + ->with('payments'); + } else { + $query->whereHas('payments', function($query) { + $query->where('payment_date', '>=', $this->startDate) + ->where('payment_date', '<=', $this->endDate) + ->withArchived(); + }) + ->with(['payments' => function($query) { + $query->where('payment_date', '>=', $this->startDate) + ->where('payment_date', '<=', $this->endDate) + ->withArchived(); + }]); + } + }]); + + foreach ($clients->get() as $client) { + $currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId(); + $amount = 0; + $paid = 0; + $taxTotals = []; + + foreach ($client->invoices as $invoice) { + foreach ($invoice->getTaxes(true) as $key => $tax) { + if ( ! isset($taxTotals[$currencyId])) { + $taxTotals[$currencyId] = []; + } + if (isset($taxTotals[$currencyId][$key])) { + $taxTotals[$currencyId][$key]['amount'] += $tax['amount']; + $taxTotals[$currencyId][$key]['paid'] += $tax['paid']; + } else { + $taxTotals[$currencyId][$key] = $tax; + } + } + + $amount += $invoice->amount; + $paid += $invoice->getAmountPaid(); + } + + foreach ($taxTotals as $currencyId => $taxes) { + foreach ($taxes as $tax) { + $this->data[] = [ + $tax['name'], + $tax['rate'] . '%', + $account->formatMoney($tax['amount'], $client), + $account->formatMoney($tax['paid'], $client) + ]; + } + + $this->addToTotals($client->currency_id, 'amount', $tax['amount']); + $this->addToTotals($client->currency_id, 'paid', $tax['paid']); + } + } + } +} diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index 2b6ec5b9aa7d..b3816a89ae77 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -125,7 +125,7 @@ class AccountRepository if ($client->name) { $data['clients'][] = [ 'value' => $client->name, - 'tokens' => $client->name, + 'tokens' => implode(',', [$client->name, $client->id_number, $client->vat_number, $client->work_phone]), 'url' => $client->present()->url, ]; } @@ -146,27 +146,18 @@ class AccountRepository } foreach ($client->contacts as $contact) { - if ($contact->getFullName()) { - $data['contacts'][] = [ - 'value' => $contact->getDisplayName(), - 'tokens' => $contact->getDisplayName(), - 'url' => $client->present()->url, - ]; - } - if ($contact->email) { - $data['contacts'][] = [ - 'value' => $contact->email, - 'tokens' => $contact->email, - 'url' => $client->present()->url, - ]; - } + $data['contacts'][] = [ + 'value' => $contact->getDisplayName(), + 'tokens' => implode(',', [$contact->first_name, $contact->last_name, $contact->email, $contact->phone]), + 'url' => $client->present()->url, + ]; } foreach ($client->invoices as $invoice) { $entityType = $invoice->getEntityType(); $data["{$entityType}s"][] = [ 'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), - 'tokens' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), + 'tokens' => implode(',', [$invoice->invoice_number, $invoice->po_number]), 'url' => $invoice->present()->url, ]; } @@ -204,6 +195,7 @@ class AccountRepository $features = array_merge($features, [ ['dashboard', '/dashboard'], + ['reports', '/reports'], ['customize_design', '/settings/customize_design'], ['new_tax_rate', '/tax_rates/create'], ['new_product', '/products/create'], @@ -287,7 +279,7 @@ class AccountRepository $invoice->user_id = $account->users()->first()->id; $invoice->public_id = $publicId; $invoice->client_id = $client->id; - $invoice->invoice_number = $account->getNextInvoiceNumber($invoice); + $invoice->invoice_number = $account->getNextNumber($invoice); $invoice->invoice_date = $renewalDate->format('Y-m-d'); $invoice->amount = $invoice->balance = $plan_cost - $credit; $invoice->invoice_type_id = INVOICE_TYPE_STANDARD; @@ -345,11 +337,15 @@ class AccountRepository if ($account) { return $account; } else { + $company = new Company(); + $company->save(); + $account = new Account(); $account->name = 'Invoice Ninja'; $account->work_email = 'contact@invoiceninja.com'; $account->work_phone = '(800) 763-1948'; $account->account_key = NINJA_ACCOUNT_KEY; + $account->company_id = $company->id; $account->save(); $random = str_random(RANDOM_KEY_LENGTH); diff --git a/app/Ninja/Repositories/ActivityRepository.php b/app/Ninja/Repositories/ActivityRepository.php index 2a910707e44b..6a1b1a1740f1 100644 --- a/app/Ninja/Repositories/ActivityRepository.php +++ b/app/Ninja/Repositories/ActivityRepository.php @@ -10,7 +10,7 @@ use App\Models\Invitation; class ActivityRepository { - public function create($entity, $activityTypeId, $balanceChange = 0, $paidToDateChange = 0, $altEntity = null) + public function create($entity, $activityTypeId, $balanceChange = 0, $paidToDateChange = 0, $altEntity = null, $notes = false) { if ($entity instanceof Client) { $client = $entity; @@ -29,6 +29,7 @@ class ActivityRepository $activity->adjustment = $balanceChange; $activity->client_id = $client ? $client->id : 0; $activity->balance = $client ? ($client->balance + $balanceChange) : 0; + $activity->notes = $notes ?: ''; $keyField = $entity->getKeyField(); $activity->$keyField = $entity->id; @@ -53,10 +54,7 @@ class ActivityRepository } else { $activity->user_id = $entity->user_id; $activity->account_id = $entity->account_id; - - if ( ! $entity instanceof Invitation) { - $activity->is_system = true; - } + $activity->is_system = true; } $activity->token_id = session('token_id'); @@ -89,6 +87,7 @@ class ActivityRepository 'activities.is_system', 'activities.balance', 'activities.adjustment', + 'activities.notes', 'users.first_name as user_first_name', 'users.last_name as user_last_name', 'users.email as user_email', diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index a465a4767abd..5156298e453b 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -2,6 +2,7 @@ use DB; use Cache; +use Auth; use App\Models\Client; use App\Models\Contact; use App\Events\ClientWasCreated; @@ -77,6 +78,9 @@ 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(); } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index e584ff238cdd..0525aa56e0dd 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -4,6 +4,7 @@ use App\Models\Account; use DB; use Utils; use Auth; +use Carbon; use App\Models\Invoice; use App\Models\InvoiceItem; use App\Models\Invitation; @@ -12,6 +13,7 @@ use App\Models\Task; use App\Models\Document; use App\Models\Expense; use App\Services\PaymentService; +use App\Jobs\SendInvoiceEmail; class InvoiceRepository extends BaseRepository { @@ -141,6 +143,7 @@ class InvoiceRepository extends BaseRepository 'frequencies.name as frequency', 'invoices.start_date', 'invoices.end_date', + 'invoices.last_sent_date', 'contacts.first_name', 'contacts.last_name', 'contacts.email', @@ -234,8 +237,8 @@ class InvoiceRepository extends BaseRepository ->where('contacts.is_primary', '=', true) ->where('invoices.is_recurring', '=', false) ->where('invoices.is_public', '=', true) - // This needs to be a setting to also hide the activity on the dashboard page - //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) + // Only show paid invoices for ninja accounts + ->whereRaw(sprintf("((accounts.account_key != '%s' and accounts.account_key != '%s') or invoices.invoice_status_id = %d)", env('NINJA_LICENSE_ACCOUNT_KEY'), NINJA_ACCOUNT_KEY, INVOICE_STATUS_PAID)) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), @@ -297,13 +300,16 @@ class InvoiceRepository extends BaseRepository $entityType = ENTITY_QUOTE; } $invoice = $account->createInvoice($entityType, $data['client_id']); - $invoice->invoice_date = date_create()->format('Y-m-d'); + $invoice->invoice_date = Utils::today(); if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) { $invoice->has_tasks = true; } if (isset($data['has_expenses']) && filter_var($data['has_expenses'], FILTER_VALIDATE_BOOLEAN)) { $invoice->has_expenses = true; } + if ($account->payment_terms != 0) { + $invoice->due_date = $account->defaultDueDate(); + } } else { $invoice = Invoice::scope($publicId)->firstOrFail(); if (Utils::isNinjaDev()) { @@ -315,8 +321,7 @@ class InvoiceRepository extends BaseRepository return $invoice; } - // set default to true for backwards compatability - if ( ! isset($data['is_public']) || filter_var($data['is_public'], FILTER_VALIDATE_BOOLEAN)) { + if (isset($data['is_public']) && filter_var($data['is_public'], FILTER_VALIDATE_BOOLEAN)) { $invoice->is_public = true; if ( ! $invoice->isSent()) { $invoice->invoice_status_id = INVOICE_STATUS_SENT; @@ -380,7 +385,7 @@ class InvoiceRepository extends BaseRepository $invoice->due_date = $data['due_date']; } } else { - if (isset($data['due_date']) || isset($data['due_date_sql'])) { + if (!empty($data['due_date']) || !empty($data['due_date_sql'])) { $invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']); } $invoice->frequency_id = 0; @@ -390,7 +395,7 @@ class InvoiceRepository extends BaseRepository if (isset($data['terms']) && trim($data['terms'])) { $invoice->terms = trim($data['terms']); - } elseif ($isNew && $account->{"{$entityType}_terms"}) { + } elseif ($isNew && ! $invoice->is_recurring && $account->{"{$entityType}_terms"}) { $invoice->terms = $account->{"{$entityType}_terms"}; } else { $invoice->terms = ''; @@ -412,7 +417,7 @@ class InvoiceRepository extends BaseRepository $invoice->invoice_design_id = isset($data['invoice_design_id']) ? $data['invoice_design_id'] : $account->invoice_design_id; - // provide backwards compatability + // provide backwards compatibility if (isset($data['tax_name']) && isset($data['tax_rate'])) { $data['tax_name1'] = $data['tax_name']; $data['tax_rate1'] = $data['tax_rate']; @@ -495,8 +500,8 @@ class InvoiceRepository extends BaseRepository $total += $invoice->custom_value2; } - $taxAmount1 = round($total * $invoice->tax_rate1 / 100, 2); - $taxAmount2 = round($total * $invoice->tax_rate2 / 100, 2); + $taxAmount1 = round($total * ($invoice->tax_rate1 ? $invoice->tax_rate1 : 0) / 100, 2); + $taxAmount2 = round($total * ($invoice->tax_rate2 ? $invoice->tax_rate2 : 0) / 100, 2); $total = round($total + $taxAmount1 + $taxAmount2, 2); $total += $itemTax; @@ -659,8 +664,8 @@ class InvoiceRepository extends BaseRepository $invoiceNumber = false; } } - $clone->invoice_number = $invoiceNumber ?: $account->getNextInvoiceNumber($clone); - $clone->invoice_date = date_create()->format('Y-m-d'); + $clone->invoice_number = $invoiceNumber ?: $account->getNextNumber($clone); + $clone->invoice_date = Utils::today(); foreach ([ 'client_id', @@ -746,19 +751,20 @@ class InvoiceRepository extends BaseRepository return $clone; } + /** + * @param Invoice $invoice + */ + public function emailInvoice(Invoice $invoice) + { + dispatch(new SendInvoiceEmail($invoice)); + } + /** * @param Invoice $invoice */ public function markSent(Invoice $invoice) { - if ( ! $invoice->isSent()) { - $invoice->invoice_status_id = INVOICE_STATUS_SENT; - } - - $invoice->is_public = true; - $invoice->save(); - - $invoice->markInvitationsSent(); + $invoice->markSent(); } /** @@ -766,13 +772,11 @@ class InvoiceRepository extends BaseRepository */ public function markPaid(Invoice $invoice) { - if (floatval($invoice->balance) <= 0) { + if ( ! $invoice->canBePaid()) { return; } - if ( ! $invoice->isSent()) { - $this->markSent($invoice); - } + $invoice->markSentIfUnsent(); $data = [ 'client_id' => $invoice->client_id, @@ -818,15 +822,16 @@ class InvoiceRepository extends BaseRepository public function findOpenInvoices($clientId, $entityType = false) { $query = Invoice::scope() - ->invoiceType(INVOICE_TYPE_STANDARD) - ->whereClientId($clientId) - ->whereIsRecurring(false) - ->whereDeletedAt(null); + ->invoiceType(INVOICE_TYPE_STANDARD) + ->whereClientId($clientId) + ->whereIsRecurring(false) + ->whereDeletedAt(null) + ->where('balance', '>', 0); if ($entityType == ENTITY_TASK) { $query->whereHasTasks(true); } elseif ($entityType == ENTITY_EXPENSE) { - $query->whereHasExpenses(true); + $query->whereHasTasks(false); } return $query->where('invoice_status_id', '<', 5) @@ -859,15 +864,15 @@ class InvoiceRepository extends BaseRepository $invoice->invoice_type_id = INVOICE_TYPE_STANDARD; $invoice->client_id = $recurInvoice->client_id; $invoice->recurring_invoice_id = $recurInvoice->id; - $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber($invoice); + $invoice->invoice_number = $recurInvoice->account->getNextNumber($invoice); $invoice->amount = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount; - $invoice->invoice_date = date_create()->format('Y-m-d'); + $invoice->invoice_date = Utils::today(); $invoice->discount = $recurInvoice->discount; $invoice->po_number = $recurInvoice->po_number; $invoice->public_notes = Utils::processVariables($recurInvoice->public_notes); - $invoice->terms = Utils::processVariables($recurInvoice->terms); - $invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer); + $invoice->terms = Utils::processVariables($recurInvoice->terms ?: $recurInvoice->account->invoice_terms); + $invoice->invoice_footer = Utils::processVariables($recurInvoice->invoice_footer ?: $recurInvoice->account->invoice_footer); $invoice->tax_name1 = $recurInvoice->tax_name1; $invoice->tax_rate1 = $recurInvoice->tax_rate1; $invoice->tax_name2 = $recurInvoice->tax_name2; diff --git a/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index 0c99521368c9..b6f37de72420 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -62,7 +62,7 @@ class PaymentRepository extends BaseRepository 'invoices.is_deleted as invoice_is_deleted', 'gateways.name as gateway_name', 'gateways.id as gateway_id', - 'payment_statuses.name as payment_status_name' + 'payment_statuses.name as status' ); $this->applyFilters($query, ENTITY_PAYMENT); diff --git a/app/Ninja/Repositories/TaxRateRepository.php b/app/Ninja/Repositories/TaxRateRepository.php index f737256e85bf..52f229afd9af 100644 --- a/app/Ninja/Repositories/TaxRateRepository.php +++ b/app/Ninja/Repositories/TaxRateRepository.php @@ -16,7 +16,13 @@ class TaxRateRepository extends BaseRepository return DB::table('tax_rates') ->where('tax_rates.account_id', '=', $accountId) ->where('tax_rates.deleted_at', '=', null) - ->select('tax_rates.public_id', 'tax_rates.name', 'tax_rates.rate', 'tax_rates.deleted_at'); + ->select( + 'tax_rates.public_id', + 'tax_rates.name', + 'tax_rates.rate', + 'tax_rates.deleted_at', + 'tax_rates.is_inclusive' + ); } public function save($data, $taxRate = null) @@ -29,7 +35,7 @@ class TaxRateRepository extends BaseRepository } else { $taxRate = TaxRate::createNew(); } - + $taxRate->fill($data); $taxRate->save(); diff --git a/app/Ninja/Transformers/AccountTransformer.php b/app/Ninja/Transformers/AccountTransformer.php index 4ddbf8f5831b..434d9e7e3aeb 100644 --- a/app/Ninja/Transformers/AccountTransformer.php +++ b/app/Ninja/Transformers/AccountTransformer.php @@ -117,6 +117,7 @@ class AccountTransformer extends EntityTransformer return [ 'account_key' => $account->account_key, 'name' => $account->present()->name, + 'id_number' => $account->id_number, 'currency_id' => (int) $account->currency_id, 'timezone_id' => (int) $account->timezone_id, 'date_format_id' => (int) $account->date_format_id, diff --git a/app/Ninja/Transformers/DocumentTransformer.php b/app/Ninja/Transformers/DocumentTransformer.php index 0ab10d12d45a..2a9ac2abbc30 100644 --- a/app/Ninja/Transformers/DocumentTransformer.php +++ b/app/Ninja/Transformers/DocumentTransformer.php @@ -3,14 +3,19 @@ use App\Models\Document; /** - * Class DocumentTransformer + * @SWG\Definition(definition="Document", @SWG\Xml(name="Document")) */ class DocumentTransformer extends EntityTransformer { /** - * @param Document $document - * @return array - */ + * @SWG\Property(property="id", type="integer", example=1, readOnly=true) + * @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) + */ + public function transform(Document $document) { return array_merge($this->getDefaults($document), [ diff --git a/app/Ninja/Transformers/ExpenseCategoryTransformer.php b/app/Ninja/Transformers/ExpenseCategoryTransformer.php index 7d79f30b1013..e8fded7de624 100644 --- a/app/Ninja/Transformers/ExpenseCategoryTransformer.php +++ b/app/Ninja/Transformers/ExpenseCategoryTransformer.php @@ -2,9 +2,20 @@ use App\Models\ExpenseCategory; +/** + * @SWG\Definition(definition="ExpenseCategory", @SWG\Xml(name="ExpenseCategory")) + */ + 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) + */ + public function transform(ExpenseCategory $expenseCategory) { return array_merge($this->getDefaults($expenseCategory), [ @@ -14,4 +25,4 @@ class ExpenseCategoryTransformer extends EntityTransformer 'archived_at' => $this->getTimestamp($expenseCategory->deleted_at), ]); } -} \ No newline at end of file +} diff --git a/app/Ninja/Transformers/ProductTransformer.php b/app/Ninja/Transformers/ProductTransformer.php index ac71ae09dee9..00a0a7c06281 100644 --- a/app/Ninja/Transformers/ProductTransformer.php +++ b/app/Ninja/Transformers/ProductTransformer.php @@ -2,8 +2,23 @@ use App\Models\Product; +/** + * @SWG\Definition(definition="Product", @SWG\Xml(name="Product")) + */ + 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="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) + */ + public function transform(Product $product) { return array_merge($this->getDefaults($product), [ @@ -17,4 +32,4 @@ class ProductTransformer extends EntityTransformer 'archived_at' => $this->getTimestamp($product->deleted_at), ]); } -} \ No newline at end of file +} diff --git a/app/Ninja/Transformers/ProjectTransformer.php b/app/Ninja/Transformers/ProjectTransformer.php index 1de02e3b4ee8..e17b3cafcd3f 100644 --- a/app/Ninja/Transformers/ProjectTransformer.php +++ b/app/Ninja/Transformers/ProjectTransformer.php @@ -2,8 +2,21 @@ use App\Models\Project; +/** + * @SWG\Definition(definition="Project", @SWG\Xml(name="Project")) + */ + 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="is_deleted", type="boolean", example=false, readOnly=true) + */ + public function transform(Project $project) { return array_merge($this->getDefaults($project), [ diff --git a/app/Ninja/Transformers/TaxRateTransformer.php b/app/Ninja/Transformers/TaxRateTransformer.php index 3b0f5ee19978..07b8aa565ad0 100644 --- a/app/Ninja/Transformers/TaxRateTransformer.php +++ b/app/Ninja/Transformers/TaxRateTransformer.php @@ -13,6 +13,7 @@ class TaxRateTransformer extends EntityTransformer * @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="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") */ @@ -23,8 +24,9 @@ class TaxRateTransformer extends EntityTransformer 'id' => (int) $taxRate->public_id, 'name' => $taxRate->name, 'rate' => (float) $taxRate->rate, + 'is_inclusive' => (bool) $taxRate->is_inclusive, 'updated_at' => $this->getTimestamp($taxRate->updated_at), 'archived_at' => $this->getTimestamp($taxRate->deleted_at), ]); } -} \ No newline at end of file +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 259ee3c171d1..1a0a7751d62c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -191,13 +191,16 @@ class AppServiceProvider extends ServiceProvider Validator::extend('valid_invoice_items', function($attribute, $value, $parameters) { $total = 0; foreach ($value as $item) { - $qty = isset($item['qty']) ? $item['qty'] : 1; - $cost = isset($item['cost']) ? $item['cost'] : 1; + $qty = !empty($item['qty']) ? $item['qty'] : 1; + $cost = !empty($item['cost']) ? $item['cost'] : 1; $total += $qty * $cost; } return $total <= MAX_INVOICE_AMOUNT; }); + Validator::extend('valid_subdomain', function($attribute, $value, $parameters) { + return ! in_array($value, ['www', 'app', 'mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner', 'info', 'ninja', 'docs', 'doc', 'documents', 'download']); + }); } /** diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5d0be86d36bc..4a23769092d6 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -36,6 +36,7 @@ class EventServiceProvider extends ServiceProvider { 'App\Events\InvoiceWasUpdated' => [ 'App\Listeners\ActivityListener@updatedInvoice', 'App\Listeners\InvoiceListener@updatedInvoice', + 'App\Listeners\SubscriptionListener@updatedInvoice', ], 'App\Events\InvoiceWasArchived' => [ 'App\Listeners\ActivityListener@archivedInvoice', @@ -67,6 +68,7 @@ class EventServiceProvider extends ServiceProvider { ], 'App\Events\QuoteWasUpdated' => [ 'App\Listeners\ActivityListener@updatedQuote', + 'App\Listeners\SubscriptionListener@updatedQuote', ], 'App\Events\QuoteWasArchived' => [ 'App\Listeners\ActivityListener@archivedQuote', diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index 4bed93de2421..0a17f72231bf 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -25,6 +25,7 @@ use App\Models\Vendor; use App\Models\Expense; use App\Models\ExpenseCategory; use App\Models\EntityModel; +use App\Ninja\Import\BaseTransformer; /** * Class ImportService @@ -142,18 +143,34 @@ class ImportService */ public function importJSON($file) { - $this->init(); + $this->initMaps(); $file = file_get_contents($file); $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; + } + } + 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); @@ -164,6 +181,7 @@ class ImportService $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_INVOICE, $jsonInvoice); @@ -242,6 +260,10 @@ class ImportService $this->checkData($entityType, count($reader->all())); $reader->each(function ($row) use ($source, $entityType, &$row_list, &$results) { + if ($this->isRowEmpty($row)) { + return; + } + $data_index = $this->transformRow($source, $entityType, $row); if ($data_index !== false) { @@ -307,7 +329,7 @@ class ImportService if ($entityType == ENTITY_INVOICE && !$data['invoice_number']) { $account = Auth::user()->account; $invoice = Invoice::createNew(); - $data['invoice_number'] = $account->getNextInvoiceNumber($invoice); + $data['invoice_number'] = $account->getNextNumber($invoice); } if (EntityModel::validate($data, $entityType) !== true) { @@ -342,6 +364,11 @@ class ImportService private function saveData($source, $entityType, $row, $data_index) { $data = $this->processedRows[$data_index]; + + if ($entityType == ENTITY_INVOICE) { + $data['is_public'] = true; + } + $entity = $this->{"{$entityType}Repo"}->save($data); // update the entity maps @@ -474,14 +501,24 @@ class ImportService $csv->heading = false; $csv->auto($filename); - Session::put("{$entityType}-data", $csv->data); - $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]; + } + foreach ($headers as $title) { if (strpos(strtolower($title), 'name') > 0) { $hasHeaders = true; @@ -503,6 +540,8 @@ class ImportService } } + Session::put("{$entityType}-data", $csv->data); + $data = [ 'entityType' => $entityType, 'data' => $csv->data, @@ -596,6 +635,9 @@ class ImportService } $row = $this->convertToObject($entityType, $row, $map); + if ($this->isRowEmpty($row)) { + continue; + } $data_index = $this->transformRow($source, $entityType, $row); if ($data_index !== false) { @@ -676,7 +718,7 @@ class ImportService { EntityModel::$notifySubscriptions = false; - foreach ([ENTITY_CLIENT, ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_QUOTE] as $entityType) { + foreach ([ENTITY_CLIENT, ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_QUOTE, ENTITY_PRODUCT] as $entityType) { $this->results[$entityType] = [ RESULT_SUCCESS => [], RESULT_FAILURE => [], @@ -788,4 +830,17 @@ class ImportService $this->maps['expense_category'][$name] = $category->id; } } + + private function isRowEmpty($row) + { + $isEmpty = true; + + foreach ($row as $key => $val) { + if (trim($val)) { + $isEmpty = false; + } + } + + return $isEmpty; + } } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 68c696695e49..73438c95c7e3 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -3,6 +3,7 @@ use App\Models\Invoice; use Utils; use Auth; +use Exception; use App\Models\Account; use App\Models\Client; use App\Models\Activity; @@ -57,6 +58,8 @@ class PaymentService extends BaseService return false; } + $invoice->markSentIfUnsent(); + if ($credits = $client->credits->sum('balance')) { $balance = $invoice->balance; $amount = min($credits, $balance); @@ -120,7 +123,11 @@ class PaymentService extends BaseService } } - return $paymentDriver->completeOnsitePurchase(false, $paymentMethod); + try { + return $paymentDriver->completeOnsitePurchase(false, $paymentMethod); + } catch (Exception $exception) { + return false; + } } public function getDatatable($clientPublicId, $search) @@ -154,6 +161,9 @@ class PaymentService extends BaseService if ($paymentDriver->refundPayment($payment, $amount)) { $successful++; } + } else { + $payment->recordRefund($amount); + $successful++; } } } diff --git a/app/Services/PushService.php b/app/Services/PushService.php index 017426814bd8..759e0271cbff 100644 --- a/app/Services/PushService.php +++ b/app/Services/PushService.php @@ -30,10 +30,6 @@ class PushService */ public function sendNotification(Invoice $invoice, $type) { - if (! IOS_PUSH_CERTIFICATE) { - return; - } - //check user has registered for push notifications if(!$this->checkDeviceExists($invoice->account)) return; @@ -43,8 +39,10 @@ class PushService foreach($devices as $device) { - if(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'ios')) - $this->pushMessage($invoice, $device['token'], $type); + if(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'ios') && IOS_DEVICE) + $this->pushMessage($invoice, $device['token'], $type, IOS_DEVICE); + elseif(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'fcm') && ANDROID_DEVICE) + $this->pushMessage($invoice, $device['token'], $type, ANDROID_DEVICE); } } @@ -57,9 +55,9 @@ class PushService * @param $token * @param $type */ - private function pushMessage(Invoice $invoice, $token, $type) + private function pushMessage(Invoice $invoice, $token, $type, $device) { - $this->pushFactory->message($token, $this->messageType($invoice, $type)); + $this->pushFactory->message($token, $this->messageType($invoice, $type), $device); } /** diff --git a/bower.json b/bower.json index 7c474751dc9b..319bf1ae0732 100644 --- a/bower.json +++ b/bower.json @@ -13,14 +13,13 @@ "knockout-sortable": "0.9.3", "font-awesome": "~4.*", "underscore": "1.7.0", - "jspdf": "1.0.272", "bootstrap-datepicker": "1.4.0", "typeahead.js": "0.11.1", "accounting": "0.3.2", "spectrum": "1.3.4", "d3": "3.4.11", "handsontable": "*", - "pdfmake": "*", + "pdfmake": "^0.1.22", "moment": "*", "jsoneditor": "*", "moment-timezone": "~0.4.0", @@ -34,7 +33,8 @@ "sweetalert2": "^5.3.8", "jSignature": "brinley/jSignature#^2.1.0", "select2": "select2-dist#^4.0.3", - "mousetrap": "^1.6.0" + "mousetrap": "^1.6.0", + "tablesorter": "jquery.tablesorter#^2.28.4" }, "resolutions": { "jquery": "~1.11" diff --git a/composer.json b/composer.json index 9bb37aa66315..715feeb2a11e 100644 --- a/composer.json +++ b/composer.json @@ -14,10 +14,9 @@ ], "require": { "php": ">=5.5.9", - "ext-mcrypt": "*", "ext-gmp": "*", "ext-gd": "*", - "turbo124/laravel-push-notification": "dev-laravel5", + "turbo124/laravel-push-notification": "2.*", "omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525", "omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "omnipay/gocardless": "dev-master", @@ -84,7 +83,8 @@ "jaybizzle/laravel-crawler-detect": "1.*", "codedge/laravel-selfupdater": "5.x-dev", "predis/predis": "^1.1", - "nwidart/laravel-modules": "^1.14" + "nwidart/laravel-modules": "^1.14", + "jonnyw/php-phantomjs": "4.*" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index 2e870d6d0182..a995ae0b3ec3 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": "5268e0c573a4094ea670e2eb7555b962", - "content-hash": "cf263546eb8ba91f81f26bdc5470573e", + "hash": "312df7e739006b94ffe7480e06284bdc", + "content-hash": "cba8dbe698812a5b877b7f6858e729e2", "packages": [ { "name": "agmscode/omnipay-agms", @@ -271,20 +271,24 @@ }, { "name": "asgrim/ofxparser", - "version": "1.1.1", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/asgrim/ofxparser.git", - "reference": "7652efea77a1c5dda007f9764d8061f870248ea1" + "reference": "8ba143295be666ae2cac05674f7d526d8e0ebed7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/asgrim/ofxparser/zipball/7652efea77a1c5dda007f9764d8061f870248ea1", - "reference": "7652efea77a1c5dda007f9764d8061f870248ea1", + "url": "https://api.github.com/repos/asgrim/ofxparser/zipball/8ba143295be666ae2cac05674f7d526d8e0ebed7", + "reference": "8ba143295be666ae2cac05674f7d526d8e0ebed7", "shasum": "" }, "require": { - "php": ">=5.4" + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.5", + "squizlabs/php_codesniffer": "~2.6" }, "type": "library", "autoload": { @@ -319,20 +323,20 @@ "open financial exchange", "parser" ], - "time": "2015-12-11 11:08:57" + "time": "2016-09-26 11:36:23" }, { "name": "aws/aws-sdk-php", - "version": "3.18.33", + "version": "3.20.13", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "d787cfc514f77f5bee6990a466bc6922c0cf1526" + "reference": "343c6c96468de5087ae5e7df4ccee2668857a5aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d787cfc514f77f5bee6990a466bc6922c0cf1526", - "reference": "d787cfc514f77f5bee6990a466bc6922c0cf1526", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/343c6c96468de5087ae5e7df4ccee2668857a5aa", + "reference": "343c6c96468de5087ae5e7df4ccee2668857a5aa", "shasum": "" }, "require": { @@ -399,20 +403,20 @@ "s3", "sdk" ], - "time": "2016-07-26 18:19:04" + "time": "2017-01-04 18:20:17" }, { "name": "barracudanetworks/archivestream-php", - "version": "1.0.3", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/barracudanetworks/ArchiveStream-php.git", - "reference": "c4e10449abde2c3283d2bda378cfb458dec08ca1" + "reference": "1bf98097d1e9b137fd40081f26abb0a17b097ef7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barracudanetworks/ArchiveStream-php/zipball/c4e10449abde2c3283d2bda378cfb458dec08ca1", - "reference": "c4e10449abde2c3283d2bda378cfb458dec08ca1", + "url": "https://api.github.com/repos/barracudanetworks/ArchiveStream-php/zipball/1bf98097d1e9b137fd40081f26abb0a17b097ef7", + "reference": "1bf98097d1e9b137fd40081f26abb0a17b097ef7", "shasum": "" }, "require": { @@ -439,32 +443,32 @@ "tar", "zip" ], - "time": "2016-05-18 21:28:06" + "time": "2017-01-13 14:52:38" }, { "name": "barryvdh/laravel-debugbar", - "version": "v2.2.2", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "c291e58d0a13953e0f68d99182ee77ebc693edc0" + "reference": "65b0465e38a9524c9d5eb2dfc0389aba23090625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c291e58d0a13953e0f68d99182ee77ebc693edc0", - "reference": "c291e58d0a13953e0f68d99182ee77ebc693edc0", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/65b0465e38a9524c9d5eb2dfc0389aba23090625", + "reference": "65b0465e38a9524c9d5eb2dfc0389aba23090625", "shasum": "" }, "require": { - "illuminate/support": "5.1.*|5.2.*", - "maximebf/debugbar": "~1.11.0", + "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*", + "maximebf/debugbar": "~1.13.0", "php": ">=5.5.9", "symfony/finder": "~2.7|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } }, "autoload": { @@ -493,20 +497,20 @@ "profiler", "webprofiler" ], - "time": "2016-05-11 13:54:43" + "time": "2017-01-05 08:53:44" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.2.1", + "version": "v2.2.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "28af7cd19ca41cc0c63dd1de2b46c2b84d31c463" + "reference": "105f14a50d0959a0e80004a15b3350fdf78f9623" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/28af7cd19ca41cc0c63dd1de2b46c2b84d31c463", - "reference": "28af7cd19ca41cc0c63dd1de2b46c2b84d31c463", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/105f14a50d0959a0e80004a15b3350fdf78f9623", + "reference": "105f14a50d0959a0e80004a15b3350fdf78f9623", "shasum": "" }, "require": { @@ -559,7 +563,7 @@ "phpstorm", "sublime" ], - "time": "2016-07-04 11:52:48" + "time": "2016-11-15 08:21:23" }, { "name": "barryvdh/reflection-docblock", @@ -612,16 +616,16 @@ }, { "name": "braintree/braintree_php", - "version": "3.15.0", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/braintree/braintree_php.git", - "reference": "1026b6cca5f2dd5dfdd5447843b920cc74d2b8ca" + "reference": "30165da99f35f0cb2fa13ef0a4bfc8340f23cdeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/braintree/braintree_php/zipball/1026b6cca5f2dd5dfdd5447843b920cc74d2b8ca", - "reference": "1026b6cca5f2dd5dfdd5447843b920cc74d2b8ca", + "url": "https://api.github.com/repos/braintree/braintree_php/zipball/30165da99f35f0cb2fa13ef0a4bfc8340f23cdeb", + "reference": "30165da99f35f0cb2fa13ef0a4bfc8340f23cdeb", "shasum": "" }, "require": { @@ -655,7 +659,7 @@ } ], "description": "Braintree PHP Client Library", - "time": "2016-07-18 21:55:50" + "time": "2017-01-13 22:56:28" }, { "name": "cardgate/omnipay-cardgate", @@ -802,20 +806,20 @@ }, { "name": "classpreloader/classpreloader", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/ClassPreloader/ClassPreloader.git", - "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a" + "reference": "bc7206aa892b5a33f4680421b69b191efd32b096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/9b10b913c2bdf90c3d2e0d726b454fb7f77c552a", - "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/bc7206aa892b5a33f4680421b69b191efd32b096", + "reference": "bc7206aa892b5a33f4680421b69b191efd32b096", "shasum": "" }, "require": { - "nikic/php-parser": "^1.0|^2.0", + "nikic/php-parser": "^1.0|^2.0|^3.0", "php": ">=5.5.9" }, "require-dev": { @@ -824,7 +828,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -852,7 +856,7 @@ "class", "preload" ], - "time": "2015-11-09 22:51:51" + "time": "2016-09-16 12:50:15" }, { "name": "coatesap/omnipay-datacash", @@ -1160,12 +1164,12 @@ "source": { "type": "git", "url": "https://github.com/delatbabel/omnipay-fatzebra.git", - "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53" + "reference": "0a5708da00bf39806881a4f2d286ec95fe3fb80c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/d0a56a8704357d91457672741a48a4cb6c7ecd53", - "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53", + "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/0a5708da00bf39806881a4f2d286ec95fe3fb80c", + "reference": "0a5708da00bf39806881a4f2d286ec95fe3fb80c", "shasum": "" }, "require": { @@ -1209,27 +1213,28 @@ "payment", "paystream" ], - "time": "2016-03-21 09:21:14" + "time": "2016-08-27 04:11:44" }, { "name": "dercoder/omnipay-ecopayz", - "version": "v1.0.1", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/dercoder/omnipay-ecopayz.git", - "reference": "583cfee3db1e1e04c74f2d3a56559d0f2af761d4" + "reference": "825e937e1b057e25cba4067e048bf5bc0a60a836" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dercoder/omnipay-ecopayz/zipball/583cfee3db1e1e04c74f2d3a56559d0f2af761d4", - "reference": "583cfee3db1e1e04c74f2d3a56559d0f2af761d4", + "url": "https://api.github.com/repos/dercoder/omnipay-ecopayz/zipball/825e937e1b057e25cba4067e048bf5bc0a60a836", + "reference": "825e937e1b057e25cba4067e048bf5bc0a60a836", "shasum": "" }, "require": { "omnipay/common": "~2.3" }, "require-dev": { - "omnipay/tests": "~2.0" + "omnipay/tests": "~2.0", + "satooshi/php-coveralls": "1.0.0" }, "type": "library", "extra": { @@ -1256,7 +1261,7 @@ "pay", "payment" ], - "time": "2015-04-07 13:50:36" + "time": "2016-09-15 16:18:21" }, { "name": "dercoder/omnipay-paysafecard", @@ -1447,35 +1452,35 @@ }, { "name": "doctrine/annotations", - "version": "v1.2.7", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" + "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", - "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/bd4461328621bde0ae6b1b2675fbc6aca4ceb558", + "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "php": ">=5.3.2" + "php": "^5.6 || ^7.0" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^5.6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Annotations\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } }, "notification-url": "https://packagist.org/downloads/", @@ -1511,20 +1516,20 @@ "docblock", "parser" ], - "time": "2015-08-31 12:32:49" + "time": "2016-12-30 15:59:45" }, { "name": "doctrine/cache", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6" + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6", - "reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", "shasum": "" }, "require": { @@ -1581,7 +1586,7 @@ "cache", "caching" ], - "time": "2015-12-31 16:37:02" + "time": "2016-10-29 11:16:17" }, { "name": "doctrine/collections", @@ -1651,16 +1656,16 @@ }, { "name": "doctrine/common", - "version": "v2.6.1", + "version": "v2.7.2", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "a579557bc689580c19fee4e27487a67fe60defc0" + "reference": "930297026c8009a567ac051fd545bf6124150347" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/a579557bc689580c19fee4e27487a67fe60defc0", - "reference": "a579557bc689580c19fee4e27487a67fe60defc0", + "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", + "reference": "930297026c8009a567ac051fd545bf6124150347", "shasum": "" }, "require": { @@ -1669,10 +1674,10 @@ "doctrine/collections": "1.*", "doctrine/inflector": "1.*", "doctrine/lexer": "1.*", - "php": "~5.5|~7.0" + "php": "~5.6|~7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0" + "phpunit/phpunit": "^5.4.6" }, "type": "library", "extra": { @@ -1720,29 +1725,29 @@ "persistence", "spl" ], - "time": "2015-12-25 13:18:31" + "time": "2017-01-13 14:02:13" }, { "name": "doctrine/dbal", - "version": "v2.5.4", + "version": "v2.5.7", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769" + "reference": "3a351369582dade60c750e2cef540eb7b568e6b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/abbdfd1cff43a7b99d027af3be709bc8fc7d4769", - "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/3a351369582dade60c750e2cef540eb7b568e6b3", + "reference": "3a351369582dade60c750e2cef540eb7b568e6b3", "shasum": "" }, "require": { - "doctrine/common": ">=2.4,<2.7-dev", + "doctrine/common": ">=2.4,<2.8-dev", "php": ">=5.3.2" }, "require-dev": { "phpunit/phpunit": "4.*", - "symfony/console": "2.*" + "symfony/console": "2.*||^3.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -1791,7 +1796,7 @@ "persistence", "queryobject" ], - "time": "2016-01-05 22:11:12" + "time": "2017-01-14 21:05:28" }, { "name": "doctrine/inflector", @@ -2330,16 +2335,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.2.1", + "version": "6.2.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "3f808fba627f2c5b69e2501217bf31af349c1427" + "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427", - "reference": "3f808fba627f2c5b69e2501217bf31af349c1427", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60", + "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60", "shasum": "" }, "require": { @@ -2388,32 +2393,32 @@ "rest", "web service" ], - "time": "2016-07-15 17:22:37" + "time": "2016-10-08 15:01:37" }, { "name": "guzzlehttp/promises", - "version": "1.2.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579" + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579", - "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", "shasum": "" }, "require": { "php": ">=5.5.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -2439,7 +2444,7 @@ "keywords": [ "promise" ], - "time": "2016-05-18 16:56:05" + "time": "2016-12-20 10:07:11" }, { "name": "guzzlehttp/psr7", @@ -2552,12 +2557,12 @@ "source": { "type": "git", "url": "https://github.com/incube8/omnipay-multicards.git", - "reference": "54f051406d8620ca3e0279f92419d3c0b04f729c" + "reference": "712b4419ef604e88587098ba3a788eb4221144fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/incube8/omnipay-multicards/zipball/54f051406d8620ca3e0279f92419d3c0b04f729c", - "reference": "54f051406d8620ca3e0279f92419d3c0b04f729c", + "url": "https://api.github.com/repos/incube8/omnipay-multicards/zipball/712b4419ef604e88587098ba3a788eb4221144fd", + "reference": "712b4419ef604e88587098ba3a788eb4221144fd", "shasum": "" }, "require": { @@ -2599,7 +2604,7 @@ "pay", "payment" ], - "time": "2016-07-28 08:44:49" + "time": "2017-01-12 06:17:38" }, { "name": "intervention/image", @@ -2607,12 +2612,12 @@ "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "45a41a38bd1e5290cd51ab773013e6f041b2b711" + "reference": "2bce9a59c43b868300b02a7d31a1e4aa67a200ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/45a41a38bd1e5290cd51ab773013e6f041b2b711", - "reference": "45a41a38bd1e5290cd51ab773013e6f041b2b711", + "url": "https://api.github.com/repos/Intervention/image/zipball/2bce9a59c43b868300b02a7d31a1e4aa67a200ae", + "reference": "2bce9a59c43b868300b02a7d31a1e4aa67a200ae", "shasum": "" }, "require": { @@ -2661,7 +2666,7 @@ "thumbnail", "watermark" ], - "time": "2016-08-19 14:41:12" + "time": "2017-01-10 14:15:56" }, { "name": "ircmaxell/password-compat", @@ -2705,6 +2710,47 @@ ], "time": "2014-11-20 16:49:30" }, + { + "name": "jakoch/phantomjs-installer", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/jakoch/phantomjs-installer.git", + "reference": "b8ee2aac9b95f9a9ee30a05a4df4a0984a8a8b85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jakoch/phantomjs-installer/zipball/b8ee2aac9b95f9a9ee30a05a4df4a0984a8a8b85", + "reference": "b8ee2aac9b95f9a9ee30a05a4df4a0984a8a8b85", + "shasum": "" + }, + "require": { + "ext-openssl": "*" + }, + "type": "custom-installer", + "autoload": { + "psr-0": { + "PhantomInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jens-André Koch", + "email": "jakoch@web.de" + } + ], + "description": "A Composer package which installs the PhantomJS binary (Linux, Windows, Mac) into `/bin` of your project.", + "keywords": [ + "binaries", + "headless", + "phantomjs" + ], + "time": "2016-01-25 16:30:30" + }, { "name": "jakub-onderka/php-console-color", "version": "0.1", @@ -2794,16 +2840,16 @@ }, { "name": "jaybizzle/crawler-detect", - "version": "v1.2.15", + "version": "v1.2.29", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "82fa7ac8598d9791a14c29fc4bb9174108300787" + "reference": "fec4b008d2c5d98d12c6a4c2227281c590d91433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/82fa7ac8598d9791a14c29fc4bb9174108300787", - "reference": "82fa7ac8598d9791a14c29fc4bb9174108300787", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/fec4b008d2c5d98d12c6a4c2227281c590d91433", + "reference": "fec4b008d2c5d98d12c6a4c2227281c590d91433", "shasum": "" }, "require": { @@ -2838,7 +2884,7 @@ "crawlerdetect", "php crawler detect" ], - "time": "2016-08-19 20:27:52" + "time": "2017-01-11 21:06:28" }, { "name": "jaybizzle/laravel-crawler-detect", @@ -2891,20 +2937,20 @@ }, { "name": "jeremeamia/SuperClosure", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/jeremeamia/super_closure.git", - "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938" + "reference": "443c3df3207f176a1b41576ee2a66968a507b3db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/29a88be2a4846d27c1613aed0c9071dfad7b5938", - "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/443c3df3207f176a1b41576ee2a66968a507b3db", + "reference": "443c3df3207f176a1b41576ee2a66968a507b3db", "shasum": "" }, "require": { - "nikic/php-parser": "^1.2|^2.0", + "nikic/php-parser": "^1.2|^2.0|^3.0", "php": ">=5.4", "symfony/polyfill-php56": "^1.0" }, @@ -2914,7 +2960,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } }, "autoload": { @@ -2945,7 +2991,7 @@ "serialize", "tokenizer" ], - "time": "2015-12-05 17:17:57" + "time": "2016-12-07 09:37:55" }, { "name": "jlapp/swaggervel", @@ -2992,6 +3038,62 @@ ], "time": "2016-01-25 15:38:17" }, + { + "name": "jonnyw/php-phantomjs", + "version": "v4.5.1", + "source": { + "type": "git", + "url": "https://github.com/jonnnnyw/php-phantomjs.git", + "reference": "cf8d9a221f4c624aa1537c55a2e181f4b50367d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jonnnnyw/php-phantomjs/zipball/cf8d9a221f4c624aa1537c55a2e181f4b50367d7", + "reference": "cf8d9a221f4c624aa1537c55a2e181f4b50367d7", + "shasum": "" + }, + "require": { + "jakoch/phantomjs-installer": "2.1.1", + "php": ">=5.3.0", + "symfony/config": "~2.3|~3.0", + "symfony/dependency-injection": "~2.3|~3.0", + "symfony/filesystem": "~2.3|~3.0", + "symfony/yaml": "~2.3|~3.0", + "twig/twig": "~1.16" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "smalot/pdfparser": "~0.9", + "zendframework/zendpdf": "~2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "JonnyW\\PhantomJs\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonny Wenmoth", + "email": "contact@jonnyw.me", + "homepage": "http://jonnyw.me/" + } + ], + "description": "A PHP wrapper for loading pages through PhantomJS", + "keywords": [ + "Headless Browser", + "phantomjs", + "testing" + ], + "time": "2016-06-28 16:00:15" + }, { "name": "justinbusschau/omnipay-secpay", "version": "2.0.6", @@ -3145,16 +3247,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.43", + "version": "v5.2.45", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "5490b8f00564bb60839002f86828e27edd1e5610" + "reference": "2a79f920d5584ec6df7cf996d922a742d11095d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/5490b8f00564bb60839002f86828e27edd1e5610", - "reference": "5490b8f00564bb60839002f86828e27edd1e5610", + "url": "https://api.github.com/repos/laravel/framework/zipball/2a79f920d5584ec6df7cf996d922a742d11095d1", + "reference": "2a79f920d5584ec6df7cf996d922a742d11095d1", "shasum": "" }, "require": { @@ -3235,7 +3337,7 @@ "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).", "symfony/css-selector": "Required to use some of the crawler integration testing tools (2.8.*|3.0.*).", "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (2.8.*|3.0.*).", - "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)." + "symfony/psr-http-message-bridge": "Required to use psr7 bridging features (0.2.*)." }, "type": "library", "extra": { @@ -3271,20 +3373,20 @@ "framework", "laravel" ], - "time": "2016-08-10 12:23:59" + "time": "2016-08-26 11:44:52" }, { "name": "laravel/socialite", - "version": "v2.0.18", + "version": "v2.0.20", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "76ee5397fcdea5a062361392abca4eb397e519a3" + "reference": "aca8de9a93a28a119714e289c8bc599bd81aa88d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/76ee5397fcdea5a062361392abca4eb397e519a3", - "reference": "76ee5397fcdea5a062361392abca4eb397e519a3", + "url": "https://api.github.com/repos/laravel/socialite/zipball/aca8de9a93a28a119714e289c8bc599bd81aa88d", + "reference": "aca8de9a93a28a119714e289c8bc599bd81aa88d", "shasum": "" }, "require": { @@ -3325,7 +3427,7 @@ "laravel", "oauth" ], - "time": "2016-06-22 12:40:16" + "time": "2016-11-01 18:49:10" }, { "name": "laravelcollective/bus", @@ -3428,20 +3530,20 @@ }, { "name": "league/flysystem", - "version": "1.0.27", + "version": "1.0.32", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9" + "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/50e2045ed70a7e75a5e30bc3662904f3b67af8a9", - "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b5c4a0031697f46e779a9d1b309c2e1b24daeab", + "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=5.5.9" }, "conflict": { "league/flysystem-sftp": "<1.0.6" @@ -3507,7 +3609,7 @@ "sftp", "storage" ], - "time": "2016-08-10 08:55:11" + "time": "2016-10-19 20:38:46" }, { "name": "league/flysystem-aws-s3-v3", @@ -3835,27 +3937,27 @@ }, { "name": "maatwebsite/excel", - "version": "v2.1.3", + "version": "2.1.8", "source": { "type": "git", "url": "https://github.com/Maatwebsite/Laravel-Excel.git", - "reference": "c6ab521e8a5f2a4acc83cb575c4a09733a5e17c9" + "reference": "d9c398f7eba0ac9caf9c104dd0d55bd6a5ff231e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/c6ab521e8a5f2a4acc83cb575c4a09733a5e17c9", - "reference": "c6ab521e8a5f2a4acc83cb575c4a09733a5e17c9", + "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/d9c398f7eba0ac9caf9c104dd0d55bd6a5ff231e", + "reference": "d9c398f7eba0ac9caf9c104dd0d55bd6a5ff231e", "shasum": "" }, "require": { - "illuminate/cache": "5.0.*|5.1.*|5.2.*", - "illuminate/config": "5.0.*|5.1.*|5.2.*", - "illuminate/filesystem": "5.0.*|5.1.*|5.2.*", - "illuminate/support": "5.0.*|5.1.*|5.2.*", + "illuminate/cache": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*", "nesbot/carbon": "~1.0", "php": ">=5.5", "phpoffice/phpexcel": "1.8.*", - "tijsverkoyen/css-to-inline-styles": "~1.5" + "tijsverkoyen/css-to-inline-styles": "~2.0" }, "require-dev": { "mockery/mockery": "~0.9", @@ -3864,10 +3966,10 @@ "phpunit/phpunit": "~4.0" }, "suggest": { - "illuminate/http": "5.0.*|5.1.*|5.2.*", - "illuminate/queue": "5.0.*|5.1.*|5.2.*", - "illuminate/routing": "5.0.*|5.1.*|5.2.*", - "illuminate/view": "5.0.*|5.1.*|5.2.*" + "illuminate/http": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/queue": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*", + "illuminate/view": "5.0.*|5.1.*|5.2.*|5.3.*" }, "type": "library", "autoload": { @@ -3898,20 +4000,20 @@ "import", "laravel" ], - "time": "2016-07-09 09:40:10" + "time": "2016-12-09 19:11:09" }, { "name": "maximebf/debugbar", - "version": "v1.11.1", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f" + "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f", - "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/afee79a236348e39a44cb837106b7c5b4897ac2a", + "reference": "afee79a236348e39a44cb837106b7c5b4897ac2a", "shasum": "" }, "require": { @@ -3930,7 +4032,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -3959,7 +4061,7 @@ "debug", "debugbar" ], - "time": "2016-01-22 12:22:23" + "time": "2017-01-05 08:46:19" }, { "name": "meebio/omnipay-creditcall", @@ -4025,12 +4127,12 @@ "source": { "type": "git", "url": "https://github.com/meebio/omnipay-secure-trading.git", - "reference": "992224a3c8dd834ee18f6f253a77ecb4c87c1c1a" + "reference": "370a79513e12db2c4c7484cf1eff331a9993bbe0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/meebio/omnipay-secure-trading/zipball/992224a3c8dd834ee18f6f253a77ecb4c87c1c1a", - "reference": "992224a3c8dd834ee18f6f253a77ecb4c87c1c1a", + "url": "https://api.github.com/repos/meebio/omnipay-secure-trading/zipball/370a79513e12db2c4c7484cf1eff331a9993bbe0", + "reference": "370a79513e12db2c4c7484cf1eff331a9993bbe0", "shasum": "" }, "require": { @@ -4075,7 +4177,7 @@ "secure trading", "securetrading" ], - "time": "2016-01-05 09:26:36" + "time": "2016-12-14 14:58:56" }, { "name": "mfauveau/omnipay-pacnet", @@ -4161,16 +4263,16 @@ }, { "name": "monolog/monolog", - "version": "1.21.0", + "version": "1.22.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952" + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952", - "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", "shasum": "" }, "require": { @@ -4181,7 +4283,7 @@ "psr/log-implementation": "1.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", "graylog2/gelf-php": "~1.0", "jakub-onderka/php-parallel-lint": "0.9", @@ -4235,7 +4337,7 @@ "logging", "psr-3" ], - "time": "2016-07-29 03:23:52" + "time": "2016-11-26 00:15:39" }, { "name": "mtdowling/cron-expression", @@ -4283,16 +4385,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "192f93e43c2c97acde7694993ab171b3de284093" + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/192f93e43c2c97acde7694993ab171b3de284093", - "reference": "192f93e43c2c97acde7694993ab171b3de284093", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", + "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", "shasum": "" }, "require": { @@ -4334,7 +4436,7 @@ "json", "jsonpath" ], - "time": "2016-01-05 18:25:05" + "time": "2016-12-03 22:08:25" }, { "name": "nesbot/carbon", @@ -4385,16 +4487,16 @@ }, { "name": "nikic/php-parser", - "version": "v2.1.0", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "47b254ea51f1d6d5dc04b9b299e88346bf2369e3" + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/47b254ea51f1d6d5dc04b9b299e88346bf2369e3", - "reference": "47b254ea51f1d6d5dc04b9b299e88346bf2369e3", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4dd659edadffdc2143e4753df655d866dbfeedf0", + "reference": "4dd659edadffdc2143e4753df655d866dbfeedf0", "shasum": "" }, "require": { @@ -4432,20 +4534,20 @@ "parser", "php" ], - "time": "2016-04-19 13:41:41" + "time": "2016-09-16 12:04:44" }, { "name": "nwidart/laravel-modules", - "version": "1.14.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "2d05b9c5ac23cb800725ba760d9a323820603d9f" + "reference": "c6c06f654dc1f946a858ff9360daaf6194219a80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/2d05b9c5ac23cb800725ba760d9a323820603d9f", - "reference": "2d05b9c5ac23cb800725ba760d9a323820603d9f", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/c6c06f654dc1f946a858ff9360daaf6194219a80", + "reference": "c6c06f654dc1f946a858ff9360daaf6194219a80", "shasum": "" }, "require": { @@ -4486,7 +4588,7 @@ "nwidart", "rad" ], - "time": "2016-10-19 09:27:32" + "time": "2017-01-12 16:33:16" }, { "name": "omnipay/2checkout", @@ -4729,16 +4831,16 @@ }, { "name": "omnipay/buckaroo", - "version": "v2.0.2", + "version": "v2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-buckaroo.git", - "reference": "aeadff6281fb4ecba1ff341bb8288e3e4a54d50e" + "reference": "7fccd4382ba87f6535cb399892c687928615dad7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-buckaroo/zipball/aeadff6281fb4ecba1ff341bb8288e3e4a54d50e", - "reference": "aeadff6281fb4ecba1ff341bb8288e3e4a54d50e", + "url": "https://api.github.com/repos/thephpleague/omnipay-buckaroo/zipball/7fccd4382ba87f6535cb399892c687928615dad7", + "reference": "7fccd4382ba87f6535cb399892c687928615dad7", "shasum": "" }, "require": { @@ -4782,7 +4884,7 @@ "pay", "payment" ], - "time": "2016-06-22 07:37:02" + "time": "2016-08-10 04:41:17" }, { "name": "omnipay/cardsave", @@ -5247,16 +5349,16 @@ }, { "name": "omnipay/manual", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-manual.git", - "reference": "db31b81dc3a9ccbc61a805dd9f922b7bfd0eb0e9" + "reference": "d896909093422f25a9deec13232b31ea69a72994" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-manual/zipball/db31b81dc3a9ccbc61a805dd9f922b7bfd0eb0e9", - "reference": "db31b81dc3a9ccbc61a805dd9f922b7bfd0eb0e9", + "url": "https://api.github.com/repos/thephpleague/omnipay-manual/zipball/d896909093422f25a9deec13232b31ea69a72994", + "reference": "d896909093422f25a9deec13232b31ea69a72994", "shasum": "" }, "require": { @@ -5300,20 +5402,20 @@ "pay", "payment" ], - "time": "2016-03-29 17:52:49" + "time": "2016-12-28 03:02:15" }, { "name": "omnipay/migs", - "version": "v2.1.1", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-migs.git", - "reference": "b49e9f2816a5cdd3c1ba2bcb46a0c73dc43c0b3b" + "reference": "c15be0960ae578d60cabc7c0a2895da41430821f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-migs/zipball/b49e9f2816a5cdd3c1ba2bcb46a0c73dc43c0b3b", - "reference": "b49e9f2816a5cdd3c1ba2bcb46a0c73dc43c0b3b", + "url": "https://api.github.com/repos/thephpleague/omnipay-migs/zipball/c15be0960ae578d60cabc7c0a2895da41430821f", + "reference": "c15be0960ae578d60cabc7c0a2895da41430821f", "shasum": "" }, "require": { @@ -5358,7 +5460,7 @@ "pay", "payment" ], - "time": "2014-09-17 00:38:25" + "time": "2016-09-22 01:58:30" }, { "name": "omnipay/mollie", @@ -5534,16 +5636,16 @@ }, { "name": "omnipay/netbanx", - "version": "v2.1.1", + "version": "v2.2", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-netbanx.git", - "reference": "a56900120e676385cc42d67c82985fcaaa752140" + "reference": "3684bbbf0dbdb977c1484d7252d776d81c68c813" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-netbanx/zipball/a56900120e676385cc42d67c82985fcaaa752140", - "reference": "a56900120e676385cc42d67c82985fcaaa752140", + "url": "https://api.github.com/repos/thephpleague/omnipay-netbanx/zipball/3684bbbf0dbdb977c1484d7252d776d81c68c813", + "reference": "3684bbbf0dbdb977c1484d7252d776d81c68c813", "shasum": "" }, "require": { @@ -5587,7 +5689,7 @@ "pay", "payment" ], - "time": "2014-09-17 00:38:46" + "time": "2016-09-21 10:52:03" }, { "name": "omnipay/omnipay", @@ -5880,16 +5982,16 @@ }, { "name": "omnipay/paypal", - "version": "v2.5.5", + "version": "v2.6.3", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-paypal.git", - "reference": "ec7fc864ae0699dd832cd727da26286a5354a35e" + "reference": "e06c8814deacc793715bb9fd3fcae5995b5d8d6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/ec7fc864ae0699dd832cd727da26286a5354a35e", - "reference": "ec7fc864ae0699dd832cd727da26286a5354a35e", + "url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/e06c8814deacc793715bb9fd3fcae5995b5d8d6b", + "reference": "e06c8814deacc793715bb9fd3fcae5995b5d8d6b", "shasum": "" }, "require": { @@ -5934,7 +6036,7 @@ "paypal", "purchase" ], - "time": "2016-07-15 04:26:38" + "time": "2016-12-22 12:35:25" }, { "name": "omnipay/pin", @@ -6054,16 +6156,16 @@ }, { "name": "omnipay/securepay", - "version": "v2.1.0", + "version": "v2.1.1", "source": { "type": "git", - "url": "https://github.com/omnipay/securepay.git", - "reference": "09503288fdb1efcd81bd0ac3c1800ce0ce13cc6e" + "url": "https://github.com/thephpleague/omnipay-securepay.git", + "reference": "e68954d455edc8337bb30e6c579d32b6e691d29a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/omnipay/securepay/zipball/09503288fdb1efcd81bd0ac3c1800ce0ce13cc6e", - "reference": "09503288fdb1efcd81bd0ac3c1800ce0ce13cc6e", + "url": "https://api.github.com/repos/thephpleague/omnipay-securepay/zipball/e68954d455edc8337bb30e6c579d32b6e691d29a", + "reference": "e68954d455edc8337bb30e6c579d32b6e691d29a", "shasum": "" }, "require": { @@ -6094,11 +6196,11 @@ }, { "name": "Omnipay Contributors", - "homepage": "https://github.com/omnipay/securepay/contributors" + "homepage": "https://github.com/thephpleague/omnipay-securepay/contributors" } ], "description": "SecurePay driver for the Omnipay payment processing library", - "homepage": "https://github.com/omnipay/securepay", + "homepage": "https://github.com/thephpleague/omnipay-securepay", "keywords": [ "gateway", "merchant", @@ -6107,7 +6209,7 @@ "payment", "securepay" ], - "time": "2014-04-14 11:26:20" + "time": "2016-11-09 04:52:42" }, { "name": "omnipay/stripe", @@ -6115,12 +6217,12 @@ "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-stripe.git", - "reference": "3d21b68403fb4214cbee07d0c4be55a1bb0b1805" + "reference": "6b05b89be73f10408b8123acc8686d55dfa8a8c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/3d21b68403fb4214cbee07d0c4be55a1bb0b1805", - "reference": "3d21b68403fb4214cbee07d0c4be55a1bb0b1805", + "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/6b05b89be73f10408b8123acc8686d55dfa8a8c3", + "reference": "6b05b89be73f10408b8123acc8686d55dfa8a8c3", "shasum": "" }, "require": { @@ -6164,7 +6266,7 @@ "payment", "stripe" ], - "time": "2016-08-06 04:11:43" + "time": "2017-01-12 06:58:49" }, { "name": "omnipay/targetpay", @@ -6546,22 +6648,30 @@ }, { "name": "psr/log", - "version": "1.0.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "shasum": "" }, + "require": { + "php": ">=5.3.0" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "psr-0": { - "Psr\\Log\\": "" + "psr-4": { + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6575,12 +6685,13 @@ } ], "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], - "time": "2012-12-21 11:40:51" + "time": "2016-10-10 12:19:37" }, { "name": "psy/psysh", @@ -6713,16 +6824,16 @@ }, { "name": "samvaughton/omnipay-barclays-epdq", - "version": "2.2.3", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/samvaughton/omnipay-barclays-epdq.git", - "reference": "ee90e438ce7cb46ef8c1001d102b5711872dabae" + "reference": "736b0ad83e905c59500d40dc41d3ea2e49c8dee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/samvaughton/omnipay-barclays-epdq/zipball/ee90e438ce7cb46ef8c1001d102b5711872dabae", - "reference": "ee90e438ce7cb46ef8c1001d102b5711872dabae", + "url": "https://api.github.com/repos/samvaughton/omnipay-barclays-epdq/zipball/736b0ad83e905c59500d40dc41d3ea2e49c8dee0", + "reference": "736b0ad83e905c59500d40dc41d3ea2e49c8dee0", "shasum": "" }, "require": { @@ -6772,7 +6883,7 @@ "payment" ], "abandoned": "digitickets/omnipay-barclays-epdq", - "time": "2016-06-09 16:42:25" + "time": "2016-10-12 15:09:24" }, { "name": "simshaun/recurr", @@ -6780,12 +6891,12 @@ "source": { "type": "git", "url": "https://github.com/simshaun/recurr.git", - "reference": "78a0d8bcee193bfc92540d5d0663884b2c954aa9" + "reference": "699a55524db1a086ba2c607bd891ecf03dbd572a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/simshaun/recurr/zipball/78a0d8bcee193bfc92540d5d0663884b2c954aa9", - "reference": "78a0d8bcee193bfc92540d5d0663884b2c954aa9", + "url": "https://api.github.com/repos/simshaun/recurr/zipball/699a55524db1a086ba2c607bd891ecf03dbd572a", + "reference": "699a55524db1a086ba2c607bd891ecf03dbd572a", "shasum": "" }, "require": { @@ -6826,69 +6937,7 @@ "recurring", "rrule" ], - "time": "2016-08-11 21:07:21" - }, - { - "name": "sly/notification-pusher", - "version": "v2.2.2", - "source": { - "type": "git", - "url": "https://github.com/Ph3nol/NotificationPusher.git", - "reference": "6112841c4b679bc4f6cf01f6cf655e24794bc670" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ph3nol/NotificationPusher/zipball/6112841c4b679bc4f6cf01f6cf655e24794bc670", - "reference": "6112841c4b679bc4f6cf01f6cf655e24794bc670", - "shasum": "" - }, - "require": { - "php": ">=5.3.2", - "symfony/console": ">=2.3", - "symfony/options-resolver": ">=2.3", - "symfony/process": ">=2.3", - "zendframework/zendservice-apple-apns": "1.*", - "zendframework/zendservice-google-gcm": "1.*" - }, - "require-dev": { - "atoum/atoum": "dev-master" - }, - "type": "standalone", - "autoload": { - "psr-0": { - "Sly": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Cédric Dugat", - "email": "cedric@dugat.me" - }, - { - "name": "Contributors", - "homepage": "https://github.com/Ph3nol/NotificationPusher/contributors" - } - ], - "description": "Standalone PHP library for easy devices notifications push.", - "homepage": "https://github.com/Ph3nol/NotificationPusher", - "keywords": [ - "android", - "apns", - "apple", - "gcm", - "iphone", - "message", - "notification", - "push", - "pusher" - ], - "time": "2013-12-08 20:35:03" + "time": "2016-12-19 21:12:08" }, { "name": "softcommerce/omnipay-paytrace", @@ -6991,23 +7040,24 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.3", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153" + "reference": "cd142238a339459b10da3d8234220963f392540c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/4cc92842069c2bbc1f28daaaf1d2576ec4dfe153", - "reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/cd142238a339459b10da3d8234220963f392540c", + "reference": "cd142238a339459b10da3d8234220963f392540c", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "mockery/mockery": "~0.9.1" + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" }, "type": "library", "extra": { @@ -7040,20 +7090,20 @@ "mail", "mailer" ], - "time": "2016-07-08 11:51:25" + "time": "2016-12-29 10:02:40" }, { "name": "symfony/class-loader", - "version": "v3.1.3", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "817f09b4c37b7688fa4342cb4642d8f2d81c1097" + "reference": "0152f7a47acd564ca62c652975c2b32ac6d613a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/817f09b4c37b7688fa4342cb4642d8f2d81c1097", - "reference": "817f09b4c37b7688fa4342cb4642d8f2d81c1097", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/0152f7a47acd564ca62c652975c2b32ac6d613a6", + "reference": "0152f7a47acd564ca62c652975c2b32ac6d613a6", "shasum": "" }, "require": { @@ -7069,7 +7119,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -7096,7 +7146,63 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2016-07-10 08:05:47" + "time": "2017-01-10 14:14:38" + }, + { + "name": "symfony/config", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "c5ea878b5a7f6a01b9a2f182f905831711b9ff3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/c5ea878b5a7f6a01b9a2f182f905831711b9ff3f", + "reference": "c5ea878b5a7f6a01b9a2f182f905831711b9ff3f", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "require-dev": { + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:32:22" }, { "name": "symfony/console", @@ -7160,16 +7266,16 @@ }, { "name": "symfony/css-selector", - "version": "v3.1.3", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2851e1932d77ce727776154d659b232d061e816a" + "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2851e1932d77ce727776154d659b232d061e816a", - "reference": "2851e1932d77ce727776154d659b232d061e816a", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f0e628f04fc055c934b3211cfabdb1c59eefbfaa", + "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa", "shasum": "" }, "require": { @@ -7178,7 +7284,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -7209,7 +7315,7 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2017-01-02 20:32:22" }, { "name": "symfony/debug", @@ -7269,17 +7375,80 @@ "time": "2016-07-30 07:22:48" }, { - "name": "symfony/event-dispatcher", - "version": "v2.8.14", + "name": "symfony/dependency-injection", + "version": "v3.2.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "22b2c97cffc6a612db82084f9e7823b095958751" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", - "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/22b2c97cffc6a612db82084f9e7823b095958751", + "reference": "22b2c97cffc6a612db82084f9e7823b095958751", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/yaml": "<3.2" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~3.2" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2017-01-10 14:21:25" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "74877977f90fb9c3e46378d5764217c55f32df34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/74877977f90fb9c3e46378d5764217c55f32df34", + "reference": "74877977f90fb9c3e46378d5764217c55f32df34", "shasum": "" }, "require": { @@ -7326,7 +7495,56 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-10-13 01:43:15" + "time": "2017-01-02 20:30:24" + }, + { + "name": "symfony/filesystem", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a0c6ef2dc78d33b58d91d3a49f49797a184d06f4", + "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2017-01-08 20:47:33" }, { "name": "symfony/finder", @@ -7379,16 +7597,16 @@ }, { "name": "symfony/http-foundation", - "version": "v2.8.14", + "version": "v2.8.16", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "4f8c167732bbf3ba4284c0915f57b332091f6b68" + "reference": "464cdde6757a40701d758112cc7ff2c6adf6e82f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4f8c167732bbf3ba4284c0915f57b332091f6b68", - "reference": "4f8c167732bbf3ba4284c0915f57b332091f6b68", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/464cdde6757a40701d758112cc7ff2c6adf6e82f", + "reference": "464cdde6757a40701d758112cc7ff2c6adf6e82f", "shasum": "" }, "require": { @@ -7430,7 +7648,7 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-11-15 23:02:12" + "time": "2017-01-08 20:43:03" }, { "name": "symfony/http-kernel", @@ -7516,16 +7734,16 @@ }, { "name": "symfony/options-resolver", - "version": "v3.1.3", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "30605874d99af0cde6c41fd39e18546330c38100" + "reference": "855429e3e9014b9dafee2a667de304c3aaa86fe6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/30605874d99af0cde6c41fd39e18546330c38100", - "reference": "30605874d99af0cde6c41fd39e18546330c38100", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/855429e3e9014b9dafee2a667de304c3aaa86fe6", + "reference": "855429e3e9014b9dafee2a667de304c3aaa86fe6", "shasum": "" }, "require": { @@ -7534,7 +7752,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -7566,7 +7784,7 @@ "configuration", "options" ], - "time": "2016-05-12 15:59:27" + "time": "2017-01-02 20:32:22" }, { "name": "symfony/polyfill-mbstring", @@ -7743,16 +7961,16 @@ }, { "name": "symfony/polyfill-php56", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a" + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/3edf57a8fbf9a927533344cef65ad7e1cf31030a", - "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/1dd42b9b89556f18092f3d1ada22cb05ac85383c", + "reference": "1dd42b9b89556f18092f3d1ada22cb05ac85383c", "shasum": "" }, "require": { @@ -7762,7 +7980,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -7795,20 +8013,20 @@ "portable", "shim" ], - "time": "2016-05-18 14:26:46" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/polyfill-util", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99" + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ef830ce3d218e622b221d6bfad42c751d974bf99", - "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/746bce0fca664ac0a575e465f65c6643faddf7fb", + "reference": "746bce0fca664ac0a575e465f65c6643faddf7fb", "shasum": "" }, "require": { @@ -7817,7 +8035,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -7847,7 +8065,7 @@ "polyfill", "shim" ], - "time": "2016-05-18 14:26:46" + "time": "2016-11-14 01:06:16" }, { "name": "symfony/process", @@ -8101,30 +8319,85 @@ "time": "2016-07-26 08:03:56" }, { - "name": "tijsverkoyen/css-to-inline-styles", - "version": "1.5.5", + "name": "symfony/yaml", + "version": "v3.2.2", "source": { "type": "git", - "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "9753fc340726e327e4d48b7c0604f85475ae0bc3" + "url": "https://github.com/symfony/yaml.git", + "reference": "50eadbd7926e31842893c957eca362b21592a97d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/9753fc340726e327e4d48b7c0604f85475ae0bc3", - "reference": "9753fc340726e327e4d48b7c0604f85475ae0bc3", + "url": "https://api.github.com/repos/symfony/yaml/zipball/50eadbd7926e31842893c957eca362b21592a97d", + "reference": "50eadbd7926e31842893c957eca362b21592a97d", "shasum": "" }, "require": { - "php": ">=5.3.0", - "symfony/css-selector": "~2.1|~3.0" + "php": ">=5.5.9" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-01-03 13:51:32" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b", + "reference": "ab03919dfd85a74ae0372f8baf9f3c7d5c03b04b", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7", + "symfony/css-selector": "^2.7|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|5.1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -8134,7 +8407,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD" + "BSD-3-Clause" ], "authors": [ { @@ -8145,25 +8418,25 @@ ], "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", - "time": "2015-12-08 16:14:14" + "time": "2016-09-20 12:50:39" }, { "name": "true/punycode", - "version": "v2.1.0", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/true/php-punycode.git", - "reference": "74033cbe9fdd3eba597f8af501947a125b3b8087" + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/true/php-punycode/zipball/74033cbe9fdd3eba597f8af501947a125b3b8087", - "reference": "74033cbe9fdd3eba597f8af501947a125b3b8087", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", "shasum": "" }, "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "phpunit/phpunit": "~4.7", @@ -8191,26 +8464,26 @@ "idna", "punycode" ], - "time": "2016-08-09 14:50:44" + "time": "2016-11-16 10:37:54" }, { "name": "turbo124/laravel-push-notification", - "version": "dev-laravel5", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/turbo124/laravel-push-notification.git", - "reference": "b703b0fe02719a540139f5d26b73ac53a198b50d" + "reference": "10a2cf7702a90cdc3064eaa9a157b973ad69bfb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/turbo124/laravel-push-notification/zipball/b703b0fe02719a540139f5d26b73ac53a198b50d", - "reference": "b703b0fe02719a540139f5d26b73ac53a198b50d", + "url": "https://api.github.com/repos/turbo124/laravel-push-notification/zipball/10a2cf7702a90cdc3064eaa9a157b973ad69bfb3", + "reference": "10a2cf7702a90cdc3064eaa9a157b973ad69bfb3", "shasum": "" }, "require": { "illuminate/support": "5.*", "php": ">=5.3.0", - "sly/notification-pusher": "2.*" + "turbo124/notification-pusher": "3.*" }, "type": "library", "autoload": { @@ -8233,7 +8506,71 @@ "notification", "push" ], - "time": "2015-06-15 13:11:17" + "time": "2017-01-09 08:49:59" + }, + { + "name": "turbo124/notification-pusher", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/turbo124/NotificationPusher.git", + "reference": "52e8298895ebbbaed3cd6b325057767f15c77b4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/turbo124/NotificationPusher/zipball/52e8298895ebbbaed3cd6b325057767f15c77b4c", + "reference": "52e8298895ebbbaed3cd6b325057767f15c77b4c", + "shasum": "" + }, + "require": { + "doctrine/inflector": "~1.0", + "php": ">=5.5", + "symfony/console": "~2.3|~3.0", + "symfony/options-resolver": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0", + "zendframework/zendservice-apple-apns": "^1.1.0", + "zendframework/zendservice-google-gcm": "1.*" + }, + "require-dev": { + "atoum/atoum": "dev-master" + }, + "bin": [ + "np" + ], + "type": "standalone", + "autoload": { + "psr-0": { + "Sly": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cédric Dugat", + "email": "cedric@dugat.me" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Ph3nol/NotificationPusher/contributors" + } + ], + "description": "Standalone PHP library for easy devices notifications push.", + "homepage": "https://github.com/Ph3nol/NotificationPusher", + "keywords": [ + "android", + "apns", + "apple", + "gcm", + "iphone", + "message", + "notification", + "push", + "pusher" + ], + "time": "2017-01-10 02:17:34" }, { "name": "twbs/bootstrap", @@ -8286,6 +8623,67 @@ ], "time": "2016-07-25 15:51:55" }, + { + "name": "twig/twig", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "ddc9e3e20ee9c0b6908f401ac8353635b750eca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ddc9e3e20ee9c0b6908f401ac8353635b750eca7", + "reference": "ddc9e3e20ee9c0b6908f401ac8353635b750eca7", + "shasum": "" + }, + "require": { + "php": ">=5.2.7" + }, + "require-dev": { + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.31-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2017-01-11 19:36:15" + }, { "name": "vink/omnipay-komoju", "version": "v1.0", @@ -8336,16 +8734,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "9ca5644c536654e9509b9d257f53c58630eb2a6a" + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/9ca5644c536654e9509b9d257f53c58630eb2a6a", - "reference": "9ca5644c536654e9509b9d257f53c58630eb2a6a", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", "shasum": "" }, "require": { @@ -8357,7 +8755,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.4-dev" } }, "autoload": { @@ -8382,7 +8780,7 @@ "env", "environment" ], - "time": "2016-06-14 14:14:52" + "time": "2016-09-01 10:05:43" }, { "name": "webpatser/laravel-countries", @@ -8390,12 +8788,12 @@ "source": { "type": "git", "url": "https://github.com/webpatser/laravel-countries.git", - "reference": "2a310c0ff1b112d3710223d3bd51e8a30c5a304d" + "reference": "0a7512a3b00eb1c9a3eabe563b4ae73693fe2f8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webpatser/laravel-countries/zipball/2a310c0ff1b112d3710223d3bd51e8a30c5a304d", - "reference": "2a310c0ff1b112d3710223d3bd51e8a30c5a304d", + "url": "https://api.github.com/repos/webpatser/laravel-countries/zipball/0a7512a3b00eb1c9a3eabe563b4ae73693fe2f8a", + "reference": "0a7512a3b00eb1c9a3eabe563b4ae73693fe2f8a", "shasum": "" }, "require": { @@ -8434,24 +8832,24 @@ "iso_3166_3", "laravel" ], - "time": "2016-02-25 10:29:59" + "time": "2016-11-24 10:51:16" }, { "name": "websight/l5-google-cloud-storage", - "version": "1.0.3", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/websightgmbh/l5-google-cloud-storage.git", - "reference": "c1cac9985dfce60010234c9dca127f3543d2d594" + "reference": "350245570862ae58497a3a6ef966306651c44771" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/websightgmbh/l5-google-cloud-storage/zipball/c1cac9985dfce60010234c9dca127f3543d2d594", - "reference": "c1cac9985dfce60010234c9dca127f3543d2d594", + "url": "https://api.github.com/repos/websightgmbh/l5-google-cloud-storage/zipball/350245570862ae58497a3a6ef966306651c44771", + "reference": "350245570862ae58497a3a6ef966306651c44771", "shasum": "" }, "require": { - "illuminate/support": "~5.0.17|5.1.*|5.2.*", + "illuminate/support": "~5.0.17|5.1.*|5.2.*|5.3.*", "superbalist/flysystem-google-storage": "^1.0" }, "type": "library", @@ -8477,7 +8875,7 @@ "laravel", "laravel5" ], - "time": "2016-03-04 11:57:00" + "time": "2016-09-21 08:17:51" }, { "name": "wepay/php-sdk", @@ -8559,21 +8957,21 @@ }, { "name": "wildbit/swiftmailer-postmark", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/wildbit/swiftmailer-postmark.git", - "reference": "175500a1f29b8a6192a2f0a147145aae345e6a21" + "reference": "7b69441324b40517d4ef69703677853fb367ee4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wildbit/swiftmailer-postmark/zipball/175500a1f29b8a6192a2f0a147145aae345e6a21", - "reference": "175500a1f29b8a6192a2f0a147145aae345e6a21", + "url": "https://api.github.com/repos/wildbit/swiftmailer-postmark/zipball/7b69441324b40517d4ef69703677853fb367ee4e", + "reference": "7b69441324b40517d4ef69703677853fb367ee4e", "shasum": "" }, "require": { "guzzlehttp/guzzle": "~6.0", - "swiftmailer/swiftmailer": "~5.1" + "swiftmailer/swiftmailer": ">=4.1.5" }, "require-dev": { "phpunit/phpunit": "~4.5" @@ -8598,7 +8996,7 @@ } ], "description": "A Swiftmailer Transport for Postmark.", - "time": "2016-07-26 11:42:22" + "time": "2016-09-13 20:43:41" }, { "name": "zendframework/zend-escaper", @@ -8795,31 +9193,31 @@ }, { "name": "zendframework/zend-stdlib", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "8bafa58574204bdff03c275d1d618aaa601588ae" + "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/8bafa58574204bdff03c275d1d618aaa601588ae", - "reference": "8bafa58574204bdff03c275d1d618aaa601588ae", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/debedcfc373a293f9250cc9aa03cf121428c8e78", + "reference": "debedcfc373a293f9250cc9aa03cf121428c8e78", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0" + "php": "^5.6 || ^7.0" }, "require-dev": { "athletic/athletic": "~0.1", - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "^2.6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev", - "dev-develop": "3.1-dev" + "dev-master": "3.1-dev", + "dev-develop": "3.2-dev" } }, "autoload": { @@ -8836,7 +9234,7 @@ "stdlib", "zf2" ], - "time": "2016-04-12 21:19:36" + "time": "2016-09-13 14:38:50" }, { "name": "zendframework/zend-uri", @@ -9045,16 +9443,16 @@ }, { "name": "zircote/swagger-php", - "version": "2.0.7", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "299df0b51f641225ed50ba5199b21e8cfc34e4f4" + "reference": "39f4c30692a4925597e7d4280fc58794fb4f3730" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/299df0b51f641225ed50ba5199b21e8cfc34e4f4", - "reference": "299df0b51f641225ed50ba5199b21e8cfc34e4f4", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/39f4c30692a4925597e7d4280fc58794fb4f3730", + "reference": "39f4c30692a4925597e7d4280fc58794fb4f3730", "shasum": "" }, "require": { @@ -9063,7 +9461,8 @@ "symfony/finder": "*" }, "require-dev": { - "squizlabs/php_codesniffer": "2.*", + "phpunit/phpunit": ">=4.8", + "squizlabs/php_codesniffer": ">=2.7", "zendframework/zend-form": "<2.8" }, "bin": [ @@ -9102,30 +9501,31 @@ "rest", "service discovery" ], - "time": "2016-05-27 09:35:51" + "time": "2016-12-16 12:39:03" } ], "packages-dev": [ { "name": "behat/gherkin", - "version": "v4.4.1", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911" + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", - "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", "shasum": "" }, "require": { "php": ">=5.3.1" }, "require-dev": { - "phpunit/phpunit": "~4.0", - "symfony/yaml": "~2.1" + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -9162,20 +9562,20 @@ "gherkin", "parser" ], - "time": "2015-12-30 14:47:00" + "time": "2016-10-30 11:50:56" }, { "name": "codeception/c3", - "version": "2.0.7", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/Codeception/c3.git", - "reference": "51eec2c3a8e093deb621f58004f0393dc0871983" + "reference": "d71e5d2b015169fce61c923bfbf41c3d83087604" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/c3/zipball/51eec2c3a8e093deb621f58004f0393dc0871983", - "reference": "51eec2c3a8e093deb621f58004f0393dc0871983", + "url": "https://api.github.com/repos/Codeception/c3/zipball/d71e5d2b015169fce61c923bfbf41c3d83087604", + "reference": "d71e5d2b015169fce61c923bfbf41c3d83087604", "shasum": "" }, "require": { @@ -9212,20 +9612,20 @@ "code coverage", "codecoverage" ], - "time": "2016-06-27 16:49:12" + "time": "2016-09-16 22:57:33" }, { "name": "codeception/codeception", - "version": "2.2.4", + "version": "2.2.7", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "ea617b8b55e6e33cdd47edeafde5d3f6466049e2" + "reference": "86770e89d266557c20dd0b1de5390e706f4770c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/ea617b8b55e6e33cdd47edeafde5d3f6466049e2", - "reference": "ea617b8b55e6e33cdd47edeafde5d3f6466049e2", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/86770e89d266557c20dd0b1de5390e706f4770c1", + "reference": "86770e89d266557c20dd0b1de5390e706f4770c1", "shasum": "" }, "require": { @@ -9236,21 +9636,21 @@ "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", "php": ">=5.4.0 <8.0", - "phpunit/php-code-coverage": ">=2.1.3", - "phpunit/phpunit": ">4.8.20 <5.5", + "phpunit/php-code-coverage": ">=2.1.3 <5.0", + "phpunit/phpunit": ">4.8.20 <6.0", "sebastian/comparator": "~1.1", "sebastian/diff": "^1.4", "symfony/browser-kit": ">=2.7 <4.0", "symfony/console": ">=2.7 <4.0", "symfony/css-selector": ">=2.7 <4.0", - "symfony/dom-crawler": ">=2.7 <4.0", + "symfony/dom-crawler": ">=2.7.5 <4.0", "symfony/event-dispatcher": ">=2.7 <4.0", "symfony/finder": ">=2.7 <4.0", "symfony/yaml": ">=2.7 <4.0" }, "require-dev": { "codeception/specify": "~0.3", - "facebook/php-sdk-v4": "~5.0", + "facebook/graph-sdk": "~5.3", "flow/jsonpath": "~0.2", "league/factory-muffin": "^3.0", "league/factory-muffin-faker": "^1.0", @@ -9259,7 +9659,8 @@ "pda/pheanstalk": "~3.0", "php-amqplib/php-amqplib": "~2.4", "predis/predis": "^1.0", - "squizlabs/php_codesniffer": "~2.0" + "squizlabs/php_codesniffer": "~2.0", + "vlucas/phpdotenv": "^2.4.0" }, "suggest": { "codeception/specify": "BDD-style code blocks", @@ -9303,7 +9704,7 @@ "functional testing", "unit testing" ], - "time": "2016-08-14 12:28:58" + "time": "2016-12-05 04:12:24" }, { "name": "doctrine/instantiator", @@ -9361,24 +9762,25 @@ }, { "name": "facebook/webdriver", - "version": "1.1.3", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "b7186fb1bcfda956d237f59face250d06ef47253" + "reference": "af21de3ae5306a8ca0bcc02a19735dadc43e83f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/b7186fb1bcfda956d237f59face250d06ef47253", - "reference": "b7186fb1bcfda956d237f59face250d06ef47253", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/af21de3ae5306a8ca0bcc02a19735dadc43e83f3", + "reference": "af21de3ae5306a8ca0bcc02a19735dadc43e83f3", "shasum": "" }, "require": { "ext-curl": "*", - "php": ">=5.3.19" + "php": "^5.5 || ~7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^1.11", + "php-mock/php-mock-phpunit": "^1.1", "phpunit/phpunit": "4.6.* || ~5.0", "squizlabs/php_codesniffer": "^2.6" }, @@ -9403,7 +9805,7 @@ "selenium", "webdriver" ], - "time": "2016-08-10 00:44:08" + "time": "2016-10-14 15:16:51" }, { "name": "phpdocumentor/reflection-common", @@ -9461,16 +9863,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", "shasum": "" }, "require": { @@ -9502,20 +9904,20 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-06-10 09:48:41" + "time": "2016-09-30 07:12:33" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2", + "version": "0.2.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", "shasum": "" }, "require": { @@ -9549,7 +9951,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-06-10 07:14:17" + "time": "2016-11-25 06:54:22" }, { "name": "phpspec/php-diff", @@ -9587,16 +9989,16 @@ }, { "name": "phpspec/phpspec", - "version": "2.5.1", + "version": "2.5.5", "source": { "type": "git", "url": "https://github.com/phpspec/phpspec.git", - "reference": "531d00ee76e9ae98279ed4dbb2419e5e0f7fb82d" + "reference": "db395f435eb8e820448e8690de1a8db86d5dd8af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/531d00ee76e9ae98279ed4dbb2419e5e0f7fb82d", - "reference": "531d00ee76e9ae98279ed4dbb2419e5e0f7fb82d", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/db395f435eb8e820448e8690de1a8db86d5dd8af", + "reference": "db395f435eb8e820448e8690de1a8db86d5dd8af", "shasum": "" }, "require": { @@ -9661,20 +10063,20 @@ "testing", "tests" ], - "time": "2016-07-16 08:34:07" + "time": "2016-12-04 21:03:31" }, { "name": "phpspec/prophecy", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", "shasum": "" }, "require": { @@ -9682,10 +10084,11 @@ "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0" + "sebastian/recursion-context": "^1.0|^2.0" }, "require-dev": { - "phpspec/phpspec": "^2.0" + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", "extra": { @@ -9723,7 +10126,7 @@ "spy", "stub" ], - "time": "2016-06-07 08:13:47" + "time": "2016-11-21 14:58:47" }, { "name": "phpunit/php-code-coverage", @@ -9789,16 +10192,16 @@ }, { "name": "phpunit/php-file-iterator", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", "shasum": "" }, "require": { @@ -9832,7 +10235,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2016-10-03 07:40:28" }, { "name": "phpunit/php-text-template", @@ -9921,16 +10324,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.8", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", "shasum": "" }, "require": { @@ -9966,20 +10369,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-09-15 10:49:45" + "time": "2016-11-15 14:06:22" }, { "name": "phpunit/phpunit", - "version": "4.8.27", + "version": "4.8.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/98b2b39a520766bec663ff5b7ff1b729db9dbfe3", + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3", "shasum": "" }, "require": { @@ -9995,7 +10398,7 @@ "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", + "sebastian/comparator": "~1.2.2", "sebastian/diff": "~1.2", "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", @@ -10038,7 +10441,7 @@ "testing", "xunit" ], - "time": "2016-07-21 06:48:14" + "time": "2016-12-09 02:45:31" }, { "name": "phpunit/phpunit-mock-objects", @@ -10098,22 +10501,22 @@ }, { "name": "sebastian/comparator", - "version": "1.2.0", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", "shasum": "" }, "require": { "php": ">=5.3.3", "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" + "sebastian/exporter": "~1.2 || ~2.0" }, "require-dev": { "phpunit/phpunit": "~4.4" @@ -10158,7 +10561,7 @@ "compare", "equality" ], - "time": "2015-07-26 15:48:44" + "time": "2016-11-19 09:18:40" }, { "name": "sebastian/diff", @@ -10470,16 +10873,16 @@ }, { "name": "symfony/browser-kit", - "version": "v3.1.3", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "d2a07cc11c5fa94820240b1e67592ffb18e347b9" + "reference": "548f8230bad9f77463b20b15993a008f03e96db5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d2a07cc11c5fa94820240b1e67592ffb18e347b9", - "reference": "d2a07cc11c5fa94820240b1e67592ffb18e347b9", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/548f8230bad9f77463b20b15993a008f03e96db5", + "reference": "548f8230bad9f77463b20b15993a008f03e96db5", "shasum": "" }, "require": { @@ -10496,7 +10899,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -10523,20 +10926,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2016-07-26 08:04:17" + "time": "2017-01-02 20:32:22" }, { "name": "symfony/dom-crawler", - "version": "v3.1.3", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "c7b9b8db3a6f2bac76dcd9a9db5446f2591897f9" + "reference": "27d9790840a4efd3b7bb8f5f4f9efc27b36b7024" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c7b9b8db3a6f2bac76dcd9a9db5446f2591897f9", - "reference": "c7b9b8db3a6f2bac76dcd9a9db5446f2591897f9", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/27d9790840a4efd3b7bb8f5f4f9efc27b36b7024", + "reference": "27d9790840a4efd3b7bb8f5f4f9efc27b36b7024", "shasum": "" }, "require": { @@ -10552,7 +10955,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -10579,73 +10982,24 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-07-26 08:04:17" - }, - { - "name": "symfony/yaml", - "version": "v3.1.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "1819adf2066880c7967df7180f4f662b6f0567ac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac", - "reference": "1819adf2066880c7967df7180f4f662b6f0567ac", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-07-17 14:02:08" + "time": "2017-01-02 20:32:22" }, { "name": "webmozart/assert", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", - "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", "shasum": "" }, "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -10654,7 +11008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -10678,13 +11032,12 @@ "check", "validate" ], - "time": "2016-08-09 15:02:57" + "time": "2016-11-23 20:04:58" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": { - "turbo124/laravel-push-notification": 20, "omnipay/mollie": 20, "omnipay/2checkout": 20, "omnipay/gocardless": 20, @@ -10716,7 +11069,6 @@ "prefer-lowest": false, "platform": { "php": ">=5.5.9", - "ext-mcrypt": "*", "ext-gmp": "*", "ext-gd": "*" }, diff --git a/config/app.php b/config/app.php index 9e1784529047..08eaa5b5d5b1 100644 --- a/config/app.php +++ b/config/app.php @@ -119,7 +119,7 @@ return [ */ 'Illuminate\Auth\AuthServiceProvider', 'Collective\Html\HtmlServiceProvider', - 'Collective\Bus\BusServiceProvider', + 'Illuminate\Bus\BusServiceProvider', 'Illuminate\Cache\CacheServiceProvider', 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider', 'Illuminate\Cookie\CookieServiceProvider', @@ -222,7 +222,6 @@ return [ 'View' => 'Illuminate\Support\Facades\View', // Added Class Aliases - 'Utils' => 'App\Libraries\Utils', 'Form' => 'Collective\Html\FormFacade', 'HTML' => 'Collective\Html\HtmlFacade', 'SSH' => 'Illuminate\Support\Facades\SSH', @@ -261,6 +260,10 @@ return [ 'Crawler' => 'Jaybizzle\LaravelCrawlerDetect\Facades\LaravelCrawlerDetect', 'Updater' => Codedge\Updater\UpdaterFacade::class, 'Module' => Nwidart\Modules\Facades\Module::class, + + 'Utils' => App\Libraries\Utils::class, + 'HTMLUtils' => App\Libraries\HTMLUtils::class, + 'Domain' => App\Constants\Domain::class, ], ]; diff --git a/config/former.php b/config/former.php index 110fffe8ae2b..549114e7edad 100644 --- a/config/former.php +++ b/config/former.php @@ -30,7 +30,7 @@ 'push_checkboxes' => true, // The value a checkbox will have in the POST array if unchecked - 'unchecked_value' => 0, + 'unchecked_value' => "0", // Required fields //////////////////////////////////////////////////////////////////// diff --git a/config/push-notification.php b/config/push-notification.php index bf7ba5c40ab9..31e51edb88a9 100644 --- a/config/push-notification.php +++ b/config/push-notification.php @@ -16,8 +16,8 @@ return [ ], 'ninjaAndroid' => [ 'environment' =>'production', - 'apiKey' =>'yourAPIKey', - 'service' =>'gcm' + 'apiKey' =>env('FCM_API_TOKEN'), + 'service' =>'fcm' ] ]; diff --git a/config/swaggervel.php b/config/swaggervel.php index 037cf930096a..223e8b02ecca 100644 --- a/config/swaggervel.php +++ b/config/swaggervel.php @@ -45,7 +45,8 @@ return array( base_path()."/tests", base_path()."/resources/views", base_path()."/config", - base_path()."/vendor" + base_path()."/vendor", + base_path()."/app/Console/Commands/stubs" ), /* diff --git a/database/migrations/2013_11_05_180133_confide_setup_users_table.php b/database/migrations/2013_11_05_180133_confide_setup_users_table.php index e9c10da1ad1e..a6538f20a49d 100644 --- a/database/migrations/2013_11_05_180133_confide_setup_users_table.php +++ b/database/migrations/2013_11_05_180133_confide_setup_users_table.php @@ -493,8 +493,8 @@ class ConfideSetupUsersTable extends Migration { $t->timestamps(); $t->unsignedInteger('account_id'); - $t->unsignedInteger('client_id'); $t->unsignedInteger('user_id'); + $t->unsignedInteger('client_id')->nullable(); $t->unsignedInteger('contact_id')->nullable(); $t->unsignedInteger('payment_id')->nullable(); $t->unsignedInteger('invoice_id')->nullable(); @@ -508,7 +508,6 @@ class ConfideSetupUsersTable extends Migration { $t->decimal('balance', 13, 2)->nullable(); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); - $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); }); } diff --git a/database/migrations/2016_11_28_092904_add_task_projects.php b/database/migrations/2016_11_28_092904_add_task_projects.php index c1c334b2a160..1a7f617d5a8f 100644 --- a/database/migrations/2016_11_28_092904_add_task_projects.php +++ b/database/migrations/2016_11_28_092904_add_task_projects.php @@ -35,7 +35,6 @@ class AddTaskProjects extends Migration Schema::table('tasks', function ($table) { $table->unsignedInteger('project_id')->nullable()->index(); - if (Schema::hasColumn('tasks', 'description')) { $table->text('description')->change(); } diff --git a/database/migrations/2017_01_01_214241_add_inclusive_taxes.php b/database/migrations/2017_01_01_214241_add_inclusive_taxes.php new file mode 100644 index 000000000000..4eebbe95e338 --- /dev/null +++ b/database/migrations/2017_01_01_214241_add_inclusive_taxes.php @@ -0,0 +1,89 @@ +boolean('is_inclusive')->default(false); + }); + + Schema::table('companies', function ($table) + { + $table->enum('bluevine_status', ['ignored', 'signed_up'])->nullable(); + }); + + DB::statement('UPDATE companies + LEFT JOIN accounts ON accounts.company_id = companies.id AND accounts.bluevine_status IS NOT NULL + SET companies.bluevine_status = accounts.bluevine_status'); + + Schema::table('accounts', function($table) + { + $table->dropColumn('bluevine_status'); + $table->text('bcc_email')->nullable(); + $table->text('client_number_prefix')->nullable(); + $table->integer('client_number_counter')->default(0)->nullable(); + $table->text('client_number_pattern')->nullable(); + $table->tinyInteger('domain_id')->default(1)->nullable()->unsigned(); + $table->tinyInteger('payment_terms')->nullable(); + }); + + Schema::table('activities', function ($table) + { + $table->text('notes')->nullable(); + }); + + Schema::table('date_formats', function ($table) + { + $table->string('format_moment')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tax_rates', function($table) + { + $table->dropColumn('is_inclusive'); + }); + + Schema::table('companies', function($table) + { + $table->dropColumn('bluevine_status'); + }); + + Schema::table('accounts', function ($table) + { + $table->enum('bluevine_status', ['ignored', 'signed_up'])->nullable(); + $table->dropColumn('bcc_email'); + $table->dropColumn('client_number_prefix'); + $table->dropColumn('client_number_counter'); + $table->dropColumn('client_number_pattern'); + $table->dropColumn('domain_id'); + $table->dropColumn('payment_terms'); + }); + + Schema::table('activities', function ($table) + { + $table->dropColumn('notes'); + }); + + Schema::table('date_formats', function ($table) + { + $table->dropColumn('format_moment'); + }); + } +} diff --git a/database/seeds/DateFormatsSeeder.php b/database/seeds/DateFormatsSeeder.php index 1495c97be16d..dd8a4b2b2589 100644 --- a/database/seeds/DateFormatsSeeder.php +++ b/database/seeds/DateFormatsSeeder.php @@ -11,19 +11,19 @@ class DateFormatsSeeder extends Seeder // Date formats $formats = [ - ['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy'], - ['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy'], - ['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy'], - ['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy'], - ['format' => 'M j, Y', 'picker_format' => 'M d, yyyy'], - ['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy'], - ['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy'], - ['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd'], - ['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy'], - ['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy'], - ['format' => 'd.m.Y', 'picker_format' => 'dd.mm.yyyy'], - ['format' => 'j. M. Y', 'picker_format' => 'd. M. yyyy'], - ['format' => 'j. F Y', 'picker_format' => 'd. MM yyyy'] + ['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'format_moment' => 'DD/MMM/YYYY'], + ['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'format_moment' => 'DD-MMM-YYYY'], + ['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'format_moment' => 'DD/MMMM/YYYY'], + ['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'format_moment' => 'DD-MMMM-YYYY'], + ['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'format_moment' => 'MMM D, YYYY'], + ['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'format_moment' => 'MMMM D, YYYY'], + ['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'format_moment' => 'ddd MMM Do, YYYY'], + ['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'format_moment' => 'YYYY-MM-DD'], + ['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'format_moment' => 'DD-MM-YYYY'], + ['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'format_moment' => 'MM/DD/YYYY'], + ['format' => 'd.m.Y', 'picker_format' => 'dd.mm.yyyy', 'format_moment' => 'D.MM.YYYY'], + ['format' => 'j. M. Y', 'picker_format' => 'd. M. yyyy', 'format_moment' => 'DD. MMM. YYYY'], + ['format' => 'j. F Y', 'picker_format' => 'd. MM yyyy', 'format_moment' => 'DD. MMMM YYYY'] ]; foreach ($formats as $format) { @@ -31,6 +31,7 @@ class DateFormatsSeeder extends Seeder $record = DateFormat::whereRaw("BINARY `format`= ?", array($format['format']))->first(); if ($record) { $record->picker_format = $format['picker_format']; + $record->format_moment = $format['format_moment']; $record->save(); } else { DateFormat::create($format); diff --git a/database/seeds/LanguageSeeder.php b/database/seeds/LanguageSeeder.php index 6f1f6535dcaa..b597d070b0e4 100644 --- a/database/seeds/LanguageSeeder.php +++ b/database/seeds/LanguageSeeder.php @@ -30,6 +30,7 @@ class LanguageSeeder extends Seeder ['name' => 'Czech', 'locale' => 'cs'], ['name' => 'Croatian', 'locale' => 'hr'], ['name' => 'Albanian', 'locale' => 'sq'], + ['name' => 'Greek', 'locale' => 'el'], ]; foreach ($languages as $language) { diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index 5d41c01193a4..d34af00820f3 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -55,7 +55,7 @@ class PaymentLibrariesSeeder extends Seeder ['name' => 'PaymentSense', 'provider' => 'PaymentSense'], ['name' => 'Realex', 'provider' => 'Realex_Remote'], ['name' => 'Sisow', 'provider' => 'Sisow'], - ['name' => 'Skrill', 'provider' => 'Skrill'], + ['name' => 'Skrill', 'provider' => 'Skrill', 'is_offsite' => true], ['name' => 'BitPay', 'provider' => 'BitPay', 'is_offsite' => true, 'sort_order' => 6], ['name' => 'Dwolla', 'provider' => 'Dwolla', 'is_offsite' => true, 'sort_order' => 5], ['name' => 'AGMS', 'provider' => 'Agms'], diff --git a/database/seeds/PaymentTypesSeeder.php b/database/seeds/PaymentTypesSeeder.php index 66addcc8b9b4..b1c8bbeaba21 100644 --- a/database/seeds/PaymentTypesSeeder.php +++ b/database/seeds/PaymentTypesSeeder.php @@ -32,6 +32,8 @@ class PaymentTypesSeeder extends Seeder array('name' => 'Maestro', 'gateway_type_id' => GATEWAY_TYPE_CREDIT_CARD), array('name' => 'Solo', 'gateway_type_id' => GATEWAY_TYPE_CREDIT_CARD), array('name' => 'Switch', 'gateway_type_id' => GATEWAY_TYPE_CREDIT_CARD), + array('name' => 'iZettle', 'gateway_type_id' => GATEWAY_TYPE_CREDIT_CARD), + array('name' => 'Swish', 'gateway_type_id' => GATEWAY_TYPE_BANK_TRANSFER), ]; foreach ($paymentTypes as $paymentType) { @@ -48,4 +50,4 @@ class PaymentTypesSeeder extends Seeder } } -} \ No newline at end of file +} diff --git a/docs/custom_modules.rst b/docs/custom_modules.rst index 1548d912db8d..58b3d06a8fc0 100644 --- a/docs/custom_modules.rst +++ b/docs/custom_modules.rst @@ -5,8 +5,6 @@ Invoice Ninja support customs modules using https://github.com/nWidart/laravel-m You can watch this `short video `_ for a quick overview of the feature. -.. Note:: This currently requires using the develop branch - Install Module """""""""""""" diff --git a/gulpfile.js b/gulpfile.js index ee46ceeaa9e7..0a68aa490c97 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -66,6 +66,12 @@ elixir(function(mix) { bowerDir + '/select2/dist/css/select2.css' ], 'public/css/select2.css'); + mix.styles([ + bowerDir + '/tablesorter/dist/css/theme.bootstrap_3.min.css', + bowerDir + '/tablesorter/dist/css/theme.bootstrap.min.css', + bowerDir + '/tablesorter/dist/css/widget.grouping.min.css' + ], 'public/css/tablesorter.css'); + /** * JS configuration @@ -76,7 +82,7 @@ elixir(function(mix) { mix.scripts([ 'pdf_viewer.js', 'compatibility.js', - 'pdfmake.js', + bowerDir + '/pdfmake/build/pdfmake.js', 'vfs.js' ], 'public/pdf.built.js'); @@ -84,6 +90,13 @@ elixir(function(mix) { bowerDir + '/bootstrap-daterangepicker/daterangepicker.js' ], 'public/js/daterangepicker.min.js'); + mix.scripts([ + bowerDir + '/tablesorter/dist/js/jquery.tablesorter.combined.js', + bowerDir + '/tablesorter/dist/js/widgets/widget-grouping.min.js', + bowerDir + '/tablesorter/dist/js/widgets/widget-uitheme.min.js', + bowerDir + '/tablesorter/dist/js/widgets/widget-filter.min.js', + ], 'public/js/tablesorter.min.js'); + mix.scripts([ bowerDir + '/select2/dist/js/select2.js', 'resources/assets/js/maximize-select2-height.js', @@ -118,7 +131,6 @@ elixir(function(mix) { bowerDir + '/typeahead.js/dist/typeahead.jquery.js', bowerDir + '/accounting/accounting.js', bowerDir + '/spectrum/spectrum.js', - bowerDir + '/jspdf/dist/jspdf.min.js', bowerDir + '/moment/moment.js', bowerDir + '/moment-timezone/builds/moment-timezone-with-data.js', //bowerDir + '/stacktrace-js/dist/stacktrace-with-polyfills.min.js', diff --git a/public/built.js b/public/built.js index 0be2e1d1c8f3..cd0b49285c38 100644 --- a/public/built.js +++ b/public/built.js @@ -1,31 +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=s.split("+")[1]:s.split("-").length>1&&(c=parseInt(s.split("-")[1])*-1),t=t.replace(s,getDatePart(i,c))}}return t}function getDatePart(t,e){return e=parseInt(e),e||(e=0),"MONTH"==t?getMonth(e):"QUARTER"==t?getQuarter(e):"YEAR"==t?getYear(e):void 0}function getMonth(t){var e=new Date,n=["January","February","March","April","May","June","July","August","September","October","November","December"],i=e.getMonth();return i=parseInt(i)+t,i%=12,i<0&&(i+=12),n[i]}function getYear(t){var e=new Date,n=e.getFullYear();return parseInt(n)+t}function getQuarter(t){var e=new Date,n=Math.floor((e.getMonth()+3)/3);return n+=t,n%=4,0==n&&(n=4),"Q"+n}function isStorageSupported(){try{return"localStorage"in window&&null!==window.localStorage}catch(t){return!1}}function isValidEmailAddress(t){var e=new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);return e.test(t)}function enableHoverClick(t,e,n){}function setAsLink(t,e){e?(t.css("text-decoration","underline"),t.css("cursor","pointer")):(t.css("text-decoration","none"),t.css("cursor","text"))}function setComboboxValue(t,e,n){t.find("input").val(e),t.find("input.form-control").val(n),e&&n?(t.find("select").combobox("setSelected"),t.find(".combobox-container").addClass("combobox-selected")):t.find(".combobox-container").removeClass("combobox-selected")}function convertDataURIToBinary(t){var e=t.indexOf(BASE64_MARKER)+BASE64_MARKER.length,n=t.substring(e);return base64DecToArr(n)}function getContactDisplayName(t){return t.first_name||t.last_name?(t.first_name||"")+" "+(t.last_name||""):t.email}function getClientDisplayName(t){var e=!!t.contacts&&t.contacts[0];return t.name?t.name:e?getContactDisplayName(e):""}function populateInvoiceComboboxes(t,e){for(var n={},i={},o={},r=$("select#client"),a=0;a1?t+=", ":n64&&t<91?t-65:t>96&&t<123?t-71:t>47&&t<58?t+4:43===t?62:47===t?63:0}function base64DecToArr(t,e){for(var n,i,o=t.replace(/[^A-Za-z0-9\+\/]/g,""),r=o.length,a=e?Math.ceil((3*r+1>>2)/e)*e:3*r+1>>2,s=new Uint8Array(a),c=0,l=0,u=0;u>>(16>>>n&24)&255;c=0}return s}function uint6ToB64(t){return t<26?t+65:t<52?t+71:t<62?t-4:62===t?43:63===t?47:65}function base64EncArr(t){for(var e=2,n="",i=t.length,o=0,r=0;r0&&4*r/3%76===0&&(n+="\r\n"),o|=t[r]<<(16>>>e&24),2!==e&&t.length-r!==1||(n+=String.fromCharCode(uint6ToB64(o>>>18&63),uint6ToB64(o>>>12&63),uint6ToB64(o>>>6&63),uint6ToB64(63&o)),o=0);return n.substr(0,n.length-2+e)+(2===e?"":1===e?"=":"==")}function UTF8ArrToStr(t){for(var e,n="",i=t.length,o=0;o251&&e<254&&o+5247&&e<252&&o+4239&&e<248&&o+3223&&e<240&&o+2191&&e<224&&o+1>>6),e[a++]=128+(63&n)):n<65536?(e[a++]=224+(n>>>12),e[a++]=128+(n>>>6&63),e[a++]=128+(63&n)):n<2097152?(e[a++]=240+(n>>>18),e[a++]=128+(n>>>12&63),e[a++]=128+(n>>>6&63),e[a++]=128+(63&n)):n<67108864?(e[a++]=248+(n>>>24),e[a++]=128+(n>>>18&63),e[a++]=128+(n>>>12&63),e[a++]=128+(n>>>6&63),e[a++]=128+(63&n)):(e[a++]=252+n/1073741824,e[a++]=128+(n>>>24&63),e[a++]=128+(n>>>18&63),e[a++]=128+(n>>>12&63),e[a++]=128+(n>>>6&63),e[a++]=128+(63&n));return e}function hexToR(t){return parseInt(cutHex(t).substring(0,2),16)}function hexToG(t){return parseInt(cutHex(t).substring(2,4),16)}function hexToB(t){return parseInt(cutHex(t).substring(4,6),16)}function cutHex(t){return"#"==t.charAt(0)?t.substring(1,7):t}function setDocHexColor(t,e){var n=hexToR(e),i=hexToG(e),o=hexToB(e);return t.setTextColor(n,i,o)}function setDocHexFill(t,e){var n=hexToR(e),i=hexToG(e),o=hexToB(e);return t.setFillColor(n,i,o)}function setDocHexDraw(t,e){var n=hexToR(e),i=hexToG(e),o=hexToB(e);return t.setDrawColor(n,i,o)}function toggleDatePicker(t){$("#"+t).datepicker("show")}function roundToTwo(t,e){var n=+(Math.round(t+"e+2")+"e-2");return e?n.toFixed(2):n||0}function roundToFour(t,e){var n=+(Math.round(t+"e+4")+"e-4");return e?n.toFixed(4):n||0}function truncate(t,e){return t&&t.length>e?t.substr(0,e-1)+"...":t}function endsWith(t,e){return t.indexOf(e,t.length-e.length)!==-1}function secondsToTime(t){t=Math.round(t);var e=Math.floor(t/3600),n=t%3600,i=Math.floor(n/60),o=n%60,r=Math.ceil(o),a={h:e,m:i,s:r};return a}function twoDigits(t){return t<10?"0"+t:t}function toSnakeCase(t){return t?t.replace(/([A-Z])/g,function(t){return"_"+t.toLowerCase()}):""}function snakeToCamel(t){return t.replace(/_([a-z])/g,function(t){return t[1].toUpperCase()})}function getDescendantProp(t,e){for(var n=e.split(".");n.length&&(t=t[n.shift()]););return t}function doubleDollarSign(t){return t?t.replace?t.replace(/\$/g,"$$$"):t:""}function truncate(t,e){return t.length>e?t.substring(0,e)+"...":t}function actionListHandler(){$("tbody tr .tr-action").closest("tr").mouseover(function(){$(this).closest("tr").find(".tr-action").show(),$(this).closest("tr").find(".tr-status").hide()}).mouseout(function(){$dropdown=$(this).closest("tr").find(".tr-action"),$dropdown.hasClass("open")||($dropdown.hide(),$(this).closest("tr").find(".tr-status").show())})}function loadImages(t){$(t+" img").each(function(t,e){var n=$(e).attr("data-src");$(e).attr("src",n),$(e).attr("data-src",n)})}function prettyJson(t){return"string"!=typeof t&&(t=JSON.stringify(t,void 0,2)),t=t.replace(/&/g,"&").replace(//g,">"),t.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,function(t){var e="number";return/^"/.test(t)?e=/:$/.test(t)?"key":"string":/true|false/.test(t)?e="boolean":/null/.test(t)&&(e="null"),t=snakeToCamel(t),''+t+""})}function searchData(t,e,n){return function(i,o){var r;if(n){var a={keys:[e]},s=new Fuse(t,a);r=s.search(i)}else r=[],substrRegex=new RegExp(escapeRegExp(i),"i"),$.each(t,function(t,n){substrRegex.test(n[e])&&r.push(n)});o(r)}}function escapeRegExp(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}function firstJSONError(t){for(var e in t)if(t.hasOwnProperty(e)){var n=t[e];for(var i in n)if(n.hasOwnProperty(i))return n[i]}return!1}function GetPdfMake(t,e,n){function i(e,n){if("string"==typeof n){if(0===n.indexOf("$firstAndLast")){var i=n.split(":");return function(t,e){return 0===t||t===e.table.body.length?parseFloat(i[1]):0}}if(0===n.indexOf("$none"))return function(t,e){return 0};if(0===n.indexOf("$notFirstAndLastColumn")){var i=n.split(":");return function(t,e){return 0===t||t===e.table.widths.length?0:parseFloat(i[1])}}if(0===n.indexOf("$notFirst")){var i=n.split(":");return function(t,e){return 0===t?0:parseFloat(i[1])}}if(0===n.indexOf("$amount")){var i=n.split(":");return function(t,e){return parseFloat(i[1])}}if(0===n.indexOf("$primaryColor")){var i=n.split(":");return NINJA.primaryColor||i[1]}if(0===n.indexOf("$secondaryColor")){var i=n.split(":");return NINJA.secondaryColor||i[1]}}if(t.features.customize_invoice_design){if("header"===e)return function(e,i){return 1===e||"1"==t.account.all_pages_header?n:""};if("footer"===e)return function(e,i){return e===i||"1"==t.account.all_pages_footer?n:""}}return"text"===e&&(n=NINJA.parseMarkdownText(n,!0)),n}function o(t){window.ninjaFontVfs[t.folder]&&(folder="fonts/"+t.folder,pdfMake.fonts[t.name]={normal:folder+"/"+t.normal,italics:folder+"/"+t.italics,bold:folder+"/"+t.bold,bolditalics:folder+"/"+t.bolditalics})}e=NINJA.decodeJavascript(t,e);var r=JSON.parse(e,i);t.invoice_design_id;if(!t.features.remove_created_by&&!isEdge){var a="function"==typeof r.footer?r.footer():r.footer;if(a)if(a.hasOwnProperty("columns"))a.columns.push({image:logoImages.imageLogo1,alignment:"right",width:130,margin:[0,0,0,0]});else{for(var s,c=0;c0&&e-1 in t))}function i(t,e,n){if(ot.isFunction(e))return ot.grep(t,function(t,i){return!!e.call(t,i,t)!==n});if(e.nodeType)return ot.grep(t,function(t){return t===e!==n});if("string"==typeof e){if(dt.test(e))return ot.filter(e,t,n);e=ot.filter(e,t)}return ot.grep(t,function(t){return ot.inArray(t,e)>=0!==n})}function o(t,e){do t=t[e];while(t&&1!==t.nodeType);return t}function r(t){var e=yt[t]={};return ot.each(t.match(Mt)||[],function(t,n){e[n]=!0}),e}function a(){ft.addEventListener?(ft.removeEventListener("DOMContentLoaded",s,!1),t.removeEventListener("load",s,!1)):(ft.detachEvent("onreadystatechange",s),t.detachEvent("onload",s))}function s(){(ft.addEventListener||"load"===event.type||"complete"===ft.readyState)&&(a(),ot.ready())}function c(t,e,n){if(void 0===n&&1===t.nodeType){var i="data-"+e.replace(Tt,"-$1").toLowerCase();if(n=t.getAttribute(i),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:_t.test(n)?ot.parseJSON(n):n)}catch(o){}ot.data(t,e,n)}else n=void 0}return n}function l(t){var e;for(e in t)if(("data"!==e||!ot.isEmptyObject(t[e]))&&"toJSON"!==e)return!1;return!0}function u(t,e,n,i){if(ot.acceptData(t)){var o,r,a=ot.expando,s=t.nodeType,c=s?ot.cache:t,l=s?t[a]:t[a]&&a;if(l&&c[l]&&(i||c[l].data)||void 0!==n||"string"!=typeof e)return l||(l=s?t[a]=Y.pop()||ot.guid++:a),c[l]||(c[l]=s?{}:{toJSON:ot.noop}),"object"!=typeof e&&"function"!=typeof e||(i?c[l]=ot.extend(c[l],e):c[l].data=ot.extend(c[l].data,e)),r=c[l],i||(r.data||(r.data={}),r=r.data),void 0!==n&&(r[ot.camelCase(e)]=n),"string"==typeof e?(o=r[e],null==o&&(o=r[ot.camelCase(e)])):o=r,o}}function h(t,e,n){if(ot.acceptData(t)){var i,o,r=t.nodeType,a=r?ot.cache:t,s=r?t[ot.expando]:ot.expando;if(a[s]){if(e&&(i=n?a[s]:a[s].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 a[s].data,l(a[s])))&&(r?ot.cleanData([t],!0):nt.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}function d(){return!0}function p(){return!1}function f(){try{return ft.activeElement}catch(t){}}function m(t){var e=Wt.split("|"),n=t.createDocumentFragment();if(n.createElement)for(;e.length;)n.createElement(e.pop());return n}function g(t,e){var n,i,o=0,r=typeof t.getElementsByTagName!==zt?t.getElementsByTagName(e||"*"):typeof t.querySelectorAll!==zt?t.querySelectorAll(e||"*"):void 0;if(!r)for(r=[],n=t.childNodes||t;null!=(i=n[o]);o++)!e||ot.nodeName(i,e)?r.push(i):ot.merge(r,g(i,e));return void 0===e||e&&ot.nodeName(t,e)?ot.merge([t],r):r}function b(t){Nt.test(t.type)&&(t.defaultChecked=t.checked)}function v(t,e){return ot.nodeName(t,"table")&&ot.nodeName(11!==e.nodeType?e:e.firstChild,"tr")?t.getElementsByTagName("tbody")[0]||t.appendChild(t.ownerDocument.createElement("tbody")):t}function M(t){return t.type=(null!==ot.find.attr(t,"type"))+"/"+t.type,t}function y(t){var e=Vt.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function A(t,e){for(var n,i=0;null!=(n=t[i]);i++)ot._data(n,"globalEval",!e||ot._data(e[i],"globalEval"))}function w(t,e){if(1===e.nodeType&&ot.hasData(t)){var n,i,o,r=ot._data(t),a=ot._data(e,r),s=r.events;if(s){delete a.handle,a.events={};for(n in s)for(i=0,o=s[n].length;i")).appendTo(e.documentElement),e=(Qt[0].contentWindow||Qt[0].contentDocument).document,e.write(),e.close(),n=_(t,e),Qt.detach()),Zt[t]=n),n}function x(t,e){return{get:function(){var n=t();if(null!=n)return n?void delete this.get:(this.get=e).apply(this,arguments)}}}function C(t,e){if(e in t)return e;for(var n=e.charAt(0).toUpperCase()+e.slice(1),i=e,o=de.length;o--;)if(e=de[o]+n,e in t)return e;return i}function S(t,e){for(var n,i,o,r=[],a=0,s=t.length;a=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(at,"ms-").replace(st,ct)},nodeName:function(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()},each:function(t,e,i){var o,r=0,a=t.length,s=n(t);if(i){if(s)for(;rw.cacheLength&&delete t[e.shift()],t[n+" "]=i}var e=[];return t}function i(t){return t[P]=!0,t}function o(t){var e=k.createElement("div");try{return!!t(e)}catch(n){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function r(t,e){for(var n=t.split("|"),i=t.length;i--;)w.attrHandle[n[i]]=e}function a(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 s(t){return function(e){var n=e.nodeName.toLowerCase(); -return"input"===n&&e.type===t}}function c(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function l(t){return i(function(e){return e=+e,i(function(n,i){for(var o,r=t([],n.length,e),a=r.length;a--;)n[o=r[a]]&&(n[o]=!(i[o]=n[o]))})})}function u(t){return t&&"undefined"!=typeof t.getElementsByTagName&&t}function h(){}function d(t){for(var e=0,n=t.length,i="";e1?function(e,n,i){for(var o=t.length;o--;)if(!t[o](e,n,i))return!1;return!0}:t[0]}function m(t,n,i){for(var o=0,r=n.length;o-1&&(i[l]=!(a[l]=h))}}else M=g(M===a?M.splice(f,M.length):M),r?r(null,a,M,c):Q.apply(a,M)})}function v(t){for(var e,n,i,o=t.length,r=w.relative[t[0].type],a=r||w.relative[" "],s=r?1:0,c=p(function(t){return t===e},a,!0),l=p(function(t){return tt(e,t)>-1},a,!0),u=[function(t,n,i){var o=!r&&(i||n!==S)||((e=n).nodeType?c(t,n,i):l(t,n,i));return e=null,o}];s1&&f(u),s>1&&d(t.slice(0,s-1).concat({value:" "===t[s-2].type?"*":""})).replace(ct,"$1"),n,s0,r=t.length>0,a=function(i,a,s,c,l){var u,h,d,p=0,f="0",m=i&&[],b=[],v=S,M=i||r&&w.find.TAG("*",l),y=X+=null==v?1:Math.random()||.1,A=M.length;for(l&&(S=a!==k&&a);f!==A&&null!=(u=M[f]);f++){if(r&&u){for(h=0;d=t[h++];)if(d(u,a,s)){c.push(u);break}l&&(X=y)}o&&((u=!d&&u)&&p--,i&&m.push(u))}if(p+=f,o&&f!==p){for(h=0;d=n[h++];)d(m,b,a,s);if(i){if(p>0)for(;f--;)m[f]||b[f]||(b[f]=G.call(c));b=g(b)}Q.apply(c,b),l&&!i&&b.length>0&&p+n.length>1&&e.uniqueSort(c)}return l&&(X=y,S=v),m};return o?i(a):a}var y,A,w,z,_,T,x,C,S,O,N,L,k,D,q,E,W,B,I,P="sizzle"+1*new Date,R=t.document,X=0,F=0,H=n(),j=n(),U=n(),$=function(t,e){return t===e&&(N=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,tt=function(t,e){for(var n=0,i=t.length;n+~]|"+nt+")"+nt+"*"),ht=new RegExp("="+nt+"*([^\\]'\"]*?)"+nt+"*\\]","g"),dt=new RegExp(at),pt=new RegExp("^"+ot+"$"),ft={ID:new RegExp("^#("+it+")"),CLASS:new RegExp("^\\.("+it+")"),TAG:new RegExp("^("+it.replace("w","w*")+")"),ATTR:new RegExp("^"+rt),PSEUDO:new RegExp("^"+at),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+nt+"*(even|odd|(([+-]|)(\\d*)n|)"+nt+"*(?:([+-]|)"+nt+"*(\\d+)|))"+nt+"*\\)|)","i"),bool:new RegExp("^(?:"+et+")$","i"),needsContext:new RegExp("^"+nt+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+nt+"*((?:-\\d)?\\d*)"+nt+"*\\)|)(?=[^-]|$)","i")},mt=/^(?:input|select|textarea|button)$/i,gt=/^h\d$/i,bt=/^[^{]+\{\s*\[native \w/,vt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Mt=/[+~]/,yt=/'|\\/g,At=new RegExp("\\\\([\\da-f]{1,6}"+nt+"?|("+nt+")|.)","ig"),wt=function(t,e,n){var i="0x"+e-65536;return i!==i||n?e:i<0?String.fromCharCode(i+65536):String.fromCharCode(i>>10|55296,1023&i|56320)},zt=function(){L()};try{Q.apply(J=Z.call(R.childNodes),R.childNodes),J[R.childNodes.length].nodeType}catch(_t){Q={apply:J.length?function(t,e){K.apply(t,Z.call(e))}:function(t,e){for(var n=t.length,i=0;t[n++]=e[i++];);t.length=n-1}}}A=e.support={},_=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},L=e.setDocument=function(t){var e,n,i=t?t.ownerDocument||t:R;return i!==k&&9===i.nodeType&&i.documentElement?(k=i,D=i.documentElement,n=i.defaultView,n&&n!==n.top&&(n.addEventListener?n.addEventListener("unload",zt,!1):n.attachEvent&&n.attachEvent("onunload",zt)),q=!_(i),A.attributes=o(function(t){return t.className="i",!t.getAttribute("className")}),A.getElementsByTagName=o(function(t){return t.appendChild(i.createComment("")),!t.getElementsByTagName("*").length}),A.getElementsByClassName=bt.test(i.getElementsByClassName),A.getById=o(function(t){return D.appendChild(t).id=P,!i.getElementsByName||!i.getElementsByName(P).length}),A.getById?(w.find.ID=function(t,e){if("undefined"!=typeof e.getElementById&&q){var n=e.getElementById(t);return n&&n.parentNode?[n]:[]}},w.filter.ID=function(t){var e=t.replace(At,wt);return function(t){return t.getAttribute("id")===e}}):(delete w.find.ID,w.filter.ID=function(t){var e=t.replace(At,wt);return function(t){var n="undefined"!=typeof t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}}),w.find.TAG=A.getElementsByTagName?function(t,e){return"undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t):A.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,i=[],o=0,r=e.getElementsByTagName(t);if("*"===t){for(;n=r[o++];)1===n.nodeType&&i.push(n);return i}return r},w.find.CLASS=A.getElementsByClassName&&function(t,e){if(q)return e.getElementsByClassName(t)},W=[],E=[],(A.qsa=bt.test(i.querySelectorAll))&&(o(function(t){D.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&E.push("[*^$]="+nt+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||E.push("\\["+nt+"*(?:value|"+et+")"),t.querySelectorAll("[id~="+P+"-]").length||E.push("~="),t.querySelectorAll(":checked").length||E.push(":checked"),t.querySelectorAll("a#"+P+"+*").length||E.push(".#.+[+~]")}),o(function(t){var e=i.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&E.push("name"+nt+"*[*^$|!~]?="),t.querySelectorAll(":enabled").length||E.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),E.push(",.*:")})),(A.matchesSelector=bt.test(B=D.matches||D.webkitMatchesSelector||D.mozMatchesSelector||D.oMatchesSelector||D.msMatchesSelector))&&o(function(t){A.disconnectedMatch=B.call(t,"div"),B.call(t,"[s!='']:x"),W.push("!=",at)}),E=E.length&&new RegExp(E.join("|")),W=W.length&&new RegExp(W.join("|")),e=bt.test(D.compareDocumentPosition),I=e||bt.test(D.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},$=e?function(t,e){if(t===e)return N=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n?n:(n=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1,1&n||!A.sortDetached&&e.compareDocumentPosition(t)===n?t===i||t.ownerDocument===R&&I(R,t)?-1:e===i||e.ownerDocument===R&&I(R,e)?1:O?tt(O,t)-tt(O,e):0:4&n?-1:1)}:function(t,e){if(t===e)return N=!0,0;var n,o=0,r=t.parentNode,s=e.parentNode,c=[t],l=[e];if(!r||!s)return t===i?-1:e===i?1:r?-1:s?1:O?tt(O,t)-tt(O,e):0;if(r===s)return a(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?a(c[o],l[o]):c[o]===R?-1:l[o]===R?1:0},i):k},e.matches=function(t,n){return e(t,null,null,n)},e.matchesSelector=function(t,n){if((t.ownerDocument||t)!==k&&L(t),n=n.replace(ht,"='$1']"),A.matchesSelector&&q&&(!W||!W.test(n))&&(!E||!E.test(n)))try{var i=B.call(t,n);if(i||A.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(o){}return e(n,k,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==k&&L(t),I(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==k&&L(t);var n=w.attrHandle[e.toLowerCase()],i=n&&Y.call(w.attrHandle,e.toLowerCase())?n(t,e,!q):void 0;return void 0!==i?i:A.attributes||!q?t.getAttribute(e):(i=t.getAttributeNode(e))&&i.specified?i.value:null},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,n=[],i=0,o=0;if(N=!A.detectDuplicates,O=!A.sortStable&&t.slice(0),t.sort($),N){for(;e=t[o++];)e===t[o]&&(i=n.push(o));for(;i--;)t.splice(n[i],1)}return O=null,t},z=e.getText=function(t){var e,n="",i=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=z(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[i++];)n+=z(e);return n},w=e.selectors={cacheLength:50,createPseudo:i,match:ft,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(At,wt),t[3]=(t[3]||t[4]||t[5]||"").replace(At,wt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return ft.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&dt.test(n)&&(e=T(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(At,wt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=H[t+" "];return e||(e=new RegExp("(^|"+nt+")"+t+"("+nt+"|$)"))&&H(t,function(t){return e.test("string"==typeof t.className&&t.className||"undefined"!=typeof t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,n,i){return function(o){var r=e.attr(o,t);return null==r?"!="===n:!n||(r+="","="===n?r===i:"!="===n?r!==i:"^="===n?i&&0===r.indexOf(i):"*="===n?i&&r.indexOf(i)>-1:"$="===n?i&&r.slice(-i.length)===i:"~="===n?(" "+r.replace(st," ")+" ").indexOf(i)>-1:"|="===n&&(r===i||r.slice(0,i.length+1)===i+"-"))}},CHILD:function(t,e,n,i,o){var r="nth"!==t.slice(0,3),a="last"!==t.slice(-4),s="of-type"===e;return 1===i&&0===o?function(t){return!!t.parentNode}:function(e,n,c){var l,u,h,d,p,f,m=r!==a?"nextSibling":"previousSibling",g=e.parentNode,b=s&&e.nodeName.toLowerCase(),v=!c&&!s;if(g){if(r){for(;m;){for(h=e;h=h[m];)if(s?h.nodeName.toLowerCase()===b:1===h.nodeType)return!1;f=m="only"===t&&!f&&"nextSibling"}return!0}if(f=[a?g.firstChild:g.lastChild],a&&v){for(u=g[P]||(g[P]={}),l=u[t]||[],p=l[0]===X&&l[1],d=l[0]===X&&l[2],h=p&&g.childNodes[p];h=++p&&h&&h[m]||(d=p=0)||f.pop();)if(1===h.nodeType&&++d&&h===e){u[t]=[X,p,d];break}}else if(v&&(l=(e[P]||(e[P]={}))[t])&&l[0]===X)d=l[1];else for(;(h=++p&&h&&h[m]||(d=p=0)||f.pop())&&((s?h.nodeName.toLowerCase()!==b:1!==h.nodeType)||!++d||(v&&((h[P]||(h[P]={}))[t]=[X,d]),h!==e)););return d-=o,d===i||d%i===0&&d/i>=0}}},PSEUDO:function(t,n){var o,r=w.pseudos[t]||w.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return r[P]?r(n):r.length>1?(o=[t,t,"",n],w.setFilters.hasOwnProperty(t.toLowerCase())?i(function(t,e){for(var i,o=r(t,n),a=o.length;a--;)i=tt(t,o[a]),t[i]=!(e[i]=o[a])}):function(t){return r(t,0,o)}):r}},pseudos:{not:i(function(t){var e=[],n=[],o=x(t.replace(ct,"$1"));return o[P]?i(function(t,e,n,i){for(var r,a=o(t,null,i,[]),s=t.length;s--;)(r=a[s])&&(t[s]=!(e[s]=r))}):function(t,i,r){return e[0]=t,o(e,null,r,n),e[0]=null,!n.pop()}}),has:i(function(t){return function(n){return e(t,n).length>0}}),contains:i(function(t){return t=t.replace(At,wt),function(e){return(e.textContent||e.innerText||z(e)).indexOf(t)>-1}}),lang:i(function(t){return pt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(At,wt).toLowerCase(),function(e){var n;do if(n=q?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return n=n.toLowerCase(),n===t||0===n.indexOf(t+"-");while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===D},focus:function(t){return t===k.activeElement&&(!k.hasFocus||k.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:function(t){return t.disabled===!1},disabled:function(t){return t.disabled===!0},checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,t.selected===!0},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!w.pseudos.empty(t)},header:function(t){return gt.test(t.nodeName)},input:function(t){return mt.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:l(function(){return[0]}),last:l(function(t,e){return[e-1]}),eq:l(function(t,e,n){return[n<0?n+e:n]}),even:l(function(t,e){for(var n=0;n=0;)t.push(i);return t}),gt:l(function(t,e,n){for(var i=n<0?n+e:n;++i2&&"ID"===(a=r[0]).type&&A.getById&&9===e.nodeType&&q&&w.relative[r[1].type]){if(e=(w.find.ID(a.matches[0].replace(At,wt),e)||[])[0],!e)return n;l&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(o=ft.needsContext.test(t)?0:r.length;o--&&(a=r[o],!w.relative[s=a.type]);)if((c=w.find[s])&&(i=c(a.matches[0].replace(At,wt),Mt.test(r[0].type)&&u(e.parentNode)||e))){if(r.splice(o,1),t=i.length&&d(r),!t)return Q.apply(n,i),n;break}}return(l||x(t,h))(i,e,!q,n,Mt.test(t)&&u(e.parentNode)||e),n},A.sortStable=P.split("").sort($).join("")===P,A.detectDuplicates=!!N,L(),A.sortDetached=o(function(t){return 1&t.compareDocumentPosition(k.createElement("div"))}),o(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||r("type|href|height|width",function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),A.attributes&&o(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||r("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),o(function(t){return null==t.getAttribute("disabled")})||r(et,function(t,e,n){var i;if(!n)return t[e]===!0?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null}),e}(t);ot.find=lt,ot.expr=lt.selectors,ot.expr[":"]=ot.expr.pseudos,ot.unique=lt.uniqueSort,ot.text=lt.getText,ot.isXMLDoc=lt.isXML,ot.contains=lt.contains;var ut=ot.expr.match.needsContext,ht=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,dt=/^.[^:#\[\.,]*$/;ot.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?ot.find.matchesSelector(i,t)?[i]:[]:ot.find.matches(t,ot.grep(e,function(t){return 1===t.nodeType}))},ot.fn.extend({find:function(t){var e,n=[],i=this,o=i.length;if("string"!=typeof t)return this.pushStack(ot(t).filter(function(){for(e=0;e1?ot.unique(n):n),n.selector=this.selector?this.selector+" "+t:t,n},filter:function(t){return this.pushStack(i(this,t||[],!1))},not:function(t){return this.pushStack(i(this,t||[],!0))},is:function(t){return!!i(this,"string"==typeof t&&ut.test(t)?ot(t):t||[],!1).length}});var pt,ft=t.document,mt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,gt=ot.fn.init=function(t,e){var n,i;if(!t)return this;if("string"==typeof t){if(n="<"===t.charAt(0)&&">"===t.charAt(t.length-1)&&t.length>=3?[null,t,null]:mt.exec(t),!n||!n[1]&&e)return!e||e.jquery?(e||pt).find(t):this.constructor(e).find(t);if(n[1]){if(e=e instanceof ot?e[0]:e,ot.merge(this,ot.parseHTML(n[1],e&&e.nodeType?e.ownerDocument||e:ft,!0)),ht.test(n[1])&&ot.isPlainObject(e))for(n in e)ot.isFunction(this[n])?this[n](e[n]):this.attr(n,e[n]);return this}if(i=ft.getElementById(n[2]),i&&i.parentNode){if(i.id!==n[2])return pt.find(t);this.length=1,this[0]=i}return this.context=ft,this.selector=t,this}return t.nodeType?(this.context=this[0]=t,this.length=1,this):ot.isFunction(t)?"undefined"!=typeof pt.ready?pt.ready(t):t(ot):(void 0!==t.selector&&(this.selector=t.selector,this.context=t.context),ot.makeArray(t,this))};gt.prototype=ot.fn,pt=ot(ft);var bt=/^(?:parents|prev(?:Until|All))/,vt={children:!0,contents:!0,next:!0,prev:!0};ot.extend({dir:function(t,e,n){for(var i=[],o=t[e];o&&9!==o.nodeType&&(void 0===n||1!==o.nodeType||!ot(o).is(n));)1===o.nodeType&&i.push(o),o=o[e];return i},sibling:function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n}}),ot.fn.extend({has:function(t){var e,n=ot(t,this),i=n.length;return this.filter(function(){for(e=0;e-1:1===n.nodeType&&ot.find.matchesSelector(n,t))){r.push(n);break}return this.pushStack(r.length>1?ot.unique(r):r)},index:function(t){return t?"string"==typeof t?ot.inArray(this[0],ot(t)):ot.inArray(t.jquery?t[0]:t,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(ot.unique(ot.merge(this.get(),ot(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),ot.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return ot.dir(t,"parentNode")},parentsUntil:function(t,e,n){return ot.dir(t,"parentNode",n)},next:function(t){return o(t,"nextSibling")},prev:function(t){return o(t,"previousSibling")},nextAll:function(t){return ot.dir(t,"nextSibling")},prevAll:function(t){return ot.dir(t,"previousSibling")},nextUntil:function(t,e,n){return ot.dir(t,"nextSibling",n)},prevUntil:function(t,e,n){return ot.dir(t,"previousSibling",n)},siblings:function(t){return ot.sibling((t.parentNode||{}).firstChild,t)},children:function(t){return ot.sibling(t.firstChild)},contents:function(t){return ot.nodeName(t,"iframe")?t.contentDocument||t.contentWindow.document:ot.merge([],t.childNodes)}},function(t,e){ot.fn[t]=function(n,i){var o=ot.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(o=ot.filter(i,o)),this.length>1&&(vt[t]||(o=ot.unique(o)),bt.test(t)&&(o=o.reverse())),this.pushStack(o)}});var Mt=/\S+/g,yt={};ot.Callbacks=function(t){t="string"==typeof t?yt[t]||r(t):ot.extend({},t);var e,n,i,o,a,s,c=[],l=!t.once&&[],u=function(r){for(n=t.memory&&r,i=!0,a=s||0,s=0,o=c.length,e=!0;c&&a-1;)c.splice(i,1),e&&(i<=o&&o--,i<=a&&a--)}),this},has:function(t){return t?ot.inArray(t,c)>-1:!(!c||!c.length)},empty:function(){return c=[],o=0,this},disable:function(){return c=l=n=void 0,this},disabled:function(){return!c},lock:function(){return l=void 0,n||h.disable(),this},locked:function(){return!l},fireWith:function(t,n){return!c||i&&!l||(n=n||[],n=[t,n.slice?n.slice():n],e?l.push(n):u(n)),this},fire:function(){return h.fireWith(this,arguments),this},fired:function(){return!!i}};return h},ot.extend({Deferred:function(t){var e=[["resolve","done",ot.Callbacks("once memory"),"resolved"],["reject","fail",ot.Callbacks("once memory"),"rejected"],["notify","progress",ot.Callbacks("memory")]],n="pending",i={state:function(){return n},always:function(){return o.done(arguments).fail(arguments),this},then:function(){var t=arguments;return ot.Deferred(function(n){ot.each(e,function(e,r){var a=ot.isFunction(t[e])&&t[e];o[r[1]](function(){var t=a&&a.apply(this,arguments);t&&ot.isFunction(t.promise)?t.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[r[0]+"With"](this===i?n.promise():this,a?[t]:arguments)})}),t=null}).promise()},promise:function(t){return null!=t?ot.extend(t,i):i}},o={};return i.pipe=i.then,ot.each(e,function(t,r){var a=r[2],s=r[3];i[r[1]]=a.add,s&&a.add(function(){n=s},e[1^t][2].disable,e[2][2].lock),o[r[0]]=function(){return o[r[0]+"With"](this===o?i:this,arguments),this},o[r[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(t){var e,n,i,o=0,r=J.call(arguments),a=r.length,s=1!==a||t&&ot.isFunction(t.promise)?a:0,c=1===s?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):--s||c.resolveWith(n,i)}};if(a>1)for(e=new Array(a),n=new Array(a),i=new Array(a);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",s,!1),t.addEventListener("load",s,!1);else{ft.attachEvent("onreadystatechange",s),t.attachEvent("onload",s);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)}a(),ot.ready()}}()}return At.promise(e)};var wt,zt="undefined";for(wt in ot(nt))break;nt.ownLast="0"!==wt,nt.inlineBlockNeedsLayout=!1,ot(function(){var t,e,n,i;n=ft.getElementsByTagName("body")[0],n&&n.style&&(e=ft.createElement("div"),i=ft.createElement("div"),i.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",n.appendChild(i).appendChild(e),typeof e.style.zoom!==zt&&(e.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",nt.inlineBlockNeedsLayout=t=3===e.offsetWidth,t&&(n.style.zoom=1)),n.removeChild(i))}),function(){var t=ft.createElement("div");if(null==nt.deleteExpando){nt.deleteExpando=!0;try{delete t.test}catch(e){nt.deleteExpando=!1}}t=null}(),ot.acceptData=function(t){var e=ot.noData[(t.nodeName+" ").toLowerCase()],n=+t.nodeType||1;return(1===n||9===n)&&(!e||e!==!0&&t.getAttribute("classid")===e)};var _t=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Tt=/([A-Z])/g;ot.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(t){return t=t.nodeType?ot.cache[t[ot.expando]]:t[ot.expando],!!t&&!l(t)},data:function(t,e,n){return u(t,e,n)},removeData:function(t,e){return h(t,e)},_data:function(t,e,n){return u(t,e,n,!0)},_removeData:function(t,e){return h(t,e,!0)}}),ot.fn.extend({data:function(t,e){var n,i,o,r=this[0],a=r&&r.attributes;if(void 0===t){if(this.length&&(o=ot.data(r),1===r.nodeType&&!ot._data(r,"parsedAttrs"))){for(n=a.length;n--;)a[n]&&(i=a[n].name,0===i.indexOf("data-")&&(i=ot.camelCase(i.slice(5)),c(r,i,o[i])));ot._data(r,"parsedAttrs",!0)}return o}return"object"==typeof t?this.each(function(){ot.data(this,t)}):arguments.length>1?this.each(function(){ot.data(this,t,e)}):r?c(r,t,ot.data(r,t)):void 0},removeData:function(t){return this.each(function(){ot.removeData(this,t)})}}),ot.extend({queue:function(t,e,n){var i;if(t)return e=(e||"fx")+"queue",i=ot._data(t,e),n&&(!i||ot.isArray(n)?i=ot._data(t,e,ot.makeArray(n)):i.push(n)),i||[]},dequeue:function(t,e){e=e||"fx";var n=ot.queue(t,e),i=n.length,o=n.shift(),r=ot._queueHooks(t,e),a=function(){ot.dequeue(t,e)};"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete r.stop,o.call(t,a,r)),!i&&r&&r.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return ot._data(t,n)||ot._data(t,n,{empty:ot.Callbacks("once memory").add(function(){ot._removeData(t,e+"queue"),ot._removeData(t,n)})})}}),ot.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length
a",nt.leadingWhitespace=3===e.firstChild.nodeType,nt.tbody=!e.getElementsByTagName("tbody").length,nt.htmlSerialize=!!e.getElementsByTagName("link").length,nt.html5Clone="<:nav>"!==ft.createElement("nav").cloneNode(!0).outerHTML,t.type="checkbox",t.checked=!0,n.appendChild(t),nt.appendChecked=t.checked,e.innerHTML="",nt.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,n.appendChild(e),e.innerHTML="",nt.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,nt.noCloneEvent=!0,e.attachEvent&&(e.attachEvent("onclick",function(){nt.noCloneEvent=!1}),e.cloneNode(!0).click()),null==nt.deleteExpando){nt.deleteExpando=!0;try{delete e.test}catch(i){nt.deleteExpando=!1}}}(),function(){var e,n,i=ft.createElement("div");for(e in{submit:!0,change:!0,focusin:!0})n="on"+e,(nt[e+"Bubbles"]=n in t)||(i.setAttribute(n,"t"),nt[e+"Bubbles"]=i.attributes[n].expando===!1);i=null}();var Lt=/^(?:input|select|textarea)$/i,kt=/^key/,Dt=/^(?:mouse|pointer|contextmenu)|click/,qt=/^(?:focusinfocus|focusoutblur)$/,Et=/^([^.]*)(?:\.(.+)|)$/;ot.event={global:{},add:function(t,e,n,i,o){var r,a,s,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++),(a=g.events)||(a=g.events={}),(u=g.handle)||(u=g.handle=function(t){return typeof ot===zt||t&&ot.event.triggered===t.type?void 0:ot.event.dispatch.apply(u.elem,arguments)},u.elem=t),e=(e||"").match(Mt)||[""],s=e.length;s--;)r=Et.exec(e[s])||[],p=m=r[1],f=(r[2]||"").split(".").sort(),p&&(l=ot.event.special[p]||{},p=(o?l.delegateType:l.bindType)||p,l=ot.event.special[p]||{},h=ot.extend({type:p,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&ot.expr.match.needsContext.test(o),namespace:f.join(".")},c),(d=a[p])||(d=a[p]=[],d.delegateCount=0,l.setup&&l.setup.call(t,i,f,u)!==!1||(t.addEventListener?t.addEventListener(p,u,!1):t.attachEvent&&t.attachEvent("on"+p,u))),l.add&&(l.add.call(t,h),h.handler.guid||(h.handler.guid=n.guid)),o?d.splice(d.delegateCount++,0,h):d.push(h),ot.event.global[p]=!0);t=null}},remove:function(t,e,n,i,o){var r,a,s,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(s=Et.exec(e[l])||[],p=m=s[1],f=(s[2]||"").split(".").sort(),p){for(h=ot.event.special[p]||{},p=(i?h.delegateType:h.bindType)||p,d=u[p]||[],s=s[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),c=r=d.length;r--;)a=d[r],!o&&m!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||i&&i!==a.selector&&("**"!==i||!a.selector)||(d.splice(r,1),a.selector&&d.delegateCount--,h.remove&&h.remove.call(t,a));c&&!d.length&&(h.teardown&&h.teardown.call(t,f,g.handle)!==!1||ot.removeEvent(t,p,g.handle),delete u[p])}else for(p in u)ot.event.remove(t,p+e[l],n,i,!0);ot.isEmptyObject(u)&&(delete g.handle,ot._removeData(t,"events"))}},trigger:function(e,n,i,o){var r,a,s,c,l,u,h,d=[i||ft],p=et.call(e,"type")?e.type:e,f=et.call(e,"namespace")?e.namespace.split("."):[];if(s=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()),a=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)||(s=s.parentNode);s;s=s.parentNode)d.push(s),u=s;u===(i.ownerDocument||ft)&&d.push(u.defaultView||u.parentWindow||t)}for(h=0;(s=d[h++])&&!e.isPropagationStopped();)e.type=h>1?c:l.bindType||p,r=(ot._data(s,"events")||{})[e.type]&&ot._data(s,"handle"),r&&r.apply(s,n),r=a&&s[a],r&&r.apply&&ot.acceptData(s)&&(e.result=r.apply(s,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)&&a&&i[p]&&!ot.isWindow(i)){ -u=i[a],u&&(i[a]=null),ot.event.triggered=p;try{i[p]()}catch(m){}ot.event.triggered=void 0,u&&(i[a]=u)}return e.result}},dispatch:function(t){t=ot.event.fix(t);var e,n,i,o,r,a=[],s=J.call(arguments),c=(ot._data(this,"events")||{})[t.type]||[],l=ot.event.special[t.type]||{};if(s[0]=t,t.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,t)!==!1){for(a=ot.event.handlers.call(this,t,c),e=0;(o=a[e++])&&!t.isPropagationStopped();)for(t.currentTarget=o.elem,r=0;(i=o.handlers[r++])&&!t.isImmediatePropagationStopped();)t.namespace_re&&!t.namespace_re.test(i.namespace)||(t.handleObj=i,t.data=i.data,n=((ot.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,s),void 0!==n&&(t.result=n)===!1&&(t.preventDefault(),t.stopPropagation()));return l.postDispatch&&l.postDispatch.call(this,t),t.result}},handlers:function(t,e){var n,i,o,r,a=[],s=e.delegateCount,c=t.target;if(s&&c.nodeType&&(!t.button||"click"!==t.type))for(;c!=this;c=c.parentNode||this)if(1===c.nodeType&&(c.disabled!==!0||"click"!==t.type)){for(o=[],r=0;r=0:ot.find(n,this,null,[c]).length),o[n]&&o.push(i);o.length&&a.push({elem:c,handlers:o})}return s]","i"),Pt=/^\s+/,Rt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Xt=/<([\w:]+)/,Ft=/\s*$/g,Jt={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:nt.htmlSerialize?[0,"",""]:[1,"X
","
"]},Gt=m(ft),Kt=Gt.appendChild(ft.createElement("div"));Jt.optgroup=Jt.option,Jt.tbody=Jt.tfoot=Jt.colgroup=Jt.caption=Jt.thead,Jt.th=Jt.td,ot.extend({clone:function(t,e,n){var i,o,r,a,s,c=ot.contains(t.ownerDocument,t);if(nt.html5Clone||ot.isXMLDoc(t)||!It.test("<"+t.nodeName+">")?r=t.cloneNode(!0):(Kt.innerHTML=t.outerHTML,Kt.removeChild(r=Kt.firstChild)),!(nt.noCloneEvent&&nt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||ot.isXMLDoc(t)))for(i=g(r),s=g(t),a=0;null!=(o=s[a]);++a)i[a]&&z(o,i[a]);if(e)if(n)for(s=s||g(t),i=i||g(r),a=0;null!=(o=s[a]);a++)w(o,i[a]);else w(t,r);return i=g(r,"script"),i.length>0&&A(i,!c&&g(t,"script")),i=s=o=null,r},buildFragment:function(t,e,n,i){for(var o,r,a,s,c,l,u,h=t.length,d=m(e),p=[],f=0;f")+u[2],o=u[0];o--;)s=s.lastChild;if(!nt.leadingWhitespace&&Pt.test(r)&&p.push(e.createTextNode(Pt.exec(r)[0])),!nt.tbody)for(r="table"!==c||Ft.test(r)?""!==u[1]||Ft.test(r)?0:s:s.firstChild,o=r&&r.childNodes.length;o--;)ot.nodeName(l=r.childNodes[o],"tbody")&&!l.childNodes.length&&r.removeChild(l);for(ot.merge(p,s.childNodes),s.textContent="";s.firstChild;)s.removeChild(s.firstChild);s=d.lastChild}else p.push(e.createTextNode(r));for(s&&d.removeChild(s),nt.appendChecked||ot.grep(g(p,"input"),b),f=0;r=p[f++];)if((!i||ot.inArray(r,i)===-1)&&(a=ot.contains(r.ownerDocument,r),s=g(d.appendChild(r),"script"),a&&A(s),n))for(o=0;r=s[o++];)$t.test(r.type||"")&&n.push(r);return s=null,d},cleanData:function(t,e){for(var n,i,o,r,a=0,s=ot.expando,c=ot.cache,l=nt.deleteExpando,u=ot.event.special;null!=(n=t[a]);a++)if((e||ot.acceptData(n))&&(o=n[s],r=o&&c[o])){if(r.events)for(i in r.events)u[i]?ot.event.remove(n,i):ot.removeEvent(n,i,r.handle);c[o]&&(delete c[o],l?delete n[s]:typeof n.removeAttribute!==zt?n.removeAttribute(s):n[s]=null,Y.push(o))}}}),ot.fn.extend({text:function(t){return Ot(this,function(t){return void 0===t?ot.text(this):this.empty().append((this[0]&&this[0].ownerDocument||ft).createTextNode(t))},null,t,arguments.length)},append:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=v(this,t);e.appendChild(t)}})},prepend:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=v(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},remove:function(t,e){for(var n,i=t?ot.filter(t,this):this,o=0;null!=(n=i[o]);o++)e||1!==n.nodeType||ot.cleanData(g(n)),n.parentNode&&(e&&ot.contains(n.ownerDocument,n)&&A(g(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){for(var t,e=0;null!=(t=this[e]);e++){for(1===t.nodeType&&ot.cleanData(g(t,!1));t.firstChild;)t.removeChild(t.firstChild);t.options&&ot.nodeName(t,"select")&&(t.options.length=0)}return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return ot.clone(this,t,e)})},html:function(t){return Ot(this,function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t)return 1===e.nodeType?e.innerHTML.replace(Bt,""):void 0;if("string"==typeof t&&!jt.test(t)&&(nt.htmlSerialize||!It.test(t))&&(nt.leadingWhitespace||!Pt.test(t))&&!Jt[(Xt.exec(t)||["",""])[1].toLowerCase()]){t=t.replace(Rt,"<$1>");try{for(;n1&&"string"==typeof d&&!nt.checkClone&&Ut.test(d))return this.each(function(n){var i=u.eq(n);p&&(t[0]=d.call(this,n,i.html())),i.domManip(t,e)});if(l&&(s=ot.buildFragment(t,this[0].ownerDocument,!1,this),n=s.firstChild,1===s.childNodes.length&&(s=n),n)){for(r=ot.map(g(s,"script"),M),o=r.length;c
t
",o=e.getElementsByTagName("td"),o[0].style.cssText="margin:0;border:0;padding:0;display:none",s=0===o[0].offsetHeight,s&&(o[0].style.display="",o[1].style.display="none",s=0===o[0].offsetHeight),n.removeChild(i))}var n,i,o,r,a,s,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==s&&e(),s},boxSizingReliable:function(){return null==a&&e(),a},pixelPosition:function(){return null==r&&e(),r},reliableMarginRight:function(){return null==c&&e(),c}}))}(),ot.swap=function(t,e,n,i){var o,r,a={};for(r in e)a[r]=t.style[r],t.style[r]=e[r];o=n.apply(t,i||[]);for(r in e)t.style[r]=a[r];return o};var re=/alpha\([^)]*\)/i,ae=/opacity\s*=\s*([^)]*)/,se=/^(none|table(?!-c[ea]).+)/,ce=new RegExp("^("+xt+")(.*)$","i"),le=new RegExp("^([+-])=("+xt+")","i"),ue={position:"absolute",visibility:"hidden",display:"block"},he={letterSpacing:"0",fontWeight:"400"},de=["Webkit","O","Moz","ms"];ot.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=ee(t,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":nt.cssFloat?"cssFloat":"styleFloat"},style:function(t,e,n,i){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var o,r,a,s=ot.camelCase(e),c=t.style;if(e=ot.cssProps[s]||(ot.cssProps[s]=C(c,s)),a=ot.cssHooks[e]||ot.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(o=a.get(t,!1,i))?o:c[e];if(r=typeof n,"string"===r&&(o=le.exec(n))&&(n=(o[1]+1)*o[2]+parseFloat(ot.css(t,e)),r="number"),null!=n&&n===n&&("number"!==r||ot.cssNumber[s]||(n+="px"),nt.clearCloneStyle||""!==n||0!==e.indexOf("background")||(c[e]="inherit"),!(a&&"set"in a&&void 0===(n=a.set(t,n,i)))))try{c[e]=n}catch(l){}}},css:function(t,e,n,i){var o,r,a,s=ot.camelCase(e);return e=ot.cssProps[s]||(ot.cssProps[s]=C(t.style,s)),a=ot.cssHooks[e]||ot.cssHooks[s],a&&"get"in a&&(r=a.get(t,!0,n)),void 0===r&&(r=ee(t,e,i)),"normal"===r&&e in he&&(r=he[e]),""===n||n?(o=parseFloat(r),n===!0||ot.isNumeric(o)?o||0:r):r}}),ot.each(["height","width"],function(t,e){ot.cssHooks[e]={get:function(t,n,i){if(n)return se.test(ot.css(t,"display"))&&0===t.offsetWidth?ot.swap(t,ue,function(){return L(t,e,i)}):L(t,e,i)},set:function(t,n,i){var o=i&&te(t);return O(t,n,i?N(t,e,i,nt.boxSizing&&"border-box"===ot.css(t,"boxSizing",!1,o),o):0)}}}),nt.opacity||(ot.cssHooks.opacity={get:function(t,e){return ae.test((e&&t.currentStyle?t.currentStyle.filter:t.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":e?"1":""},set:function(t,e){var n=t.style,i=t.currentStyle,o=ot.isNumeric(e)?"alpha(opacity="+100*e+")":"",r=i&&i.filter||n.filter||"";n.zoom=1,(e>=1||""===e)&&""===ot.trim(r.replace(re,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===e||i&&!i.filter)||(n.filter=re.test(r)?r.replace(re,o):r+" "+o)}}),ot.cssHooks.marginRight=x(nt.reliableMarginRight,function(t,e){if(e)return ot.swap(t,{display:"inline-block"},ee,[t,"marginRight"])}),ot.each({margin:"",padding:"",border:"Width"},function(t,e){ot.cssHooks[t+e]={expand:function(n){for(var i=0,o={},r="string"==typeof n?n.split(" "):[n];i<4;i++)o[t+Ct[i]+e]=r[i]||r[i-2]||r[0];return o}},ne.test(t)||(ot.cssHooks[t+e].set=O)}),ot.fn.extend({css:function(t,e){return Ot(this,function(t,e,n){var i,o,r={},a=0;if(ot.isArray(e)){for(i=te(t),o=e.length;a1)},show:function(){return S(this,!0)},hide:function(){return S(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){St(this)?ot(this).show():ot(this).hide()})}}),ot.Tween=k,k.prototype={constructor:k,init:function(t,e,n,i,o,r){this.elem=t,this.prop=n,this.easing=o||"swing",this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=r||(ot.cssNumber[n]?"":"px")},cur:function(){var t=k.propHooks[this.prop];return t&&t.get?t.get(this):k.propHooks._default.get(this)},run:function(t){var e,n=k.propHooks[this.prop];return this.options.duration?this.pos=e=ot.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):k.propHooks._default.set(this),this}},k.prototype.init.prototype=k.prototype,k.propHooks={_default:{get:function(t){var e;return null==t.elem[t.prop]||t.elem.style&&null!=t.elem.style[t.prop]?(e=ot.css(t.elem,t.prop,""),e&&"auto"!==e?e:0):t.elem[t.prop]},set:function(t){ot.fx.step[t.prop]?ot.fx.step[t.prop](t):t.elem.style&&(null!=t.elem.style[ot.cssProps[t.prop]]||ot.cssHooks[t.prop])?ot.style(t.elem,t.prop,t.now+t.unit):t.elem[t.prop]=t.now}}},k.propHooks.scrollTop=k.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},ot.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2}},ot.fx=k.prototype.init,ot.fx.step={};var pe,fe,me=/^(?:toggle|show|hide)$/,ge=new RegExp("^(?:([+-])=|)("+xt+")([a-z%]*)$","i"),be=/queueHooks$/,ve=[W],Me={"*":[function(t,e){var n=this.createTween(t,e),i=n.cur(),o=ge.exec(e),r=o&&o[3]||(ot.cssNumber[t]?"":"px"),a=(ot.cssNumber[t]||"px"!==r&&+i)&&ge.exec(ot.css(n.elem,t)),s=1,c=20;if(a&&a[3]!==r){r=r||a[3],o=o||[],a=+i||1;do s=s||".5",a/=s,ot.style(n.elem,t,a+r);while(s!==(s=n.cur()/i)&&1!==s&&--c)}return o&&(a=n.start=+a||+i||0,n.unit=r,n.end=o[1]?a+(o[1]+1)*o[2]:+o[2]),n}]};ot.Animation=ot.extend(I,{tweener:function(t,e){ot.isFunction(t)?(e=t,t=["*"]):t=t.split(" ");for(var n,i=0,o=t.length;i
a",i=e.getElementsByTagName("a")[0],n=ft.createElement("select"),o=n.appendChild(ft.createElement("option")),t=e.getElementsByTagName("input")[0],i.style.cssText="top:1px",nt.getSetAttribute="t"!==e.className,nt.style=/top/.test(i.getAttribute("style")),nt.hrefNormalized="/a"===i.getAttribute("href"),nt.checkOn=!!t.value,nt.optSelected=o.selected,nt.enctype=!!ft.createElement("form").enctype,n.disabled=!0,nt.optDisabled=!o.disabled,t=ft.createElement("input"),t.setAttribute("value",""),nt.input=""===t.getAttribute("value"),t.value="t",t.setAttribute("type","radio"),nt.radioValue="t"===t.value}();var ye=/\r/g;ot.fn.extend({val:function(t){var e,n,i,o=this[0];{if(arguments.length)return i=ot.isFunction(t),this.each(function(n){var o;1===this.nodeType&&(o=i?t.call(this,n,ot(this).val()):t,null==o?o="":"number"==typeof o?o+="":ot.isArray(o)&&(o=ot.map(o,function(t){return null==t?"":t+""})),e=ot.valHooks[this.type]||ot.valHooks[this.nodeName.toLowerCase()],e&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))});if(o)return e=ot.valHooks[o.type]||ot.valHooks[o.nodeName.toLowerCase()],e&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:(n=o.value,"string"==typeof n?n.replace(ye,""):null==n?"":n)}}}),ot.extend({valHooks:{option:{get:function(t){var e=ot.find.attr(t,"value");return null!=e?e:ot.trim(ot.text(t))}},select:{get:function(t){for(var e,n,i=t.options,o=t.selectedIndex,r="select-one"===t.type||o<0,a=r?null:[],s=r?o+1:i.length,c=o<0?s:r?o:0;c=0)try{i.selected=n=!0}catch(s){i.scrollHeight}else i.selected=!1;return n||(t.selectedIndex=-1),o}}}}),ot.each(["radio","checkbox"],function(){ot.valHooks[this]={set:function(t,e){if(ot.isArray(e))return t.checked=ot.inArray(ot(t).val(),e)>=0}},nt.checkOn||(ot.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})});var Ae,we,ze=ot.expr.attrHandle,_e=/^(?:checked|selected)$/i,Te=nt.getSetAttribute,xe=nt.input;ot.fn.extend({attr:function(t,e){return Ot(this,ot.attr,t,e,arguments.length>1)},removeAttr:function(t){return this.each(function(){ot.removeAttr(this,t)})}}),ot.extend({attr:function(t,e,n){var i,o,r=t.nodeType;if(t&&3!==r&&8!==r&&2!==r)return typeof t.getAttribute===zt?ot.prop(t,e,n):(1===r&&ot.isXMLDoc(t)||(e=e.toLowerCase(),i=ot.attrHooks[e]||(ot.expr.match.bool.test(e)?we:Ae)),void 0===n?i&&"get"in i&&null!==(o=i.get(t,e))?o:(o=ot.find.attr(t,e),null==o?void 0:o):null!==n?i&&"set"in i&&void 0!==(o=i.set(t,n,e))?o:(t.setAttribute(e,n+""),n):void ot.removeAttr(t,e))},removeAttr:function(t,e){var n,i,o=0,r=e&&e.match(Mt);if(r&&1===t.nodeType)for(;n=r[o++];)i=ot.propFix[n]||n,ot.expr.match.bool.test(n)?xe&&Te||!_e.test(n)?t[i]=!1:t[ot.camelCase("default-"+n)]=t[i]=!1:ot.attr(t,n,""),t.removeAttribute(Te?n:i)},attrHooks:{type:{set:function(t,e){if(!nt.radioValue&&"radio"===e&&ot.nodeName(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}}}),we={set:function(t,e,n){return e===!1?ot.removeAttr(t,n):xe&&Te||!_e.test(n)?t.setAttribute(!Te&&ot.propFix[n]||n,n):t[ot.camelCase("default-"+n)]=t[n]=!0,n}},ot.each(ot.expr.match.bool.source.match(/\w+/g),function(t,e){var n=ze[e]||ot.find.attr;ze[e]=xe&&Te||!_e.test(e)?function(t,e,i){var o,r;return i||(r=ze[e],ze[e]=o,o=null!=n(t,e,i)?e.toLowerCase():null,ze[e]=r),o}:function(t,e,n){if(!n)return t[ot.camelCase("default-"+e)]?e.toLowerCase():null}}),xe&&Te||(ot.attrHooks.value={set:function(t,e,n){return ot.nodeName(t,"input")?void(t.defaultValue=e):Ae&&Ae.set(t,e,n)}}),Te||(Ae={set:function(t,e,n){var i=t.getAttributeNode(n);if(i||t.setAttributeNode(i=t.ownerDocument.createAttribute(n)),i.value=e+="","value"===n||e===t.getAttribute(n))return e}},ze.id=ze.name=ze.coords=function(t,e,n){var i;if(!n)return(i=t.getAttributeNode(e))&&""!==i.value?i.value:null},ot.valHooks.button={get:function(t,e){var n=t.getAttributeNode(e);if(n&&n.specified)return n.value},set:Ae.set},ot.attrHooks.contenteditable={set:function(t,e,n){Ae.set(t,""!==e&&e,n)}},ot.each(["width","height"],function(t,e){ot.attrHooks[e]={set:function(t,n){if(""===n)return t.setAttribute(e,"auto"),n}}})),nt.style||(ot.attrHooks.style={get:function(t){return t.style.cssText||void 0},set:function(t,e){return t.style.cssText=e+""}});var Ce=/^(?:input|select|textarea|button|object)$/i,Se=/^(?:a|area)$/i;ot.fn.extend({prop:function(t,e){return Ot(this,ot.prop,t,e,arguments.length>1)},removeProp:function(t){return t=ot.propFix[t]||t,this.each(function(){try{this[t]=void 0,delete this[t]}catch(e){}})}}),ot.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(t,e,n){var i,o,r,a=t.nodeType;if(t&&3!==a&&8!==a&&2!==a)return r=1!==a||!ot.isXMLDoc(t),r&&(e=ot.propFix[e]||e,o=ot.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:t[e]=n:o&&"get"in o&&null!==(i=o.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=ot.find.attr(t,"tabindex");return e?parseInt(e,10):Ce.test(t.nodeName)||Se.test(t.nodeName)&&t.href?0:-1}}}}),nt.hrefNormalized||ot.each(["href","src"],function(t,e){ot.propHooks[e]={get:function(t){return t.getAttribute(e,4)}}}),nt.optSelected||(ot.propHooks.selected={get:function(t){var e=t.parentNode;return e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex),null}}),ot.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){ot.propFix[this.toLowerCase()]=this}),nt.enctype||(ot.propFix.enctype="encoding");var Oe=/[\t\r\n\f]/g;ot.fn.extend({addClass:function(t){var e,n,i,o,r,a,s=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)||[];s=0;)i=i.replace(" "+o+" "," ");a=t?ot.trim(i):"",n.className!==a&&(n.className=a)}return this},toggleClass:function(t,e){ -var n=typeof t;return"boolean"==typeof e&&"string"===n?e?this.addClass(t):this.removeClass(t):ot.isFunction(t)?this.each(function(n){ot(this).toggleClass(t.call(this,n,this.className,e),e)}):this.each(function(){if("string"===n)for(var e,i=0,o=ot(this),r=t.match(Mt)||[];e=r[i++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else n!==zt&&"boolean"!==n||(this.className&&ot._data(this,"__className__",this.className),this.className=this.className||t===!1?"":ot._data(this,"__className__")||"")})},hasClass:function(t){for(var e=" "+t+" ",n=0,i=this.length;n=0)return!0;return!1}}),ot.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(t,e){ot.fn[e]=function(t,n){return arguments.length>0?this.on(e,null,t,n):this.trigger(e)}}),ot.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)},bind:function(t,e,n){return this.on(t,null,e,n)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,n,i){return this.on(e,t,n,i)},undelegate:function(t,e,n){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",n)}});var Ne=ot.now(),Le=/\?/,ke=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;ot.parseJSON=function(e){if(t.JSON&&t.JSON.parse)return t.JSON.parse(e+"");var n,i=null,o=ot.trim(e+"");return o&&!ot.trim(o.replace(ke,function(t,e,o,r){return n&&e&&(i=0),0===i?t:(n=o||e,i+=!r-!o,"")}))?Function("return "+o)():ot.error("Invalid JSON: "+e)},ot.parseXML=function(e){var n,i;if(!e||"string"!=typeof e)return null;try{t.DOMParser?(i=new DOMParser,n=i.parseFromString(e,"text/xml")):(n=new ActiveXObject("Microsoft.XMLDOM"),n.async="false",n.loadXML(e))}catch(o){n=void 0}return n&&n.documentElement&&!n.getElementsByTagName("parsererror").length||ot.error("Invalid XML: "+e),n};var De,qe,Ee=/#.*$/,We=/([?&])_=[^&]*/,Be=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Ie=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Pe=/^(?:GET|HEAD)$/,Re=/^\/\//,Xe=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Fe={},He={},je="*/".concat("*");try{qe=location.href}catch(Ue){qe=ft.createElement("a"),qe.href="",qe=qe.href}De=Xe.exec(qe.toLowerCase())||[],ot.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qe,type:"GET",isLocal:Ie.test(De[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":je,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":ot.parseJSON,"text xml":ot.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?X(X(t,ot.ajaxSettings),e):X(ot.ajaxSettings,t)},ajaxPrefilter:P(Fe),ajaxTransport:P(He),ajax:function(t,e){function n(t,e,n,i){var o,u,b,v,y,w=e;2!==M&&(M=2,s&&clearTimeout(s),l=void 0,a=i||"",A.readyState=t>0?4:0,o=t>=200&&t<300||304===t,n&&(v=F(h,A,n)),v=H(h,v,A,o),o?(h.ifModified&&(y=A.getResponseHeader("Last-Modified"),y&&(ot.lastModified[r]=y),y=A.getResponseHeader("etag"),y&&(ot.etag[r]=y)),204===t||"HEAD"===h.type?w="nocontent":304===t?w="notmodified":(w=v.state,u=v.data,b=v.error,o=!b)):(b=w,!t&&w||(w="error",t<0&&(t=0))),A.status=t,A.statusText=(e||w)+"",o?f.resolveWith(d,[u,w,A]):f.rejectWith(d,[A,w,b]),A.statusCode(g),g=void 0,c&&p.trigger(o?"ajaxSuccess":"ajaxError",[A,h,o?u:b]),m.fireWith(d,[A,w]),c&&(p.trigger("ajaxComplete",[A,h]),--ot.active||ot.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var i,o,r,a,s,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(a);)u[e[1].toLowerCase()]=e[2];e=u[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return 2===M?a: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(Ee,"").replace(Re,De[1]+"//"),h.type=e.method||e.type||h.method||h.type,h.dataTypes=ot.trim(h.dataType||"*").toLowerCase().match(Mt)||[""],null==h.crossDomain&&(i=Xe.exec(h.url.toLowerCase()),h.crossDomain=!(!i||i[1]===De[1]&&i[2]===De[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(De[3]||("http:"===De[1]?"80":"443")))),h.data&&h.processData&&"string"!=typeof h.data&&(h.data=ot.param(h.data,h.traditional)),R(Fe,h,e,A),2===M)return A;c=ot.event&&h.global,c&&0===ot.active++&&ot.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Pe.test(h.type),r=h.url,h.hasContent||(h.data&&(r=h.url+=(Le.test(r)?"&":"?")+h.data,delete h.data),h.cache===!1&&(h.url=We.test(r)?r.replace(We,"$1_="+Ne++):r+(Le.test(r)?"&":"?")+"_="+Ne++)),h.ifModified&&(ot.lastModified[r]&&A.setRequestHeader("If-Modified-Since",ot.lastModified[r]),ot.etag[r]&&A.setRequestHeader("If-None-Match",ot.etag[r])),(h.data&&h.hasContent&&h.contentType!==!1||e.contentType)&&A.setRequestHeader("Content-Type",h.contentType),A.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+je+"; q=0.01":""):h.accepts["*"]);for(o in h.headers)A.setRequestHeader(o,h.headers[o]);if(h.beforeSend&&(h.beforeSend.call(d,A,h)===!1||2===M))return A.abort();y="abort";for(o in{success:1,error:1,complete:1})A[o](h[o]);if(l=R(He,h,e,A)){A.readyState=1,c&&p.trigger("ajaxSend",[A,h]),h.async&&h.timeout>0&&(s=setTimeout(function(){A.abort("timeout")},h.timeout));try{M=1,l.send(b,n)}catch(w){if(!(M<2))throw w;n(-1,w)}}else n(-1,"No Transport");return A},getJSON:function(t,e,n){return ot.get(t,e,n,"json")},getScript:function(t,e){return ot.get(t,void 0,e,"script")}}),ot.each(["get","post"],function(t,e){ot[e]=function(t,n,i,o){return ot.isFunction(n)&&(o=o||i,i=n,n=void 0),ot.ajax({url:t,type:e,dataType:o,data:n,success:i})}}),ot._evalUrl=function(t){return ot.ajax({url:t,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},ot.fn.extend({wrapAll:function(t){if(ot.isFunction(t))return this.each(function(e){ot(this).wrapAll(t.call(this,e))});if(this[0]){var e=ot(t,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstChild&&1===t.firstChild.nodeType;)t=t.firstChild;return t}).append(this)}return this},wrapInner:function(t){return ot.isFunction(t)?this.each(function(e){ot(this).wrapInner(t.call(this,e))}):this.each(function(){var e=ot(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=ot.isFunction(t);return this.each(function(n){ot(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(){return this.parent().each(function(){ot.nodeName(this,"body")||ot(this).replaceWith(this.childNodes)}).end()}}),ot.expr.filters.hidden=function(t){return t.offsetWidth<=0&&t.offsetHeight<=0||!nt.reliableHiddenOffsets()&&"none"===(t.style&&t.style.display||ot.css(t,"display"))},ot.expr.filters.visible=function(t){return!ot.expr.filters.hidden(t)};var $e=/%20/g,Ve=/\[\]$/,Ye=/\r?\n/g,Je=/^(?:submit|button|image|reset|file)$/i,Ge=/^(?:input|select|textarea|keygen)/i;ot.param=function(t,e){var n,i=[],o=function(t,e){e=ot.isFunction(e)?e():null==e?"":e,i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)};if(void 0===e&&(e=ot.ajaxSettings&&ot.ajaxSettings.traditional),ot.isArray(t)||t.jquery&&!ot.isPlainObject(t))ot.each(t,function(){o(this.name,this.value)});else for(n in t)j(n,t[n],e,o);return i.join("&").replace($e,"+")},ot.fn.extend({serialize:function(){return ot.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=ot.prop(this,"elements");return t?ot.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!ot(this).is(":disabled")&&Ge.test(this.nodeName)&&!Je.test(t)&&(this.checked||!Nt.test(t))}).map(function(t,e){var n=ot(this).val();return null==n?null:ot.isArray(n)?ot.map(n,function(t){return{name:e.name,value:t.replace(Ye,"\r\n")}}):{name:e.name,value:n.replace(Ye,"\r\n")}}).get()}}),ot.ajaxSettings.xhr=void 0!==t.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&U()||$()}:U;var Ke=0,Qe={},Ze=ot.ajaxSettings.xhr();t.attachEvent&&t.attachEvent("onunload",function(){for(var t in Qe)Qe[t](void 0,!0)}),nt.cors=!!Ze&&"withCredentials"in Ze,Ze=nt.ajax=!!Ze,Ze&&ot.ajaxTransport(function(t){if(!t.crossDomain||nt.cors){var e;return{send:function(n,i){var o,r=t.xhr(),a=++Ke;if(r.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)r[o]=t.xhrFields[o];t.mimeType&&r.overrideMimeType&&r.overrideMimeType(t.mimeType),t.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(o in n)void 0!==n[o]&&r.setRequestHeader(o,n[o]+"");r.send(t.hasContent&&t.data||null),e=function(n,o){var s,c,l;if(e&&(o||4===r.readyState))if(delete Qe[a],e=void 0,r.onreadystatechange=ot.noop,o)4!==r.readyState&&r.abort();else{l={},s=r.status,"string"==typeof r.responseText&&(l.text=r.responseText);try{c=r.statusText}catch(u){c=""}s||!t.isLocal||t.crossDomain?1223===s&&(s=204):s=l.text?200:404}l&&i(s,c,l,r.getAllResponseHeaders())},t.async?4===r.readyState?setTimeout(e):r.onreadystatechange=Qe[a]=e:e()},abort:function(){e&&e(void 0,!0)}}}}),ot.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(t){return ot.globalEval(t),t}}}),ot.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET",t.global=!1)}),ot.ajaxTransport("script",function(t){if(t.crossDomain){var e,n=ft.head||ot("head")[0]||ft.documentElement;return{send:function(i,o){e=ft.createElement("script"),e.async=!0,t.scriptCharset&&(e.charset=t.scriptCharset),e.src=t.url,e.onload=e.onreadystatechange=function(t,n){(n||!e.readyState||/loaded|complete/.test(e.readyState))&&(e.onload=e.onreadystatechange=null,e.parentNode&&e.parentNode.removeChild(e),e=null,n||o(200,"success"))},n.insertBefore(e,n.firstChild)},abort:function(){e&&e.onload(void 0,!0)}}}});var tn=[],en=/(=)\?(?=&|$)|\?\?/;ot.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var t=tn.pop()||ot.expando+"_"+Ne++;return this[t]=!0,t}}),ot.ajaxPrefilter("json jsonp",function(e,n,i){var o,r,a,s=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(s||"jsonp"===e.dataTypes[0])return o=e.jsonpCallback=ot.isFunction(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,s?e[s]=e[s].replace(en,"$1"+o):e.jsonp!==!1&&(e.url+=(Le.test(e.url)?"&":"?")+e.jsonp+"="+o),e.converters["script json"]=function(){return a||ot.error(o+" was not called"),a[0]},e.dataTypes[0]="json",r=t[o],t[o]=function(){a=arguments},i.always(function(){t[o]=r,e[o]&&(e.jsonpCallback=n.jsonpCallback,tn.push(o)),a&&ot.isFunction(r)&&r(a[0]),a=r=void 0}),"script"}),ot.parseHTML=function(t,e,n){if(!t||"string"!=typeof t)return null;"boolean"==typeof e&&(n=e,e=!1),e=e||ft;var i=ht.exec(t),o=!n&&[];return i?[e.createElement(i[1])]:(i=ot.buildFragment([t],e,o),o&&o.length&&ot(o).remove(),ot.merge([],i.childNodes))};var nn=ot.fn.load;ot.fn.load=function(t,e,n){if("string"!=typeof t&&nn)return nn.apply(this,arguments);var i,o,r,a=this,s=t.indexOf(" ");return s>=0&&(i=ot.trim(t.slice(s,t.length)),t=t.slice(0,s)),ot.isFunction(e)?(n=e,e=void 0):e&&"object"==typeof e&&(r="POST"),a.length>0&&ot.ajax({url:t,type:r,dataType:"html",data:e}).done(function(t){o=arguments,a.html(i?ot("
").append(ot.parseHTML(t)).find(i):t)}).complete(n&&function(t,e){a.each(n,o||[t.responseText,e,t])}),this},ot.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(t,e){ot.fn[e]=function(t){return this.on(e,t)}}),ot.expr.filters.animated=function(t){return ot.grep(ot.timers,function(e){return t===e.elem}).length};var on=t.document.documentElement;ot.offset={setOffset:function(t,e,n){var i,o,r,a,s,c,l,u=ot.css(t,"position"),h=ot(t),d={};"static"===u&&(t.style.position="relative"),s=h.offset(),r=ot.css(t,"top"),c=ot.css(t,"left"),l=("absolute"===u||"fixed"===u)&&ot.inArray("auto",[r,c])>-1,l?(i=h.position(),a=i.top,o=i.left):(a=parseFloat(r)||0,o=parseFloat(c)||0),ot.isFunction(e)&&(e=e.call(t,n,s)),null!=e.top&&(d.top=e.top-s.top+a),null!=e.left&&(d.left=e.left-s.left+o),"using"in e?e.using.call(t,d):h.css(d)}},ot.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ot.offset.setOffset(this,t,e)});var e,n,i={top:0,left:0},o=this[0],r=o&&o.ownerDocument;if(r)return e=r.documentElement,ot.contains(e,o)?(typeof o.getBoundingClientRect!==zt&&(i=o.getBoundingClientRect()),n=V(r),{top:i.top+(n.pageYOffset||e.scrollTop)-(e.clientTop||0),left:i.left+(n.pageXOffset||e.scrollLeft)-(e.clientLeft||0)}):i},position:function(){if(this[0]){var t,e,n={top:0,left:0},i=this[0];return"fixed"===ot.css(i,"position")?e=i.getBoundingClientRect():(t=this.offsetParent(),e=this.offset(),ot.nodeName(t[0],"html")||(n=t.offset()),n.top+=ot.css(t[0],"borderTopWidth",!0),n.left+=ot.css(t[0],"borderLeftWidth",!0)),{top:e.top-n.top-ot.css(i,"marginTop",!0),left:e.left-n.left-ot.css(i,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||on;t&&!ot.nodeName(t,"html")&&"static"===ot.css(t,"position");)t=t.offsetParent;return t||on})}}),ot.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,e){var n=/Y/.test(e);ot.fn[t]=function(i){return Ot(this,function(t,i,o){var r=V(t);return void 0===o?r?e in r?r[e]:r.document.documentElement[i]:t[i]:void(r?r.scrollTo(n?ot(r).scrollLeft():o,n?o:ot(r).scrollTop()):t[i]=o)},t,i,arguments.length,null)}}),ot.each(["top","left"],function(t,e){ot.cssHooks[e]=x(nt.pixelPosition,function(t,n){if(n)return n=ee(t,e),ie.test(n)?ot(t).position()[e]+"px":n})}),ot.each({Height:"height",Width:"width"},function(t,e){ot.each({padding:"inner"+t,content:e,"":"outer"+t},function(n,i){ot.fn[i]=function(i,o){var r=arguments.length&&(n||"boolean"!=typeof i),a=n||(i===!0||o===!0?"margin":"border");return Ot(this,function(e,n,i){var o;return ot.isWindow(e)?e.document.documentElement["client"+t]:9===e.nodeType?(o=e.documentElement,Math.max(e.body["scroll"+t],o["scroll"+t],e.body["offset"+t],o["offset"+t],o["client"+t])):void 0===i?ot.css(e,n,a):ot.style(e,n,i,a)},e,r?i:void 0,r,null)}})}),ot.fn.size=function(){return this.length},ot.fn.andSelf=ot.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return ot});var rn=t.jQuery,an=t.$;return ot.noConflict=function(e){return t.$===ot&&(t.$=an),e&&t.jQuery===ot&&(t.jQuery=rn),ot},typeof e===zt&&(t.jQuery=t.$=ot),ot}),function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(t){function e(e,i){var o,r,a,s=e.nodeName.toLowerCase();return"area"===s?(o=e.parentNode,r=o.name,!(!e.href||!r||"map"!==o.nodeName.toLowerCase())&&(a=t("img[usemap='#"+r+"']")[0],!!a&&n(a))):(/input|select|textarea|button|object/.test(s)?!e.disabled:"a"===s?e.href||i:i)&&n(e)}function n(e){return t.expr.filters.visible(e)&&!t(e).parents().addBack().filter(function(){return"hidden"===t.css(this,"visibility")}).length}function i(t){for(var e,n;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(n=parseInt(t.css("zIndex"),10),!isNaN(n)&&0!==n))return n;t=t.parent()}return 0}function o(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=r(t("
"))}function r(e){var n="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.delegate(n,"mouseout",function(){t(this).removeClass("ui-state-hover"),this.className.indexOf("ui-datepicker-prev")!==-1&&t(this).removeClass("ui-datepicker-prev-hover"),this.className.indexOf("ui-datepicker-next")!==-1&&t(this).removeClass("ui-datepicker-next-hover")}).delegate(n,"mouseover",a)}function a(){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 s(e,n){t.extend(e,n);for(var i in n)null==n[i]&&(e[i]=n[i]);return e}function c(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.extend(t.ui,{version:"1.11.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),t.fn.extend({scrollParent:function(e){var n=this.css("position"),i="absolute"===n,o=e?/(auto|scroll|hidden)/:/(auto|scroll)/,r=this.parents().filter(function(){var e=t(this);return(!i||"static"!==e.css("position"))&&o.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==n&&r.length?r:t(this[0].ownerDocument||document)},uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(n){return!!t.data(n,e)}}):function(e,n,i){return!!t.data(e,i[3])},focusable:function(n){return e(n,!isNaN(t.attr(n,"tabindex")))},tabbable:function(n){var i=t.attr(n,"tabindex"),o=isNaN(i);return(o||i>=0)&&e(n,!o)}}),t("").outerWidth(1).jquery||t.each(["Width","Height"],function(e,n){function i(e,n,i,r){return t.each(o,function(){n-=parseFloat(t.css(e,"padding"+this))||0,i&&(n-=parseFloat(t.css(e,"border"+this+"Width"))||0),r&&(n-=parseFloat(t.css(e,"margin"+this))||0)}),n}var o="Width"===n?["Left","Right"]:["Top","Bottom"],r=n.toLowerCase(),a={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?a["inner"+n].call(this):this.each(function(){t(this).css(r,i(this,e)+"px")})},t.fn["outer"+n]=function(e,o){return"number"!=typeof e?a["outer"+n].call(this,e):this.each(function(){t(this).css(r,i(this,e,!0,o)+"px")})}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t("").data("a-b","a").removeData("a-b").data("a-b")&&(t.fn.removeData=function(e){return function(n){return arguments.length?e.call(this,t.camelCase(n)):e.call(this)}}(t.fn.removeData)),t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),t.fn.extend({focus:function(e){return function(n,i){return"number"==typeof n?this.each(function(){var e=this;setTimeout(function(){t(e).focus(),i&&i.call(e)},n)}):e.apply(this,arguments)}}(t.fn.focus),disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(e){if(void 0!==e)return this.css("zIndex",e);if(this.length)for(var n,i,o=t(this[0]);o.length&&o[0]!==document;){if(n=o.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(i=parseInt(o.css("zIndex"),10),!isNaN(i)&&0!==i))return i;o=o.parent()}return 0}}),t.ui.plugin={add:function(e,n,i){var o,r=t.ui[e].prototype;for(o in i)r.plugins[o]=r.plugins[o]||[],r.plugins[o].push([n,i[o]])},call:function(t,e,n,i){var o,r=t.plugins[e];if(r&&(i||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(o=0;o",options:{disabled:!1,create:null},_createWidget:function(e,n){n=t(n||this.defaultElement||this)[0],this.element=t(n),this.uuid=l++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),n!==this&&(t.data(n,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===n&&this.destroy()}}),this.document=t(n.style?n.ownerDocument:n.document||n),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(e,n){var i,o,r,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},i=e.split("."),e=i.shift(),i.length){for(o=a[e]=t.widget.extend({},this.options[e]),r=0;r=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}});!function(){function e(t,e,n){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?n/100:1)]; -}function n(e,n){return parseInt(t.css(e,n),10)||0}function i(e){var n=e[0];return 9===n.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(n)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:n.preventDefault?{width:0,height:0,offset:{top:n.pageY,left:n.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var o,r,a=Math.max,s=Math.abs,c=Math.round,l=/left|center|right/,u=/top|center|bottom/,h=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==o)return o;var e,n,i=t("
"),r=i.children()[0];return t("body").append(i),e=r.offsetWidth,i.css("overflow","scroll"),n=r.offsetWidth,e===n&&(n=i[0].clientWidth),i.remove(),o=e-n},getScrollInfo:function(e){var n=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),i=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),o="scroll"===n||"auto"===n&&e.width0?"right":"center",vertical:r<0?"top":i>0?"bottom":"middle"};ma(s(i),s(r))?c.important="horizontal":c.important="vertical",o.using.call(this,t,c)}),u.offset(t.extend(C,{using:l}))})},t.ui.position={fit:{left:function(t,e){var n,i=e.within,o=i.isWindow?i.scrollLeft:i.offset.left,r=i.width,s=t.left-e.collisionPosition.marginLeft,c=o-s,l=s+e.collisionWidth-r-o;e.collisionWidth>r?c>0&&l<=0?(n=t.left+c+e.collisionWidth-r-o,t.left+=c-n):l>0&&c<=0?t.left=o:c>l?t.left=o+r-e.collisionWidth:t.left=o:c>0?t.left+=c:l>0?t.left-=l:t.left=a(t.left-s,t.left)},top:function(t,e){var n,i=e.within,o=i.isWindow?i.scrollTop:i.offset.top,r=e.within.height,s=t.top-e.collisionPosition.marginTop,c=o-s,l=s+e.collisionHeight-r-o;e.collisionHeight>r?c>0&&l<=0?(n=t.top+c+e.collisionHeight-r-o,t.top+=c-n):l>0&&c<=0?t.top=o:c>l?t.top=o+r-e.collisionHeight:t.top=o:c>0?t.top+=c:l>0?t.top-=l:t.top=a(t.top-s,t.top)}},flip:{left:function(t,e){var n,i,o=e.within,r=o.offset.left+o.scrollLeft,a=o.width,c=o.isWindow?o.scrollLeft:o.offset.left,l=t.left-e.collisionPosition.marginLeft,u=l-c,h=l+e.collisionWidth-a-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-a-r,(n<0||n0&&(i=t.left-e.collisionPosition.marginLeft+d+p+f-c,(i>0||s(i)u&&(i<0||i0&&(n=t.top-e.collisionPosition.marginTop+p+f+m-c,t.top+p+f+m>h&&(n>0||s(n)10&&o<11,e.innerHTML="",n.removeChild(e)}()}();t.ui.position,t.widget("ui.accordion",{version:"1.11.2",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),e.active<0&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e=this.options.icons;e&&(t("").addClass("ui-accordion-header-icon ui-icon "+e.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(e.header).addClass(e.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?void this._activate(e):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void("disabled"===t&&(this.element.toggleClass("ui-state-disabled",!!e).attr("aria-disabled",e),this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!e))))},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var n=t.ui.keyCode,i=this.headers.length,o=this.headers.index(e.target),r=!1;switch(e.keyCode){case n.RIGHT:case n.DOWN:r=this.headers[(o+1)%i];break;case n.LEFT:case n.UP:r=this.headers[(o-1+i)%i];break;case n.SPACE:case n.ENTER:this._eventHandler(e);break;case n.HOME:r=this.headers[0];break;case n.END:r=this.headers[i-1]}r&&(t(e.target).attr("tabIndex",-1),t(r).attr("tabIndex",0),r.focus(),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().focus()},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-state-default ui-corner-all"),this.panels=this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide(),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,n=this.options,i=n.heightStyle,o=this.element.parent();this.active=this._findActive(n.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(){var e=t(this),n=e.uniqueId().attr("id"),i=e.next(),o=i.uniqueId().attr("id");e.attr("aria-controls",o),i.attr("aria-labelledby",n)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(n.event),"fill"===i?(e=o.height(),this.element.siblings(":visible").each(function(){var n=t(this),i=n.css("position");"absolute"!==i&&"fixed"!==i&&(e-=n.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===i&&(e=0,this.headers.next().each(function(){e=Math.max(e,t(this).css("height","").height())}).height(e))},_activate:function(e){var n=this._findActive(e)[0];n!==this.active[0]&&(n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var n={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){n[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,n),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var n=this.options,i=this.active,o=t(e.currentTarget),r=o[0]===i[0],a=r&&n.collapsible,s=a?t():o.next(),c=i.next(),l={oldHeader:i,oldPanel:c,newHeader:a?t():o,newPanel:s};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,l)===!1||(n.active=!a&&this.headers.index(o),this.active=r?t():o,this._toggle(l),i.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),r||(o.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&o.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),o.next().addClass("ui-accordion-content-active")))},_toggle:function(e){var n=e.newPanel,i=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=i,this.options.animate?this._animate(n,i,e):(i.hide(),n.show(),this._toggleComplete(e)),i.attr({"aria-hidden":"true"}),i.prev().attr("aria-selected","false"),n.length&&i.length?i.prev().attr({tabIndex:-1,"aria-expanded":"false"}):n.length&&this.headers.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),n.attr("aria-hidden","false").prev().attr({"aria-selected":"true",tabIndex:0,"aria-expanded":"true"})},_animate:function(t,e,n){var i,o,r,a=this,s=0,c=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},items:"> *",menus:"ul",position:{my:"left-1 top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var n=t(e.target);!this.mouseHandled&&n.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),n.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&t(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var n=t(e.currentTarget);n.siblings(".ui-state-active").removeClass("ui-state-active"),this.focus(e,n)}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var n=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,n)},blur:function(e){this._delay(function(){t.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-menu-icons ui-front").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").removeUniqueId().removeClass("ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var e=t(this);e.data("ui-menu-submenu-carat")&&e.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(e){var n,i,o,r,a=!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:a=!1,i=this.previousFilter||"",o=String.fromCharCode(e.keyCode),r=!1,clearTimeout(this.filterTimer),o===i?r=!0:o=i+o,n=this._filterMenuItems(o),n=r&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(o=String.fromCharCode(e.keyCode),n=this._filterMenuItems(o)),n.length?(this.focus(e,n),this.previousFilter=o,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active.is(".ui-state-disabled")||(this.active.is("[aria-haspopup='true']")?this.expand(t):this.select(t))},refresh:function(){var e,n,i=this,o=this.options.icons.submenu,r=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-front").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),n=e.parent(),i=t("").addClass("ui-menu-icon ui-icon "+o).data("ui-menu-submenu-carat",!0);n.attr("aria-haspopup","true").prepend(i),e.attr("aria-labelledby",n.attr("id"))}),e=r.add(this.element),n=e.find(this.options.items),n.not(".ui-menu-item").each(function(){var e=t(this);i._isDivider(e)&&e.addClass("ui-widget-content ui-menu-divider")}),n.not(".ui-menu-item, .ui-menu-divider").addClass("ui-menu-item").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),n.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){"icons"===t&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(e.submenu),"disabled"===t&&this.element.toggleClass("ui-state-disabled",!!e).attr("aria-disabled",e),this._super(t,e)},focus:function(t,e){var n,i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.addClass("ui-state-focus").removeClass("ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),this.active.parent().closest(".ui-menu-item").addClass("ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=e.children(".ui-menu"),n.length&&t&&/^mouse/.test(t.type)&&this._startOpening(n),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var n,i,o,r,a,s;this._hasScroll()&&(n=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,i=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,o=e.offset().top-this.activeMenu.offset().top-n-i,r=this.activeMenu.scrollTop(),a=this.activeMenu.height(),s=e.outerHeight(),o<0?this.activeMenu.scrollTop(r+o):o+s>a&&this.activeMenu.scrollTop(r+o-a+s))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this.active.removeClass("ui-state-focus"),this.active=null,this._trigger("blur",t,{item:this.active}))},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var n=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(e,n){clearTimeout(this.timer),this.timer=this._delay(function(){var i=n?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));i.length||(i=this.element),this._close(i),this.blur(e),this.activeMenu=i},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find(".ui-state-active").not(".ui-state-focus").removeClass("ui-state-active")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,n){var i;this.active&&(i="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),i&&i.length&&this.active||(i=this.activeMenu.find(this.options.items)[e]()),this.focus(n,i)},nextPage:function(e){var n,i,o;return this.active?void(this.isLastItem()||(this._hasScroll()?(i=this.active.offset().top,o=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=t(this),n.offset().top-i-o<0}),this.focus(e,n)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]()))):void this.next(e)},previousPage:function(e){var n,i,o;return this.active?void(this.isFirstItem()||(this._hasScroll()?(i=this.active.offset().top,o=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=t(this),n.offset().top-i+o>0}),this.focus(e,n)):this.focus(e,this.activeMenu.find(this.options.items).first()))):void this.next(e)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,n,i,o=this.element[0].nodeName.toLowerCase(),r="textarea"===o,a="input"===o;this.isMultiLine=!!r||!a&&this.element.prop("isContentEditable"),this.valueMethod=this.element[r||a?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(o){if(this.element.prop("readOnly"))return e=!0,i=!0,void(n=!0);e=!1,i=!1,n=!1;var r=t.ui.keyCode;switch(o.keyCode){case r.PAGE_UP:e=!0,this._move("previousPage",o);break;case r.PAGE_DOWN:e=!0,this._move("nextPage",o);break;case r.UP:e=!0,this._keyEvent("previous",o);break;case r.DOWN:e=!0,this._keyEvent("next",o);break;case r.ENTER:this.menu.active&&(e=!0,o.preventDefault(),this.menu.select(o));break;case r.TAB:this.menu.active&&this.menu.select(o);break;case r.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(o),o.preventDefault());break;default:n=!0,this._searchTimeout(o)}},keypress:function(i){if(e)return e=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||i.preventDefault());if(!n){var o=t.ui.keyCode;switch(i.keyCode){case o.PAGE_UP:this._move("previousPage",i);break;case o.PAGE_DOWN:this._move("nextPage",i);break;case o.UP:this._keyEvent("previous",i);break;case o.DOWN:this._keyEvent("next",i)}}},input:function(t){return i?(i=!1,void t.preventDefault()):void this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?void delete this.cancelBlur:(clearTimeout(this.searching),this.close(t),void this._change(t))}}),this._initSource(),this.menu=t("
");var s=t("a",n),c=s[0],l=s[1],u=s[2],h=s[3];e.oApi._fnBindAction(c,{action:"first"},a),e.oApi._fnBindAction(l,{action:"previous"},a),e.oApi._fnBindAction(u,{action:"next"},a),e.oApi._fnBindAction(h,{action:"last"},a),e.aanFeatures.p||(n.id=e.sTableId+"_paginate",c.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",u.id=e.sTableId+"_next",h.id=e.sTableId+"_last")},fnUpdate:function(e,n){if(e.aanFeatures.p){var i,o,r,a,s,c=e.oInstance.fnPagingInfo(),l=t.fn.dataTableExt.oPagination.iFullNumbersShowPages,u=Math.floor(l/2),h=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),d=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,p="",f=(e.oClasses,e.aanFeatures.p);for(e._iDisplayLength===-1?(i=1,o=1,d=1):h=h-u?(i=h-l+1,o=h):(i=d-Math.ceil(l/2)+1,o=i+l-1),r=i;r<=o;r++)p+=d!==r?'
  • '+e.fnFormatNumber(r)+"
  • ":'
  • '+e.fnFormatNumber(r)+"
  • ";for(r=0,a=f.length;r",o[0];);return 4h.a.l(e,t[n])&&e.push(t[n]);return e},ya:function(t,e){t=t||[];for(var n=[],i=0,o=t.length;ii?n&&t.push(e):n||t.splice(i,1)},na:l,extend:s,ra:c,sa:l?c:s,A:a,Oa:function(t,e){if(!t)return t;var n,i={};for(n in t)t.hasOwnProperty(n)&&(i[n]=e(t[n],n,t));return i},Fa:function(t){for(;t.firstChild;)h.removeNode(t.firstChild)},ec:function(t){t=h.a.R(t);for(var e=n.createElement("div"),i=0,o=t.length;if?t.setAttribute("selected",e):t.selected=e},ta:function(e){return null===e||e===t?"":e.trim?e.trim():e.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},oc:function(t,e){for(var n=[],i=(t||"").split(e),o=0,r=i.length;ot.length)&&t.substring(0,e.length)===e},Sb:function(t,e){if(t===e)return!0;if(11===t.nodeType)return!1;if(e.contains)return e.contains(3===t.nodeType?t.parentNode:t);if(e.compareDocumentPosition)return 16==(16&e.compareDocumentPosition(t));for(;t&&t!=e;)t=t.parentNode;return!!t},Ea:function(t){return h.a.Sb(t,t.ownerDocument.documentElement)},eb:function(t){return!!h.a.hb(t,h.a.Ea)},B:function(t){return t&&t.tagName&&t.tagName.toLowerCase()},q:function(t,e,n){var i=f&&p[e];if(!i&&o)o(t).bind(e,n);else if(i||"function"!=typeof t.addEventListener){if("undefined"==typeof t.attachEvent)throw Error("Browser doesn't support addEventListener or attachEvent");var r=function(e){n.call(t,e)},a="on"+e;t.attachEvent(a,r),h.a.u.ja(t,function(){t.detachEvent(a,r)})}else t.addEventListener(e,n,!1)},ha:function(t,i){if(!t||!t.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var r;if("input"===h.a.B(t)&&t.type&&"click"==i.toLowerCase()?(r=t.type,r="checkbox"==r||"radio"==r):r=!1,o&&!r)o(t).trigger(i);else if("function"==typeof n.createEvent){if("function"!=typeof t.dispatchEvent)throw Error("The supplied element doesn't support dispatchEvent");r=n.createEvent(d[i]||"HTMLEvents"),r.initEvent(i,!0,!0,e,0,0,0,0,0,!1,!1,!1,!1,0,t),t.dispatchEvent(r)}else if(r&&t.click)t.click();else{if("undefined"==typeof t.fireEvent)throw Error("Browser doesn't support triggering events");t.fireEvent("on"+i)}},c:function(t){return h.v(t)?t():t},Sa:function(t){return h.v(t)?t.o():t},ua:function(t,e,n){if(e){var i=/\S+/g,o=t.className.match(i)||[];h.a.r(e.match(i),function(t){h.a.Y(o,t,n)}),t.className=o.join(" ")}},Xa:function(e,n){var i=h.a.c(n);null!==i&&i!==t||(i="");var o=h.e.firstChild(e);!o||3!=o.nodeType||h.e.nextSibling(o)?h.e.U(e,[e.ownerDocument.createTextNode(i)]):o.data=i,h.a.Vb(e)},Cb:function(t,e){if(t.name=e,7>=f)try{t.mergeAttributes(n.createElement(""),!1)}catch(i){}},Vb:function(t){9<=f&&(t=1==t.nodeType?t:t.parentNode,t.style&&(t.style.zoom=t.style.zoom))},Tb:function(t){if(f){var e=t.style.width;t.style.width=0,t.style.width=e}},ic:function(t,e){t=h.a.c(t),e=h.a.c(e);for(var n=[],i=t;i<=e;i++)n.push(i);return n},R:function(t){for(var e=[],n=0,i=t.length;n",""]||!r.indexOf("",""]||(!r.indexOf("",""]||[0,"",""],t="ignored
    "+r[1]+t+r[2]+"
    ","function"==typeof e.innerShiv?i.appendChild(e.innerShiv(t)):i.innerHTML=t;r[0]--;)i=i.lastChild;i=h.a.R(i.lastChild.childNodes)}return i},h.a.Va=function(e,n){if(h.a.Fa(e),n=h.a.c(n),null!==n&&n!==t)if("string"!=typeof n&&(n=n.toString()),o)o(e).html(n);else for(var i=h.a.Qa(n),r=0;r"},Hb:function(e,i){var o=n[e];if(o===t)throw Error("Couldn't find any memo with ID "+e+". Perhaps it's already been unmemoized.");try{return o.apply(null,i||[]),!0}finally{delete n[e]}},Ib:function(t,n){var i=[];e(t,i);for(var o=0,r=i.length;or[0]?c+r[0]:r[0]),c);for(var c=1===l?c:Math.min(e+(r[1]||0),c),l=e+l-2,u=Math.max(c,l),d=[],p=[],f=2;ee;e++)t=t();return t})},h.toJSON=function(t,e,n){return t=h.Gb(t),h.a.Ya(t,e,n)},i.prototype={save:function(t,e){var n=h.a.l(this.keys,t);0<=n?this.ab[n]=e:(this.keys.push(t),this.ab.push(e))},get:function(e){return e=h.a.l(this.keys,e),0<=e?this.ab[e]:t}}}(),h.b("toJS",h.Gb),h.b("toJSON",h.toJSON),function(){h.i={p:function(e){switch(h.a.B(e)){case"option":return!0===e.__ko__hasDomDataOptionValue__?h.a.f.get(e,h.d.options.Pa):7>=h.a.oa?e.getAttributeNode("value")&&e.getAttributeNode("value").specified?e.value:e.text:e.value;case"select":return 0<=e.selectedIndex?h.i.p(e.options[e.selectedIndex]):t;default:return e.value}},X:function(e,n,i){switch(h.a.B(e)){case"option":switch(typeof n){case"string":h.a.f.set(e,h.d.options.Pa,t),"__ko__hasDomDataOptionValue__"in e&&delete e.__ko__hasDomDataOptionValue__,e.value=n;break;default:h.a.f.set(e,h.d.options.Pa,n),e.__ko__hasDomDataOptionValue__=!0,e.value="number"==typeof n?n:""}break;case"select":""!==n&&null!==n||(n=t);for(var o,r=-1,a=0,s=e.options.length;a=c){e&&a.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,a=r?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,s=r?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,c={ul:!0,ol:!0};h.e={Q:{},childNodes:function(e){return t(e)?i(e):e.childNodes},da:function(e){if(t(e)){e=h.e.childNodes(e);for(var n=0,i=e.length;n=h.a.oa&&n in g?(n=g[n],o?e.removeAttribute(n):e[n]=i):o||e.setAttribute(n,i.toString()),"name"===n&&h.a.Cb(e,o?"":i.toString())})}},function(){h.d.checked={after:["value","attr"],init:function(e,n,i){function o(){return i.has("checkedValue")?h.a.c(i.get("checkedValue")):e.value}function r(){var t=e.checked,r=d?o():t;if(!h.ca.pa()&&(!c||t)){var a=h.k.t(n);l?u!==r?(t&&(h.a.Y(a,r,!0),h.a.Y(a,u,!1)),u=r):h.a.Y(a,r,t):h.g.va(a,i,"checked",r,!0)}}function a(){var t=h.a.c(n());e.checked=l?0<=h.a.l(t,o()):s?t:o()===t}var s="checkbox"==e.type,c="radio"==e.type;if(s||c){var l=s&&h.a.c(n())instanceof Array,u=l?o():t,d=c||l;c&&!e.name&&h.d.uniqueName.init(e,function(){return!0}),h.ba(r,null,{G:e}),h.a.q(e,"click",r),h.ba(a,null,{G:e})}}},h.g.W.checked=!0,h.d.checkedValue={update:function(t,e){t.value=h.a.c(e())}}}(),h.d.css={update:function(t,e){var n=h.a.c(e());"object"==typeof n?h.a.A(n,function(e,n){n=h.a.c(n),h.a.ua(t,e,n)}):(n=String(n||""),h.a.ua(t,t.__ko__cssValue,!1),t.__ko__cssValue=n,h.a.ua(t,n,!0))}},h.d.enable={update:function(t,e){var n=h.a.c(e());n&&t.disabled?t.removeAttribute("disabled"):n||t.disabled||(t.disabled=!0)}},h.d.disable={update:function(t,e){h.d.enable.update(t,function(){return!h.a.c(e())})}},h.d.event={init:function(t,e,n,i,o){var r=e()||{};h.a.A(r,function(r){"string"==typeof r&&h.a.q(t,r,function(t){var a,s=e()[r];if(s){try{var c=h.a.R(arguments);i=o.$data,c.unshift(i),a=s.apply(i,c)}finally{!0!==a&&(t.preventDefault?t.preventDefault():t.returnValue=!1)}!1===n.get(r+"Bubble")&&(t.cancelBubble=!0,t.stopPropagation&&t.stopPropagation())}})})}},h.d.foreach={vb:function(t){return function(){var e=t(),n=h.a.Sa(e);return n&&"number"!=typeof n.length?(h.a.c(e),{foreach:n.data,as:n.as,includeDestroyed:n.includeDestroyed,afterAdd:n.afterAdd,beforeRemove:n.beforeRemove,afterRender:n.afterRender,beforeMove:n.beforeMove,afterMove:n.afterMove,templateEngine:h.K.Ja}):{foreach:e,templateEngine:h.K.Ja}}},init:function(t,e){return h.d.template.init(t,h.d.foreach.vb(e))},update:function(t,e,n,i,o){return h.d.template.update(t,h.d.foreach.vb(e),n,i,o)}},h.g.aa.foreach=!1,h.e.Q.foreach=!0,h.d.hasfocus={init:function(t,e,n){function i(i){t.__ko_hasfocusUpdating=!0;var o=t.ownerDocument;if("activeElement"in o){var r;try{r=o.activeElement}catch(a){r=o.body}i=r===t}o=e(),h.g.va(o,n,"hasfocus",i,!0),t.__ko_hasfocusLastValue=i,t.__ko_hasfocusUpdating=!1}var o=i.bind(null,!0),r=i.bind(null,!1);h.a.q(t,"focus",o),h.a.q(t,"focusin",o),h.a.q(t,"blur",r),h.a.q(t,"focusout",r)},update:function(t,e){var n=!!h.a.c(e());t.__ko_hasfocusUpdating||t.__ko_hasfocusLastValue===n||(n?t.focus():t.blur(),h.k.t(h.a.ha,null,[t,n?"focusin":"focusout"]))}},h.g.W.hasfocus=!0,h.d.hasFocus=h.d.hasfocus,h.g.W.hasFocus=!0,h.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(t,e){h.a.Va(t,e())}},u("if"),u("ifnot",!1,!0),u("with",!0,!1,function(t,e){return t.createChildContext(e)});var b={};h.d.options={init:function(t){if("select"!==h.a.B(t))throw Error("options binding applies only to SELECT elements");for(;0","#comment",o)})},Mb:function(t,e){return h.w.Na(function(n,i){ -var o=n.nextSibling;o&&o.nodeName.toLowerCase()===e&&h.xa(o,t,i)})}}}(),h.b("__tr_ambtns",h.Za.Mb),function(){h.n={},h.n.j=function(t){this.j=t},h.n.j.prototype.text=function(){var t=h.a.B(this.j),t="script"===t?"text":"textarea"===t?"value":"innerHTML";if(0==arguments.length)return this.j[t];var e=arguments[0];"innerHTML"===t?h.a.Va(this.j,e):this.j[t]=e};var e=h.a.f.L()+"_";h.n.j.prototype.data=function(t){return 1===arguments.length?h.a.f.get(this.j,e+t):void h.a.f.set(this.j,e+t,arguments[1])};var n=h.a.f.L();h.n.Z=function(t){this.j=t},h.n.Z.prototype=new h.n.j,h.n.Z.prototype.text=function(){if(0==arguments.length){var e=h.a.f.get(this.j,n)||{};return e.$a===t&&e.Ba&&(e.$a=e.Ba.innerHTML),e.$a}h.a.f.set(this.j,n,{$a:arguments[0]})},h.n.j.prototype.nodes=function(){return 0==arguments.length?(h.a.f.get(this.j,n)||{}).Ba:void h.a.f.set(this.j,n,{Ba:arguments[0]})},h.b("templateSources",h.n),h.b("templateSources.domElement",h.n.j),h.b("templateSources.anonymousTemplate",h.n.Z)}(),function(){function e(t,e,n){var i;for(e=h.e.nextSibling(e);t&&(i=t)!==e;)t=h.e.nextSibling(i),n(i,t)}function n(t,n){if(t.length){var i=t[0],o=t[t.length-1],r=i.parentNode,a=h.J.instance,s=a.preprocessNode;if(s){if(e(i,o,function(t,e){var n=t.previousSibling,r=s.call(a,t);r&&(t===i&&(i=r[0]||e),t===o&&(o=r[r.length-1]||n))}),t.length=0,!i)return;i===o?t.push(i):(t.push(i,o),h.a.ea(t,r))}e(i,o,function(t){1!==t.nodeType&&8!==t.nodeType||h.fb(n,t)}),e(i,o,function(t){1!==t.nodeType&&8!==t.nodeType||h.w.Ib(t,[n])}),h.a.ea(t,r)}}function i(t){return t.nodeType?t:0h.a.oa?0:t.nodes)?t.nodes():null;return e?h.a.R(e.cloneNode(!0).childNodes):(t=t.text(),h.a.Qa(t))},h.K.Ja=new h.K,h.Wa(h.K.Ja),h.b("nativeTemplateEngine",h.K),function(){h.La=function(){var t=this.ac=function(){if(!o||!o.tmpl)return 0;try{if(0<=o.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(t){}return 1}();this.renderTemplateSource=function(e,i,r){if(r=r||{},2>t)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var a=e.data("precompiled");return a||(a=e.text()||"",a=o.template(null,"{{ko_with $item.koBindingContext}}"+a+"{{/ko_with}}"),e.data("precompiled",a)),e=[i.$data],i=o.extend({koBindingContext:i},r.templateOptions),i=o.tmpl(a,e,i),i.appendTo(n.createElement("div")),o.fragments={},i},this.createJavaScriptEvaluatorBlock=function(t){return"{{ko_code ((function() { return "+t+" })()) }}"},this.addTemplate=function(t,e){n.write("")},0=0&&(u&&(u.splice(m,1),t.processAllDeferredBindingUpdates&&t.processAllDeferredBindingUpdates()),p.splice(g,0,A)),l(v,n,null),t.processAllDeferredBindingUpdates&&t.processAllDeferredBindingUpdates(),_.afterMove&&_.afterMove.call(this,b,a,s)}y&&y.apply(this,arguments)},connectWith:!!_.connectClass&&"."+_.connectClass})),void 0!==_.isEnabled&&t.computed({read:function(){A.sortable(s(_.isEnabled)?"enable":"disable")},disposeWhenNodeIsRemoved:u})},0);return t.utils.domNodeDisposal.addDisposeCallback(u,function(){(A.data("ui-sortable")||A.data("sortable"))&&A.sortable("destroy"),clearTimeout(T)}),{controlsDescendantBindings:!0}},update:function(e,n,i,r,a){var s=p(n,"foreach");l(e,o,s.foreach),t.bindingHandlers.template.update(e,function(){return s},i,r,a)},connectClass:"ko_container",allowDrop:!0,afterMove:null,beforeMove:null,options:{}},t.bindingHandlers.draggable={init:function(n,i,o,r,c){var u=s(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,a,u),t.utils.extend(d,h),d.connectToSortable=!!m&&"."+m,e(n).draggable(d),void 0!==g&&t.computed({read:function(){e(n).draggable(s(g)?"enable":"disable")},disposeWhenNodeIsRemoved:n}),t.bindingHandlers.template.init(n,function(){return f},o,r,c)},update:function(e,n,i,o,r){var a=p(n,"data");return t.bindingHandlers.template.update(e,function(){return a},i,o,r)},connectClass:t.bindingHandlers.sortable.connectClass,options:{helper:"clone"}}}),function(){var t=this,e=t._,n=Array.prototype,i=Object.prototype,o=Function.prototype,r=n.push,a=n.slice,s=n.concat,c=i.toString,l=i.hasOwnProperty,u=Array.isArray,h=Object.keys,d=o.bind,p=function(t){return t instanceof p?t:this instanceof p?void(this._wrapped=t):new p(t)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=p),exports._=p):t._=p,p.VERSION="1.7.0";var f=function(t,e,n){if(void 0===e)return t;switch(null==n?3:n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)};case 4:return function(n,i,o,r){return t.call(e,n,i,o,r)}}return function(){return t.apply(e,arguments)}};p.iteratee=function(t,e,n){return null==t?p.identity:p.isFunction(t)?f(t,e,n):p.isObject(t)?p.matches(t):p.property(t)},p.each=p.forEach=function(t,e,n){if(null==t)return t;e=f(e,n);var i,o=t.length;if(o===+o)for(i=0;i=0)},p.invoke=function(t,e){var n=a.call(arguments,2),i=p.isFunction(e);return p.map(t,function(t){return(i?e:t[e]).apply(t,n)})},p.pluck=function(t,e){return p.map(t,p.property(e))},p.where=function(t,e){return p.filter(t,p.matches(e))},p.findWhere=function(t,e){return p.find(t,p.matches(e))},p.max=function(t,e,n){var i,o,r=-(1/0),a=-(1/0);if(null==e&&null!=t){t=t.length===+t.length?t:p.values(t);for(var s=0,c=t.length;sr&&(r=i)}else e=p.iteratee(e,n),p.each(t,function(t,n,i){o=e(t,n,i),(o>a||o===-(1/0)&&r===-(1/0))&&(r=t,a=o)});return r},p.min=function(t,e,n){var i,o,r=1/0,a=1/0;if(null==e&&null!=t){t=t.length===+t.length?t:p.values(t);for(var s=0,c=t.length;si||void 0===n)return 1;if(n>>1;n(t[s])=0;)if(t[i]===e)return i;return-1},p.range=function(t,e,n){arguments.length<=1&&(e=t||0,t=0),n=n||1;for(var i=Math.max(Math.ceil((e-t)/n),0),o=Array(i),r=0;re?(clearTimeout(a),a=null,s=l,r=t.apply(i,o),a||(i=o=null)):a||n.trailing===!1||(a=setTimeout(c,u)),r}},p.debounce=function(t,e,n){var i,o,r,a,s,c=function(){var l=p.now()-a;l0?i=setTimeout(c,e-l):(i=null,n||(s=t.apply(r,o),i||(r=o=null)))};return function(){r=this,o=arguments,a=p.now();var l=n&&!i;return i||(i=setTimeout(c,e)),l&&(s=t.apply(r,o),r=o=null),s}},p.wrap=function(t,e){return p.partial(e,t)},p.negate=function(t){return function(){return!t.apply(this,arguments)}},p.compose=function(){var t=arguments,e=t.length-1;return function(){for(var n=e,i=t[e].apply(this,arguments);n--;)i=t[n].call(this,i);return i}},p.after=function(t,e){return function(){if(--t<1)return e.apply(this,arguments)}},p.before=function(t,e){var n;return function(){return--t>0?n=e.apply(this,arguments):e=null,n}},p.once=p.partial(p.before,2),p.keys=function(t){if(!p.isObject(t))return[];if(h)return h(t);var e=[];for(var n in t)p.has(t,n)&&e.push(n);return e},p.values=function(t){for(var e=p.keys(t),n=e.length,i=Array(n),o=0;o":">",'"':""","'":"'","`":"`"},A=p.invert(y),w=function(t){var e=function(e){return t[e]},n="(?:"+p.keys(t).join("|")+")",i=RegExp(n),o=RegExp(n,"g");return function(t){return t=null==t?"":""+t,i.test(t)?t.replace(o,e):t}};p.escape=w(y),p.unescape=w(A),p.result=function(t,e){if(null!=t){var n=t[e];return p.isFunction(n)?t[e]():n}};var z=0;p.uniqueId=function(t){var e=++z+"";return t?t+e:e},p.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var _=/(.)^/,T={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},x=/\\|'|\r|\n|\u2028|\u2029/g,C=function(t){return"\\"+T[t]};p.template=function(t,e,n){!e&&n&&(e=n),e=p.defaults({},e,p.templateSettings);var i=RegExp([(e.escape||_).source,(e.interpolate||_).source,(e.evaluate||_).source].join("|")+"|$","g"),o=0,r="__p+='";t.replace(i,function(e,n,i,a,s){return r+=t.slice(o,s).replace(x,C),o=s+e.length,n?r+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":i?r+="'+\n((__t=("+i+"))==null?'':__t)+\n'":a&&(r+="';\n"+a+"\n__p+='"),e}),r+="';\n",e.variable||(r="with(obj||{}){\n"+r+"}\n"),r="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+r+"return __p;\n";try{var a=new Function(e.variable||"obj","_",r)}catch(s){throw s.source=r,s}var c=function(t){return a.call(this,t,p)},l=e.variable||"obj";return c.source="function("+l+"){\n"+r+"}",c},p.chain=function(t){var e=p(t);return e._chain=!0,e};var S=function(t){return this._chain?p(t).chain():t};p.mixin=function(t){p.each(p.functions(t),function(e){var n=p[e]=t[e];p.prototype[e]=function(){var t=[this._wrapped];return r.apply(t,arguments),S.call(this,n.apply(p,t))}})},p.mixin(p),p.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=n[t];p.prototype[t]=function(){var n=this._wrapped;return e.apply(n,arguments),"shift"!==t&&"splice"!==t||0!==n.length||delete n[0],S.call(this,n)}}),p.each(["concat","join","slice"],function(t){var e=n[t];p.prototype[t]=function(){return S.call(this,e.apply(this._wrapped,arguments))}}),p.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return p})}.call(this),function(t,e){function n(){return new Date(Date.UTC.apply(Date,arguments))}function i(){var t=new Date;return n(t.getFullYear(),t.getMonth(),t.getDate())}function o(t,e){return t.getUTCFullYear()===e.getUTCFullYear()&&t.getUTCMonth()===e.getUTCMonth()&&t.getUTCDate()===e.getUTCDate()}function r(t){return function(){return this[t].apply(this,arguments)}}function a(e,n){function i(t,e){return e.toLowerCase()}var o,r=t(e).data(),a={},s=new RegExp("^"+n.toLowerCase()+"([A-Z])");n=new RegExp("^"+n.toLowerCase());for(var c in r)n.test(c)&&(o=c.replace(s,i),a[o]=r[c]);return a}function s(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=-a+f-n,g=a+r-(f+h+n),b=Math.max(m,g)===g?"top":"bottom"),this.picker.addClass("datepicker-orient-"+b),"top"===b?f+=h:f-=n+parseInt(this.picker.css("padding-top")),this.o.rtl){var v=o-(p+d);this.picker.css({top:f,right:v,zIndex:l})}else this.picker.css({top:f,left:p,zIndex:l});return this},_allow_update:!0,update:function(){if(!this._allow_update)return this;var e=this.dates.copy(),n=[],i=!1;return arguments.length?(t.each(arguments,t.proxy(function(t,e){e instanceof Date&&(e=this._local_to_utc(e)),n.push(e)},this)),i=!0):(n=this.isInput?this.element.val():this.element.data("date")||this.element.find("input").val(),n=n&&this.o.multidate?n.split(this.o.multidateSeparator):[n],delete this.element.data().date),n=t.map(n,t.proxy(function(t){return g.parseDate(t,this.o.format,this.o.language)},this)),n=t.grep(n,t.proxy(function(t){return tthis.o.endDate||!t},this),!0),this.dates.replace(n),this.dates.length?this.viewDate=new Date(this.dates.get(-1)):this.viewDatethis.o.endDate&&(this.viewDate=new Date(this.o.endDate)),i?this.setValue():n.length&&String(e)!==String(this.dates)&&this._trigger("changeDate"),!this.dates.length&&e.length&&this._trigger("clearDate"),this.fill(),this},fillDow:function(){var t=this.o.weekStart,e="";if(this.o.calendarWeeks){this.picker.find(".datepicker-days thead tr:first-child .datepicker-switch").attr("colspan",function(t,e){return parseInt(e)+1});var n=' ';e+=n}for(;t'+m[this.o.language].daysMin[t++%7]+"";e+="",this.picker.find(".datepicker-days thead").append(e)},fillMonths:function(){for(var t="",e=0;e<12;)t+=''+m[this.o.language].monthsShort[e++]+"";this.picker.find(".datepicker-months td").html(t)},setRange:function(e){e&&e.length?this.range=t.map(e,function(t){return t.valueOf()}):delete this.range,this.fill()},getClassNames:function(e){var n=[],i=this.viewDate.getUTCFullYear(),r=this.viewDate.getUTCMonth(),a=new Date;return e.getUTCFullYear()i||e.getUTCFullYear()===i&&e.getUTCMonth()>r)&&n.push("new"),this.focusDate&&e.valueOf()===this.focusDate.valueOf()&&n.push("focused"),this.o.todayHighlight&&e.getUTCFullYear()===a.getFullYear()&&e.getUTCMonth()===a.getMonth()&&e.getUTCDate()===a.getDate()&&n.push("today"),this.dates.contains(e)!==-1&&n.push("active"),(e.valueOf()this.o.endDate||t.inArray(e.getUTCDay(),this.o.daysOfWeekDisabled)!==-1)&&n.push("disabled"),this.o.datesDisabled.length>0&&t.grep(this.o.datesDisabled,function(t){return o(e,t)}).length>0&&n.push("disabled","disabled-date"),this.range&&(e>this.range[0]&&e"),this.o.calendarWeeks)){var y=new Date(+p+(this.o.weekStart-p.getUTCDay()-7)%7*864e5),A=new Date(Number(y)+(11-y.getUTCDay())%7*864e5),w=new Date(Number(w=n(A.getUTCFullYear(),0,1))+(11-w.getUTCDay())%7*864e5),z=(A-w)/864e5/7+1;M.push(''+z+"")}if(v=this.getClassNames(p),v.push("day"),this.o.beforeShowDay!==t.noop){var _=this.o.beforeShowDay(this._utc_to_local(p));_===e?_={}:"boolean"==typeof _?_={enabled:_}:"string"==typeof _&&(_={classes:_}),_.enabled===!1&&v.push("disabled"),_.classes&&(v=v.concat(_.classes.split(/\s+/))),_.tooltip&&(i=_.tooltip)}v=t.unique(v),M.push('"+p.getUTCDate()+""),i=null,p.getUTCDay()===this.o.weekEnd&&M.push(""),p.setUTCDate(p.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").empty().append(M.join(""));var T=this.picker.find(".datepicker-months").find("th:eq(1)").text(r).end().find("span").removeClass("active");if(t.each(this.dates,function(t,e){e.getUTCFullYear()===r&&T.eq(e.getUTCMonth()).addClass("active")}),(rl)&&T.addClass("disabled"),r===s&&T.slice(0,c).addClass("disabled"),r===l&&T.slice(u+1).addClass("disabled"),this.o.beforeShowMonth!==t.noop){var x=this;t.each(T,function(e,n){if(!t(n).hasClass("disabled")){var i=new Date(r,e,1),o=x.o.beforeShowMonth(i);o===!1&&t(n).addClass("disabled")}})}M="",r=10*parseInt(r/10,10);var C=this.picker.find(".datepicker-years").find("th:eq(1)").text(r+"-"+(r+9)).end().find("td");r-=1;for(var S,O=t.map(this.dates,function(t){return t.getUTCFullYear()}),N=-1;N<11;N++)S=["year"],N===-1?S.push("old"):10===N&&S.push("new"),t.inArray(r,O)!==-1&&S.push("active"),(rl)&&S.push("disabled"),M+=''+r+"",r+=1;C.html(M)}},updateNavArrows:function(){if(this._allow_update){var t=new Date(this.viewDate),e=t.getUTCFullYear(),n=t.getUTCMonth();switch(this.viewMode){case 0:this.o.startDate!==-(1/0)&&e<=this.o.startDate.getUTCFullYear()&&n<=this.o.startDate.getUTCMonth()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.o.endDate!==1/0&&e>=this.o.endDate.getUTCFullYear()&&n>=this.o.endDate.getUTCMonth()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"});break;case 1:case 2:this.o.startDate!==-(1/0)&&e<=this.o.startDate.getUTCFullYear()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.o.endDate!==1/0&&e>=this.o.endDate.getUTCFullYear()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"})}}},click:function(e){e.preventDefault();var i,o,r,a=t(e.target).closest("span, td, th");if(1===a.length)switch(a[0].nodeName.toLowerCase()){case"th":switch(a[0].className){case"datepicker-switch":this.showMode(1);break;case"prev":case"next":var s=g.modes[this.viewMode].navStep*("prev"===a[0].className?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,s),this._trigger("changeMonth",this.viewDate);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,s),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":a.hasClass("disabled")||(this.viewDate.setUTCDate(1),a.hasClass("month")?(r=1,o=a.parent().find("span").index(a),i=this.viewDate.getUTCFullYear(),this.viewDate.setUTCMonth(o),this._trigger("changeMonth",this.viewDate),1===this.o.minViewMode&&this._setDate(n(i,o,r))):(r=1,o=0,i=parseInt(a.text(),10)||0,this.viewDate.setUTCFullYear(i),this._trigger("changeYear",this.viewDate),2===this.o.minViewMode&&this._setDate(n(i,o,r))),this.showMode(-1),this.fill());break;case"td":a.hasClass("day")&&!a.hasClass("disabled")&&(r=parseInt(a.text(),10)||1,i=this.viewDate.getUTCFullYear(),o=this.viewDate.getUTCMonth(),a.hasClass("old")?0===o?(o=11,i-=1):o-=1:a.hasClass("new")&&(11===o?(o=0,i+=1):o+=1),this._setDate(n(i,o,r)))}this.picker.is(":visible")&&this._focused_from&&t(this._focused_from).focus(),delete this._focused_from},_toggle_multidate:function(t){var e=this.dates.contains(t);if(t||this.dates.clear(),e!==-1?(this.o.multidate===!0||this.o.multidate>1||this.o.toggleActive)&&this.dates.remove(e):this.o.multidate===!1?(this.dates.clear(),this.dates.push(t)):this.dates.push(t),"number"==typeof this.o.multidate)for(;this.dates.length>this.o.multidate;)this.dates.remove(0)},_setDate:function(t,e){e&&"date"!==e||this._toggle_multidate(t&&new Date(t)),e&&"view"!==e||(this.viewDate=t&&new Date(t)),this.fill(),this.setValue(),e&&"view"===e||this._trigger("changeDate");var n;this.isInput?n=this.element:this.component&&(n=this.element.find("input")),n&&n.change(),!this.o.autoclose||e&&"date"!==e||this.hide()},moveMonth:function(t,n){if(!t)return e;if(!n)return t;var i,o,r=new Date(t.valueOf()),a=r.getUTCDate(),s=r.getUTCMonth(),c=Math.abs(n);if(n=n>0?1:-1,1===c)o=n===-1?function(){return r.getUTCMonth()===s}:function(){return r.getUTCMonth()!==i},i=s+n,r.setUTCMonth(i),(i<0||i>11)&&(i=(i+12)%12);else{for(var l=0;l=this.o.startDate&&t<=this.o.endDate},keydown:function(t){if(!this.picker.is(":visible"))return void(27===t.keyCode&&this.show());var e,n,o,r=!1,a=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(a,e),this._trigger("changeYear",this.viewDate)):t.shiftKey?(n=this.moveMonth(this.dates.get(-1)||i(),e),o=this.moveMonth(a,e),this._trigger("changeMonth",this.viewDate)):(n=new Date(this.dates.get(-1)||i()),n.setUTCDate(n.getUTCDate()+e),o=new Date(a),o.setUTCDate(a.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(a,e),this._trigger("changeYear",this.viewDate)):t.shiftKey?(n=this.moveMonth(this.dates.get(-1)||i(),e),o=this.moveMonth(a,e),this._trigger("changeMonth",this.viewDate)):(n=new Date(this.dates.get(-1)||i()),n.setUTCDate(n.getUTCDate()+7*e),o=new Date(a),o.setUTCDate(a.getUTCDate()+7*e)),this.dateWithinRange(o)&&(this.focusDate=this.viewDate=o,this.setValue(),this.fill(),t.preventDefault());break;case 32:break;case 13:a=this.focusDate||this.dates.get(-1)||this.viewDate,this.o.keyboardNavigation&&(this._toggle_multidate(a),r=!0),this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.setValue(),this.fill(),this.picker.is(":visible")&&(t.preventDefault(),"function"==typeof t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,this.o.autoclose&&this.hide());break;case 9:this.focusDate=null,this.viewDate=this.dates.get(-1)||this.viewDate,this.fill(),this.hide()}if(r){this.dates.length?this._trigger("changeDate"):this._trigger("clearDate");var s;this.isInput?s=this.element:this.component&&(s=this.element.find("input")),s&&s.change()}},showMode:function(t){t&&(this.viewMode=Math.max(this.o.minViewMode,Math.min(2,this.viewMode+t))),this.picker.children("div").hide().filter(".datepicker-"+g.modes[this.viewMode].clsName).css("display","block"),this.updateNavArrows()}};var u=function(e,n){this.element=t(e),this.inputs=t.map(n.inputs,function(t){return t.jquery?t[0]:t}),delete n.inputs,d.call(t(this.inputs),n).bind("changeDate",t.proxy(this.dateUpdated,this)),this.pickers=t.map(this.inputs,function(e){return t(e).data("datepicker")}),this.updateDates()};u.prototype={updateDates:function(){this.dates=t.map(this.pickers,function(t){return t.getUTCDate()}),this.updateRanges()},updateRanges:function(){var e=t.map(this.dates,function(t){return t.valueOf()});t.each(this.pickers,function(t,n){n.setRange(e)})},dateUpdated:function(e){if(!this.updating){this.updating=!0;var n=t(e.target).data("datepicker"),i=n.getUTCDate(),o=t.inArray(e.target,this.inputs),r=o-1,a=o+1,s=this.inputs.length;if(o!==-1){if(t.each(this.pickers,function(t,e){e.getUTCDate()||e.setUTCDate(i)}),i=0&&ithis.dates[a])for(;athis.dates[a];)this.pickers[a++].setUTCDate(i);this.updateDates(),delete this.updating}}},remove:function(){t.map(this.pickers,function(t){t.remove()}),delete this.element.data().datepicker}};var h=t.fn.datepicker,d=function(n){var i=Array.apply(null,arguments);i.shift();var o;return this.each(function(){var r=t(this),c=r.data("datepicker"),h="object"==typeof n&&n;if(!c){var d=a(this,"date"),f=t.extend({},p,d,h),m=s(f.language),g=t.extend({},p,m,d,h);if(r.hasClass("input-daterange")||g.inputs){var b={inputs:g.inputs||r.find("input").toArray()};r.data("datepicker",c=new u(this,t.extend(g,b)))}else r.data("datepicker",c=new l(this,g))}if("string"==typeof n&&"function"==typeof c[n]&&(o=c[n].apply(c,i),o!==e))return!1}),o!==e?o:this};t.fn.datepicker=d;var p=t.fn.datepicker.defaults={autoclose:!1,beforeShowDay:t.noop,beforeShowMonth:t.noop,calendarWeeks:!1,clearBtn:!1,toggleActive:!1,daysOfWeekDisabled:[],datesDisabled:[],endDate:1/0,forceParse:!0,format:"mm/dd/yyyy",keyboardNavigation:!0,language:"en",minViewMode:0,multidate:!1,multidateSeparator:",",orientation:"auto",rtl:!1,startDate:-(1/0),startView:0,todayBtn:!1,todayHighlight:!1,weekStart:0,disableTouchKeyboard:!1,enableOnReadonly:!0,container:"body"},f=t.fn.datepicker.locale_opts=["format","rtl","weekStart"];t.fn.datepicker.Constructor=l;var m=t.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear"}},g={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(t){return t%4===0&&t%100!==0||t%400===0},getDaysInMonth:function(t,e){return[31,g.isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]},validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,parseFormat:function(t){var e=t.replace(this.validParts,"\0").split("\0"),n=t.match(this.validParts);if(!e||!e.length||!n||0===n.length)throw new Error("Invalid date format.");return{separators:e,parts:n}},parseDate:function(i,o,r){function a(){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 s,c,u,h=/([\-+]\d+)([dmwy])/,d=i.match(/([\-+]\d+)([dmwy])/g);if(/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(i)){for(i=new Date,u=0;u«»',contTemplate:'',footTemplate:''};g.template='
    '+g.headTemplate+""+g.footTemplate+'
    '+g.headTemplate+g.contTemplate+g.footTemplate+'
    '+g.headTemplate+g.contTemplate+g.footTemplate+"
    ",t.fn.datepicker.DPGlobal=g,t.fn.datepicker.noConflict=function(){return t.fn.datepicker=h,this},t.fn.datepicker.version="1.4.0",t(document).on("focus.datepicker.data-api click.datepicker.data-api",'[data-provide="datepicker"]',function(e){var n=t(this);n.data("datepicker")||(e.preventDefault(),d.call(n,"show"))}),t(function(){d.call(t('[data-provide="datepicker-inline"]'))})}(window.jQuery),!function(t){t.fn.datepicker.dates.de={days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"],daysShort:["Son","Mon","Die","Mit","Don","Fre","Sam","Son"],daysMin:["So","Mo","Di","Mi","Do","Fr","Sa","So"],months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],today:"Heute",clear:"Löschen",weekStart:1,format:"dd.mm.yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.da={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag","Søndag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør","Søn"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø","Sø"],months:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"I Dag",clear:"Nulstil"}}(jQuery),!function(t){t.fn.datepicker.dates["pt-BR"]={days:["Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado","Domingo"],daysShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb","Dom"],daysMin:["Do","Se","Te","Qu","Qu","Se","Sa","Do"],months:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthsShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],today:"Hoje",clear:"Limpar"}}(jQuery),!function(t){t.fn.datepicker.dates.nl={days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag","zondag"],daysShort:["zo","ma","di","wo","do","vr","za","zo"],daysMin:["zo","ma","di","wo","do","vr","za","zo"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthsShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],today:"Vandaag",clear:"Wissen",weekStart:1,format:"dd-mm-yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.fr={days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"],daysShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam.","dim."],daysMin:["d","l","ma","me","j","v","s","d"],months:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthsShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],today:"Aujourd'hui",clear:"Effacer",weekStart:1,format:"dd/mm/yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato","Domenica"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab","Dom"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa","Do"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",clear:"Cancella",weekStart:1,format:"dd/mm/yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.lt={days:["Sekmadienis","Pirmadienis","Antradienis","Trečiadienis","Ketvirtadienis","Penktadienis","Šeštadienis","Sekmadienis"],daysShort:["S","Pr","A","T","K","Pn","Š","S"],daysMin:["Sk","Pr","An","Tr","Ke","Pn","Št","Sk"],months:["Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis","Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis"],monthsShort:["Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rugp","Rugs","Spa","Lap","Gru"],today:"Šiandien",weekStart:1}}(jQuery),!function(t){t.fn.datepicker.dates.no={days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],daysShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],daysMin:["Sø","Ma","Ti","On","To","Fr","Lø"],months:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],today:"I dag",clear:"Nullstill",weekStart:1,format:"dd.mm.yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.es={days:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado","Domingo"],daysShort:["Dom","Lun","Mar","Mié","Jue","Vie","Sáb","Dom"],daysMin:["Do","Lu","Ma","Mi","Ju","Vi","Sa","Do"],months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],monthsShort:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],today:"Hoy",clear:"Borrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery),!function(t){t.fn.datepicker.dates.sv={days:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag","Söndag"],daysShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör","Sön"],daysMin:["Sö","Må","Ti","On","To","Fr","Lö","Sö"],months:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthsShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],today:"Idag",format:"yyyy-mm-dd",weekStart:1,clear:"Rensa"}}(jQuery),function(){var t,e,n,i,o,r,a,s,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};a=function(){},e=function(){function t(){}return t.prototype.addEventListener=t.prototype.on,t.prototype.on=function(t,e){return this._callbacks=this._callbacks||{},this._callbacks[t]||(this._callbacks[t]=[]),this._callbacks[t].push(e),this},t.prototype.emit=function(){var t,e,n,i,o,r;if(i=arguments[0],t=2<=arguments.length?c.call(arguments,1):[],this._callbacks=this._callbacks||{},n=this._callbacks[i])for(o=0,r=n.length;o
    '),this.element.appendChild(e)),i=e.getElementsByTagName("span")[0],i&&(null!=i.textContent?i.textContent=this.options.dictFallbackMessage:null!=i.innerText&&(i.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(t){var e,n,i;return e={srcX:0,srcY:0,srcWidth:t.width,srcHeight:t.height},n=t.width/t.height,e.optWidth=this.options.thumbnailWidth,e.optHeight=this.options.thumbnailHeight,null==e.optWidth&&null==e.optHeight?(e.optWidth=e.srcWidth,e.optHeight=e.srcHeight):null==e.optWidth?e.optWidth=n*e.optHeight:null==e.optHeight&&(e.optHeight=1/n*e.optWidth),i=e.optWidth/e.optHeight,t.heighti?(e.srcHeight=t.height,e.srcWidth=e.srcHeight*i):(e.srcWidth=t.width,e.srcHeight=e.srcWidth/i),e.srcX=(t.width-e.srcWidth)/2,e.srcY=(t.height-e.srcHeight)/2,e},drop:function(t){return this.element.classList.remove("dz-drag-hover")},dragstart:a,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:a,reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(t){var e,i,o,r,a,s,c,l,u,h,d,p,f;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){for(t.previewElement=n.createElement(this.options.previewTemplate.trim()),t.previewTemplate=t.previewElement,this.previewsContainer.appendChild(t.previewElement),h=t.previewElement.querySelectorAll("[data-dz-name]"),r=0,c=h.length;r'+this.options.dictRemoveFile+""),t.previewElement.appendChild(t._removeLink)),i=function(e){return function(i){return i.preventDefault(),i.stopPropagation(),t.status===n.UPLOADING?n.confirm(e.options.dictCancelUploadConfirmation,function(){return e.removeFile(t)}):e.options.dictRemoveFileConfirmation?n.confirm(e.options.dictRemoveFileConfirmation,function(){return e.removeFile(t)}):e.removeFile(t)}}(this),p=t.previewElement.querySelectorAll("[data-dz-remove]"),f=[],s=0,u=p.length;s\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n \n Check\n \n \n \n \n \n
    \n
    \n \n Error\n \n \n \n \n \n \n \n
    \n'},i=function(){var t,e,n,i,o,r,a;for(i=arguments[0],n=2<=arguments.length?c.call(arguments,1):[],r=0,a=n.length;r'+this.options.dictDefaultMessage+"")),this.clickableElements.length&&(i=function(t){return function(){return t.hiddenFileInput&&t.hiddenFileInput.parentNode.removeChild(t.hiddenFileInput),t.hiddenFileInput=document.createElement("input"),t.hiddenFileInput.setAttribute("type","file"),(null==t.options.maxFiles||t.options.maxFiles>1)&&t.hiddenFileInput.setAttribute("multiple","multiple"),t.hiddenFileInput.className="dz-hidden-input",null!=t.options.acceptedFiles&&t.hiddenFileInput.setAttribute("accept",t.options.acceptedFiles),null!=t.options.capture&&t.hiddenFileInput.setAttribute("capture",t.options.capture),t.hiddenFileInput.style.visibility="hidden",t.hiddenFileInput.style.position="absolute",t.hiddenFileInput.style.top="0",t.hiddenFileInput.style.left="0",t.hiddenFileInput.style.height="0",t.hiddenFileInput.style.width="0",document.querySelector(t.options.hiddenInputContainer).appendChild(t.hiddenFileInput),t.hiddenFileInput.addEventListener("change",function(){var e,n,o,r;if(n=t.hiddenFileInput.files,n.length)for(o=0,r=n.length;o',this.options.dictFallbackText&&(i+="

    "+this.options.dictFallbackText+"

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