From 92e5bf60d8455c57ed98ff7bca14dce2c1d5f583 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 27 Feb 2016 22:12:22 +0200 Subject: [PATCH 01/12] Broke up seeders --- app/Http/Controllers/AppController.php | 6 +- database/seeds/CountriesSeeder.php | 170 +++++++++-- database/seeds/DatabaseSeeder.php | 4 + database/seeds/PaymentLibrariesSeeder.php | 340 ---------------------- readme.md | 2 +- 5 files changed, 161 insertions(+), 361 deletions(-) diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 2ca874b5c01a..60583b7231a5 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -251,7 +251,11 @@ class AppController extends BaseController 'PaymentLibraries', 'Fonts', 'Banks', - 'InvoiceStatus' + 'InvoiceStatus', + 'Currencies', + 'DateFormats', + 'InvoiceDesigns', + 'PaymentTerms', ] as $seeder) { Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder")); } diff --git a/database/seeds/CountriesSeeder.php b/database/seeds/CountriesSeeder.php index 7fe9c9e3fb05..ff5c0e9948f9 100644 --- a/database/seeds/CountriesSeeder.php +++ b/database/seeds/CountriesSeeder.php @@ -1,5 +1,6 @@ delete(); - //Get all of the countries - $countries = Countries::getList(); - foreach ($countries as $countryId => $country){ - DB::table('countries')->insert(array( - 'id' => $countryId, - 'capital' => ((isset($country['capital'])) ? $country['capital'] : null), - 'citizenship' => ((isset($country['citizenship'])) ? $country['citizenship'] : null), - 'country_code' => $country['country-code'], - 'currency' => ((isset($country['currency'])) ? $country['currency'] : null), - 'currency_code' => ((isset($country['currency_code'])) ? $country['currency_code'] : null), - 'currency_sub_unit' => ((isset($country['currency_sub_unit'])) ? $country['currency_sub_unit'] : null), - 'full_name' => ((isset($country['full_name'])) ? $country['full_name'] : null), - 'iso_3166_2' => $country['iso_3166_2'], - 'iso_3166_3' => $country['iso_3166_3'], - 'name' => $country['name'], - 'region_code' => $country['region-code'], - 'sub_region_code' => $country['sub-region-code'], - 'eea' => (bool)$country['eea'] - )); + if (DB::table('countries')->count() == 0) { + //Get all of the countries + $countries = Countries::getList(); + foreach ($countries as $countryId => $country){ + DB::table('countries')->insert(array( + 'id' => $countryId, + 'capital' => ((isset($country['capital'])) ? $country['capital'] : null), + 'citizenship' => ((isset($country['citizenship'])) ? $country['citizenship'] : null), + 'country_code' => $country['country-code'], + 'currency' => ((isset($country['currency'])) ? $country['currency'] : null), + 'currency_code' => ((isset($country['currency_code'])) ? $country['currency_code'] : null), + 'currency_sub_unit' => ((isset($country['currency_sub_unit'])) ? $country['currency_sub_unit'] : null), + 'full_name' => ((isset($country['full_name'])) ? $country['full_name'] : null), + 'iso_3166_2' => $country['iso_3166_2'], + 'iso_3166_3' => $country['iso_3166_3'], + 'name' => $country['name'], + 'region_code' => $country['region-code'], + 'sub_region_code' => $country['sub-region-code'], + 'eea' => (bool)$country['eea'] + )); + } + } + + // Source: http://www.bitboost.com/ref/international-address-formats.html + // Source: https://en.wikipedia.org/wiki/Linguistic_issues_concerning_the_euro + $countries = [ + 'AR' => [ + 'swap_postal_code' => true, + ], + 'AT' => [ // Austria + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'BE' => [ + 'swap_postal_code' => true, + ], + 'BG' => [ // Belgium + 'swap_currency_symbol' => true, + ], + 'CH' => [ + 'swap_postal_code' => true, + ], + 'CZ' => [ // Czech Republic + 'swap_currency_symbol' => true, + ], + 'DE' => [ // Germany + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'DK' => [ + 'swap_postal_code' => true, + ], + 'EE' => [ // Estonia + 'swap_currency_symbol' => true, + ], + 'ES' => [ // Spain + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'FI' => [ // Finland + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'FR' => [ // France + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'GR' => [ // Greece + 'swap_currency_symbol' => true, + ], + 'HR' => [ // Croatia + 'swap_currency_symbol' => true, + ], + 'HU' => [ // Hungary + 'swap_currency_symbol' => true, + ], + 'GL' => [ + 'swap_postal_code' => true, + ], + 'IE' => [ // Ireland + 'thousand_separator' => ',', + 'decimal_separator' => '.', + ], + 'IL' => [ + 'swap_postal_code' => true, + ], + 'IS' => [ // Iceland + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'IT' => [ // Italy + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'LT' => [ // Lithuania + 'swap_currency_symbol' => true, + ], + 'LU' => [ + 'swap_postal_code' => true, + ], + 'MY' => [ + 'swap_postal_code' => true, + ], + 'MX' => [ + 'swap_postal_code' => true, + ], + 'NL' => [ + 'swap_postal_code' => true, + ], + 'PL' => [ // Poland + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'PT' => [ // Portugal + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'RO' => [ // Romania + 'swap_currency_symbol' => true, + ], + 'SE' => [ // Sweden + 'swap_postal_code' => true, + 'swap_currency_symbol' => true, + ], + 'SI' => [ // Slovenia + 'swap_currency_symbol' => true, + ], + 'SK' => [ // Slovakia + 'swap_currency_symbol' => true, + ], + 'UY' => [ + 'swap_postal_code' => true, + ], + ]; + + foreach ($countries as $code => $data) { + $country = Country::where('iso_3166_2', '=', $code)->first(); + if (isset($data['swap_postal_code'])) { + $country->swap_postal_code = true; + } + if (isset($data['swap_currency_symbol'])) { + $country->swap_currency_symbol = true; + } + if (isset($data['thousand_separator'])) { + $country->thousand_separator = $data['thousand_separator']; + } + if (isset($data['decimal_separator'])) { + $country->decimal_separator = $data['decimal_separator']; + } + $country->save(); } } } \ No newline at end of file diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 3584253c4777..8791f30e71fb 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -19,5 +19,9 @@ class DatabaseSeeder extends Seeder $this->call('FontsSeeder'); $this->call('BanksSeeder'); $this->call('InvoiceStatusSeeder'); + $this->call('CurrenciesSeeder'); + $this->call('DateFormatsSeeder'); + $this->call('InvoiceDesignsSeeder'); + $this->call('PaymentTermsSeeder'); } } diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index d4d01c76fd92..373f82b5cf6c 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -14,16 +14,6 @@ class PaymentLibrariesSeeder extends Seeder { Eloquent::unguard(); - $this->createGateways(); - $this->createPaymentTerms(); - $this->createDateFormats(); - $this->createDatetimeFormats(); - $this->createInvoiceDesigns(); - $this->updateLocalization(); - } - - private function createGateways() { - $gateways = [ ['name' => 'BeanStream', 'provider' => 'BeanStream', 'payment_library_id' => 2], ['name' => 'Psigate', 'provider' => 'Psigate', 'payment_library_id' => 2], @@ -71,334 +61,4 @@ class PaymentLibrariesSeeder extends Seeder } } - - private function createPaymentTerms() { - - $paymentTerms = [ - ['num_days' => -1, 'name' => 'Net 0'], - ]; - - foreach ($paymentTerms as $paymentTerm) { - if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get()) { - PaymentTerm::create($paymentTerm); - } - } - - $currencies = [ - ['name' => 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'South African Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Kenyan Shilling', 'code' => 'KES', 'symbol' => 'KSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Canadian Dollar', 'code' => 'CAD', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Philippine Peso', 'code' => 'PHP', 'symbol' => 'P ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Indian Rupee', 'code' => 'INR', 'symbol' => 'Rs. ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Australian Dollar', 'code' => 'AUD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => '', 'precision' => '0', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'], - ['name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Thai Baht', 'code' => 'THB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Nigerian Naira', 'code' => 'NGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Bangladeshi Taka', 'code' => 'BDT', 'symbol' => 'Tk', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'United Arab Emirates Dirham', 'code' => 'AED', 'symbol' => 'DH ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => 'E£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Colombian Peso', 'code' => 'COP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'West African Franc', 'code' => 'XOF', 'symbol' => 'CFA ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Chinese Renminbi', 'code' => 'CNY', 'symbol' => 'RMB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Rwandan Franc', 'code' => 'RWF', 'symbol' => 'RF ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Tanzanian Shilling', 'code' => 'TZS', 'symbol' => 'TSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Netherlands Antillean Guilder', 'code' => 'ANG', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Trinidad and Tobago Dollar', 'code' => 'TTD', 'symbol' => 'TT$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'East Caribbean Dollar', 'code' => 'XCD', 'symbol' => 'EC$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Ghanaian Cedi', 'code' => 'GHS', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'], - ['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'], - ['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], - ['name' => 'Romanian New Leu', 'code' => 'RON', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], - ]; - - foreach ($currencies as $currency) { - $record = Currency::whereCode($currency['code'])->first(); - if ($record) { - $record->name = $currency['name']; - $record->symbol = $currency['symbol']; - $record->thousand_separator = $currency['thousand_separator']; - $record->decimal_separator = $currency['decimal_separator']; - $record->save(); - } else { - Currency::create($currency); - } - } - } - - private function createDateFormats() { - - $formats = [ - ['format' => 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'], - ['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'], - ['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'], - ['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'], - ['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'], - ['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'], - ['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'], - ['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'label' => '2013-03-10'], - ['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'label' => '20-03-2013'], - ['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'label' => '03/20/2013'] - ]; - - foreach ($formats as $format) { - $record = DateFormat::whereLabel($format['label'])->first(); - if ($record) { - $record->format = $format['format']; - $record->picker_format = $format['picker_format']; - $record->save(); - } else { - DateFormat::create($format); - } - } - } - - private function createDatetimeFormats() { - - $formats = [ - [ - 'format' => 'd/M/Y g:i a', - 'format_moment' => 'DD/MMM/YYYY h:mm:ss a', - 'label' => '10/Mar/2013' - ], - [ - 'format' => 'd-M-Y g:i a', - 'format_moment' => 'DD-MMM-YYYY h:mm:ss a', - 'label' => '10-Mar-2013' - ], - [ - 'format' => 'd/F/Y g:i a', - 'format_moment' => 'DD/MMMM/YYYY h:mm:ss a', - 'label' => '10/March/2013' - ], - [ - 'format' => 'd-F-Y g:i a', - 'format_moment' => 'DD-MMMM-YYYY h:mm:ss a', - 'label' => '10-March-2013' - ], - [ - 'format' => 'M j, Y g:i a', - 'format_moment' => 'MMM D, YYYY h:mm:ss a', - 'label' => 'Mar 10, 2013 6:15 pm' - ], - [ - 'format' => 'F j, Y g:i a', - 'format_moment' => 'MMMM D, YYYY h:mm:ss a', - 'label' => 'March 10, 2013 6:15 pm' - ], - [ - 'format' => 'D M jS, Y g:i a', - 'format_moment' => 'ddd MMM Do, YYYY h:mm:ss a', - 'label' => 'Mon March 10th, 2013 6:15 pm' - ], - [ - 'format' => 'Y-m-d g:i a', - 'format_moment' => 'YYYY-MMM-DD h:mm:ss a', - 'label' => '2013-03-10 6:15 pm' - ], - [ - 'format' => 'd-m-Y g:i a', - 'format_moment' => 'DD-MM-YYYY h:mm:ss a', - 'label' => '20-03-2013 6:15 pm' - ], - [ - 'format' => 'm/d/Y g:i a', - 'format_moment' => 'MM/DD/YYYY h:mm:ss a', - 'label' => '03/20/2013 6:15 pm' - ] - ]; - - foreach ($formats as $format) { - $record = DatetimeFormat::whereLabel($format['label'])->first(); - if ($record) { - $record->format = $format['format']; - $record->format_moment = $format['format_moment']; - $record->save(); - } else { - DatetimeFormat::create($format); - } - } - } - - private function createInvoiceDesigns() { - $designs = [ - 'Clean', - 'Bold', - 'Modern', - 'Plain', - 'Business', - 'Creative', - 'Elegant', - 'Hipster', - 'Playful', - 'Photo', - ]; - - for ($i=0; $ifirst(); - if (!$record) { - $record = new InvoiceDesign; - $record->id = $i + 1; - $record->name = $design; - } - $record->pdfmake = $pdfmake; - $record->save(); - } - } - } - } - - private function updateLocalization() { - // Source: http://www.bitboost.com/ref/international-address-formats.html - // Source: https://en.wikipedia.org/wiki/Linguistic_issues_concerning_the_euro - $countries = [ - 'AR' => [ - 'swap_postal_code' => true, - ], - 'AT' => [ // Austria - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'BE' => [ - 'swap_postal_code' => true, - ], - 'BG' => [ // Belgium - 'swap_currency_symbol' => true, - ], - 'CH' => [ - 'swap_postal_code' => true, - ], - 'CZ' => [ // Czech Republic - 'swap_currency_symbol' => true, - ], - 'DE' => [ // Germany - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'DK' => [ - 'swap_postal_code' => true, - ], - 'EE' => [ // Estonia - 'swap_currency_symbol' => true, - ], - 'ES' => [ // Spain - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'FI' => [ // Finland - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'FR' => [ // France - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'GR' => [ // Greece - 'swap_currency_symbol' => true, - ], - 'HR' => [ // Croatia - 'swap_currency_symbol' => true, - ], - 'HU' => [ // Hungary - 'swap_currency_symbol' => true, - ], - 'GL' => [ - 'swap_postal_code' => true, - ], - 'IE' => [ // Ireland - 'thousand_separator' => ',', - 'decimal_separator' => '.', - ], - 'IL' => [ - 'swap_postal_code' => true, - ], - 'IS' => [ // Iceland - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'IT' => [ // Italy - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'LT' => [ // Lithuania - 'swap_currency_symbol' => true, - ], - 'LU' => [ - 'swap_postal_code' => true, - ], - 'MY' => [ - 'swap_postal_code' => true, - ], - 'MX' => [ - 'swap_postal_code' => true, - ], - 'NL' => [ - 'swap_postal_code' => true, - ], - 'PL' => [ // Poland - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'PT' => [ // Portugal - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'RO' => [ // Romania - 'swap_currency_symbol' => true, - ], - 'SE' => [ // Sweden - 'swap_postal_code' => true, - 'swap_currency_symbol' => true, - ], - 'SI' => [ // Slovenia - 'swap_currency_symbol' => true, - ], - 'SK' => [ // Slovakia - 'swap_currency_symbol' => true, - ], - 'UY' => [ - 'swap_postal_code' => true, - ], - ]; - - foreach ($countries as $code => $data) { - $country = Country::where('iso_3166_2', '=', $code)->first(); - if (isset($data['swap_postal_code'])) { - $country->swap_postal_code = true; - } - if (isset($data['swap_currency_symbol'])) { - $country->swap_currency_symbol = true; - } - if (isset($data['thousand_separator'])) { - $country->thousand_separator = $data['thousand_separator']; - } - if (isset($data['decimal_separator'])) { - $country->decimal_separator = $data['decimal_separator']; - } - $country->save(); - } - } - } diff --git a/readme.md b/readme.md index 1addcb2fd9d1..e1c72c73e29a 100644 --- a/readme.md +++ b/readme.md @@ -19,7 +19,7 @@ * [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) - $30 ### Requirements -* PHP >= 5.4.0 +* PHP >= 5.5.9 * MCrypt PHP Extension * MySQL From 694929c05e76710e09c1244d5d9043930c7a24ae Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 27 Feb 2016 22:12:38 +0200 Subject: [PATCH 02/12] Broke up seeders --- database/seeds/CurrenciesSeeder.php | 69 ++++++++++++++++ database/seeds/DateFormatsSeeder.php | 102 ++++++++++++++++++++++++ database/seeds/InvoiceDesignsSeeder.php | 43 ++++++++++ database/seeds/PaymentTermsSeeder.php | 22 +++++ 4 files changed, 236 insertions(+) create mode 100644 database/seeds/CurrenciesSeeder.php create mode 100644 database/seeds/DateFormatsSeeder.php create mode 100644 database/seeds/InvoiceDesignsSeeder.php create mode 100644 database/seeds/PaymentTermsSeeder.php diff --git a/database/seeds/CurrenciesSeeder.php b/database/seeds/CurrenciesSeeder.php new file mode 100644 index 000000000000..9d46bbd12358 --- /dev/null +++ b/database/seeds/CurrenciesSeeder.php @@ -0,0 +1,69 @@ + 'US Dollar', 'code' => 'USD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Pound Sterling', 'code' => 'GBP', 'symbol' => '£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Euro', 'code' => 'EUR', 'symbol' => '€', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'South African Rand', 'code' => 'ZAR', 'symbol' => 'R', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Danish Krone', 'code' => 'DKK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Israeli Shekel', 'code' => 'ILS', 'symbol' => 'NIS ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Swedish Krona', 'code' => 'SEK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Kenyan Shilling', 'code' => 'KES', 'symbol' => 'KSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Canadian Dollar', 'code' => 'CAD', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Philippine Peso', 'code' => 'PHP', 'symbol' => 'P ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Indian Rupee', 'code' => 'INR', 'symbol' => 'Rs. ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Australian Dollar', 'code' => 'AUD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Singapore Dollar', 'code' => 'SGD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => '', 'precision' => '0', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'], + ['name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Thai Baht', 'code' => 'THB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Nigerian Naira', 'code' => 'NGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Bangladeshi Taka', 'code' => 'BDT', 'symbol' => 'Tk', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'United Arab Emirates Dirham', 'code' => 'AED', 'symbol' => 'DH ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => 'E£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Colombian Peso', 'code' => 'COP', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'West African Franc', 'code' => 'XOF', 'symbol' => 'CFA ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Chinese Renminbi', 'code' => 'CNY', 'symbol' => 'RMB ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Rwandan Franc', 'code' => 'RWF', 'symbol' => 'RF ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Tanzanian Shilling', 'code' => 'TZS', 'symbol' => 'TSh ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Netherlands Antillean Guilder', 'code' => 'ANG', 'symbol' => '', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Trinidad and Tobago Dollar', 'code' => 'TTD', 'symbol' => 'TT$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'East Caribbean Dollar', 'code' => 'XCD', 'symbol' => 'EC$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Ghanaian Cedi', 'code' => 'GHS', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'], + ['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'], + ['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Romanian New Leu', 'code' => 'RON', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ]; + + foreach ($currencies as $currency) { + $record = Currency::whereCode($currency['code'])->first(); + if ($record) { + $record->name = $currency['name']; + $record->symbol = $currency['symbol']; + $record->thousand_separator = $currency['thousand_separator']; + $record->decimal_separator = $currency['decimal_separator']; + $record->save(); + } else { + Currency::create($currency); + } + } + } +} diff --git a/database/seeds/DateFormatsSeeder.php b/database/seeds/DateFormatsSeeder.php new file mode 100644 index 000000000000..40568ca3000a --- /dev/null +++ b/database/seeds/DateFormatsSeeder.php @@ -0,0 +1,102 @@ + 'd/M/Y', 'picker_format' => 'dd/M/yyyy', 'label' => '10/Mar/2013'], + ['format' => 'd-M-Y', 'picker_format' => 'dd-M-yyyy', 'label' => '10-Mar-2013'], + ['format' => 'd/F/Y', 'picker_format' => 'dd/MM/yyyy', 'label' => '10/March/2013'], + ['format' => 'd-F-Y', 'picker_format' => 'dd-MM-yyyy', 'label' => '10-March-2013'], + ['format' => 'M j, Y', 'picker_format' => 'M d, yyyy', 'label' => 'Mar 10, 2013'], + ['format' => 'F j, Y', 'picker_format' => 'MM d, yyyy', 'label' => 'March 10, 2013'], + ['format' => 'D M j, Y', 'picker_format' => 'D MM d, yyyy', 'label' => 'Mon March 10, 2013'], + ['format' => 'Y-m-d', 'picker_format' => 'yyyy-mm-dd', 'label' => '2013-03-10'], + ['format' => 'd-m-Y', 'picker_format' => 'dd-mm-yyyy', 'label' => '20-03-2013'], + ['format' => 'm/d/Y', 'picker_format' => 'mm/dd/yyyy', 'label' => '03/20/2013'] + ]; + + foreach ($formats as $format) { + $record = DateFormat::whereLabel($format['label'])->first(); + if ($record) { + $record->format = $format['format']; + $record->picker_format = $format['picker_format']; + $record->save(); + } else { + DateFormat::create($format); + } + } + + // Date/time formats + $formats = [ + [ + 'format' => 'd/M/Y g:i a', + 'format_moment' => 'DD/MMM/YYYY h:mm:ss a', + 'label' => '10/Mar/2013' + ], + [ + 'format' => 'd-M-Y g:i a', + 'format_moment' => 'DD-MMM-YYYY h:mm:ss a', + 'label' => '10-Mar-2013' + ], + [ + 'format' => 'd/F/Y g:i a', + 'format_moment' => 'DD/MMMM/YYYY h:mm:ss a', + 'label' => '10/March/2013' + ], + [ + 'format' => 'd-F-Y g:i a', + 'format_moment' => 'DD-MMMM-YYYY h:mm:ss a', + 'label' => '10-March-2013' + ], + [ + 'format' => 'M j, Y g:i a', + 'format_moment' => 'MMM D, YYYY h:mm:ss a', + 'label' => 'Mar 10, 2013 6:15 pm' + ], + [ + 'format' => 'F j, Y g:i a', + 'format_moment' => 'MMMM D, YYYY h:mm:ss a', + 'label' => 'March 10, 2013 6:15 pm' + ], + [ + 'format' => 'D M jS, Y g:i a', + 'format_moment' => 'ddd MMM Do, YYYY h:mm:ss a', + 'label' => 'Mon March 10th, 2013 6:15 pm' + ], + [ + 'format' => 'Y-m-d g:i a', + 'format_moment' => 'YYYY-MMM-DD h:mm:ss a', + 'label' => '2013-03-10 6:15 pm' + ], + [ + 'format' => 'd-m-Y g:i a', + 'format_moment' => 'DD-MM-YYYY h:mm:ss a', + 'label' => '20-03-2013 6:15 pm' + ], + [ + 'format' => 'm/d/Y g:i a', + 'format_moment' => 'MM/DD/YYYY h:mm:ss a', + 'label' => '03/20/2013 6:15 pm' + ] + ]; + + foreach ($formats as $format) { + $record = DatetimeFormat::whereLabel($format['label'])->first(); + if ($record) { + $record->format = $format['format']; + $record->format_moment = $format['format_moment']; + $record->save(); + } else { + DatetimeFormat::create($format); + } + } + } +} diff --git a/database/seeds/InvoiceDesignsSeeder.php b/database/seeds/InvoiceDesignsSeeder.php new file mode 100644 index 000000000000..0e07067e1ec8 --- /dev/null +++ b/database/seeds/InvoiceDesignsSeeder.php @@ -0,0 +1,43 @@ +first(); + if (!$record) { + $record = new InvoiceDesign; + $record->id = $i + 1; + $record->name = $design; + } + $record->pdfmake = $pdfmake; + $record->save(); + } + } + } + } + +} diff --git a/database/seeds/PaymentTermsSeeder.php b/database/seeds/PaymentTermsSeeder.php new file mode 100644 index 000000000000..3dd8c48533e7 --- /dev/null +++ b/database/seeds/PaymentTermsSeeder.php @@ -0,0 +1,22 @@ + -1, 'name' => 'Net 0'], + ]; + + foreach ($paymentTerms as $paymentTerm) { + if (!DB::table('payment_terms')->where('name', '=', $paymentTerm['name'])->get()) { + PaymentTerm::create($paymentTerm); + } + } + } + +} From 96e7fab55cb2f4868c49692a6bb9f2d39853e6e8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 27 Feb 2016 22:41:23 +0200 Subject: [PATCH 03/12] Matched vendor when importing expenses --- app/Services/BankAccountService.php | 57 ++++++++++++------- app/Services/ImportService.php | 2 +- .../views/accounts/bank_account.blade.php | 1 + 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/app/Services/BankAccountService.php b/app/Services/BankAccountService.php index 622c759c8216..c1da419600a3 100644 --- a/app/Services/BankAccountService.php +++ b/app/Services/BankAccountService.php @@ -4,7 +4,6 @@ use stdClass; use Utils; use URL; use Hash; -use App\Models\Gateway; use App\Models\BankSubaccount; use App\Models\Vendor; use App\Models\Expense; @@ -37,7 +36,7 @@ class BankAccountService extends BaseService public function loadBankAccounts($bankId, $username, $password, $includeTransactions = true) { - if ( ! $bankId || ! $username || ! $password) { + if (! $bankId || ! $username || ! $password) { return false; } @@ -47,12 +46,13 @@ class BankAccountService extends BaseService ->withTrashed() ->get(['transaction_id']) ->toArray(); - $expenses = array_flip(array_map(function($val) { + $expenses = array_flip(array_map(function ($val) { return $val['transaction_id']; }, $expenses)); + $vendorMap = $this->createVendorMap(); $bankAccounts = BankSubaccount::scope() - ->whereHas('bank_account', function($query) use ($bankId) { + ->whereHas('bank_account', function ($query) use ($bankId) { $query->where('bank_id', '=', $bankId); }) ->get(); @@ -64,13 +64,13 @@ class BankAccountService extends BaseService $finance = new Finance(); $finance->banks[$bankId] = $bank->getOFXBank($finance); $finance->banks[$bankId]->logins[] = new Login($finance->banks[$bankId], $username, $password); - + foreach ($finance->banks as $bank) { foreach ($bank->logins as $login) { $login->setup(); foreach ($login->accounts as $account) { $account->setup($includeTransactions); - if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions)) { + if ($account = $this->parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap)) { $data[] = $account; } } @@ -83,9 +83,9 @@ class BankAccountService extends BaseService } } - private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions) + private function parseBankAccount($account, $bankAccounts, $expenses, $includeTransactions, $vendorMap) { - $obj = new stdClass; + $obj = new stdClass(); $obj->account_name = ''; // look up bank account name @@ -106,7 +106,7 @@ class BankAccountService extends BaseService $obj->balance = Utils::formatMoney($account->ledgerBalance, CURRENCY_DOLLAR); if ($includeTransactions) { - $ofxParser = new \OfxParser\Parser; + $ofxParser = new \OfxParser\Parser(); $ofx = $ofxParser->loadFromString($account->response); $obj->start_date = $ofx->BankAccount->Statement->startDate; @@ -121,7 +121,13 @@ class BankAccountService extends BaseService if ($transaction->amount >= 0) { continue; } - $transaction->vendor = $this->prepareValue(substr($transaction->name, 0, 20)); + + // if vendor has already been imported use current name + $vendorName = trim(substr($transaction->name, 0, 20)); + $key = strtolower($vendorName); + $vendor = isset($vendorMap[$key]) ? $vendorMap[$key] : null; + + $transaction->vendor = $vendor ? $vendor->name : $this->prepareValue($vendorName); $transaction->info = $this->prepareValue(substr($transaction->name, 20)); $transaction->memo = $this->prepareValue($transaction->memo); $transaction->date = \Auth::user()->account->formatDate($transaction->date); @@ -133,15 +139,13 @@ class BankAccountService extends BaseService return $obj; } - private function prepareValue($value) { + private function prepareValue($value) + { return ucwords(strtolower(trim($value))); } - public function importExpenses($bankId, $input) { - $countVendors = 0; - $countExpenses = 0; - - // create a vendor map + private function createVendorMap() + { $vendorMap = []; $vendors = Vendor::scope() ->withTrashed() @@ -151,6 +155,15 @@ class BankAccountService extends BaseService $vendorMap[strtolower($vendor->transaction_name)] = $vendor; } + return $vendorMap; + } + + public function importExpenses($bankId, $input) + { + $vendorMap = $this->createVendorMap(); + $countVendors = 0; + $countExpenses = 0; + foreach ($input as $transaction) { $vendorName = $transaction['vendor']; $key = strtolower($vendorName); @@ -165,7 +178,7 @@ class BankAccountService extends BaseService $field => $info, 'name' => $vendorName, 'transaction_name' => $transaction['vendor_orig'], - 'vendorcontact' => [] + 'vendorcontact' => [], ]); $vendorMap[$key] = $vendor; $vendorMap[$transaction['vendor_orig']] = $vendor; @@ -191,7 +204,8 @@ class BankAccountService extends BaseService ]); } - private function determineInfoField($value) { + private function determineInfoField($value) + { if (preg_match("/^[0-9\-\(\)\.]+$/", $value)) { return 'work_phone'; } elseif (strpos($value, '.') !== false) { @@ -215,7 +229,7 @@ class BankAccountService extends BaseService 'bank_name', function ($model) { return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name); - } + }, ], [ 'bank_library_id', @@ -233,9 +247,8 @@ class BankAccountService extends BaseService uctrans('texts.edit_bank_account'), function ($model) { return URL::to("bank_accounts/{$model->public_id}/edit"); - } + }, ] ]; } - -} \ No newline at end of file +} diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index b557e067b2bf..c17a8e359a80 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -1,4 +1,4 @@ - Date: Sat, 27 Feb 2016 22:51:22 +0200 Subject: [PATCH 04/12] Made dataviz bars wider --- app/Libraries/Utils.php | 2 +- resources/views/reports/d3.blade.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 14c3a6f2b802..c7e003b02090 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -72,7 +72,7 @@ class Utils public static function requireHTTPS() { - if (Request::root() === 'http://ninja.dev:8000') { + if (Request::root() === 'http://ninja.dev' || Request::root() === 'http://ninja.dev:8000') { return false; } diff --git a/resources/views/reports/d3.blade.php b/resources/views/reports/d3.blade.php index b9548fee7c3a..f6b61e830f17 100644 --- a/resources/views/reports/d3.blade.php +++ b/resources/views/reports/d3.blade.php @@ -128,12 +128,12 @@ var arc = d3.svg.arc() .innerRadius(function(d) { return d.r }) - .outerRadius(function(d) { return d.r - 5 }) + .outerRadius(function(d) { return d.r - 10 }) .startAngle(0); var fullArc = d3.svg.arc() - .innerRadius(function(d) { return d.r }) - .outerRadius(function(d) { return d.r - 5 }) + .innerRadius(function(d) { return d.r - 1 }) + .outerRadius(function(d) { return d.r - 9 }) .startAngle(0) .endAngle(2 * Math.PI); From e3519c9c39752224fa4980f7c8e1749891dff33f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 27 Feb 2016 22:53:02 +0200 Subject: [PATCH 05/12] Made dataviz bars wider --- resources/views/reports/d3.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/reports/d3.blade.php b/resources/views/reports/d3.blade.php index f6b61e830f17..3a3450b076d6 100644 --- a/resources/views/reports/d3.blade.php +++ b/resources/views/reports/d3.blade.php @@ -128,12 +128,12 @@ var arc = d3.svg.arc() .innerRadius(function(d) { return d.r }) - .outerRadius(function(d) { return d.r - 10 }) + .outerRadius(function(d) { return d.r - 8 }) .startAngle(0); var fullArc = d3.svg.arc() .innerRadius(function(d) { return d.r - 1 }) - .outerRadius(function(d) { return d.r - 9 }) + .outerRadius(function(d) { return d.r - 7 }) .startAngle(0) .endAngle(2 * Math.PI); From f2d7a01f94f07bc99ff3cf2878e18b9df8677107 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 27 Feb 2016 23:00:27 +0200 Subject: [PATCH 06/12] Fix for report defaults --- app/Http/Controllers/ReportController.php | 6 +----- resources/views/reports/chart_builder.blade.php | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 97582f4ea76c..1c4c033b9ed3 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -114,11 +114,7 @@ class ReportController extends BaseController } else { $params['columns'] = []; $params['displayData'] = []; - $params['reportTotals'] = [ - 'amount' => [], - 'balance' => [], - 'paid' => [], - ]; + $params['reportTotals'] = []; $params['labels'] = []; $params['datasets'] = []; $params['scaleStepWidth'] = 100; diff --git a/resources/views/reports/chart_builder.blade.php b/resources/views/reports/chart_builder.blade.php index defe7405e08b..0a04f4554061 100644 --- a/resources/views/reports/chart_builder.blade.php +++ b/resources/views/reports/chart_builder.blade.php @@ -48,7 +48,7 @@ @if (!Auth::user()->isPro()) @endif From 1c47f4e03f3926f9377929abe0f25155193f76ef Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 28 Feb 2016 10:13:58 +0200 Subject: [PATCH 07/12] If product key is blank remove column from PDF --- public/built.js | 33 ++++++++++++++++++++++++++------- public/js/pdf.pdfmake.js | 26 +++++++++++++++++++------- public/js/script.js | 7 +++++++ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/public/built.js b/public/built.js index 1d94fa1247a6..1c8aad7f62eb 100644 --- a/public/built.js +++ b/public/built.js @@ -30461,6 +30461,7 @@ function calculateAmounts(invoice) { var total = 0; var hasTaxes = false; var taxes = {}; + invoice.has_product_key = false; // sum line item for (var i=0; i Date: Sun, 28 Feb 2016 13:59:52 +0200 Subject: [PATCH 08/12] Added custom invoice item fields --- app/Http/Controllers/AccountController.php | 2 + app/Models/Invoice.php | 4 ++ app/Ninja/Repositories/InvoiceRepository.php | 9 +++- ...02_28_081424_add_custom_invoice_fields.php | 51 +++++++++++++++++++ public/built.js | 23 +++++++++ public/js/pdf.pdfmake.js | 23 +++++++++ resources/lang/en/texts.php | 8 +-- .../views/accounts/invoice_settings.blade.php | 30 +++++++++-- resources/views/invoices/edit.blade.php | 18 ++++++- resources/views/invoices/knockout.blade.php | 2 + 10 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2016_02_28_081424_add_custom_invoice_fields.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 90b6ef930b3a..a3ba45a5ccb6 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -684,6 +684,8 @@ class AccountController extends BaseController $account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false; $account->custom_invoice_text_label1 = trim(Input::get('custom_invoice_text_label1')); $account->custom_invoice_text_label2 = trim(Input::get('custom_invoice_text_label2')); + $account->custom_invoice_item_label1 = trim(Input::get('custom_invoice_item_label1')); + $account->custom_invoice_item_label2 = trim(Input::get('custom_invoice_item_label2')); $account->invoice_number_counter = Input::get('invoice_number_counter'); $account->quote_number_prefix = Input::get('quote_number_prefix'); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index e60a06f8d4a4..340ce419b757 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -455,12 +455,16 @@ class Invoice extends EntityModel implements BalanceAffecting 'show_item_taxes', 'custom_invoice_text_label1', 'custom_invoice_text_label2', + 'custom_invoice_item_label1', + 'custom_invoice_item_label2', ]); foreach ($this->invoice_items as $invoiceItem) { $invoiceItem->setVisible([ 'product_key', 'notes', + 'custom_value1', + 'custom_value2', 'cost', 'qty', 'tax_name', diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 3c4072122d1f..aa0e09f632b1 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -398,7 +398,7 @@ class InvoiceRepository extends BaseRepository foreach ($data['invoice_items'] as $item) { $item = (array) $item; - if (!$item['cost'] && !$item['product_key'] && !$item['notes']) { + if (empty($item['cost']) && empty($item['product_key']) && empty($item['notes']) && empty($item['custom_value1']) && empty($item['custom_value2'])) { continue; } @@ -439,6 +439,13 @@ class InvoiceRepository extends BaseRepository $invoiceItem->qty = Utils::parseFloat($item['qty']); $invoiceItem->tax_rate = 0; + if (isset($item['custom_value1'])) { + $invoiceItem->custom_value1 = $item['custom_value1']; + } + if (isset($item['custom_value2'])) { + $invoiceItem->custom_value2 = $item['custom_value2']; + } + if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) { $invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']); $invoiceItem['tax_name'] = trim($item['tax_name']); diff --git a/database/migrations/2016_02_28_081424_add_custom_invoice_fields.php b/database/migrations/2016_02_28_081424_add_custom_invoice_fields.php new file mode 100644 index 000000000000..3364261e3350 --- /dev/null +++ b/database/migrations/2016_02_28_081424_add_custom_invoice_fields.php @@ -0,0 +1,51 @@ +string('custom_invoice_item_label1')->nullable(); + $table->string('custom_invoice_item_label2')->nullable(); + $table->string('recurring_invoice_number_prefix')->default('R'); + $table->boolean('enable_client_portal')->default(true); + $table->text('invoice_fields')->nullable(); + $table->text('devices')->nullable(); + }); + + Schema::table('invoice_items', function($table) { + $table->string('custom_value1')->nullable(); + $table->string('custom_value2')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function($table) { + $table->dropColumn('custom_invoice_item_label1'); + $table->dropColumn('custom_invoice_item_label2'); + $table->dropColumn('recurring_invoice_number_prefix'); + $table->dropColumn('enable_client_portal'); + $table->dropColumn('invoice_fields'); + $table->dropColumn('devices'); + }); + + Schema::table('accounts', function($table) { + $table->dropColumn('custom_value1'); + $table->dropColumn('custom_value2'); + }); + } +} diff --git a/public/built.js b/public/built.js index 1c8aad7f62eb..51c66b75cd28 100644 --- a/public/built.js +++ b/public/built.js @@ -31216,6 +31216,13 @@ NINJA.invoiceColumns = function(invoice) columns.push("*") + if (account.custom_invoice_item_label1) { + columns.push("10%"); + } + if (account.custom_invoice_item_label2) { + columns.push("10%"); + } + var count = 3; if (account.hide_quantity == '1') { count--; @@ -31226,6 +31233,7 @@ NINJA.invoiceColumns = function(invoice) for (var i=0; i 'Customize the invoice link subdomain or display the invoice on your own website.', 'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', 'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.', - 'custom_client_fields_helps' => 'Add a text input to the client create/edit page and display the label and value on the PDF.', + 'custom_client_fields_helps' => 'Add a field when creating/editing a client and display the label and value on the PDF.', 'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', - 'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.', - 'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', + 'custom_invoice_fields_helps' => 'Add a field when creating an invoice and display the label and value on the PDF.', + 'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.', 'token_expired' => 'Validation token was expired. Please try again.', 'invoice_link' => 'Invoice Link', 'button_confirmation_message' => 'Click to confirm your email address.', @@ -1045,6 +1045,8 @@ $LANG = array( 'new_product' => 'New Product', 'new_tax_rate' => 'New Tax Rate', 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', ); diff --git a/resources/views/accounts/invoice_settings.blade.php b/resources/views/accounts/invoice_settings.blade.php index af9bdefadae9..e0194dcd0f84 100644 --- a/resources/views/accounts/invoice_settings.blade.php +++ b/resources/views/accounts/invoice_settings.blade.php @@ -108,10 +108,21 @@
@@ -153,6 +164,17 @@
+
+
+ + {!! Former::text('custom_invoice_item_label1') + ->label(trans('texts.field_label')) !!} + {!! Former::text('custom_invoice_item_label2') + ->label(trans('texts.field_label')) + ->help(trans('texts.custom_invoice_item_fields_help')) !!} + +
+
diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 456ef501f76a..548cc41e232c 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -189,6 +189,12 @@ {{ $invoiceLabels['item'] }} {{ $invoiceLabels['description'] }} + @if ($account->custom_invoice_item_label1) + {{ $account->custom_invoice_item_label1 }} + @endif + @if ($account->custom_invoice_item_label2) + {{ $account->custom_invoice_item_label2 }} + @endif {{ $invoiceLabels['unit_cost'] }} {{ $invoiceLabels['quantity'] }} {{ trans('texts.tax') }} @@ -215,6 +221,16 @@ + @if ($account->custom_invoice_item_label1) + + + + @endif + @if ($account->custom_invoice_item_label2) + + + + @endif @@ -243,7 +259,7 @@ - +
diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index 1f1d210b8716..adeb7759915a 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -714,6 +714,8 @@ function ItemModel(data) { self.notes = ko.observable(''); self.cost = ko.observable(0); self.qty = ko.observable(0); + self.custom_value1 = ko.observable(''); + self.custom_value2 = ko.observable(''); self.tax_name = ko.observable(''); self.tax_rate = ko.observable(0); self.task_public_id = ko.observable(''); From faa3a5e620a3e4f71041883a39d1b5ae8ccc5912 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 28 Feb 2016 14:09:42 +0200 Subject: [PATCH 09/12] Updated PHP version check to 5.5.9 --- app/Http/Middleware/StartupCheck.php | 3 +++ resources/views/setup.blade.php | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index dc9f9420f973..14344e10e956 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -48,6 +48,9 @@ class StartupCheck $file = storage_path() . '/version.txt'; $version = @file_get_contents($file); if ($version != NINJA_VERSION) { + if (version_compare(phpversion(), '5.5.9', '<')) { + dd('Please update PHP to >= 5.5.9'); + } $handle = fopen($file, 'w'); fwrite($handle, NINJA_VERSION); fclose($handle); diff --git a/resources/views/setup.blade.php b/resources/views/setup.blade.php index aad74d2015ad..650fc3089187 100644 --- a/resources/views/setup.blade.php +++ b/resources/views/setup.blade.php @@ -26,8 +26,8 @@

Invoice Ninja Setup

- @if (version_compare(phpversion(), '5.4.0', '<')) -
Warning: The application requires PHP >= 5.4.0
+ @if (version_compare(phpversion(), '5.5.9', '<')) +
Warning: The application requires PHP >= 5.5.9
@endif @if (!function_exists('proc_open'))
Warning: proc_open must be enabled.
From 553c548f31ec946f70e04746137107f6204846f5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 28 Feb 2016 20:36:11 +0200 Subject: [PATCH 10/12] Enabled setting recurring invoice number prefix --- app/Http/Controllers/AccountController.php | 1 + app/Ninja/Repositories/InvoiceRepository.php | 2 +- resources/lang/en/texts.php | 5 +++-- .../views/accounts/invoice_settings.blade.php | 20 +++++++++++++++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index a3ba45a5ccb6..7d141e8cb215 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -694,6 +694,7 @@ class AccountController extends BaseController $account->invoice_footer = Input::get('invoice_footer'); $account->quote_terms = Input::get('quote_terms'); $account->auto_convert_quote = Input::get('auto_convert_quote'); + $account->recurring_invoice_number_prefix = Input::get('recurring_invoice_number_prefix'); if (Input::has('recurring_hour')) { $account->recurring_hour = Input::get('recurring_hour'); diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index aa0e09f632b1..52b47998b5a6 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -610,7 +610,7 @@ class InvoiceRepository extends BaseRepository $invoice = Invoice::createNew($recurInvoice); $invoice->client_id = $recurInvoice->client_id; $invoice->recurring_invoice_id = $recurInvoice->id; - $invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber($recurInvoice); + $invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice); $invoice->amount = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount; $invoice->invoice_date = date_create()->format('Y-m-d'); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 5032cf1c5375..9c9c8e9a5912 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -845,7 +845,7 @@ $LANG = array( 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', 'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', 'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.', - 'custom_client_fields_helps' => 'Add a field when creating/editing a client and display the label and value on the PDF.', + 'custom_client_fields_helps' => 'Add a field when creating a client and display the label and value on the PDF.', 'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', 'custom_invoice_fields_helps' => 'Add a field when creating an invoice and display the label and value on the PDF.', 'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.', @@ -1047,7 +1047,8 @@ $LANG = array( 'invoiced_amount' => 'Invoiced Amount', 'invoice_item_fields' => 'Invoice Item Fields', 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', - + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.' ); return $LANG; diff --git a/resources/views/accounts/invoice_settings.blade.php b/resources/views/accounts/invoice_settings.blade.php index e0194dcd0f84..09b730737e0d 100644 --- a/resources/views/accounts/invoice_settings.blade.php +++ b/resources/views/accounts/invoice_settings.blade.php @@ -35,8 +35,15 @@
@@ -92,6 +99,15 @@ trans('texts.next_quote_number', ['number' => $account->previewNextInvoiceNumber(ENTITY_QUOTE)])) !!} +
+
+
+
+ + {!! Former::text('recurring_invoice_number_prefix') + ->label(trans('texts.prefix')) + ->help(trans('texts.recurring_invoice_number_prefix_help')) !!} +
From d465c0d01952805e448ebecafacb9a1b348e3ee3 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 28 Feb 2016 20:54:31 +0200 Subject: [PATCH 11/12] Handle token mismatch in get_started route --- app/Exceptions/Handler.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index ba656f6c0131..9d17fb099057 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -47,13 +47,16 @@ class Handler extends ExceptionHandler { if ($e instanceof ModelNotFoundException) { return Redirect::to('/'); } elseif ($e instanceof \Illuminate\Session\TokenMismatchException) { - // https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e - return redirect() - ->back() - ->withInput($request->except('password', '_token')) - ->with([ - 'warning' => trans('texts.token_expired') - ]); + // prevent loop since the page auto-submits + if ($request->path() != 'get_started') { + // https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e + return redirect() + ->back() + ->withInput($request->except('password', '_token')) + ->with([ + 'warning' => trans('texts.token_expired') + ]); + } } // In production, except for maintenance mode, we'll show a custom error screen From 6a9b2130c5e358b81f5e3f1b89d46a9f3e76bbd6 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 28 Feb 2016 22:43:43 +0200 Subject: [PATCH 12/12] Added fuzzy search using fuse.js --- Gruntfile.js | 3 +- app/Ninja/Repositories/AccountRepository.php | 34 ++--- bower.json | 5 +- public/built.js | 28 ++++- public/css/built.css | 125 +++++++++---------- public/css/typeahead.js-bootstrap.css | 125 +++++++++---------- readme.md | 2 + resources/views/header.blade.php | 83 +++++++++--- 8 files changed, 227 insertions(+), 178 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 8b50d2d5b2fe..e0a13ccd6a0d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -95,13 +95,14 @@ module.exports = function(grunt) { 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js', - 'public/vendor/typeahead.js/dist/typeahead.min.js', + 'public/vendor/typeahead.js/dist/typeahead.jquery.min.js', 'public/vendor/accounting/accounting.min.js', 'public/vendor/spectrum/spectrum.js', 'public/vendor/jspdf/dist/jspdf.min.js', 'public/vendor/moment/min/moment.min.js', 'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js', 'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js', + 'public/vendor/fuse.js/src/fuse.min.js', //'public/vendor/moment-duration-format/lib/moment-duration-format.js', //'public/vendor/handsontable/dist/jquery.handsontable.full.min.js', //'public/vendor/pdfmake/build/pdfmake.min.js', diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index 08a546fa9e48..42bef1d1e5d7 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -74,8 +74,7 @@ class AccountRepository { $data = $this->getAccountSearchData(); - $type = trans('texts.navigation'); - $data[$type] = $this->getNavigationSearchData(); + $data['navigation'] = $this->getNavigationSearchData(); return $data; } @@ -83,10 +82,10 @@ class AccountRepository private function getAccountSearchData() { $data = [ - trans('texts.clients') => [], - trans('texts.contacts') => [], - trans('texts.invoices') => [], - trans('texts.quotes') => [], + 'clients' => [], + 'contacts' => [], + 'invoices' => [], + 'quotes' => [], ]; $clients = Client::scope() @@ -95,26 +94,31 @@ class AccountRepository foreach ($clients as $client) { if ($client->name) { - $data[trans('texts.clients')][] = [ + $data['clients'][] = [ 'value' => $client->name, - 'tokens' => explode(' ', $client->name), 'url' => $client->present()->url, ]; } foreach ($client->contacts as $contact) { - $data[trans('texts.contacts')][] = [ - 'value' => $contact->getDisplayName(), - 'tokens' => explode(' ', $contact->getFullName() . ' ' . $contact->email), - 'url' => $client->present()->url, - ]; + if ($contact->getFullName()) { + $data['contacts'][] = [ + 'value' => $contact->getDisplayName(), + 'url' => $client->present()->url, + ]; + } + if ($contact->email) { + $data[trans('texts.contacts')][] = [ + 'value' => $contact->email, + 'url' => $client->present()->url, + ]; + } } foreach ($client->invoices as $invoice) { $entityType = $invoice->getEntityType(); - $data[trans("texts.{$entityType}s")][] = [ + $data["{$entityType}s"][] = [ 'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), - 'tokens' => explode(' ', $invoice->invoice_number . ' ' . intval($invoice->invoice_number) . ' ' . $client->getDisplayName()), 'url' => $invoice->present()->url, ]; } diff --git a/bower.json b/bower.json index c48c74feec7e..c395df671586 100644 --- a/bower.json +++ b/bower.json @@ -14,7 +14,7 @@ "underscore": "1.7.0", "jspdf": "1.0.272", "bootstrap-datepicker": "1.4.0", - "typeahead.js": "0.9.3", + "typeahead.js": "0.11.1", "accounting": "0.3.2", "spectrum": "1.3.4", "d3": "3.4.11", @@ -25,7 +25,8 @@ "moment-timezone": "~0.4.0", "quill": "~0.20.0", "datetimepicker": "~2.4.5", - "stacktrace-js": "~1.0.1" + "stacktrace-js": "~1.0.1", + "fuse.js": "~2.0.2" }, "resolutions": { "jquery": "~1.11" diff --git a/public/built.js b/public/built.js index 51c66b75cd28..cfb777e86e0e 100644 --- a/public/built.js +++ b/public/built.js @@ -27171,12 +27171,12 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p !function(a){a.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(a){a.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); /*! - * typeahead.js 0.9.3 - * https://github.com/twitter/typeahead - * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT + * typeahead.js 0.11.1 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT */ -!function(a){var b="0.9.3",c={isMsie:function(){var a=/(msie) ([\w.]+)/i.exec(navigator.userAgent);return a?parseInt(a[2],10):!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},bind:a.proxy,bindAll:function(b){var c;for(var d in b)a.isFunction(c=b[d])&&(b[d]=a.proxy(c,b))},indexOf:function(a,b){for(var c=0;c=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},tokenizeQuery:function(b){return a.trim(b).toLowerCase().split(/[\s]+/)},tokenizeText:function(b){return a.trim(b).toLowerCase().split(/[\s\-_]+/)},getProtocol:function(){return location.protocol},noop:function(){}},d=function(){var a=/\s+/;return{on:function(b,c){var d;if(!c)return this;for(this._callbacks=this._callbacks||{},b=b.split(a);d=b.shift();)this._callbacks[d]=this._callbacks[d]||[],this._callbacks[d].push(c);return this},trigger:function(b,c){var d,e;if(!this._callbacks)return this;for(b=b.split(a);d=b.shift();)if(e=this._callbacks[d])for(var f=0;fa;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return c.isNumber(d)&&b()>d?!0:!1}}:{get:c.noop,set:c.noop,remove:c.noop,clear:c.noop,isExpired:c.noop},c.mixin(a.prototype,g),a}(),g=function(){function a(a){c.bindAll(this),a=a||{},this.sizeLimit=a.sizeLimit||10,this.cache={},this.cachedKeysByAge=[]}return c.mixin(a.prototype,{get:function(a){return this.cache[a]},set:function(a,b){var c;this.cachedKeysByAge.length===this.sizeLimit&&(c=this.cachedKeysByAge.shift(),delete this.cache[c]),this.cache[a]=b,this.cachedKeysByAge.push(a)}}),a}(),h=function(){function b(a){c.bindAll(this),a=c.isString(a)?{url:a}:a,i=i||new g,h=c.isNumber(a.maxParallelRequests)?a.maxParallelRequests:h||6,this.url=a.url,this.wildcard=a.wildcard||"%QUERY",this.filter=a.filter,this.replace=a.replace,this.ajaxSettings={type:"get",cache:a.cache,timeout:a.timeout,dataType:a.dataType||"json",beforeSend:a.beforeSend},this._get=(/^throttle$/i.test(a.rateLimitFn)?c.throttle:c.debounce)(this._get,a.rateLimitWait||300)}function d(){j++}function e(){j--}function f(){return h>j}var h,i,j=0,k={};return c.mixin(b.prototype,{_get:function(a,b){function c(c){var e=d.filter?d.filter(c):c;b&&b(e),i.set(a,c)}var d=this;f()?this._sendRequest(a).done(c):this.onDeckRequestArgs=[].slice.call(arguments,0)},_sendRequest:function(b){function c(){e(),k[b]=null,f.onDeckRequestArgs&&(f._get.apply(f,f.onDeckRequestArgs),f.onDeckRequestArgs=null)}var f=this,g=k[b];return g||(d(),g=k[b]=a.ajax(b,this.ajaxSettings).always(c)),g},get:function(a,b){var d,e,f=this,g=encodeURIComponent(a||"");return b=b||c.noop,d=this.replace?this.replace(this.url,g):this.url.replace(this.wildcard,g),(e=i.get(d))?c.defer(function(){b(f.filter?f.filter(e):e)}):this._get(d,b),!!e}}),b}(),i=function(){function d(b){c.bindAll(this),c.isString(b.template)&&!b.engine&&a.error("no template engine specified"),b.local||b.prefetch||b.remote||a.error("one of local, prefetch, or remote is required"),this.name=b.name||c.getUniqueId(),this.limit=b.limit||5,this.minLength=b.minLength||1,this.header=b.header,this.footer=b.footer,this.valueKey=b.valueKey||"value",this.template=e(b.template,b.engine,this.valueKey),this.local=b.local,this.prefetch=b.prefetch,this.remote=b.remote,this.itemHash={},this.adjacencyList={},this.storage=b.name?new f(b.name):null}function e(a,b,d){var e,f;return c.isFunction(a)?e=a:c.isString(a)?(f=b.compile(a),e=c.bind(f.render,f)):e=function(a){return"

"+a[d]+"

"},e}var g={thumbprint:"thumbprint",protocol:"protocol",itemHash:"itemHash",adjacencyList:"adjacencyList"};return c.mixin(d.prototype,{_processLocalData:function(a){this._mergeProcessedData(this._processData(a))},_loadPrefetchData:function(d){function e(a){var b=d.filter?d.filter(a):a,e=m._processData(b),f=e.itemHash,h=e.adjacencyList;m.storage&&(m.storage.set(g.itemHash,f,d.ttl),m.storage.set(g.adjacencyList,h,d.ttl),m.storage.set(g.thumbprint,n,d.ttl),m.storage.set(g.protocol,c.getProtocol(),d.ttl)),m._mergeProcessedData(e)}var f,h,i,j,k,l,m=this,n=b+(d.thumbprint||"");return this.storage&&(f=this.storage.get(g.thumbprint),h=this.storage.get(g.protocol),i=this.storage.get(g.itemHash),j=this.storage.get(g.adjacencyList)),k=f!==n||h!==c.getProtocol(),d=c.isString(d)?{url:d}:d,d.ttl=c.isNumber(d.ttl)?d.ttl:864e5,i&&j&&!k?(this._mergeProcessedData({itemHash:i,adjacencyList:j}),l=a.Deferred().resolve()):l=a.getJSON(d.url).done(e),l},_transformDatum:function(a){var b=c.isString(a)?a:a[this.valueKey],d=a.tokens||c.tokenizeText(b),e={value:b,tokens:d};return c.isString(a)?(e.datum={},e.datum[this.valueKey]=a):e.datum=a,e.tokens=c.filter(e.tokens,function(a){return!c.isBlankString(a)}),e.tokens=c.map(e.tokens,function(a){return a.toLowerCase()}),e},_processData:function(a){var b=this,d={},e={};return c.each(a,function(a,f){var g=b._transformDatum(f),h=c.getUniqueId(g.value);d[h]=g,c.each(g.tokens,function(a,b){var d=b.charAt(0),f=e[d]||(e[d]=[h]);!~c.indexOf(f,h)&&f.push(h)})}),{itemHash:d,adjacencyList:e}},_mergeProcessedData:function(a){var b=this;c.mixin(this.itemHash,a.itemHash),c.each(a.adjacencyList,function(a,c){var d=b.adjacencyList[a];b.adjacencyList[a]=d?d.concat(c):c})},_getLocalSuggestions:function(a){var b,d=this,e=[],f=[],g=[];return c.each(a,function(a,b){var d=b.charAt(0);!~c.indexOf(e,d)&&e.push(d)}),c.each(e,function(a,c){var e=d.adjacencyList[c];return e?(f.push(e),(!b||e.length").css({position:"absolute",left:"-9999px",visibility:"hidden",whiteSpace:"nowrap",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function f(a,b){return a=(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),b=(b||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),a===b}return c.mixin(b.prototype,d,{_handleFocus:function(){this.trigger("focused")},_handleBlur:function(){this.trigger("blured")},_handleSpecialKeyEvent:function(a){var b=this.specialKeyCodeMap[a.which||a.keyCode];b&&this.trigger(b+"Keyed",a)},_compareQueryToInputValue:function(){var a=this.getInputValue(),b=f(this.query,a),c=b?this.query.length!==a.length:!1;c?this.trigger("whitespaceChanged",{value:this.query}):b||this.trigger("queryChanged",{value:this.query=a})},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),!b&&this._compareQueryToInputValue()},getHintValue:function(){return this.$hint.val()},setHintValue:function(a){this.$hint.val(a)},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},isOverflow:function(){return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>this.$input.width()},isCursorAtEnd:function(){var a,b=this.$input.val().length,d=this.$input[0].selectionStart;return c.isNumber(d)?d===b:document.selection?(a=document.selection.createRange(),a.moveStart("character",-b),b===a.text.length):!0}}),b}(),k=function(){function b(b){c.bindAll(this),this.isOpen=!1,this.isEmpty=!0,this.isMouseOverDropdown=!1,this.$menu=a(b.menu).on("mouseenter.tt",this._handleMouseenter).on("mouseleave.tt",this._handleMouseleave).on("click.tt",".tt-suggestion",this._handleSelection).on("mouseover.tt",".tt-suggestion",this._handleMouseover)}function e(a){return a.data("suggestion")}var f={suggestionsList:''},g={suggestionsList:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"}};return c.mixin(b.prototype,d,{_handleMouseenter:function(){this.isMouseOverDropdown=!0},_handleMouseleave:function(){this.isMouseOverDropdown=!1},_handleMouseover:function(b){var c=a(b.currentTarget);this._getSuggestions().removeClass("tt-is-under-cursor"),c.addClass("tt-is-under-cursor")},_handleSelection:function(b){var c=a(b.currentTarget);this.trigger("suggestionSelected",e(c))},_show:function(){this.$menu.css("display","block")},_hide:function(){this.$menu.hide()},_moveCursor:function(a){var b,c,d,f;if(this.isVisible()){if(b=this._getSuggestions(),c=b.filter(".tt-is-under-cursor"),c.removeClass("tt-is-under-cursor"),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return this.trigger("cursorRemoved"),void 0;-1>d&&(d=b.length-1),f=b.eq(d).addClass("tt-is-under-cursor"),this._ensureVisibility(f),this.trigger("cursorMoved",e(f))}},_getSuggestions:function(){return this.$menu.find(".tt-suggestions > .tt-suggestion")},_ensureVisibility:function(a){var b=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),c=this.$menu.scrollTop(),d=a.position().top,e=d+a.outerHeight(!0);0>d?this.$menu.scrollTop(c+d):e>b&&this.$menu.scrollTop(c+(e-b))},destroy:function(){this.$menu.off(".tt"),this.$menu=null},isVisible:function(){return this.isOpen&&!this.isEmpty},closeUnlessMouseIsOverDropdown:function(){this.isMouseOverDropdown||this.close()},close:function(){this.isOpen&&(this.isOpen=!1,this.isMouseOverDropdown=!1,this._hide(),this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){var b={left:"0",right:"auto"},c={left:"auto",right:" 0"};"ltr"===a?this.$menu.css(b):this.$menu.css(c)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getSuggestionUnderCursor:function(){var a=this._getSuggestions().filter(".tt-is-under-cursor").first();return a.length>0?e(a):null},getFirstSuggestion:function(){var a=this._getSuggestions().first();return a.length>0?e(a):null},renderSuggestions:function(b,d){var e,h,i,j,k,l="tt-dataset-"+b.name,m='
%body
',n=this.$menu.find("."+l);0===n.length&&(h=a(f.suggestionsList).css(g.suggestionsList),n=a("
").addClass(l).append(b.header).append(h).append(b.footer).appendTo(this.$menu)),d.length>0?(this.isEmpty=!1,this.isOpen&&this._show(),i=document.createElement("div"),j=document.createDocumentFragment(),c.each(d,function(c,d){d.dataset=b.name,e=b.template(d.datum),i.innerHTML=m.replace("%body",e),k=a(i.firstChild).css(g.suggestion).data("suggestion",d),k.children().each(function(){a(this).css(g.suggestionChild)}),j.appendChild(k[0])}),n.show().find(".tt-suggestions").html(j)):this.clearSuggestions(b.name),this.trigger("suggestionsRendered")},clearSuggestions:function(a){var b=a?this.$menu.find(".tt-dataset-"+a):this.$menu.find('[class^="tt-dataset-"]'),c=b.find(".tt-suggestions");b.hide(),c.empty(),0===this._getSuggestions().length&&(this.isEmpty=!0,this._hide())}}),b}(),l=function(){function b(a){var b,d,f;c.bindAll(this),this.$node=e(a.input),this.datasets=a.datasets,this.dir=null,this.eventBus=a.eventBus,b=this.$node.find(".tt-dropdown-menu"),d=this.$node.find(".tt-query"),f=this.$node.find(".tt-hint"),this.dropdownView=new k({menu:b}).on("suggestionSelected",this._handleSelection).on("cursorMoved",this._clearHint).on("cursorMoved",this._setInputValueToSuggestionUnderCursor).on("cursorRemoved",this._setInputValueToQuery).on("cursorRemoved",this._updateHint).on("suggestionsRendered",this._updateHint).on("opened",this._updateHint).on("closed",this._clearHint).on("opened closed",this._propagateEvent),this.inputView=new j({input:d,hint:f}).on("focused",this._openDropdown).on("blured",this._closeDropdown).on("blured",this._setInputValueToQuery).on("enterKeyed tabKeyed",this._handleSelection).on("queryChanged",this._clearHint).on("queryChanged",this._clearSuggestions).on("queryChanged",this._getSuggestions).on("whitespaceChanged",this._updateHint).on("queryChanged whitespaceChanged",this._openDropdown).on("queryChanged whitespaceChanged",this._setLanguageDirection).on("escKeyed",this._closeDropdown).on("escKeyed",this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed",this._managePreventDefault).on("upKeyed downKeyed",this._moveDropdownCursor).on("upKeyed downKeyed",this._openDropdown).on("tabKeyed leftKeyed rightKeyed",this._autocomplete)}function e(b){var c=a(g.wrapper),d=a(g.dropdown),e=a(b),f=a(g.hint);c=c.css(h.wrapper),d=d.css(h.dropdown),f.css(h.hint).css({backgroundAttachment:e.css("background-attachment"),backgroundClip:e.css("background-clip"),backgroundColor:e.css("background-color"),backgroundImage:e.css("background-image"),backgroundOrigin:e.css("background-origin"),backgroundPosition:e.css("background-position"),backgroundRepeat:e.css("background-repeat"),backgroundSize:e.css("background-size")}),e.data("ttAttrs",{dir:e.attr("dir"),autocomplete:e.attr("autocomplete"),spellcheck:e.attr("spellcheck"),style:e.attr("style")}),e.addClass("tt-query").attr({autocomplete:"off",spellcheck:!1}).css(h.query);try{!e.attr("dir")&&e.attr("dir","auto")}catch(i){}return e.wrap(c).parent().prepend(f).append(d)}function f(a){var b=a.find(".tt-query");c.each(b.data("ttAttrs"),function(a,d){c.isUndefined(d)?b.removeAttr(a):b.attr(a,d)}),b.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter(a),a.remove()}var g={wrapper:'',hint:'',dropdown:''},h={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},query:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"}};return c.isMsie()&&c.mixin(h.query,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),c.isMsie()&&c.isMsie()<=7&&(c.mixin(h.wrapper,{display:"inline",zoom:"1"}),c.mixin(h.query,{marginTop:"-1px"})),c.mixin(b.prototype,d,{_managePreventDefault:function(a){var b,c,d=a.data,e=!1;switch(a.type){case"tabKeyed":b=this.inputView.getHintValue(),c=this.inputView.getInputValue(),e=b&&b!==c;break;case"upKeyed":case"downKeyed":e=!d.shiftKey&&!d.ctrlKey&&!d.metaKey}e&&d.preventDefault()},_setLanguageDirection:function(){var a=this.inputView.getLanguageDirection();a!==this.dir&&(this.dir=a,this.$node.css("direction",a),this.dropdownView.setLanguageDirection(a))},_updateHint:function(){var a,b,d,e,f,g=this.dropdownView.getFirstSuggestion(),h=g?g.value:null,i=this.dropdownView.isVisible(),j=this.inputView.isOverflow();h&&i&&!j&&(a=this.inputView.getInputValue(),b=a.replace(/\s{2,}/g," ").replace(/^\s+/g,""),d=c.escapeRegExChars(b),e=new RegExp("^(?:"+d+")(.*$)","i"),f=e.exec(h),this.inputView.setHintValue(a+(f?f[1]:"")))},_clearHint:function(){this.inputView.setHintValue("")},_clearSuggestions:function(){this.dropdownView.clearSuggestions()},_setInputValueToQuery:function(){this.inputView.setInputValue(this.inputView.getQuery())},_setInputValueToSuggestionUnderCursor:function(a){var b=a.data;this.inputView.setInputValue(b.value,!0)},_openDropdown:function(){this.dropdownView.open()},_closeDropdown:function(a){this.dropdownView["blured"===a.type?"closeUnlessMouseIsOverDropdown":"close"]()},_moveDropdownCursor:function(a){var b=a.data;b.shiftKey||b.ctrlKey||b.metaKey||this.dropdownView["upKeyed"===a.type?"moveCursorUp":"moveCursorDown"]()},_handleSelection:function(a){var b="suggestionSelected"===a.type,d=b?a.data:this.dropdownView.getSuggestionUnderCursor();d&&(this.inputView.setInputValue(d.value),b?this.inputView.focus():a.data.preventDefault(),b&&c.isMsie()?c.defer(this.dropdownView.close):this.dropdownView.close(),this.eventBus.trigger("selected",d.datum,d.dataset))},_getSuggestions:function(){var a=this,b=this.inputView.getQuery();c.isBlankString(b)||c.each(this.datasets,function(c,d){d.getSuggestions(b,function(c){b===a.inputView.getQuery()&&a.dropdownView.renderSuggestions(d,c)})})},_autocomplete:function(a){var b,c,d,e,f;("rightKeyed"!==a.type&&"leftKeyed"!==a.type||(b=this.inputView.isCursorAtEnd(),c="ltr"===this.inputView.getLanguageDirection()?"leftKeyed"===a.type:"rightKeyed"===a.type,b&&!c))&&(d=this.inputView.getQuery(),e=this.inputView.getHintValue(),""!==e&&d!==e&&(f=this.dropdownView.getFirstSuggestion(),this.inputView.setInputValue(f.value),this.eventBus.trigger("autocompleted",f.datum,f.dataset)))},_propagateEvent:function(a){this.eventBus.trigger(a.type)},destroy:function(){this.inputView.destroy(),this.dropdownView.destroy(),f(this.$node),this.$node=null},setQuery:function(a){this.inputView.setQuery(a),this.inputView.setInputValue(a),this._clearHint(),this._clearSuggestions(),this._getSuggestions()}}),b}();!function(){var b,d={},f="ttView";b={initialize:function(b){function g(){var b,d=a(this),g=new e({el:d});b=c.map(h,function(a){return a.initialize()}),d.data(f,new l({input:d,eventBus:g=new e({el:d}),datasets:h})),a.when.apply(a,b).always(function(){c.defer(function(){g.trigger("initialized")})})}var h;return b=c.isArray(b)?b:[b],0===b.length&&a.error("no datasets provided"),h=c.map(b,function(a){var b=d[a.name]?d[a.name]:new i(a);return a.name&&(d[a.name]=b),b}),this.each(g)},destroy:function(){function b(){var b=a(this),c=b.data(f);c&&(c.destroy(),b.removeData(f))}return this.each(b)},setQuery:function(b){function c(){var c=a(this).data(f);c&&c.setQuery(b)}return this.each(c)}},jQuery.fn.typeahead=function(a){return b[a]?b[a].apply(this,[].slice.call(arguments,1)):b.initialize.apply(this,arguments)}}()}(window.jQuery); +!function(a,b){"function"==typeof define&&define.amd?define("typeahead.js",["jquery"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("jquery")):b(jQuery)}(this,function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},isElement:function(a){return!(!a||1!==a.nodeType)},isJQuery:function(b){return b instanceof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,identity:function(a){return a},clone:function(b){return a.extend(!0,{},b)},getIdGenerator:function(){var a=0;return function(){return a++}},templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},stringify:function(a){return b.isString(a)?a:JSON.stringify(a)},noop:function(){}}}(),c=function(){"use strict";function a(a){var g,h;return h=b.mixin({},f,a),g={css:e(),classes:h,html:c(h),selectors:d(h)},{css:g.css,html:g.html,classes:g.classes,selectors:g.selectors,mixin:function(a){b.mixin(a,g)}}}function c(a){return{wrapper:'',menu:'
'}}function d(a){var c={};return b.each(a,function(a,b){c[b]="."+a}),c}function e(){var a={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 b.isMsie()&&b.mixin(a.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),a}var f={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 a}(),d=function(){"use strict";function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d,e;return d="typeahead:",e={render:"rendered",cursorchange:"cursorchanged",select:"selected",autocomplete:"autocompleted"},b.mixin(c.prototype,{_trigger:function(b,c){var e;return e=a.Event(d+b),(c=c||[]).unshift(e),this.$el.trigger.apply(this.$el,c),e},before:function(a){var b,c;return b=[].slice.call(arguments,1),c=this._trigger("before"+a,b),c.isDefaultPrevented()},trigger:function(a){var b;this._trigger(a,[].slice.call(arguments,1)),(b=e[a])&&this._trigger(b,[].slice.call(arguments,1))}}),c}(),e=function(){"use strict";function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0,f=a.length;!d&&f>e;e+=1)d=a[e].apply(b,c)===!1;return!d}return d}function g(){var a;return a=window.setImmediate?function(a){setImmediate(function(){a()})}:function(a){setTimeout(function(){a()},0)}}function h(a,b){return a.bind?a.bind(b):function(){a.apply(b,[].slice.call(arguments,0))}}var i=/\s+/,j=g();return{onSync:c,onAsync:b,off:d,trigger:e}}(),f=function(a){"use strict";function c(a,c,d){for(var e,f=[],g=0,h=a.length;h>g;g++)f.push(b.escapeRegExChars(a[g]));return e=d?"\\b("+f.join("|")+")\\b":"("+f.join("|")+")",c?new RegExp(e):new RegExp(e,"i")}var d={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(e){function f(b){var c,d,f;return(c=h.exec(b.data))&&(f=a.createElement(e.tagName),e.className&&(f.className=e.className),d=b.splitText(c.index),d.splitText(c[0].length),f.appendChild(d.cloneNode(!0)),b.parentNode.replaceChild(f,d)),!!c}function g(a,b){for(var c,d=3,e=0;e