diff --git a/.env.example b/.env.example index 20c977c663e9..c0d6668fb52a 100644 --- a/.env.example +++ b/.env.example @@ -20,11 +20,27 @@ MAIL_FROM_ADDRESS MAIL_FROM_NAME MAIL_PASSWORD +#POSTMARK_API_TOKEN= + PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' LOG=single REQUIRE_HTTPS=false API_SECRET=password -GOOGLE_CLIENT_ID -GOOGLE_CLIENT_SECRET -GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google \ No newline at end of file +#TRUSTED_PROXIES= + +#SESSION_DRIVER= +#SESSION_DOMAIN= +#SESSION_ENCRYPT= +#SESSION_SECURE= + +#CACHE_DRIVER= +#CACHE_HOST= +#CACHE_PORT1= +#CACHE_PORT2= + +#GOOGLE_CLIENT_ID= +#GOOGLE_CLIENT_SECRET= +#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google + +#GOOGLE_MAPS_API_KEY= \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 9a1a2f53b643..f76fe32447b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php sudo: true php: - - 5.5 + - 5.5.9 # - 5.6 # - 7.0 # - hhvm @@ -34,7 +34,6 @@ install: # these providers require referencing git commit's which cause Travis to fail - sed -i '/mollie/d' composer.json - sed -i '/2checkout/d' composer.json - - sed -i '/omnipay-neteller/d' composer.json - travis_retry composer install --prefer-dist; before_script: @@ -65,16 +64,18 @@ before_script: script: - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php - #- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance QuoteCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php + - php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php diff --git a/Gruntfile.js b/Gruntfile.js index 05dab35047ec..e0a13ccd6a0d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -95,19 +95,19 @@ 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', //'public/vendor/pdfmake/build/vfs_fonts.js', //'public/js/vfs_fonts.js', - 'public/js/lightbox.min.js', 'public/js/bootstrap-combobox.js', 'public/js/script.js', 'public/js/pdf.pdfmake.js', @@ -140,7 +140,6 @@ module.exports = function(grunt) { 'public/vendor/spectrum/spectrum.css', 'public/css/bootstrap-combobox.css', 'public/css/typeahead.js-bootstrap.css', - 'public/css/lightbox.css', //'public/vendor/handsontable/dist/jquery.handsontable.full.css', 'public/css/style.css', ], diff --git a/LICENSE b/LICENSE index 2f9d7d69a9b5..9fe9b899c55a 100644 --- a/LICENSE +++ b/LICENSE @@ -13,7 +13,7 @@ open-source software. 1. Redistributions of source code, in whole or part and with or without modification requires the express permission of the author and must prominently -display "Powered by InvoiceNinja" or the Invoice Ninja logo in verifiable form +display "Powered by InvoiceNinja" and the Invoice Ninja logo in verifiable form with hyperlink to said site. 2. Neither the name nor any trademark of the Author may be used to endorse or promote products derived from this software without specific diff --git a/app/Console/Commands/ChargeRenewalInvoices.php b/app/Console/Commands/ChargeRenewalInvoices.php new file mode 100644 index 000000000000..39d63ee44c06 --- /dev/null +++ b/app/Console/Commands/ChargeRenewalInvoices.php @@ -0,0 +1,63 @@ +mailer = $mailer; + $this->accountRepo = $repo; + $this->paymentService = $paymentService; + } + + public function fire() + { + $this->info(date('Y-m-d').' ChargeRenewalInvoices...'); + + $account = $this->accountRepo->getNinjaAccount(); + $invoices = Invoice::whereAccountId($account->id) + ->whereDueDate(date('Y-m-d')) + ->with('client') + ->orderBy('id') + ->get(); + + $this->info(count($invoices).' invoices found'); + + foreach ($invoices as $invoice) { + $this->info("Charging invoice {$invoice->invoice_number}"); + $this->paymentService->autoBillInvoice($invoice); + } + + $this->info('Done'); + } + + protected function getArguments() + { + return array( + //array('example', InputArgument::REQUIRED, 'An example argument.'), + ); + } + + protected function getOptions() + { + return array( + //array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null), + ); + } +} diff --git a/app/Console/Commands/SendRenewalInvoices.php b/app/Console/Commands/SendRenewalInvoices.php index 1e8ea1b49eb1..0faf1304a964 100644 --- a/app/Console/Commands/SendRenewalInvoices.php +++ b/app/Console/Commands/SendRenewalInvoices.php @@ -47,7 +47,7 @@ class SendRenewalInvoices extends Command } $client = $this->accountRepo->getNinjaClient($account); - $invitation = $this->accountRepo->createNinjaInvoice($client); + $invitation = $this->accountRepo->createNinjaInvoice($client, $account); // set the due date to 10 days from now $invoice = $invitation->invoice; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index bd2720afa96f..d04eab9fad91 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel 'App\Console\Commands\ResetData', 'App\Console\Commands\CheckData', 'App\Console\Commands\SendRenewalInvoices', + 'App\Console\Commands\ChargeRenewalInvoices', 'App\Console\Commands\SendReminders', 'App\Console\Commands\TestOFX', 'App\Console\Commands\GenerateResources', 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 diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index 77e85243d7a9..02697ba039de 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -19,6 +19,8 @@ use App\Ninja\Transformers\UserAccountTransformer; use App\Http\Controllers\BaseAPIController; use Swagger\Annotations as SWG; +use App\Events\UserSignedUp; +use App\Http\Requests\RegisterRequest; use App\Http\Requests\UpdateAccountRequest; class AccountApiController extends BaseAPIController @@ -32,13 +34,20 @@ class AccountApiController extends BaseAPIController $this->accountRepo = $accountRepo; } + public function register(RegisterRequest $request) + { + + $account = $this->accountRepo->create($request->first_name, $request->last_name, $request->email, $request->password); + $user = $account->users()->first(); + + Auth::login($user, true); + event(new UserSignedUp()); + + return $this->processLogin($request); + } + public function login(Request $request) { - if ( ! env(API_SECRET) || $request->api_secret !== env(API_SECRET)) { - sleep(ERROR_DELAY); - return $this->errorResponse(['message'=>'Invalid secret'],401); - } - if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) { return $this->processLogin($request); } else { @@ -52,7 +61,7 @@ class AccountApiController extends BaseAPIController // Create a new token only if one does not already exist $user = Auth::user(); $this->accountRepo->createTokens($user, $request->token_name); - + $users = $this->accountRepo->findUsers($user, 'account.account_tokens'); $transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name); $data = $this->createCollection($users, $transformer, 'user_account'); @@ -65,24 +74,7 @@ class AccountApiController extends BaseAPIController $account = Auth::user()->account; $updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false; - $map = [ - 'users' => [], - 'clients' => ['contacts'], - 'invoices' => ['invoice_items', 'user', 'client', 'payments'], - 'products' => [], - 'tax_rates' => [], - 'expenses' => ['client', 'invoice', 'vendor'], - 'payments' => ['invoice'], - ]; - - foreach ($map as $key => $values) { - $account->load([$key => function($query) use ($values, $updatedAt) { - $query->withTrashed()->with($values); - if ($updatedAt) { - $query->where('updated_at', '>=', $updatedAt); - } - }]); - } + $account->loadAllData($updatedAt); $transformer = new AccountTransformer(null, $request->serializer); $account = $this->createItem($account, $transformer, 'account'); @@ -117,4 +109,82 @@ class AccountApiController extends BaseAPIController return $this->response($account); } + + public function addDeviceToken(Request $request) + { + $account = Auth::user()->account; + + //scan if this user has a token already registered (tokens can change, so we need to use the users email as key) + $devices = json_decode($account->devices,TRUE); + + + for($x=0; $xusername) { + $devices[$x]['token'] = $request->token; //update + $account->devices = json_encode($devices); + $account->save(); + $devices[$x]['account_key'] = $account->account_key; + + return $this->response($devices[$x]); + } + } + + //User does not have a device, create new record + + $newDevice = [ + 'token' => $request->token, + 'email' => $request->email, + 'device' => $request->device, + 'account_key' => $account->account_key, + 'notify_sent' => TRUE, + 'notify_viewed' => TRUE, + 'notify_approved' => TRUE, + 'notify_paid' => TRUE, + ]; + + $devices[] = $newDevice; + $account->devices = json_encode($devices); + $account->save(); + + return $this->response($newDevice); + + } + + public function updatePushNotifications(Request $request) + { + $account = Auth::user()->account; + + $devices = json_decode($account->devices, TRUE); + + if(count($devices) < 1) + return $this->errorResponse(['message'=>'No registered devices.'], 400); + + for($x=0; $xusername) + { + + $newDevice = [ + 'token' => $devices[$x]['token'], + 'email' => $devices[$x]['email'], + 'device' => $devices[$x]['device'], + 'account_key' => $account->account_key, + 'notify_sent' => $request->notify_sent, + 'notify_viewed' => $request->notify_viewed, + 'notify_approved' => $request->notify_approved, + 'notify_paid' => $request->notify_paid, + ]; + + //unset($devices[$x]); + + $devices[$x] = $newDevice; + $account->devices = json_encode($devices); + $account->save(); + + return $this->response($newDevice); + } + } + + } } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 6c0f59a3ba91..912ade56f962 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -15,6 +15,7 @@ use Response; use Request; use App\Models\Affiliate; use App\Models\License; +use App\Models\Invoice; use App\Models\User; use App\Models\Account; use App\Models\Gateway; @@ -25,7 +26,7 @@ use App\Ninja\Repositories\AccountRepository; use App\Ninja\Repositories\ReferralRepository; use App\Ninja\Mailers\UserMailer; use App\Ninja\Mailers\ContactMailer; -use App\Events\UserLoggedIn; +use App\Events\UserSignedUp; use App\Events\UserSettingsChanged; use App\Services\AuthService; @@ -40,7 +41,7 @@ class AccountController extends BaseController public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository) { - parent::__construct(); + //parent::__construct(); $this->accountRepo = $accountRepo; $this->userMailer = $userMailer; @@ -99,7 +100,7 @@ class AccountController extends BaseController } Auth::login($user, true); - event(new UserLoggedIn()); + event(new UserSignedUp()); $redirectTo = Input::get('redirect_to') ?: 'invoices/create'; @@ -122,7 +123,8 @@ class AccountController extends BaseController public function getSearchData() { - $data = $this->accountRepo->getSearchData(); + $account = Auth::user()->account; + $data = $this->accountRepo->getSearchData($account); return Response::json($data); } @@ -135,8 +137,6 @@ class AccountController extends BaseController if ($section == ACCOUNT_COMPANY_DETAILS) { return self::showCompanyDetails(); - } elseif ($section == ACCOUNT_USER_DETAILS) { - return self::showUserDetails(); } elseif ($section == ACCOUNT_LOCALIZATION) { return self::showLocalization(); } elseif ($section == ACCOUNT_PAYMENTS) { @@ -150,7 +150,7 @@ class AccountController extends BaseController } elseif ($section == ACCOUNT_INVOICE_DESIGN || $section == ACCOUNT_CUSTOMIZE_DESIGN) { return self::showInvoiceDesign($section); } elseif ($section == ACCOUNT_CLIENT_PORTAL) { - return self::showClientViewStyling(); + return self::showClientPortal(); } elseif ($section === ACCOUNT_TEMPLATES_AND_REMINDERS) { return self::showTemplates(); } elseif ($section === ACCOUNT_PRODUCTS) { @@ -230,7 +230,7 @@ class AccountController extends BaseController return View::make('accounts.details', $data); } - private function showUserDetails() + public function showUserDetails() { $oauthLoginUrls = []; foreach (AuthService::$providers as $provider) { @@ -393,12 +393,27 @@ class AccountController extends BaseController if ($section == ACCOUNT_CUSTOMIZE_DESIGN) { $data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design; + + // sample invoice to help determine variables + $invoice = Invoice::scope() + ->with('client', 'account') + ->where('is_quote', '=', false) + ->where('is_recurring', '=', false) + ->first(); + + if ($invoice) { + $invoice->hidePrivateFields(); + unset($invoice->account); + unset($invoice->invoice_items); + unset($invoice->client->contacts); + $data['sampleInvoice'] = $invoice; + } } return View::make("accounts.{$section}", $data); } - private function showClientViewStyling() + private function showClientPortal() { $account = Auth::user()->account->load('country'); $css = $account->client_view_css ? $account->client_view_css : ''; @@ -414,8 +429,11 @@ class AccountController extends BaseController $data = [ 'client_view_css' => $css, + 'enable_portal_password' => $account->enable_portal_password, + 'send_portal_password' => $account->send_portal_password, 'title' => trans("texts.client_portal"), 'section' => ACCOUNT_CLIENT_PORTAL, + 'account' => $account, ]; return View::make("accounts.client_portal", $data); @@ -447,8 +465,6 @@ class AccountController extends BaseController { if ($section === ACCOUNT_COMPANY_DETAILS) { return AccountController::saveDetails(); - } elseif ($section === ACCOUNT_USER_DETAILS) { - return AccountController::saveUserDetails(); } elseif ($section === ACCOUNT_LOCALIZATION) { return AccountController::saveLocalization(); } elseif ($section === ACCOUNT_NOTIFICATIONS) { @@ -528,6 +544,11 @@ class AccountController extends BaseController $account = Auth::user()->account; $account->client_view_css = $sanitized_css; + + $account->enable_client_portal = !!Input::get('enable_client_portal'); + $account->enable_portal_password = !!Input::get('enable_portal_password'); + $account->send_portal_password = !!Input::get('send_portal_password'); + $account->save(); Session::flash('message', trans('texts.updated_settings')); @@ -668,6 +689,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'); @@ -676,6 +699,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'); @@ -736,7 +760,7 @@ class AccountController extends BaseController } $labels = []; - foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms'] as $field) { + foreach (['item', 'description', 'unit_cost', 'quantity', 'line_total', 'terms', 'balance_due', 'partial_due'] as $field) { $labels[$field] = Input::get("labels_{$field}"); } $account->invoice_labels = json_encode($labels); @@ -811,7 +835,7 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_COMPANY_DETAILS); } - private function saveUserDetails() + public function saveUserDetails() { $user = Auth::user(); $rules = ['email' => 'email|required|unique:users,email,'.$user->id.',id']; diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 58b831503b78..0bc730129fe1 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -24,7 +24,7 @@ class AccountGatewayController extends BaseController public function __construct(AccountGatewayService $accountGatewayService) { - parent::__construct(); + //parent::__construct(); $this->accountGatewayService = $accountGatewayService; } diff --git a/app/Http/Controllers/ActivityController.php b/app/Http/Controllers/ActivityController.php index 44d87429d691..897e1d2a978b 100644 --- a/app/Http/Controllers/ActivityController.php +++ b/app/Http/Controllers/ActivityController.php @@ -15,7 +15,7 @@ class ActivityController extends BaseController public function __construct(ActivityService $activityService) { - parent::__construct(); + //parent::__construct(); $this->activityService = $activityService; } diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 606374a0d5de..394323d7420a 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -30,7 +30,7 @@ class AppController extends BaseController public function __construct(AccountRepository $accountRepo, Mailer $mailer, EmailService $emailService) { - parent::__construct(); + //parent::__construct(); $this->accountRepo = $accountRepo; $this->mailer = $mailer; @@ -78,7 +78,7 @@ class AppController extends BaseController } elseif (!$valid) { return Redirect::to('/setup')->withInput(); } - + if (Utils::isDatabaseSetup() && Account::count() > 0) { return Redirect::to('/'); } @@ -114,7 +114,7 @@ class AppController extends BaseController } Cache::flush(); Artisan::call('optimize', array('--force' => true)); - + $firstName = trim(Input::get('first_name')); $lastName = trim(Input::get('last_name')); $email = trim(strtolower(Input::get('email'))); @@ -152,7 +152,7 @@ class AppController extends BaseController $_ENV['DB_DATABASE'] = $db['type']['database']; $_ENV['DB_USERNAME'] = $db['type']['username']; $_ENV['DB_PASSWORD'] = $db['type']['password']; - + if ($mail) { $_ENV['MAIL_DRIVER'] = $mail['driver']; $_ENV['MAIL_PORT'] = $mail['port']; @@ -184,7 +184,7 @@ class AppController extends BaseController foreach ($database['connections'][$dbType] as $key => $val) { Config::set("database.connections.{$dbType}.{$key}", $val); } - + try { DB::reconnect(); $valid = DB::connection()->getDatabaseName() ? true : false; @@ -206,7 +206,7 @@ class AppController extends BaseController Config::set('mail.from.address', $email); Config::set('mail.from.name', $fromName); - + $data = [ 'text' => 'Test email', ]; @@ -231,7 +231,8 @@ class AppController extends BaseController } Artisan::call('optimize', array('--force' => true)); } catch (Exception $e) { - Response::make($e->getMessage(), 500); + Utils::logError($e); + return Response::make($e->getMessage(), 500); } } @@ -243,6 +244,7 @@ class AppController extends BaseController if (!Utils::isNinjaProd()) { try { set_time_limit(60 * 5); + Artisan::call('optimize', array('--force' => true)); Cache::flush(); Session::flush(); Artisan::call('migrate', array('--force' => true)); @@ -250,15 +252,19 @@ 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")); } - Artisan::call('optimize', array('--force' => true)); Event::fire(new UserSettingsChanged()); Session::flash('message', trans('texts.processed_updates')); } catch (Exception $e) { - Response::make($e->getMessage(), 500); + Utils::logError($e); + return Response::make($e->getMessage(), 500); } } @@ -276,7 +282,7 @@ class AppController extends BaseController { $messageId = Input::get('MessageID'); return $this->emailService->markOpened($messageId) ? RESULT_SUCCESS : RESULT_FAILURE; - + return RESULT_SUCCESS; } @@ -288,7 +294,7 @@ class AppController extends BaseController } if (Utils::getResllerType() == RESELLER_REVENUE_SHARE) { - $payments = DB::table('accounts') + $data = DB::table('accounts') ->leftJoin('payments', 'payments.account_id', '=', 'accounts.id') ->leftJoin('clients', 'clients.id', '=', 'payments.client_id') ->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY) @@ -300,15 +306,9 @@ class AppController extends BaseController 'payments.amount' ]); } else { - $payments = DB::table('accounts') - ->leftJoin('payments', 'payments.account_id', '=', 'accounts.id') - ->leftJoin('clients', 'clients.id', '=', 'payments.client_id') - ->where('accounts.account_key', '=', NINJA_ACCOUNT_KEY) - ->where('payments.is_deleted', '=', false) - ->groupBy('clients.id') - ->count(); + $data = DB::table('users')->count(); } - return json_encode($payments); + return json_encode($data); } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 5ed231cedc82..bd8813912d25 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -10,8 +10,6 @@ use App\Events\UserLoggedIn; use App\Http\Controllers\Controller; use App\Ninja\Repositories\AccountRepository; use App\Services\AuthService; -use Illuminate\Contracts\Auth\Guard; -use Illuminate\Contracts\Auth\Registrar; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; class AuthController extends Controller { @@ -29,7 +27,6 @@ class AuthController extends Controller { use AuthenticatesAndRegistersUsers; - protected $loginPath = '/login'; protected $redirectTo = '/dashboard'; protected $authService; protected $accountRepo; @@ -41,16 +38,38 @@ class AuthController extends Controller { * @param \Illuminate\Contracts\Auth\Registrar $registrar * @return void */ - public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo, AuthService $authService) + public function __construct(AccountRepository $repo, AuthService $authService) { - $this->auth = $auth; - $this->registrar = $registrar; $this->accountRepo = $repo; $this->authService = $authService; //$this->middleware('guest', ['except' => 'getLogout']); } + public function validator(array $data) + { + return Validator::make($data, [ + 'name' => 'required|max:255', + 'email' => 'required|email|max:255|unique:users', + 'password' => 'required|confirmed|min:6', + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * @return User + */ + public function create(array $data) + { + return User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => bcrypt($data['password']), + ]); + } + public function authLogin($provider, Request $request) { return $this->authService->execute($provider, $request->has('code')); diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php index 3741a8157983..bd7d0bb0ef8b 100644 --- a/app/Http/Controllers/Auth/PasswordController.php +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -1,8 +1,6 @@ auth = $auth; - $this->passwords = $passwords; - $this->middleware('guest'); } diff --git a/app/Http/Controllers/BankAccountController.php b/app/Http/Controllers/BankAccountController.php index 9222a4cab1e3..bce86bce42f1 100644 --- a/app/Http/Controllers/BankAccountController.php +++ b/app/Http/Controllers/BankAccountController.php @@ -27,7 +27,7 @@ class BankAccountController extends BaseController public function __construct(BankAccountService $bankAccountService, BankAccountRepository $bankAccountRepo) { - parent::__construct(); + //parent::__construct(); $this->bankAccountService = $bankAccountService; $this->bankAccountRepo = $bankAccountRepo; @@ -91,7 +91,7 @@ class BankAccountController extends BaseController $publicId = Input::get('public_id'); $username = trim(Input::get('bank_username')); $password = trim(Input::get('bank_password')); - + if ($publicId) { $bankAccount = BankAccount::scope($publicId)->firstOrFail(); if ($username != $bankAccount->username) { diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 1a2f6c8dc526..5124097636a9 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -1,10 +1,14 @@ layout = View::make($this->layout); } } - - public function __construct() - { - $this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put'))); + + protected function checkViewPermission($object, &$response = null){ + if(!$object->canView()){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkEditPermission($object, &$response = null){ + if(!$object->canEdit()){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkCreatePermission(&$response = null){ + if(!call_user_func(array($this->model, 'canCreate'))){ + $response = response('Unauthorized.', 401); + return false; + } + return true; + } + + protected function checkUpdatePermission($input, &$response = null){ + $creating = empty($input['public_id']) || $input['public_id'] == '-1'; + + if($creating){ + return $this->checkCreatePermission($response); + } + else{ + $object = call_user_func(array($this->model, 'scope'), $input['public_id'])->firstOrFail(); + return $this->checkEditPermission($object, $response); + } } } diff --git a/app/Http/Controllers/ClientApiController.php b/app/Http/Controllers/ClientApiController.php index 4bac33236646..fd3b33df6aa5 100644 --- a/app/Http/Controllers/ClientApiController.php +++ b/app/Http/Controllers/ClientApiController.php @@ -174,7 +174,7 @@ class ClientApiController extends BaseAPIController if(!$client) return $this->errorResponse(['message'=>'Client not found.'],400); - + $transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer')); $data = $this->createItem($client, $transformer, ENTITY_CLIENT); @@ -203,7 +203,7 @@ class ClientApiController extends BaseAPIController * ) * ) */ - + public function destroy($publicId) { diff --git a/app/Http/Controllers/ClientAuth/AuthController.php b/app/Http/Controllers/ClientAuth/AuthController.php new file mode 100644 index 000000000000..c88c8a4b85ea --- /dev/null +++ b/app/Http/Controllers/ClientAuth/AuthController.php @@ -0,0 +1,79 @@ +first(); + if ($invitation && !$invitation->is_deleted) { + $invoice = $invitation->invoice; + $client = $invoice->client; + $account = $client->account; + + $data['hideLogo'] = $account->isWhiteLabel(); + $data['clientViewCSS'] = $account->clientViewCSS(); + $data['clientFontUrl'] = $account->getFontsUrl(); + } + } + + return view('clientauth.login')->with($data); + } + + /** + * Get the needed authorization credentials from the request. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function getCredentials(Request $request) + { + $credentials = $request->only('password'); + $credentials['id'] = null; + + $invitation_key = session('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $credentials['id'] = $invitation->contact_id; + } + } + + return $credentials; + } + + /** + * Validate the user login request. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function validateLogin(Request $request) + { + $this->validate($request, [ + 'password' => 'required', + ]); + } +} diff --git a/app/Http/Controllers/ClientAuth/PasswordController.php b/app/Http/Controllers/ClientAuth/PasswordController.php new file mode 100644 index 000000000000..beefb016123e --- /dev/null +++ b/app/Http/Controllers/ClientAuth/PasswordController.php @@ -0,0 +1,197 @@ +middleware('guest'); + Config::set("auth.defaults.passwords","client"); + } + + public function showLinkRequestForm() + { + $data = array(); + $invitation_key = session('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $invoice = $invitation->invoice; + $client = $invoice->client; + $account = $client->account; + + $data['hideLogo'] = $account->isWhiteLabel(); + $data['clientViewCSS'] = $account->clientViewCSS(); + $data['clientFontUrl'] = $account->getFontsUrl(); + } + } + + return view('clientauth.password')->with($data); + } + + /** + * Send a reset link to the given user. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function sendResetLinkEmail(Request $request) + { + $broker = $this->getBroker(); + + $contact_id = null; + $invitation_key = session('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $contact_id = $invitation->contact_id; + } + } + + $response = Password::broker($broker)->sendResetLink(array('id'=>$contact_id), function (Message $message) { + $message->subject($this->getEmailSubject()); + }); + + switch ($response) { + case Password::RESET_LINK_SENT: + return $this->getSendResetLinkEmailSuccessResponse($response); + + case Password::INVALID_USER: + default: + return $this->getSendResetLinkEmailFailureResponse($response); + } + } + + /** + * Display the password reset view for the given token. + * + * If no token is present, display the link request form. + * + * @param \Illuminate\Http\Request $request + * @param string|null $invitation_key + * @param string|null $token + * @return \Illuminate\Http\Response + */ + public function showResetForm(Request $request, $invitation_key = null, $token = null) + { + if (is_null($token)) { + return $this->getEmail(); + } + + $data = compact('token', 'invitation_key'); + $invitation_key = session('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $invoice = $invitation->invoice; + $client = $invoice->client; + $account = $client->account; + + $data['hideLogo'] = $account->isWhiteLabel(); + $data['clientViewCSS'] = $account->clientViewCSS(); + $data['clientFontUrl'] = $account->getFontsUrl(); + } + } + + return view('clientauth.reset')->with($data); + } + + + + /** + * Display the password reset view for the given token. + * + * If no token is present, display the link request form. + * + * @param \Illuminate\Http\Request $request + * @param string|null $invitation_key + * @param string|null $token + * @return \Illuminate\Http\Response + */ + public function getReset(Request $request, $invitation_key = null, $token = null) + { + return $this->showResetForm($request, $invitation_key, $token); + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function reset(Request $request) + { + $this->validate($request, $this->getResetValidationRules()); + + $credentials = $request->only( + 'password', 'password_confirmation', 'token' + ); + + $credentials['id'] = null; + + $invitation_key = $request->input('invitation_key'); + if($invitation_key){ + $invitation = Invitation::where('invitation_key', '=', $invitation_key)->first(); + if ($invitation && !$invitation->is_deleted) { + $credentials['id'] = $invitation->contact_id; + } + } + + $broker = $this->getBroker(); + + $response = Password::broker($broker)->reset($credentials, function ($user, $password) { + $this->resetPassword($user, $password); + }); + + switch ($response) { + case Password::PASSWORD_RESET: + return $this->getResetSuccessResponse($response); + + default: + return $this->getResetFailureResponse($request, $response); + } + } + + /** + * Get the password reset validation rules. + * + * @return array + */ + protected function getResetValidationRules() + { + return [ + 'token' => 'required', + 'password' => 'required|confirmed|min:6', + ]; + } +} diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 84145afe9c1c..7e1c01de9e6e 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -20,6 +20,9 @@ use App\Models\Size; use App\Models\PaymentTerm; use App\Models\Industry; use App\Models\Currency; +use App\Models\Payment; +use App\Models\Credit; +use App\Models\Expense; use App\Models\Country; use App\Models\Task; use App\Ninja\Repositories\ClientRepository; @@ -32,10 +35,11 @@ class ClientController extends BaseController { protected $clientService; protected $clientRepo; + protected $model = 'App\Models\Client'; public function __construct(ClientRepository $clientRepo, ClientService $clientService) { - parent::__construct(); + //parent::__construct(); $this->clientRepo = $clientRepo; $this->clientService = $clientService; @@ -77,10 +81,16 @@ class ClientController extends BaseController */ public function store(CreateClientRequest $request) { - $client = $this->clientService->save($request->input()); + $data = $request->input(); + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $client = $this->clientService->save($data); + Session::flash('message', trans('texts.created_client')); - + return redirect()->to($client->getRoute()); } @@ -93,23 +103,37 @@ class ClientController extends BaseController public function show($publicId) { $client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail(); + + if(!$this->checkViewPermission($client, $response)){ + return $response; + } + Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT); - $actionLinks = [ - ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id] - ]; - - if (Utils::isPro()) { - array_push($actionLinks, ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]); + $actionLinks = []; + if(Task::canCreate()){ + $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id]; + } + if (Utils::isPro() && Invoice::canCreate()) { + $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id]; + } + + if(!empty($actionLinks)){ + $actionLinks[] = \DropdownButton::DIVIDER; + } + + if(Payment::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id]; + } + + if(Credit::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]; + } + + if(Expense::canCreate()){ + $actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]; } - array_push($actionLinks, - \DropdownButton::DIVIDER, - ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id], - ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id], - ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id] - ); - $data = array( 'actionLinks' => $actionLinks, 'showBreadcrumbs' => false, @@ -132,7 +156,11 @@ class ClientController extends BaseController */ public function create() { - if (Client::scope()->count() > Auth::user()->getMaxNumClients()) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + + if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) { return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]); } @@ -157,6 +185,11 @@ class ClientController extends BaseController public function edit($publicId) { $client = Client::scope($publicId)->with('contacts')->firstOrFail(); + + if(!$this->checkEditPermission($client, $response)){ + return $response; + } + $data = [ 'client' => $client, 'method' => 'PUT', @@ -199,10 +232,16 @@ class ClientController extends BaseController */ public function update(UpdateClientRequest $request) { - $client = $this->clientService->save($request->input()); + $data = $request->input(); + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $client = $this->clientService->save($data); + Session::flash('message', trans('texts.updated_client')); - + return redirect()->to($client->getRoute()); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 27b3f45272fd..b3a6ac7b1f59 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -1,11 +1,11 @@ creditRepo = $creditRepo; $this->creditService = $creditService; @@ -56,6 +57,10 @@ class CreditController extends BaseController public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + $data = array( 'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId, //'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId, @@ -72,6 +77,11 @@ class CreditController extends BaseController public function edit($publicId) { $credit = Credit::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($credit, $response)){ + return $response; + } + $credit->credit_date = Utils::fromSqlDate($credit->credit_date); $data = array( @@ -88,9 +98,9 @@ class CreditController extends BaseController public function store(CreateCreditRequest $request) { $credit = $this->creditRepo->save($request->input()); - + Session::flash('message', trans('texts.created_credit')); - + return redirect()->to($credit->client->getRoute()); } diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php new file mode 100644 index 000000000000..06393e3ddc3c --- /dev/null +++ b/app/Http/Controllers/DashboardApiController.php @@ -0,0 +1,179 @@ +hasPermission('view_all'); + $user_id = Auth::user()->id; + + // total_income, billed_clients, invoice_sent and active_clients + $select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients, + SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent, + COUNT(DISTINCT clients.id) active_clients'); + $metrics = DB::table('accounts') + ->select($select) + ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') + ->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id') + ->where('accounts.id', '=', Auth::user()->account_id) + ->where('clients.is_deleted', '=', false) + ->where('invoices.is_deleted', '=', false) + ->where('invoices.is_recurring', '=', false) + ->where('invoices.is_quote', '=', false); + + if(!$view_all){ + $metrics = $metrics->where(function($query) use($user_id){ + $query->where('invoices.user_id', '=', $user_id); + $query->orwhere(function($query) use($user_id){ + $query->where('invoices.user_id', '=', null); + $query->where('clients.user_id', '=', $user_id); + }); + }); + } + + $metrics = $metrics->groupBy('accounts.id') + ->first(); + + $select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id'); + $paidToDate = DB::table('accounts') + ->select($select) + ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') + ->where('accounts.id', '=', Auth::user()->account_id) + ->where('clients.is_deleted', '=', false); + + if(!$view_all){ + $paidToDate = $paidToDate->where('clients.user_id', '=', $user_id); + } + + $paidToDate = $paidToDate->groupBy('accounts.id') + ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) + ->get(); + + $select = DB::raw('AVG(invoices.amount) as invoice_avg, clients.currency_id as currency_id'); + $averageInvoice = DB::table('accounts') + ->select($select) + ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') + ->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id') + ->where('accounts.id', '=', Auth::user()->account_id) + ->where('clients.is_deleted', '=', false) + ->where('invoices.is_deleted', '=', false) + ->where('invoices.is_quote', '=', false) + ->where('invoices.is_recurring', '=', false); + + if(!$view_all){ + $averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id); + } + + $averageInvoice = $averageInvoice->groupBy('accounts.id') + ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) + ->get(); + + $select = DB::raw('SUM(clients.balance) as value, clients.currency_id as currency_id'); + $balances = DB::table('accounts') + ->select($select) + ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') + ->where('accounts.id', '=', Auth::user()->account_id) + ->where('clients.is_deleted', '=', false) + ->groupBy('accounts.id') + ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) + ->get(); + + $pastDue = DB::table('invoices') + ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->where('invoices.account_id', '=', Auth::user()->account_id) + ->where('clients.deleted_at', '=', null) + ->where('contacts.deleted_at', '=', null) + ->where('invoices.is_recurring', '=', false) + //->where('invoices.is_quote', '=', false) + ->where('invoices.balance', '>', 0) + ->where('invoices.is_deleted', '=', false) + ->where('invoices.deleted_at', '=', null) + ->where('contacts.is_primary', '=', true) + ->where('invoices.due_date', '<', date('Y-m-d')); + + if(!$view_all){ + $pastDue = $pastDue->where('invoices.user_id', '=', $user_id); + } + + $pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) + ->orderBy('invoices.due_date', 'asc') + ->take(50) + ->get(); + + $upcoming = DB::table('invoices') + ->leftJoin('clients', 'clients.id', '=', 'invoices.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->where('invoices.account_id', '=', Auth::user()->account_id) + ->where('clients.deleted_at', '=', null) + ->where('contacts.deleted_at', '=', null) + ->where('invoices.deleted_at', '=', null) + ->where('invoices.is_recurring', '=', false) + //->where('invoices.is_quote', '=', false) + ->where('invoices.balance', '>', 0) + ->where('invoices.is_deleted', '=', false) + ->where('contacts.is_primary', '=', true) + ->where('invoices.due_date', '>=', date('Y-m-d')) + ->orderBy('invoices.due_date', 'asc'); + + if(!$view_all){ + $upcoming = $upcoming->where('invoices.user_id', '=', $user_id); + } + + $upcoming = $upcoming->take(50) + ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) + ->get(); + + $payments = DB::table('payments') + ->leftJoin('clients', 'clients.id', '=', 'payments.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id') + ->where('payments.account_id', '=', Auth::user()->account_id) + ->where('payments.is_deleted', '=', false) + ->where('invoices.is_deleted', '=', false) + ->where('clients.is_deleted', '=', false) + ->where('contacts.deleted_at', '=', null) + ->where('contacts.is_primary', '=', true); + + if(!$view_all){ + $payments = $payments->where('payments.user_id', '=', $user_id); + } + + $payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id']) + ->orderBy('payments.payment_date', 'desc') + ->take(50) + ->get(); + + $hasQuotes = false; + foreach ([$upcoming, $pastDue] as $data) { + foreach ($data as $invoice) { + if ($invoice->is_quote) { + $hasQuotes = true; + } + } + } + + + $data = [ + 'id' => 1, + 'paidToDate' => $paidToDate[0]->value, + 'paidToDateCurrency' => $paidToDate[0]->currency_id, + 'balances' => $balances[0]->value, + 'balancesCurrency' => $balances[0]->currency_id, + 'averageInvoice' => $averageInvoice[0]->invoice_avg, + 'averageInvoiceCurrency' => $averageInvoice[0]->currency_id, + 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, + 'activeClients' => $metrics ? $metrics->active_clients : 0, + ]; + + + + return $this->response($data); + + } +} diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 1e26fd4a715a..b66f38615d9b 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -11,7 +11,9 @@ class DashboardController extends BaseController { public function index() { - + $view_all = !Auth::user()->hasPermission('view_all'); + $user_id = Auth::user()->id; + // total_income, billed_clients, invoice_sent and active_clients $select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients, SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent, @@ -24,8 +26,19 @@ class DashboardController extends BaseController ->where('clients.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false) ->where('invoices.is_recurring', '=', false) - ->where('invoices.is_quote', '=', false) - ->groupBy('accounts.id') + ->where('invoices.is_quote', '=', false); + + if(!$view_all){ + $metrics = $metrics->where(function($query) use($user_id){ + $query->where('invoices.user_id', '=', $user_id); + $query->orwhere(function($query) use($user_id){ + $query->where('invoices.user_id', '=', null); + $query->where('clients.user_id', '=', $user_id); + }); + }); + } + + $metrics = $metrics->groupBy('accounts.id') ->first(); $select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id'); @@ -33,8 +46,13 @@ class DashboardController extends BaseController ->select($select) ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') ->where('accounts.id', '=', Auth::user()->account_id) - ->where('clients.is_deleted', '=', false) - ->groupBy('accounts.id') + ->where('clients.is_deleted', '=', false); + + if(!$view_all){ + $paidToDate = $paidToDate->where('clients.user_id', '=', $user_id); + } + + $paidToDate = $paidToDate->groupBy('accounts.id') ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); @@ -47,8 +65,13 @@ class DashboardController extends BaseController ->where('clients.is_deleted', '=', false) ->where('invoices.is_deleted', '=', false) ->where('invoices.is_quote', '=', false) - ->where('invoices.is_recurring', '=', false) - ->groupBy('accounts.id') + ->where('invoices.is_recurring', '=', false); + + if(!$view_all){ + $averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id); + } + + $averageInvoice = $averageInvoice->groupBy('accounts.id') ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); @@ -63,9 +86,14 @@ class DashboardController extends BaseController ->get(); $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) + ->where('activities.activity_type_id', '>', 0); + + if(!$view_all){ + $activities = $activities->where('activities.user_id', '=', $user_id); + } + + $activities = $activities->orderBy('activities.created_at', 'desc') ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account') - ->where('activity_type_id', '>', 0) - ->orderBy('created_at', 'desc') ->take(50) ->get(); @@ -81,8 +109,13 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('invoices.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) - ->where('invoices.due_date', '<', date('Y-m-d')) - ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) + ->where('invoices.due_date', '<', date('Y-m-d')); + + if(!$view_all){ + $pastDue = $pastDue->where('invoices.user_id', '=', $user_id); + } + + $pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->orderBy('invoices.due_date', 'asc') ->take(50) ->get(); @@ -100,9 +133,14 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '>=', date('Y-m-d')) - ->orderBy('invoices.due_date', 'asc') - ->take(50) - ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote']) + ->orderBy('invoices.due_date', 'asc'); + + if(!$view_all){ + $upcoming = $upcoming->where('invoices.user_id', '=', $user_id); + } + + $upcoming = $upcoming->take(50) + ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->get(); $payments = DB::table('payments') @@ -114,8 +152,13 @@ class DashboardController extends BaseController ->where('invoices.is_deleted', '=', false) ->where('clients.is_deleted', '=', false) ->where('contacts.deleted_at', '=', null) - ->where('contacts.is_primary', '=', true) - ->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id']) + ->where('contacts.is_primary', '=', true); + + if(!$view_all){ + $payments = $payments->where('payments.user_id', '=', $user_id); + } + + $payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id']) ->orderBy('payments.payment_date', 'desc') ->take(50) ->get(); diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 5535b863e857..b1fd28e41941 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -25,10 +25,11 @@ class ExpenseController extends BaseController // Expenses protected $expenseRepo; protected $expenseService; + protected $model = 'App\Models\Expense'; public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService) { - parent::__construct(); + // parent::__construct(); $this->expenseRepo = $expenseRepo; $this->expenseService = $expenseService; @@ -44,7 +45,7 @@ class ExpenseController extends BaseController return View::make('list', array( 'entityType' => ENTITY_EXPENSE, 'title' => trans('texts.expenses'), - 'sortCol' => '1', + 'sortCol' => '3', 'columns' => Utils::trans([ 'checkbox', 'vendor', @@ -70,6 +71,10 @@ class ExpenseController extends BaseController public function create($vendorPublicId = null, $clientPublicId = null) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if($vendorPublicId != 0) { $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); } else { @@ -95,6 +100,11 @@ class ExpenseController extends BaseController public function edit($publicId) { $expense = Expense::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($expense, $response)){ + return $response; + } + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); $actions = []; diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 540e38d97296..db74adb89cee 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -89,6 +89,8 @@ class ExportController extends BaseController if ($key === 'quotes') { $key = 'invoices'; $data['entityType'] = ENTITY_QUOTE; + } elseif ($key === 'recurringInvoices') { + $key = 'recurring_invoices'; } $sheet->loadView("export.{$key}", $data); }); @@ -109,8 +111,7 @@ class ExportController extends BaseController if ($request->input(ENTITY_CLIENT)) { $data['clients'] = Client::scope() ->with('user', 'contacts', 'country') - ->withTrashed() - ->where('is_deleted', '=', false) + ->withArchived() ->get(); $data['contacts'] = Contact::scope() @@ -126,33 +127,36 @@ class ExportController extends BaseController if ($request->input(ENTITY_TASK)) { $data['tasks'] = Task::scope() ->with('user', 'client.contacts') - ->withTrashed() - ->where('is_deleted', '=', false) + ->withArchived() ->get(); } if ($request->input(ENTITY_INVOICE)) { $data['invoices'] = Invoice::scope() ->with('user', 'client.contacts', 'invoice_status') - ->withTrashed() - ->where('is_deleted', '=', false) + ->withArchived() ->where('is_quote', '=', false) ->where('is_recurring', '=', false) ->get(); $data['quotes'] = Invoice::scope() ->with('user', 'client.contacts', 'invoice_status') - ->withTrashed() - ->where('is_deleted', '=', false) + ->withArchived() ->where('is_quote', '=', true) ->where('is_recurring', '=', false) ->get(); + + $data['recurringInvoices'] = Invoice::scope() + ->with('user', 'client.contacts', 'invoice_status', 'frequency') + ->withArchived() + ->where('is_quote', '=', false) + ->where('is_recurring', '=', true) + ->get(); } if ($request->input(ENTITY_PAYMENT)) { $data['payments'] = Payment::scope() - ->withTrashed() - ->where('is_deleted', '=', false) + ->withArchived() ->with('user', 'client.contacts', 'payment_type', 'invoice', 'account_gateway.gateway') ->get(); } @@ -161,14 +165,14 @@ class ExportController extends BaseController if ($request->input(ENTITY_VENDOR)) { $data['clients'] = Vendor::scope() ->with('user', 'vendorcontacts', 'country') - ->withTrashed() - ->where('is_deleted', '=', false) + ->withArchived() ->get(); $data['vendor_contacts'] = VendorContact::scope() ->with('user', 'vendor.contacts') ->withTrashed() ->get(); + /* $data['expenses'] = Credit::scope() ->with('user', 'client.contacts') diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 571ac731938c..f01c4a6f1354 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -17,7 +17,7 @@ class HomeController extends BaseController public function __construct(Mailer $mailer) { - parent::__construct(); + //parent::__construct(); $this->mailer = $mailer; } diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index b078fb6f955e..38618475064b 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -13,7 +13,7 @@ class ImportController extends BaseController { public function __construct(ImportService $importService) { - parent::__construct(); + //parent::__construct(); $this->importService = $importService; } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index f99e12a4d740..46f40b0bf251 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -149,7 +149,7 @@ class InvoiceApiController extends BaseAPIController $client = Client::scope()->whereHas('contacts', function($query) use ($email) { $query->where('email', '=', $email); })->first(); - + if (!$client) { $validator = Validator::make(['email'=>$email], ['email' => 'email']); if ($validator->fails()) { @@ -220,7 +220,7 @@ class InvoiceApiController extends BaseAPIController { $account = Auth::user()->account; $account->loadLocalizationSettings($client); - + // set defaults for optional fields $fields = [ 'discount' => 0, diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index bb124c07c46c..5a14ea624449 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -34,10 +34,11 @@ class InvoiceController extends BaseController protected $clientRepo; protected $invoiceService; protected $recurringInvoiceService; + protected $model = 'App\Models\Invoice'; public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService) { - parent::__construct(); + // parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; @@ -51,6 +52,7 @@ class InvoiceController extends BaseController $data = [ 'title' => trans('texts.invoices'), 'entityType' => ENTITY_INVOICE, + 'sortCol' => '3', 'columns' => Utils::trans([ 'checkbox', 'invoice_number', @@ -87,9 +89,14 @@ class InvoiceController extends BaseController { $account = Auth::user()->account; $invoice = Invoice::scope($publicId) - ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items') + ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'payments') ->withTrashed() ->firstOrFail(); + + if(!$this->checkEditPermission($invoice, $response)){ + return $response; + } + $entityType = $invoice->getEntityType(); $contactIds = DB::table('invitations') @@ -99,6 +106,8 @@ class InvoiceController extends BaseController ->where('invitations.deleted_at', '=', null) ->select('contacts.public_id')->lists('public_id'); + $clients = Client::scope()->withTrashed()->with('contacts', 'country'); + if ($clone) { $invoice->id = $invoice->public_id = null; $invoice->invoice_number = $account->getNextInvoiceNumber($invoice); @@ -111,6 +120,7 @@ class InvoiceController extends BaseController Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType()); $method = 'PUT'; $url = "{$entityType}s/{$publicId}"; + $clients->whereId($invoice->client_id); } $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); @@ -145,6 +155,14 @@ class InvoiceController extends BaseController if (!$invoice->is_recurring && $invoice->balance > 0) { $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) { @@ -156,8 +174,12 @@ class InvoiceController extends BaseController $lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null; + if(!Auth::user()->hasPermission('view_all')){ + $clients = $clients->where('clients.user_id', '=', Auth::user()->id); + } + $data = array( - 'clients' => Client::scope()->withTrashed()->with('contacts', 'country')->whereId($invoice->client_id)->get(), + 'clients' => $clients->get(), 'entityType' => $entityType, 'showBreadcrumbs' => $clone, 'invoice' => $invoice, @@ -203,7 +225,11 @@ class InvoiceController extends BaseController public function create($clientPublicId = 0, $isRecurring = false) { - $account = Auth::user()->account; + if(!$this->checkCreatePermission($response)){ + return $response; + } + + $account = Auth::user()->account; $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $clientId = null; @@ -214,8 +240,13 @@ class InvoiceController extends BaseController $invoice = $account->createInvoice($entityType, $clientId); $invoice->public_id = 0; + $clients = Client::scope()->with('contacts', 'country')->orderBy('name'); + if(!Auth::user()->hasPermission('view_all')){ + $clients = $clients->where('clients.user_id', '=', Auth::user()->id); + } + $data = [ - 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), + 'clients' => $clients->get(), 'entityType' => $invoice->getEntityType(), 'invoice' => $invoice, 'method' => 'POST', @@ -332,10 +363,16 @@ class InvoiceController extends BaseController */ public function store(SaveInvoiceWithClientRequest $request) { + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + $action = Input::get('action'); $entityType = Input::get('entityType'); - $invoice = $this->invoiceService->save($request->input()); + $invoice = $this->invoiceService->save($data, true); $entityType = $invoice->getEntityType(); $message = trans("texts.created_{$entityType}"); @@ -366,10 +403,16 @@ class InvoiceController extends BaseController */ public function update(SaveInvoiceWithClientRequest $request) { + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + $action = Input::get('action'); $entityType = Input::get('entityType'); - $invoice = $this->invoiceService->save($request->input()); + $invoice = $this->invoiceService->save($data, true); $entityType = $invoice->getEntityType(); $message = trans("texts.updated_{$entityType}"); Session::flash('message', $message); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 8d769c126053..ebede5711bd5 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -30,9 +30,11 @@ use App\Http\Requests\UpdatePaymentRequest; class PaymentController extends BaseController { + protected $model = 'App\Models\Payment'; + public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService) { - parent::__construct(); + // parent::__construct(); $this->paymentRepo = $paymentRepo; $this->invoiceRepo = $invoiceRepo; @@ -46,6 +48,7 @@ class PaymentController extends BaseController return View::make('list', array( 'entityType' => ENTITY_PAYMENT, 'title' => trans('texts.payments'), + 'sortCol' => '6', 'columns' => Utils::trans([ 'checkbox', 'invoice', @@ -66,6 +69,10 @@ class PaymentController extends BaseController public function create($clientPublicId = 0, $invoicePublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + $invoices = Invoice::scope() ->where('is_recurring', '=', false) ->where('is_quote', '=', false) @@ -92,6 +99,11 @@ class PaymentController extends BaseController public function edit($publicId) { $payment = Payment::scope($publicId)->firstOrFail(); + + if(!$this->checkEditPermission($payment, $response)){ + return $response; + } + $payment->payment_date = Utils::fromSqlDate($payment->payment_date); $data = array( @@ -573,6 +585,11 @@ class PaymentController extends BaseController public function store(CreatePaymentRequest $request) { $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } + $input['invoice_id'] = Invoice::getPrivateId($input['invoice']); $input['client_id'] = Client::getPrivateId($input['client']); $payment = $this->paymentRepo->save($input); @@ -590,6 +607,11 @@ class PaymentController extends BaseController public function update(UpdatePaymentRequest $request) { $input = $request->input(); + + if(!$this->checkUpdatePermission($input, $response)){ + return $response; + } + $payment = $this->paymentRepo->save($input); Session::flash('message', trans('texts.updated_payment')); @@ -620,6 +642,6 @@ class PaymentController extends BaseController $message .= $error ?: trans('texts.payment_error'); Session::flash('error', $message); - Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message)); + Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true); } } diff --git a/app/Http/Controllers/PaymentTermController.php b/app/Http/Controllers/PaymentTermController.php index 623ca1bf42da..35b57ce26d9d 100644 --- a/app/Http/Controllers/PaymentTermController.php +++ b/app/Http/Controllers/PaymentTermController.php @@ -22,8 +22,8 @@ class PaymentTermController extends BaseController public function __construct(PaymentTermService $paymentTermService) { - parent::__construct(); - + //parent::__construct(); + $this->paymentTermService = $paymentTermService; } @@ -99,5 +99,5 @@ class PaymentTermController extends BaseController return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS); } - + } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index e25f486688d5..bd5eed57db94 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -21,7 +21,7 @@ class ProductController extends BaseController public function __construct(ProductService $productService) { - parent::__construct(); + //parent::__construct(); $this->productService = $productService; } diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 9d806061d88c..d067ff38a740 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -34,10 +34,7 @@ class PublicClientController extends BaseController public function view($invitationKey) { if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { - return response()->view('error', [ - 'error' => trans('texts.invoice_not_found'), - 'hideHeader' => true, - ]); + return $this->returnError(); } $invoice = $invitation->invoice; @@ -53,7 +50,8 @@ class PublicClientController extends BaseController ]); } - if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { + if (!Input::has('phantomjs') && !Input::has('silent') && !Session::has($invitationKey) + && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { if ($invoice->is_quote) { event(new QuoteInvitationWasViewed($invoice, $invitation)); } else { @@ -104,7 +102,9 @@ class PublicClientController extends BaseController // Checkout.com requires first getting a payment token $checkoutComToken = false; $checkoutComKey = false; + $checkoutComDebug = false; if ($accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM)) { + $checkoutComDebug = $accountGateway->getConfigField('testMode'); if ($checkoutComToken = $this->paymentService->getCheckoutComToken($invitation)) { $checkoutComKey = $accountGateway->getConfigField('publicApiKey'); $invitation->transaction_reference = $checkoutComToken; @@ -118,6 +118,7 @@ class PublicClientController extends BaseController 'showBreadcrumbs' => false, 'hideLogo' => $account->isWhiteLabel(), 'hideHeader' => $account->isNinjaAccount(), + 'hideDashboard' => !$account->enable_client_portal, 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'invoice' => $invoice->hidePrivateFields(), @@ -128,6 +129,7 @@ class PublicClientController extends BaseController 'paymentURL' => $paymentURL, 'checkoutComToken' => $checkoutComToken, 'checkoutComKey' => $checkoutComKey, + 'checkoutComDebug' => $checkoutComDebug, 'phantomjs' => Input::has('phantomjs'), ); @@ -188,11 +190,16 @@ class PublicClientController extends BaseController if (!$invitation = $this->getInvitation()) { return $this->returnError(); } + $account = $invitation->account; $invoice = $invitation->invoice; $client = $invoice->client; $color = $account->primary_color ? $account->primary_color : '#0b4d78'; + if (!$account->enable_client_portal) { + return $this->returnError(); + } + $data = [ 'color' => $color, 'account' => $account, @@ -244,6 +251,7 @@ class PublicClientController extends BaseController $data = [ 'color' => $color, 'hideLogo' => $account->isWhiteLabel(), + 'hideDashboard' => !$account->enable_client_portal, 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'title' => trans('texts.invoices'), @@ -275,6 +283,7 @@ class PublicClientController extends BaseController $data = [ 'color' => $color, 'hideLogo' => $account->isWhiteLabel(), + 'hideDashboard' => !$account->enable_client_portal, 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'entityType' => ENTITY_PAYMENT, @@ -293,7 +302,7 @@ class PublicClientController extends BaseController $payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch')); return Datatable::query($payments) - ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; }) + ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })->toHtml() ->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : 'Manual entry'; }) ->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? 'Online payment' : ''); }) ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) @@ -312,6 +321,7 @@ class PublicClientController extends BaseController $data = [ 'color' => $color, 'hideLogo' => $account->isWhiteLabel(), + 'hideDashboard' => !$account->enable_client_portal, 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'title' => trans('texts.quotes'), @@ -332,13 +342,11 @@ class PublicClientController extends BaseController return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch')); } - private function returnError() + private function returnError($error = false) { return response()->view('error', [ - 'error' => trans('texts.invoice_not_found'), + 'error' => $error ?: trans('texts.invoice_not_found'), 'hideHeader' => true, - 'clientViewCSS' => $account->clientViewCSS(), - 'clientFontUrl' => $account->getFontsUrl(), ]); } diff --git a/app/Http/Controllers/QuoteApiController.php b/app/Http/Controllers/QuoteApiController.php index 3e3cfa580c23..0868fbe97188 100644 --- a/app/Http/Controllers/QuoteApiController.php +++ b/app/Http/Controllers/QuoteApiController.php @@ -52,7 +52,7 @@ class QuoteApiController extends BaseAPIController } $invoices = $invoices->orderBy('created_at', 'desc')->paginate(); - + $transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer')); $paginator = $paginator->paginate(); diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 47b5c1a338e4..0c2dc3a8a5ba 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -33,10 +33,11 @@ class QuoteController extends BaseController protected $invoiceRepo; protected $clientRepo; protected $invoiceService; + protected $model = 'App\Models\Invoice'; public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService) { - parent::__construct(); + // parent::__construct(); $this->mailer = $mailer; $this->invoiceRepo = $invoiceRepo; @@ -53,6 +54,7 @@ class QuoteController extends BaseController $data = [ 'title' => trans('texts.quotes'), 'entityType' => ENTITY_QUOTE, + 'sortCol' => '3', 'columns' => Utils::trans([ 'checkbox', 'quote_number', @@ -78,6 +80,10 @@ class QuoteController extends BaseController public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (!Utils::isPro()) { return Redirect::to('/invoices/create'); } @@ -99,7 +105,7 @@ class QuoteController extends BaseController 'title' => trans('texts.new_quote'), ]; $data = array_merge($data, self::getViewModel()); - + return View::make('invoices.edit', $data); } @@ -136,7 +142,7 @@ class QuoteController extends BaseController Session::flash('message', trans('texts.converted_to_invoice')); return Redirect::to('invoices/'.$clone->public_id); } - + $count = $this->invoiceService->bulk($ids, $action); if ($count > 0) { diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index c59370647bb0..2982e04e3312 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -9,7 +9,7 @@ class RecurringInvoiceController extends BaseController public function __construct(InvoiceRepository $invoiceRepo) { - parent::__construct(); + //parent::__construct(); $this->invoiceRepo = $invoiceRepo; } @@ -29,7 +29,7 @@ class RecurringInvoiceController extends BaseController 'action' ]) ]; - + return response()->view('list', $data); } diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 1757cb58501c..f1feb65b4bd9 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -10,6 +10,9 @@ use DatePeriod; use Session; use View; use App\Models\Account; +use App\Models\Client; +use App\Models\Payment; +use App\Models\Expense; class ReportController extends BaseController { @@ -47,6 +50,7 @@ class ReportController extends BaseController $groupBy = Input::get('group_by'); $chartType = Input::get('chart_type'); $reportType = Input::get('report_type'); + $dateField = Input::get('date_field'); $startDate = Utils::toSqlDate(Input::get('start_date'), false); $endDate = Utils::toSqlDate(Input::get('end_date'), false); $enableReport = Input::get('enable_report') ? true : false; @@ -55,6 +59,7 @@ class ReportController extends BaseController $groupBy = 'MONTH'; $chartType = 'Bar'; $reportType = ENTITY_INVOICE; + $dateField = FILTER_INVOICE_DATE; $startDate = Utils::today(false)->modify('-3 month'); $endDate = Utils::today(false); $enableReport = true; @@ -76,6 +81,8 @@ class ReportController extends BaseController ENTITY_CLIENT => trans('texts.client'), ENTITY_INVOICE => trans('texts.invoice'), ENTITY_PAYMENT => trans('texts.payment'), + ENTITY_EXPENSE => trans('texts.expenses'), + ENTITY_TAX_RATE => trans('texts.taxes'), ]; $params = [ @@ -94,10 +101,11 @@ class ReportController extends BaseController if (Auth::user()->account->isPro()) { if ($enableReport) { - $params = array_merge($params, self::generateReport($reportType, $groupBy, $startDate, $endDate)); + $isExport = $action == 'export'; + $params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport)); - if ($action == 'export') { - self::export($params['exportData'], $params['reportTotals']); + if ($isExport) { + self::export($params['displayData'], $params['columns'], $params['reportTotals']); } } if ($enableChart) { @@ -106,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; @@ -212,165 +216,320 @@ class ReportController extends BaseController ]; } - private function generateReport($reportType, $groupBy, $startDate, $endDate) + private function generateReport($reportType, $startDate, $endDate, $dateField, $isExport) { if ($reportType == ENTITY_CLIENT) { - $columns = ['client', 'amount', 'paid', 'balance']; + return $this->generateClientReport($startDate, $endDate, $isExport); } elseif ($reportType == ENTITY_INVOICE) { - $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance']; - } else { - $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; + return $this->generateInvoiceReport($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); } + } - $query = DB::table('invoices') - ->join('accounts', 'accounts.id', '=', 'invoices.account_id') - ->join('clients', 'clients.id', '=', 'invoices.client_id') - ->join('contacts', 'contacts.client_id', '=', 'clients.id') - ->where('invoices.account_id', '=', Auth::user()->account_id) - ->where('invoices.is_deleted', '=', false) - ->where('clients.is_deleted', '=', false) - ->where('contacts.deleted_at', '=', null) - ->where('invoices.invoice_date', '>=', $startDate->format('Y-m-d')) - ->where('invoices.invoice_date', '<=', $endDate->format('Y-m-d')) - ->where('invoices.is_quote', '=', false) - ->where('invoices.is_recurring', '=', false) - ->where('contacts.is_primary', '=', true); + private function generateTaxRateReport($startDate, $endDate, $dateField, $isExport) + { + $columns = ['tax_name', 'tax_rate', 'amount', 'paid']; - $select = [ - DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), - 'accounts.country_id', - 'contacts.first_name', - 'contacts.last_name', - 'contacts.email', - 'clients.name as client_name', - 'clients.public_id as client_public_id', - 'invoices.public_id as invoice_public_id' - ]; - - if ($reportType == ENTITY_CLIENT) { - $query->groupBy('clients.id'); - array_push($select, DB::raw('sum(invoices.amount) amount'), DB::raw('sum(invoices.balance) balance'), DB::raw('sum(invoices.amount - invoices.balance) paid')); - } else { - $query->orderBy('invoices.id'); - array_push($select, 'invoices.invoice_number', 'invoices.amount', 'invoices.balance', 'invoices.invoice_date'); - if ($reportType == ENTITY_INVOICE) { - array_push($select, DB::raw('(invoices.amount - invoices.balance) paid')); - } else { - $query->join('payments', 'payments.invoice_id', '=', 'invoices.id') - ->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id') - ->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id') - ->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id'); - array_push($select, 'payments.payment_date', 'payments.amount as paid', 'payment_types.name as payment_type', 'gateways.name as gateway'); - } - } - - $query->select($select); - $data = $query->get(); - - $lastInvoiceId = null; - $sameAsLast = false; + $account = Auth::user()->account; $displayData = []; + $reportTotals = []; - $exportData = []; - $reportTotals = [ - 'amount' => [], - 'balance' => [], - 'paid' => [], - ]; + $clients = Client::scope() + ->withArchived() + ->with('contacts') + ->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) { + $query->withArchived(); + if ($dateField == FILTER_PAYMENT_DATE) { + $query->where('invoice_date', '>=', $startDate) + ->where('invoice_date', '<=', $endDate) + ->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() + ->with('payment_type', 'account_gateway.gateway'); + }, 'invoice_items']); + } + }]); - foreach ($data as $record) { - $sameAsLast = ($lastInvoiceId == $record->invoice_public_id); - $lastInvoiceId = $record->invoice_public_id; + foreach ($clients->get() as $client) { + $currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId(); + $amount = 0; + $paid = 0; + $taxTotals = []; - $displayRow = []; - if ($sameAsLast) { - array_push($displayRow, '', '', '', ''); - } else { - array_push($displayRow, link_to('/clients/'.$record->client_public_id, Utils::getClientDisplayName($record))); - if ($reportType != ENTITY_CLIENT) { - array_push($displayRow, - link_to('/invoices/'.$record->invoice_public_id, $record->invoice_number), - Utils::fromSqlDate($record->invoice_date, true) - ); + 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; + } } - array_push($displayRow, Utils::formatMoney($record->amount, $record->currency_id, $record->country_id)); - } - if ($reportType != ENTITY_PAYMENT) { - array_push($displayRow, Utils::formatMoney($record->paid, $record->currency_id, $record->country_id)); - } - if ($reportType == ENTITY_PAYMENT) { - array_push($displayRow, - Utils::fromSqlDate($record->payment_date, true), - Utils::formatMoney($record->paid, $record->currency_id, $record->country_id), - $record->gateway ?: $record->payment_type - ); - } else { - array_push($displayRow, Utils::formatMoney($record->balance, $record->currency_id, $record->country_id)); + + $amount += $invoice->amount; + $paid += $invoice->getAmountPaid(); } - // export data - $exportRow = []; - if ($sameAsLast) { - $exportRow[trans('texts.client')] = ' '; - $exportRow[trans('texts.invoice_number')] = ' '; - $exportRow[trans('texts.invoice_date')] = ' '; - $exportRow[trans('texts.amount')] = ' '; - } else { - $exportRow[trans('texts.client')] = Utils::getClientDisplayName($record); - if ($reportType != ENTITY_CLIENT) { - $exportRow[trans('texts.invoice_number')] = $record->invoice_number; - $exportRow[trans('texts.invoice_date')] = Utils::fromSqlDate($record->invoice_date, true); + foreach ($taxTotals as $currencyId => $taxes) { + foreach ($taxes as $tax) { + $displayData[] = [ + $tax['name'], + $tax['rate'] . '%', + $account->formatMoney($tax['amount'], $client), + $account->formatMoney($tax['paid'], $client) + ]; } - $exportRow[trans('texts.amount')] = Utils::formatMoney($record->amount, $record->currency_id, $record->country_id); + + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']); + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']); } - if ($reportType != ENTITY_PAYMENT) { - $exportRow[trans('texts.paid')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id); - } - if ($reportType == ENTITY_PAYMENT) { - $exportRow[trans('texts.payment_date')] = Utils::fromSqlDate($record->payment_date, true); - $exportRow[trans('texts.payment_amount')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id); - $exportRow[trans('texts.method')] = $record->gateway ?: $record->payment_type; - } else { - $exportRow[trans('texts.balance')] = Utils::formatMoney($record->balance, $record->currency_id, $record->country_id); - } - - $displayData[] = $displayRow; - $exportData[] = $exportRow; - - $accountCurrencyId = Auth::user()->account->currency_id; - $currencyId = $record->currency_id ? $record->currency_id : ($accountCurrencyId ? $accountCurrencyId : DEFAULT_CURRENCY); - if (!isset($reportTotals['amount'][$currencyId])) { - $reportTotals['amount'][$currencyId] = 0; - $reportTotals['balance'][$currencyId] = 0; - $reportTotals['paid'][$currencyId] = 0; - } - if (!$sameAsLast) { - $reportTotals['amount'][$currencyId] += $record->amount; - $reportTotals['balance'][$currencyId] += $record->balance; - } - $reportTotals['paid'][$currencyId] += $record->paid; } return [ 'columns' => $columns, 'displayData' => $displayData, 'reportTotals' => $reportTotals, - 'exportData' => $exportData + ]; + + } + + 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() + ->withTrashed() + ->where('is_deleted', '=', false) + ->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->amount, $client), + $payment->present()->method, + ]; + + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount); + } + + return [ + 'columns' => $columns, + 'displayData' => $displayData, + 'reportTotals' => $reportTotals, ]; } - private function export($data, $totals) + 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->where('invoice_date', '>=', $startDate) + ->where('invoice_date', '<=', $endDate) + ->where('is_deleted', '=', false) + ->where('is_quote', '=', false) + ->where('is_recurring', '=', false) + ->with(['payments' => function($query) { + $query->withTrashed() + ->with('payment_type', 'account_gateway.gateway') + ->where('is_deleted', '=', false); + }, '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->amount, $client) : '', + $payment ? $payment->present()->method : '', + ]; + if ($payment) { + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount); + } + } + + $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, + ]; + } + + 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('is_quote', '=', false) + ->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, + ]; + } + + private function generateExpenseReport($startDate, $endDate, $isExport) + { + $columns = ['vendor', 'client', 'date', 'expense_amount', 'invoiced_amount']; + + $account = Auth::user()->account; + $displayData = []; + $reportTotals = []; + + $expenses = Expense::scope() + ->withTrashed() + ->with('client.contacts', 'vendor') + ->where('expense_date', '>=', $startDate) + ->where('expense_date', '<=', $endDate); + + + foreach ($expenses->get() as $expense) { + $amount = $expense->amount; + $invoiced = $expense->present()->invoiced_amount; + + $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), + Utils::formatMoney($invoiced, $expense->invoice_currency_id), + ]; + + $reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount); + $reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0); + + $reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'invoiced', $invoiced); + $reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'invoiced', 0); + } + + return [ + 'columns' => $columns, + 'displayData' => $displayData, + 'reportTotals' => $reportTotals, + ]; + } + + 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; + } + + private function export($data, $columns, $totals) { $output = fopen('php://output', 'w') or Utils::fatalError(); header('Content-Type:application/csv'); header('Content-Disposition:attachment;filename=ninja-report.csv'); - Utils::exportData($output, $data); + Utils::exportData($output, $data, Utils::trans($columns)); + + fwrite($output, trans('texts.totals')); + foreach ($totals as $currencyId => $fields) { + foreach ($fields as $key => $value) { + fwrite($output, ',' . trans("texts.{$key}")); + } + fwrite($output, "\n"); + break; + } - foreach (['amount', 'paid', 'balance'] as $type) { - $csv = trans("texts.{$type}").','; - foreach ($totals[$type] as $currencyId => $amount) { - $csv .= Utils::formatMoney($amount, $currencyId).','; + foreach ($totals as $currencyId => $fields) { + $csv = Utils::getFromCache($currencyId, 'currencies')->name . ','; + foreach ($fields as $key => $value) { + $csv .= '"' . Utils::formatMoney($value, $currencyId).'",'; } fwrite($output, $csv."\n"); } diff --git a/app/Http/Controllers/TaskApiController.php b/app/Http/Controllers/TaskApiController.php index a302944d2b62..926ed2f98ad3 100644 --- a/app/Http/Controllers/TaskApiController.php +++ b/app/Http/Controllers/TaskApiController.php @@ -49,7 +49,7 @@ class TaskApiController extends BaseAPIController $tasks->whereHas('client', $filter); $paginator->whereHas('client', $filter); } - + $tasks = $tasks->orderBy('created_at', 'desc')->paginate(); $paginator = $paginator->paginate(); $transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer')); @@ -84,11 +84,11 @@ class TaskApiController extends BaseAPIController { $data = Input::all(); $taskId = isset($data['id']) ? $data['id'] : false; - + if (isset($data['client_id']) && $data['client_id']) { $data['client'] = $data['client_id']; } - + $task = $this->taskRepo->save($taskId, $data); $task = Task::scope($task->public_id)->with('client')->first(); diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 735689064a41..59f49da2514f 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -22,10 +22,11 @@ class TaskController extends BaseController { protected $taskRepo; protected $taskService; + protected $model = 'App\Models\Task'; public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService) { - parent::__construct(); + // parent::__construct(); $this->taskRepo = $taskRepo; $this->invoiceRepo = $invoiceRepo; @@ -84,6 +85,9 @@ class TaskController extends BaseController */ public function create($clientPublicId = 0) { + if(!$this->checkCreatePermission($response)){ + return $response; + } $this->checkTimezone(); $data = [ @@ -113,6 +117,10 @@ class TaskController extends BaseController $task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail(); + if(!$this->checkEditPermission($task, $response)){ + return $response; + } + $actions = []; if ($task->invoice) { $actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; @@ -175,6 +183,10 @@ class TaskController extends BaseController private function save($publicId = null) { $action = Input::get('action'); + + if(!$this->checkUpdatePermission(array('public_id'=>$publicId)/* Hacky, but works */, $response)){ + return $response; + } if (in_array($action, ['archive', 'delete', 'restore'])) { return self::bulk(); diff --git a/app/Http/Controllers/TaxRateController.php b/app/Http/Controllers/TaxRateController.php index acda2602a4d0..223f7491092e 100644 --- a/app/Http/Controllers/TaxRateController.php +++ b/app/Http/Controllers/TaxRateController.php @@ -25,7 +25,7 @@ class TaxRateController extends BaseController public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo) { - parent::__construct(); + //parent::__construct(); $this->taxRateService = $taxRateService; $this->taxRateRepo = $taxRateRepo; @@ -76,9 +76,9 @@ class TaxRateController extends BaseController public function update(UpdateTaxRateRequest $request, $publicId) { $taxRate = TaxRate::scope($publicId)->firstOrFail(); - + $this->taxRateRepo->save($request->input(), $taxRate); - + Session::flash('message', trans('texts.updated_tax_rate')); return Redirect::to('settings/' . ACCOUNT_TAX_RATES); } diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php index 604b94d576b3..8e255d6057be 100644 --- a/app/Http/Controllers/TokenController.php +++ b/app/Http/Controllers/TokenController.php @@ -20,7 +20,7 @@ class TokenController extends BaseController public function __construct(TokenService $tokenService) { - parent::__construct(); + //parent::__construct(); $this->tokenService = $tokenService; } diff --git a/app/Http/Controllers/UserApiController.php b/app/Http/Controllers/UserApiController.php index ee525c1dd9ae..fcd48787b134 100644 --- a/app/Http/Controllers/UserApiController.php +++ b/app/Http/Controllers/UserApiController.php @@ -42,7 +42,7 @@ class UserApiController extends BaseAPIController return $this->save($request); } */ - + public function update(UpdateUserRequest $request, $userPublicId) { /* diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 45549a1030dc..2e3f675aa53e 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -11,9 +11,9 @@ use Request; use Redirect; use Session; use URL; +use Password; use Utils; use Validator; -use Illuminate\Auth\Passwords\TokenRepositoryInterface; use App\Models\User; use App\Http\Requests; use App\Ninja\Repositories\AccountRepository; @@ -30,7 +30,7 @@ class UserController extends BaseController public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer, UserService $userService) { - parent::__construct(); + //parent::__construct(); $this->accountRepo = $accountRepo; $this->contactMailer = $contactMailer; @@ -77,7 +77,6 @@ class UserController extends BaseController 'user' => $user, 'method' => 'PUT', 'url' => 'users/'.$publicId, - 'title' => trans('texts.edit_user'), ]; return View::make('users.edit', $data); @@ -120,7 +119,6 @@ class UserController extends BaseController 'user' => null, 'method' => 'POST', 'url' => 'users', - 'title' => trans('texts.add_user'), ]; return View::make('users.edit', $data); @@ -130,7 +128,7 @@ class UserController extends BaseController { $action = Input::get('bulk_action'); $id = Input::get('bulk_public_id'); - + $user = User::where('account_id', '=', Auth::user()->account_id) ->where('public_id', '=', $id) ->withTrashed() @@ -192,6 +190,8 @@ class UserController extends BaseController $user->last_name = trim(Input::get('last_name')); $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); + $user->is_admin = boolval(Input::get('is_admin')); + $user->permissions = Input::get('permissions'); } else { $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id) ->orderBy('public_id', 'DESC')->first(); @@ -202,10 +202,12 @@ class UserController extends BaseController $user->last_name = trim(Input::get('last_name')); $user->username = trim(Input::get('email')); $user->email = trim(Input::get('email')); + $user->is_admin = boolval(Input::get('is_admin')); $user->registered = true; $user->password = str_random(RANDOM_KEY_LENGTH); $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); $user->public_id = $lastUser->public_id + 1; + $user->permissions = Input::get('permissions'); } $user->save(); @@ -219,7 +221,7 @@ class UserController extends BaseController Session::flash('message', $message); } - + return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT); } @@ -240,10 +242,10 @@ class UserController extends BaseController * * @param string $code */ - public function confirm($code, TokenRepositoryInterface $tokenRepo) + public function confirm($code) { $user = User::where('confirmation_code', '=', $code)->get()->first(); - + if ($user) { $notice_msg = trans('texts.security.confirmation'); @@ -253,7 +255,7 @@ class UserController extends BaseController if ($user->public_id) { //Auth::login($user); - $token = $tokenRepo->create($user); + $token = Password::getRepository()->create($user); return Redirect::to("/password/reset/{$token}"); } else { @@ -294,7 +296,7 @@ class UserController extends BaseController return Redirect::to('/')->with('clearGuestKey', true); } */ - + public function changePassword() { // check the current password is correct @@ -326,7 +328,7 @@ class UserController extends BaseController $oldUserId = Auth::user()->id; $referer = Request::header('referer'); $account = $this->accountRepo->findUserAccounts($newUserId, $oldUserId); - + if ($account) { if ($account->hasUserId($newUserId) && $account->hasUserId($oldUserId)) { Auth::loginUsingId($newUserId); @@ -337,7 +339,7 @@ class UserController extends BaseController Session::put('_token', str_random(40)); } } - + return Redirect::to($referer); } diff --git a/app/Http/Controllers/VendorApiController.php b/app/Http/Controllers/VendorApiController.php index e2cec175bada..4c32ee1eb3c5 100644 --- a/app/Http/Controllers/VendorApiController.php +++ b/app/Http/Controllers/VendorApiController.php @@ -83,7 +83,7 @@ class VendorApiController extends BaseAPIController public function store(CreateVendorRequest $request) { $vendor = $this->vendorRepo->save($request->input()); - + $vendor = Vendor::scope($vendor->public_id) ->with('country', 'vendorcontacts', 'industry', 'size', 'currency') ->first(); diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index 6a9bae5049dc..989246f824ca 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -30,10 +30,11 @@ class VendorController extends BaseController { protected $vendorService; protected $vendorRepo; + protected $model = 'App\Models\Vendor'; public function __construct(VendorRepository $vendorRepo, VendorService $vendorService) { - parent::__construct(); + //parent::__construct(); $this->vendorRepo = $vendorRepo; $this->vendorService = $vendorService; @@ -76,7 +77,13 @@ class VendorController extends BaseController */ public function store(CreateVendorRequest $request) { - $vendor = $this->vendorService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $vendor = $this->vendorService->save($data); Session::flash('message', trans('texts.created_vendor')); @@ -92,6 +99,11 @@ class VendorController extends BaseController public function show($publicId) { $vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail(); + + if(!$this->checkViewPermission($vendor, $response)){ + return $response; + } + Utils::trackViewed($vendor->getDisplayName(), 'vendor'); $actionLinks = [ @@ -119,6 +131,10 @@ class VendorController extends BaseController */ public function create() { + if(!$this->checkCreatePermission($response)){ + return $response; + } + if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) { return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]); } @@ -144,6 +160,11 @@ class VendorController extends BaseController public function edit($publicId) { $vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail(); + + if(!$this->checkEditPermission($vendor, $response)){ + return $response; + } + $data = [ 'vendor' => $vendor, 'method' => 'PUT', @@ -180,7 +201,13 @@ class VendorController extends BaseController */ public function update(UpdateVendorRequest $request) { - $vendor = $this->vendorService->save($request->input()); + $data = $request->input(); + + if(!$this->checkUpdatePermission($data, $response)){ + return $response; + } + + $vendor = $this->vendorService->save($data); Session::flash('message', trans('texts.updated_vendor')); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 72ffce9c8bf3..1142338b200e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -28,6 +28,7 @@ class Kernel extends HttpKernel { protected $routeMiddleware = [ 'auth' => 'App\Http\Middleware\Authenticate', 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', + 'permissions.required' => 'App\Http\Middleware\PermissionsRequired', 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 'api' => 'App\Http\Middleware\ApiCheck', ]; diff --git a/app/Http/Middleware/ApiCheck.php b/app/Http/Middleware/ApiCheck.php index 5632e7de4e90..63e370cf4192 100644 --- a/app/Http/Middleware/ApiCheck.php +++ b/app/Http/Middleware/ApiCheck.php @@ -21,11 +21,15 @@ class ApiCheck { */ public function handle($request, Closure $next) { - $loggingIn = $request->is('api/v1/login'); + $loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register'); $headers = Utils::getApiHeaders(); if ($loggingIn) { - // do nothing + // check API secret + if ( ! $request->api_secret || ! env(API_SECRET) || ! hash_equals($request->api_secret, env(API_SECRET))) { + sleep(ERROR_DELAY); + return Response::json('Invalid secret', 403, $headers); + } } else { // check for a valid token $token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']); @@ -34,7 +38,7 @@ class ApiCheck { Auth::loginUsingId($token->user_id); Session::set('token_id', $token->id); } else { - sleep(3); + sleep(ERROR_DELAY); return Response::json('Invalid token', 403, $headers); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 1780b299f005..a6d1363e4cdd 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -1,28 +1,13 @@ auth = $auth; - } - /** * Handle an incoming request. * @@ -30,9 +15,46 @@ class Authenticate { * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle($request, Closure $next, $guard = 'user') { - if ($this->auth->guest()) + $authenticated = Auth::guard($guard)->check(); + + if($guard == 'client' && !empty($request->invitation_key)){ + $old_key = session('invitation_key'); + if($old_key && $old_key != $request->invitation_key){ + if($this->getInvitationContactId($old_key) != $this->getInvitationContactId($request->invitation_key)){ + // This is a different client; reauthenticate + $authenticated = false; + Auth::guard($guard)->logout(); + } + } + Session::put('invitation_key', $request->invitation_key); + } + + if($guard=='client'){ + $invitation_key = session('invitation_key'); + $account_id = $this->getInvitationAccountId($invitation_key); + + if(Auth::guard('user')->check() && Auth::user('user')->account_id === $account_id){ + // This is an admin; let them pretend to be a client + $authenticated = true; + } + + // Does this account require portal passwords? + $account = Account::whereId($account_id)->first(); + if(!$account->enable_portal_password || !$account->isPro()){ + $authenticated = true; + } + + if(!$authenticated){ + $contact = Contact::whereId($this->getInvitationContactId($invitation_key))->first(); + if($contact && !$contact->password){ + $authenticated = true; + } + } + } + + if (!$authenticated) { if ($request->ajax()) { @@ -40,11 +62,30 @@ class Authenticate { } else { - return redirect()->guest('/login'); + return redirect()->guest($guard=='client'?'/client/login':'/login'); } } return $next($request); } - + + protected function getInvitation($key){ + $invitation = Invitation::withTrashed()->where('invitation_key', '=', $key)->first(); + if ($invitation && !$invitation->is_deleted) { + return $invitation; + } + else return null; + } + + protected function getInvitationContactId($key){ + $invitation = $this->getInvitation($key); + + return $invitation?$invitation->contact_id:null; + } + + protected function getInvitationAccountId($key){ + $invitation = $this->getInvitation($key); + + return $invitation?$invitation->account_id:null; + } } diff --git a/app/Http/Middleware/PermissionsRequired.php b/app/Http/Middleware/PermissionsRequired.php new file mode 100644 index 000000000000..af0e0015a37f --- /dev/null +++ b/app/Http/Middleware/PermissionsRequired.php @@ -0,0 +1,57 @@ + [action => permission] + */ + static protected $actions = []; + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next, $guard = 'user') + { + // Get the current route. + $route = $request->route(); + + // Get the current route actions. + $actions = $route->getAction(); + + // Check if we have any permissions to check the user has. + if ($permissions = !empty($actions['permissions']) ? $actions['permissions'] : null) + { + if(!Auth::user($guard)->hasPermission($permissions, !empty($actions['permissions_require_all']))){ + return response('Unauthorized.', 401); + } + } + + // Check controller permissions + $action = explode('@', $request->route()->getActionName()); + if(isset(static::$actions[$action[0]]) && isset(static::$actions[$action[0]][$action[1]])) { + $controller_permissions = static::$actions[$action[0]][$action[1]]; + if(!Auth::user($guard)->hasPermission($controller_permissions)){ + return response('Unauthorized.', 401); + } + } + + return $next($request); + } + + /** + * add a controller's action permission + * + * @param \App\Http\Controllers\Controller $controller + * @param array $permissions + */ + public static function addPermission(\App\Http\Controllers\Controller $controller, $permissions) + { + static::$actions[get_class($controller)] = $permissions; + } +} 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/app/Http/Requests/CreateExpenseRequest.php b/app/Http/Requests/CreateExpenseRequest.php index 78f6eeee77ed..85a93eb1974d 100644 --- a/app/Http/Requests/CreateExpenseRequest.php +++ b/app/Http/Requests/CreateExpenseRequest.php @@ -24,7 +24,7 @@ class CreateExpenseRequest extends Request public function rules() { return [ - 'amount' => 'positive', + 'amount' => 'numeric', ]; } } diff --git a/app/Http/Requests/RegisterRequest.php b/app/Http/Requests/RegisterRequest.php new file mode 100644 index 000000000000..91a27556924c --- /dev/null +++ b/app/Http/Requests/RegisterRequest.php @@ -0,0 +1,67 @@ +req = $req; + } + + + public function authorize() + { + return true; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + + $rules = [ + 'email' => 'required|unique:users', + 'first_name' => 'required', + 'last_name' => 'required', + 'password' => 'required', + ]; + + return $rules; + } + + public function response(array $errors) + { + /* If the user is not validating from a mobile app - pass through parent::response */ + if(!isset($this->req->api_secret)) + return parent::response($errors); + + /* If the user is validating from a mobile app - pass through first error string and return error */ + foreach($errors as $error) { + foreach ($error as $key => $value) { + + $message['error'] = ['message'=>$value]; + $message = json_encode($message, JSON_PRETTY_PRINT); + $headers = Utils::getApiHeaders(); + + return Response::make($message, 400, $headers); + } + } + } + +} diff --git a/app/Http/Requests/UpdateExpenseRequest.php b/app/Http/Requests/UpdateExpenseRequest.php index 7b67ca89230e..a4d3855deaec 100644 --- a/app/Http/Requests/UpdateExpenseRequest.php +++ b/app/Http/Requests/UpdateExpenseRequest.php @@ -24,7 +24,7 @@ class UpdateExpenseRequest extends Request public function rules() { return [ - 'amount' => 'positive', + 'amount' => 'numeric', 'expense_date' => 'required', ]; } diff --git a/app/Http/routes.php b/app/Http/routes.php index 55ca02d103cf..4a773bc6766a 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -35,17 +35,20 @@ Route::get('/keep_alive', 'HomeController@keepAlive'); Route::post('/get_started', 'AccountController@getStarted'); // Client visible pages -Route::get('view/{invitation_key}', 'PublicClientController@view'); -Route::get('download/{invitation_key}', 'PublicClientController@download'); -Route::get('view', 'HomeController@viewLogo'); -Route::get('approve/{invitation_key}', 'QuoteController@approve'); -Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment'); -Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); -Route::get('complete', 'PaymentController@offsite_payment'); -Route::get('client/quotes', 'PublicClientController@quoteIndex'); -Route::get('client/invoices', 'PublicClientController@invoiceIndex'); -Route::get('client/payments', 'PublicClientController@paymentIndex'); -Route::get('client/dashboard', 'PublicClientController@dashboard'); +Route::group(['middleware' => 'auth:client'], function() { + Route::get('view/{invitation_key}', 'PublicClientController@view'); + Route::get('download/{invitation_key}', 'PublicClientController@download'); + Route::get('view', 'HomeController@viewLogo'); + Route::get('approve/{invitation_key}', 'QuoteController@approve'); + Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment'); + Route::post('payment/{invitation_key}', 'PaymentController@do_payment'); + Route::get('complete', 'PaymentController@offsite_payment'); + Route::get('client/quotes', 'PublicClientController@quoteIndex'); + Route::get('client/invoices', 'PublicClientController@invoiceIndex'); + Route::get('client/payments', 'PublicClientController@paymentIndex'); + Route::get('client/dashboard', 'PublicClientController@dashboard'); +}); + Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable')); Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable')); @@ -65,16 +68,25 @@ Route::post('/hook/email_bounced', 'AppController@emailBounced'); Route::post('/hook/email_opened', 'AppController@emailOpened'); // Laravel auth routes -get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister')); -post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister')); -get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper')); -post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper')); -get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); -get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); -post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); -get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); -post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); -get('/user/confirm/{code}', 'UserController@confirm'); +Route::get('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@getRegister')); +Route::post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@postRegister')); +Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper')); +Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper')); +Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper')); +Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); +Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); +Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); +Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); +Route::get('/user/confirm/{code}', 'UserController@confirm'); + +// Client auth +Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin')); +Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin')); +Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout')); +Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail')); +Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail')); +Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset')); +Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset')); if (Utils::isNinja()) { @@ -87,12 +99,80 @@ if (Utils::isReseller()) { Route::post('/reseller_stats', 'AppController@stats'); } -Route::group(['middleware' => 'auth'], function() { +Route::group(['middleware' => 'auth:user'], function() { Route::get('dashboard', 'DashboardController@index'); Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); + + Route::get('settings/user_details', 'AccountController@showUserDetails'); + Route::post('settings/user_details', 'AccountController@saveUserDetails'); + Route::resource('clients', 'ClientController'); + Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); + Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); + Route::post('clients/bulk', 'ClientController@bulk'); + + Route::resource('tasks', 'TaskController'); + Route::get('api/tasks/{client_id?}', array('as'=>'api.tasks', 'uses'=>'TaskController@getDatatable')); + Route::get('tasks/create/{client_id?}', 'TaskController@create'); + Route::post('tasks/bulk', 'TaskController@bulk'); + + Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable')); + + Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory'); + Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory'); + + Route::resource('invoices', 'InvoiceController'); + Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable')); + Route::get('invoices/create/{client_id?}', 'InvoiceController@create'); + Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring'); + Route::get('recurring_invoices', 'RecurringInvoiceController@index'); + Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice'); + Route::post('invoices/bulk', 'InvoiceController@bulk'); + Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); + + Route::get('quotes/create/{client_id?}', 'QuoteController@create'); + Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice'); + Route::get('quotes/{public_id}/edit', 'InvoiceController@edit'); + Route::put('quotes/{public_id}', 'InvoiceController@update'); + Route::get('quotes/{public_id}', 'InvoiceController@edit'); + Route::post('quotes', 'InvoiceController@store'); + Route::get('quotes', 'QuoteController@index'); + Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable')); + Route::post('quotes/bulk', 'QuoteController@bulk'); + + Route::resource('payments', 'PaymentController'); + Route::get('payments/create/{client_id?}/{invoice_id?}', 'PaymentController@create'); + Route::get('api/payments/{client_id?}', array('as'=>'api.payments', 'uses'=>'PaymentController@getDatatable')); + Route::post('payments/bulk', 'PaymentController@bulk'); + + Route::resource('credits', 'CreditController'); + Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create'); + Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable')); + Route::post('credits/bulk', 'CreditController@bulk'); + + Route::get('/resend_confirmation', 'AccountController@resendConfirmation'); + Route::post('/update_setup', 'AppController@updateSetup'); + + + // vendor + Route::resource('vendors', 'VendorController'); + Route::get('api/vendor', array('as'=>'api.vendors', 'uses'=>'VendorController@getDatatable')); + Route::post('vendors/bulk', 'VendorController@bulk'); + + // Expense + Route::resource('expenses', 'ExpenseController'); + Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create'); + Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable')); + Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor')); + Route::post('expenses/bulk', 'ExpenseController@bulk'); +}); + +Route::group([ + 'middleware' => ['auth:user', 'permissions.required'], + 'permissions' => 'admin', +], function() { Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); Route::resource('users', 'UserController'); Route::post('users/bulk', 'UserController@bulk'); @@ -148,66 +228,6 @@ Route::group(['middleware' => 'auth'], function() { Route::post('bank_accounts/bulk', 'BankAccountController@bulk'); Route::post('bank_accounts/validate', 'BankAccountController@validateAccount'); Route::post('bank_accounts/import_expenses/{bank_id}', 'BankAccountController@importExpenses'); - - Route::resource('clients', 'ClientController'); - Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable')); - Route::get('api/activities/{client_id?}', array('as'=>'api.activities', 'uses'=>'ActivityController@getDatatable')); - Route::post('clients/bulk', 'ClientController@bulk'); - - Route::resource('tasks', 'TaskController'); - Route::get('api/tasks/{client_id?}', array('as'=>'api.tasks', 'uses'=>'TaskController@getDatatable')); - Route::get('tasks/create/{client_id?}', 'TaskController@create'); - Route::post('tasks/bulk', 'TaskController@bulk'); - - Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable')); - - Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory'); - Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory'); - - Route::resource('invoices', 'InvoiceController'); - Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable')); - Route::get('invoices/create/{client_id?}', 'InvoiceController@create'); - Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring'); - Route::get('recurring_invoices', 'RecurringInvoiceController@index'); - Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice'); - Route::post('invoices/bulk', 'InvoiceController@bulk'); - Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); - - Route::get('quotes/create/{client_id?}', 'QuoteController@create'); - Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice'); - Route::get('quotes/{public_id}/edit', 'InvoiceController@edit'); - Route::put('quotes/{public_id}', 'InvoiceController@update'); - Route::get('quotes/{public_id}', 'InvoiceController@edit'); - Route::post('quotes', 'InvoiceController@store'); - Route::get('quotes', 'QuoteController@index'); - Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable')); - Route::post('quotes/bulk', 'QuoteController@bulk'); - - Route::resource('payments', 'PaymentController'); - Route::get('payments/create/{client_id?}/{invoice_id?}', 'PaymentController@create'); - Route::get('api/payments/{client_id?}', array('as'=>'api.payments', 'uses'=>'PaymentController@getDatatable')); - Route::post('payments/bulk', 'PaymentController@bulk'); - - Route::resource('credits', 'CreditController'); - Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create'); - Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable')); - Route::post('credits/bulk', 'CreditController@bulk'); - - get('/resend_confirmation', 'AccountController@resendConfirmation'); - post('/update_setup', 'AppController@updateSetup'); - - - // vendor - Route::resource('vendors', 'VendorController'); - Route::get('api/vendor', array('as'=>'api.vendors', 'uses'=>'VendorController@getDatatable')); - Route::post('vendors/bulk', 'VendorController@bulk'); - - // Expense - Route::resource('expenses', 'ExpenseController'); - Route::get('expenses/create/{vendor_id?}/{client_id?}', 'ExpenseController@create'); - Route::get('api/expense', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable')); - Route::get('api/expenseVendor/{id}', array('as'=>'api.expense', 'uses'=>'ExpenseController@getDatatableVendor')); - Route::post('expenses/bulk', 'ExpenseController@bulk'); }); // Route groups for API @@ -215,6 +235,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() { Route::get('ping', 'ClientApiController@ping'); Route::post('login', 'AccountApiController@login'); + Route::post('register', 'AccountApiController@register'); Route::get('static', 'AccountApiController@getStaticData'); Route::get('accounts', 'AccountApiController@show'); Route::put('accounts', 'AccountApiController@update'); @@ -234,6 +255,9 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::resource('tax_rates', 'TaxRateApiController'); Route::resource('users', 'UserApiController'); Route::resource('expenses','ExpenseApiController'); + Route::post('add_token', 'AccountApiController@addDeviceToken'); + Route::post('update_notifications', 'AccountApiController@updatePushNotifications'); + Route::get('dashboard', 'DashboardApiController@index'); // Vendor Route::resource('vendors', 'VendorApiController'); @@ -485,6 +509,8 @@ if (!defined('CONTACT_EMAIL')) { 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); @@ -509,7 +535,7 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com'); - define('NINJA_VERSION', '2.5.0.4'); + define('NINJA_VERSION', '2.5.1'); define('NINJA_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); @@ -527,6 +553,8 @@ if (!defined('CONTACT_EMAIL')) { define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'); define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'); + 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); @@ -549,6 +577,9 @@ if (!defined('CONTACT_EMAIL')) { define('TEST_PASSWORD', 'password'); define('API_SECRET', 'API_SECRET'); + define('IOS_PRODUCTION_PUSH','ninjaIOS'); + define('IOS_DEV_PUSH','devNinjaIOS'); + define('TOKEN_BILLING_DISABLED', 1); define('TOKEN_BILLING_OPT_IN', 2); define('TOKEN_BILLING_OPT_OUT', 3); @@ -572,6 +603,9 @@ if (!defined('CONTACT_EMAIL')) { 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'); @@ -581,6 +615,7 @@ if (!defined('CONTACT_EMAIL')) { 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'); @@ -668,8 +703,9 @@ if (Utils::isNinjaDev()) { */ /* -if (Utils::isNinjaDev() && Auth::check() && Auth::user()->id === 1) +if (Utils::isNinjaDev()) { - Auth::loginUsingId(1); + //ini_set('memory_limit','1024M'); + //Auth::loginUsingId(1); } -*/ +*/ \ No newline at end of file diff --git a/app/Libraries/OFX.php b/app/Libraries/OFX.php index a696aeee4cd9..5563e917acf7 100644 --- a/app/Libraries/OFX.php +++ b/app/Libraries/OFX.php @@ -104,8 +104,8 @@ class Login "".$this->bank->org."\n". "".$this->bank->fid."\n". "\n". - "QMOFX\n". - "1900\n". + "QWIN\n". + "2500\n". "\n". "\n". "\n". @@ -171,8 +171,8 @@ class Account "".$this->login->bank->org."\n". "".$this->login->bank->fid."\n". "\n". - "QMOFX\n". - "1900\n". + "QWIN\n". + "2500\n". "\n". "\n"; if ($this->type == 'BANK') { diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index d0b17df7f021..439247f337c0 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; } @@ -118,6 +118,21 @@ class Utils return Auth::check() && Auth::user()->isPro(); } + public static function isAdmin() + { + return Auth::check() && Auth::user()->is_admin; + } + + public static function hasPermission($permission, $requireAll = false) + { + return Auth::check() && Auth::user()->hasPermission($permission, $requireAll); + } + + public static function hasAllPermissions($permission) + { + return Auth::check() && Auth::user()->hasPermissions($permission); + } + public static function isTrial() { return Auth::check() && Auth::user()->isTrial(); @@ -127,6 +142,13 @@ class Utils { return App::getLocale() == 'en'; } + + public static function getLocaleRegion() + { + $parts = explode('_', App::getLocale()); + + return count($parts) ? $parts[0] : 'en'; + } public static function getUserType() { @@ -225,7 +247,7 @@ class Utils return "***{$class}*** [{$code}] : {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}"; } - public static function logError($error, $context = 'PHP') + public static function logError($error, $context = 'PHP', $info = false) { if ($error instanceof Exception) { $error = self::getErrorString($error); @@ -249,7 +271,11 @@ class Utils 'count' => Session::get('error_count', 0), ]; - Log::error($error."\n", $data); + if ($info) { + Log::info($error."\n", $data); + } else { + Log::error($error."\n", $data); + } /* Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message) @@ -598,8 +624,8 @@ class Utils private static function getMonth($offset) { - $months = [ "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December", ]; + $months = [ "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", ]; $month = intval(date('n')) - 1; @@ -610,7 +636,7 @@ class Utils $month += 12; } - return $months[$month]; + return trans('texts.' . $months[$month]); } private static function getQuarter($offset) @@ -774,9 +800,11 @@ class Utils return $str; } - public static function exportData($output, $data) + public static function exportData($output, $data, $headers = false) { - if (count($data) > 0) { + if ($headers) { + fputcsv($output, $headers); + } elseif (count($data) > 0) { fputcsv($output, array_keys($data[0])); } diff --git a/app/Listeners/NotificationListener.php b/app/Listeners/NotificationListener.php index aba304457528..29a190eeb712 100644 --- a/app/Listeners/NotificationListener.php +++ b/app/Listeners/NotificationListener.php @@ -9,16 +9,20 @@ use App\Events\InvoiceInvitationWasViewed; use App\Events\QuoteInvitationWasViewed; use App\Events\QuoteInvitationWasApproved; use App\Events\PaymentWasCreated; +use App\Ninja\Notifications; +use App\Services\PushService; class NotificationListener { protected $userMailer; protected $contactMailer; + protected $pushService; - public function __construct(UserMailer $userMailer, ContactMailer $contactMailer) + public function __construct(UserMailer $userMailer, ContactMailer $contactMailer, PushService $pushService) { $this->userMailer = $userMailer; $this->contactMailer = $contactMailer; + $this->pushService = $pushService; } private function sendEmails($invoice, $type, $payment = null) @@ -35,26 +39,31 @@ class NotificationListener public function emailedInvoice(InvoiceWasEmailed $event) { $this->sendEmails($event->invoice, 'sent'); + $this->pushService->sendNotification($event->invoice, 'sent'); } public function emailedQuote(QuoteWasEmailed $event) { $this->sendEmails($event->quote, 'sent'); + $this->pushService->sendNotification($event->quote, 'sent'); } public function viewedInvoice(InvoiceInvitationWasViewed $event) { $this->sendEmails($event->invoice, 'viewed'); + $this->pushService->sendNotification($event->invoice, 'viewed'); } public function viewedQuote(QuoteInvitationWasViewed $event) { $this->sendEmails($event->quote, 'viewed'); + $this->pushService->sendNotification($event->quote, 'viewed'); } public function approvedQuote(QuoteInvitationWasApproved $event) { $this->sendEmails($event->quote, 'approved'); + $this->pushService->sendNotification($event->quote, 'approved'); } public function createdPayment(PaymentWasCreated $event) @@ -66,6 +75,8 @@ class NotificationListener $this->contactMailer->sendPaymentConfirmation($event->payment); $this->sendEmails($event->payment->invoice, 'paid', $event->payment); + + $this->pushService->sendNotification($event->payment->invoice, 'paid'); } } \ No newline at end of file diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php index c3f1d9d80339..fd8c39dba303 100644 --- a/app/Listeners/SubscriptionListener.php +++ b/app/Listeners/SubscriptionListener.php @@ -24,66 +24,46 @@ class SubscriptionListener { public function createdClient(ClientWasCreated $event) { - if ( ! Auth::check()) { - return; - } - - $transformer = new ClientTransformer(Auth::user()->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CLIENT, $event->client, $transformer); + $transformer = new ClientTransformer($event->client->account); + $this->checkSubscriptions(EVENT_CREATE_CLIENT, $event->client, $transformer); } public function createdQuote(QuoteWasCreated $event) { - if ( ! Auth::check()) { - return; - } - - $transformer = new InvoiceTransformer(Auth::user()->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); + $transformer = new InvoiceTransformer($event->quote->account); + $this->checkSubscriptions(EVENT_CREATE_QUOTE, $event->quote, $transformer, ENTITY_CLIENT); } public function createdPayment(PaymentWasCreated $event) { - if ( ! Auth::check()) { - return; - } - - $transformer = new PaymentTransformer(Auth::user()->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]); - } - - public function createdCredit(CreditWasCreated $event) - { - if ( ! Auth::check()) { - return; - } - - //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_CREDIT, $event->credit); + $transformer = new PaymentTransformer($event->payment->account); + $this->checkSubscriptions(EVENT_CREATE_PAYMENT, $event->payment, $transformer, [ENTITY_CLIENT, ENTITY_INVOICE]); } public function createdInvoice(InvoiceWasCreated $event) { - if ( ! Auth::check()) { - return; - } + $transformer = new InvoiceTransformer($event->invoice->account); + $this->checkSubscriptions(EVENT_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); + } - $transformer = new InvoiceTransformer(Auth::user()->account); - $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_INVOICE, $event->invoice, $transformer, ENTITY_CLIENT); + public function createdCredit(CreditWasCreated $event) + { + } public function createdVendor(VendorWasCreated $event) { - //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor); + } public function createdExpense(ExpenseWasCreated $event) { - //$this->checkSubscriptions(ACTIVITY_TYPE_CREATE_EXPENSE, $event->expense); + } - private function checkSubscriptions($activityTypeId, $entity, $transformer, $include = '') + private function checkSubscriptions($eventId, $entity, $transformer, $include = '') { - $subscription = $entity->account->getSubscription($activityTypeId); + $subscription = $entity->account->getSubscription($eventId); if ($subscription) { $manager = new Manager(); @@ -93,7 +73,12 @@ class SubscriptionListener $resource = new Item($entity, $transformer, $entity->getEntityType()); $data = $manager->createData($resource)->toArray(); + // For legacy Zapier support + if (isset($data['client_id'])) { + $data['client_name'] = $entity->client->getDisplayName(); + } + Utils::notifyZapier($subscription, $data); } } -} \ No newline at end of file +} diff --git a/app/Models/Account.php b/app/Models/Account.php index d3f64bb2eec0..f6a7194bdf6f 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -439,7 +439,7 @@ class Account extends Eloquent return $height; } - public function createInvoice($entityType, $clientId = null) + public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null) { $invoice = Invoice::createNew(); @@ -614,6 +614,28 @@ class Account extends Eloquent $this->save(); } + public function loadAllData($updatedAt = null) + { + $map = [ + 'users' => [], + 'clients' => ['contacts'], + 'invoices' => ['invoice_items', 'user', 'client', 'payments'], + 'products' => [], + 'tax_rates' => [], + 'expenses' => ['client', 'invoice', 'vendor'], + 'payments' => ['invoice'], + ]; + + foreach ($map as $key => $values) { + $this->load([$key => function($query) use ($values, $updatedAt) { + $query->withTrashed()->with($values); + if ($updatedAt) { + $query->where('updated_at', '>=', $updatedAt); + } + }]); + } + } + public function loadLocalizationSettings($client = false) { $this->load('timezone', 'date_format', 'datetime_format', 'language'); @@ -661,7 +683,7 @@ class Account extends Eloquent 'subtotal', 'paid_to_date', 'balance_due', - 'amount_due', + 'partial_due', 'terms', 'your_invoice', 'quote', @@ -1001,7 +1023,7 @@ class Account extends Eloquent return true; } - public function showCustomField($field, $entity) + public function showCustomField($field, $entity = false) { if ($this->isPro()) { return $this->$field ? true : false; @@ -1050,7 +1072,13 @@ class Account extends Eloquent public function hasLargeFont() { - return stripos($this->getBodyFontName(), 'chinese') || stripos($this->getHeaderFontName(), 'chinese'); + foreach (['chinese', 'japanese'] as $language) { + if (stripos($this->getBodyFontName(), $language) || stripos($this->getHeaderFontName(), $language)) { + return true; + } + } + + return false; } public function getFontsUrl($protocol = ''){ diff --git a/app/Models/Client.php b/app/Models/Client.php index 2167af0ddfc6..92232647117c 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -154,7 +154,15 @@ class Client extends EntityModel $contact = Contact::createNew(); $contact->send_invoice = true; } - + + if (!Utils::isPro() || $this->account->enable_portal_password){ + if(!empty($data['password']) && $data['password']!='-%unchanged%-'){ + $contact->password = bcrypt($data['password']); + } else if(empty($data['password'])){ + $contact->password = null; + } + } + $contact->fill($data); $contact->is_primary = $isPrimary; @@ -272,6 +280,11 @@ class Client extends EntityModel return $token ? "https://dashboard.stripe.com/customers/{$token}" : false; } + public function getAmount() + { + return $this->balance + $this->paid_to_date; + } + public function getCurrencyId() { if ($this->currency_id) { diff --git a/app/Models/Contact.php b/app/Models/Contact.php index a95f40bab059..9c86c4ce5b84 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -3,10 +3,14 @@ use HTML; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Auth\Authenticatable; +use Illuminate\Auth\Passwords\CanResetPassword; +use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; +use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; -class Contact extends EntityModel +class Contact extends EntityModel implements AuthenticatableContract, CanResetPasswordContract { - use SoftDeletes; + use SoftDeletes, Authenticatable, CanResetPassword; protected $dates = ['deleted_at']; protected $fillable = [ diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 9e3fd6ec53e8..aa6544e52f8b 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -42,7 +42,7 @@ class EntityModel extends Eloquent { $className = get_called_class(); - return $className::scope($publicId)->withTrashed()->pluck('id'); + return $className::scope($publicId)->withTrashed()->value('id'); } public function getActivityKey() @@ -81,6 +81,11 @@ class EntityModel extends Eloquent return $query; } + public function scopeWithArchived($query) + { + return $query->withTrashed()->where('is_deleted', '=', false); + } + public function getName() { return $this->public_id; @@ -108,4 +113,56 @@ class EntityModel extends Eloquent $name = $parts[count($parts)-1]; return strtolower($name) . '_id'; } + + public static function canCreate() { + return Auth::user()->hasPermission('create_all'); + } + + public function canEdit() { + return static::canEditItem($this); + } + + public static function canEditItem($item) { + return Auth::user()->hasPermission('edit_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id); + } + + public static function canEditItemById($item_id) { + if(Auth::user()->hasPermission('edit_all')) { + return true; + } + + return static::whereId($item_id)->first()->user_id == Auth::user()->id; + } + + public static function canEditItemByOwner($user_id) { + if(Auth::user()->hasPermission('edit_all')) { + return true; + } + + return Auth::user()->id == $user_id; + } + + public function canView() { + return static::canViewItem($this); + } + + public static function canViewItem($item) { + return Auth::user()->hasPermission('view_all') || (isset($item->user_id) && Auth::user()->id == $item->user_id); + } + + public static function canViewItemById($item_id) { + if(Auth::user()->hasPermission('view_all')) { + return true; + } + + return static::whereId($item_id)->first()->user_id == Auth::user()->id; + } + + public static function canViewItemByOwner($user_id) { + if(Auth::user()->hasPermission('view_all')) { + return true; + } + + return Auth::user()->id == $user_id; + } } diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 06621518403b..681e8315c512 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -75,6 +75,8 @@ class Gateway extends Eloquent $link = 'https://bitpay.com/dashboard/signup'; } elseif ($this->id == GATEWAY_DWOLLA) { $link = 'https://www.dwolla.com/register'; + } elseif ($this->id == GATEWAY_SAGE_PAY_DIRECT || $this->id == GATEWAY_SAGE_PAY_SERVER) { + $link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99'; } $key = 'texts.gateway_help_'.$this->id; diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 4e15934efeb3..340ce419b757 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -129,13 +129,21 @@ class Invoice extends EntityModel implements BalanceAffecting return false; } - public function getAmountPaid() + public function getAmountPaid($calculate = false) { if ($this->is_quote || $this->is_recurring) { return 0; } - return ($this->amount - $this->balance); + if ($calculate) { + $amount = 0; + foreach ($this->payments as $payment) { + $amount += $payment->amount; + } + return $amount; + } else { + return ($this->amount - $this->balance); + } } public function trashed() @@ -192,6 +200,11 @@ class Invoice extends EntityModel implements BalanceAffecting return $this->hasMany('App\Models\Invoice', 'recurring_invoice_id'); } + public function frequency() + { + return $this->belongsTo('App\Models\Frequency'); + } + public function invitations() { return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id'); @@ -442,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', @@ -752,6 +769,98 @@ class Invoice extends EntityModel implements BalanceAffecting return Utils::decodePDF($pdfString); } + + public function getItemTaxable($invoiceItem, $invoiceTotal) + { + $total = $invoiceItem->qty * $invoiceItem->cost; + + if ($this->discount > 0) { + if ($this->is_amount_discount) { + $total -= $invoiceTotal ? ($total / $invoiceTotal * $this->discount) : 0; + } else { + $total *= (100 - $this->discount) / 100; + $total = round($total, 2); + } + } + + return $total; + } + + public function getTaxable() + { + $total = 0; + + foreach ($this->invoice_items as $invoiceItem) { + $total += $invoiceItem->qty * $invoiceItem->cost; + } + + if ($this->discount > 0) { + if ($this->is_amount_discount) { + $total -= $this->discount; + } else { + $total *= (100 - $this->discount) / 100; + $total = round($total, 2); + } + } + + if ($this->custom_value1 && $this->custom_taxes1) { + $total += $this->custom_value1; + } + + if ($this->custom_value2 && $this->custom_taxes2) { + $total += $this->custom_value2; + } + + return $total; + } + + public function getTaxes($calculatePaid = false) + { + $taxes = []; + $taxable = $this->getTaxable(); + + if ($this->tax_rate && $this->tax_name) { + $taxAmount = $taxable * ($this->tax_rate / 100); + $taxAmount = round($taxAmount, 2); + + if ($taxAmount) { + $taxes[$this->tax_name.$this->tax_rate] = [ + 'name' => $this->tax_name, + 'rate' => $this->tax_rate, + 'amount' => $taxAmount, + 'paid' => round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2) + ]; + } + } + + foreach ($this->invoice_items as $invoiceItem) { + if ( ! $invoiceItem->tax_rate || ! $invoiceItem->tax_name) { + continue; + } + + $taxAmount = $this->getItemTaxable($invoiceItem, $taxable); + $taxAmount = $taxable * ($invoiceItem->tax_rate / 100); + $taxAmount = round($taxAmount, 2); + + if ($taxAmount) { + $key = $invoiceItem->tax_name.$invoiceItem->tax_rate; + + if ( ! isset($taxes[$key])) { + $taxes[$key] = [ + 'amount' => 0, + 'paid' => 0 + ]; + } + + $taxes[$key]['amount'] += $taxAmount; + $taxes[$key]['paid'] += $this->amount && $taxAmount ? round($this->getAmountPaid($calculatePaid) / $this->amount * $taxAmount, 2) : 0; + $taxes[$key]['name'] = $invoiceItem->tax_name; + $taxes[$key]['rate'] = $invoiceItem->tax_rate; + } + } + + return $taxes; + } } Invoice::creating(function ($invoice) { diff --git a/app/Models/Product.php b/app/Models/Product.php index 6f03676618e1..4098c67063d2 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -1,5 +1,6 @@ belongsTo('App\Models\TaxRate'); } + + public function canEdit() { + return Auth::user()->hasPermission('admin'); + } } diff --git a/app/Models/TaxRate.php b/app/Models/TaxRate.php index 7adf6f768b5f..15c39a7572a0 100644 --- a/app/Models/TaxRate.php +++ b/app/Models/TaxRate.php @@ -16,4 +16,8 @@ class TaxRate extends EntityModel { return ENTITY_TAX_RATE; } + + public function canEdit() { + return Auth::user()->hasPermission('admin'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 9f66f0d2af8f..1b2ae7815a36 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,7 +14,12 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Database\Eloquent\SoftDeletes; class User extends Model implements AuthenticatableContract, CanResetPasswordContract { - + public static $all_permissions = array( + 'create_all' => 0b0001, + 'view_all' => 0b0010, + 'edit_all' => 0b0100, + ); + use Authenticatable, CanResetPassword; /** @@ -253,7 +258,69 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon && $this->email != $this->getOriginal('email') && $this->getOriginal('confirmed'); } + + + + /** + * Set the permissions attribute on the model. + * + * @param mixed $value + * @return $this + */ + protected function setPermissionsAttribute($value){ + if(empty($value)) { + $this->attributes['permissions'] = 0; + } else { + $bitmask = 0; + foreach($value as $permission){ + $bitmask = $bitmask | static::$all_permissions[$permission]; + } + $this->attributes['permissions'] = $bitmask; + } + + return $this; + } + + /** + * Expands the value of the permissions attribute + * + * @param mixed $value + * @return mixed + */ + protected function getPermissionsAttribute($value){ + $permissions = array(); + foreach(static::$all_permissions as $permission => $bitmask){ + if(($value & $bitmask) == $bitmask) { + $permissions[$permission] = $permission; + } + } + + return $permissions; + } + + /** + * Checks to see if the user has the required permission + * + * @param mixed $permission Either a single permission or an array of possible permissions + * @param boolean True to require all permissions, false to require only one + * @return boolean + */ + public function hasPermission($permission, $requireAll = false){ + if ($this->is_admin) { + return true; + } else if(is_string($permission)){ + return !empty($this->permissions[$permission]); + } else if(is_array($permission)) { + if($requireAll){ + return count(array_diff($permission, $this->permissions)) == 0; + } else { + return count(array_intersect($permission, $this->permissions)) > 0; + } + } + + return false; + } } User::updating(function ($user) { diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index ce21a6446364..573a581e4774 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -5,6 +5,7 @@ use DB; use Carbon; use App\Events\VendorWasCreated; use App\Events\VendorWasUpdated; +use App\Events\VendorWasDeleted; use Laracasts\Presenter\PresentableTrait; use Illuminate\Database\Eloquent\SoftDeletes; diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index fd51dd5b6a71..e526300a5dd9 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -1,6 +1,6 @@ $invitation, 'amount' => $invoice->getRequestedAmount() ]; + + if (empty($invitation->contact->password) && $account->isPro() && $account->enable_portal_password && $account->send_portal_password) { + // The contact needs a password + $variables['password'] = $password = $this->generatePassword(); + $invitation->contact->password = bcrypt($password); + $invitation->contact->save(); + } $data = [ 'body' => $this->processVariables($body, $variables), @@ -143,6 +151,28 @@ class ContactMailer extends Mailer return $response; } } + + protected function generatePassword($length = 9) + { + $sets = array( + 'abcdefghjkmnpqrstuvwxyz', + 'ABCDEFGHJKMNPQRSTUVWXYZ', + '23456789', + ); + $all = ''; + $password = ''; + foreach($sets as $set) + { + $password .= $set[array_rand(str_split($set))]; + $all .= $set; + } + $all = str_split($all); + for($i = 0; $i < $length - count($sets); $i++) + $password .= $all[array_rand($all)]; + $password = str_shuffle($password); + + return $password; + } public function sendPaymentConfirmation(Payment $payment) { @@ -232,6 +262,7 @@ class ContactMailer extends Mailer $client = $data['client']; $invitation = $data['invitation']; $invoice = $invitation->invoice; + $passwordHTML = isset($data['password'])?'

'.trans('texts.password').': '.$data['password'].'

':false; $variables = [ '$footer' => $account->getEmailFooter(), @@ -245,10 +276,11 @@ class ContactMailer extends Mailer '$invoice' => $invoice->invoice_number, '$quote' => $invoice->invoice_number, '$link' => $invitation->getLink(), - '$viewLink' => $invitation->getLink(), - '$viewButton' => HTML::emailViewButton($invitation->getLink(), $invoice->getEntityType()), - '$paymentLink' => $invitation->getLink('payment'), - '$paymentButton' => HTML::emailPaymentButton($invitation->getLink('payment')), + '$password' => $passwordHTML, + '$viewLink' => $invitation->getLink().'$password', + '$viewButton' => Form::emailViewButton($invitation->getLink(), $invoice->getEntityType()).'$password', + '$paymentLink' => $invitation->getLink('payment').'$password', + '$paymentButton' => Form::emailPaymentButton($invitation->getLink('payment')).'$password', '$customClient1' => $account->custom_client_label1, '$customClient2' => $account->custom_client_label2, '$customInvoice1' => $account->custom_invoice_text_label1, @@ -260,10 +292,21 @@ class ContactMailer extends Mailer $camelType = Gateway::getPaymentTypeName($type); $type = Utils::toSnakeCase($camelType); $variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}"; - $variables["\${$camelType}Button"] = HTML::emailPaymentButton($invitation->getLink('payment') . "/{$type}"); + $variables["\${$camelType}Button"] = Form::emailPaymentButton($invitation->getLink('payment') . "/{$type}"); } - + + $includesPasswordPlaceholder = strpos($template, '$password') !== false; + $str = str_replace(array_keys($variables), array_values($variables), $template); + + if(!$includesPasswordPlaceholder && $passwordHTML){ + $pos = strrpos($str, '$password'); + if($pos !== false) + { + $str = substr_replace($str, $passwordHTML, $pos, 9/* length of "$password" */); + } + } + $str = str_replace('$password', '', $str); $str = autolink($str, 100); return $str; diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index c30c9a10d74c..7afcc2548c93 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -81,7 +81,7 @@ class Mailer $emailError = $exception->getMessage(); } - Utils::logError("Email Error: $emailError"); + //Utils::logError("Email Error: $emailError"); if (isset($data['invitation'])) { $invitation = $data['invitation']; diff --git a/app/Ninja/Notifications/PushFactory.php b/app/Ninja/Notifications/PushFactory.php new file mode 100644 index 000000000000..7f882ff1d0a0 --- /dev/null +++ b/app/Ninja/Notifications/PushFactory.php @@ -0,0 +1,96 @@ +certificate - Development or production. + * + * Static variables defined in routes.php + * + * IOS_PRODUCTION_PUSH + * IOS_DEV_PUSH + */ + + public function __construct() + { + $this->certificate = IOS_DEV_PUSH; + } + + /** + * customMessage function + * + * Send a message with a nested custom payload to perform additional trickery within application + * + * @access public + * + * @param $token + * @param $message + * @param $messageArray + * + * @return void + */ + public function customMessage($token, $message, $messageArray) + { + $customMessage = PushNotification::Message($message, $messageArray); + + $this->message($token, $customMessage); + } + + /** + * message function + * + * Send a plain text only message to a single device. + * + * @access public + * + * @param $token - device token + * @param $message - user specific message + * + * @return void + * + */ + + public function message($token, $message) + { + PushNotification::app($this->certificate) + ->to($token) + ->send($message); + } + + /** + * getFeedback function + * + * Returns an array of expired/invalid tokens to be removed from iOS PUSH notifications. + * + * We need to run this once ~ 24hrs + * + * @access public + * + * @param string $token - A valid token (can be any valid token) + * @param string $message - Nil value for message + * + * @return array + */ + public function getFeedback($token, $message = '') + { + + $feedback = PushNotification::app($this->certificate) + ->to($token) + ->send($message); + + return $feedback->getFeedback(); + } + +} \ No newline at end of file diff --git a/app/Ninja/Presenters/ClientPresenter.php b/app/Ninja/Presenters/ClientPresenter.php index 97551185d514..bb32a8b62fb0 100644 --- a/app/Ninja/Presenters/ClientPresenter.php +++ b/app/Ninja/Presenters/ClientPresenter.php @@ -1,5 +1,6 @@ {$text}"; + } + public function url() + { + return URL::to('/clients/' . $this->entity->public_id); + } + + public function link() + { + return link_to('/clients/' . $this->entity->public_id, $this->entity->getDisplayName()); } } \ No newline at end of file diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php index 9cede24d039e..6b66080ded85 100644 --- a/app/Ninja/Presenters/ExpensePresenter.php +++ b/app/Ninja/Presenters/ExpensePresenter.php @@ -20,4 +20,14 @@ class ExpensePresenter extends Presenter { { return round($this->entity->amount * $this->entity->exchange_rate, 2); } + + public function invoiced_amount() + { + return $this->entity->invoice_id ? $this->converted_amount() : 0; + } + + public function link() + { + return link_to('/expenses/' . $this->entity->public_id, $this->entity->name); + } } \ No newline at end of file diff --git a/app/Ninja/Presenters/InvoicePresenter.php b/app/Ninja/Presenters/InvoicePresenter.php index fb292a8859b9..ebb3297d5c1a 100644 --- a/app/Ninja/Presenters/InvoicePresenter.php +++ b/app/Ninja/Presenters/InvoicePresenter.php @@ -1,5 +1,6 @@ entity->partial) { - return 'amount_due'; + return 'partial_due'; } elseif ($this->entity->is_quote) { return 'total'; } else { @@ -40,9 +41,15 @@ class InvoicePresenter extends Presenter { public function status() { - $status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft'; - $status = strtolower($status); - return trans("texts.status_{$status}"); + if ($this->entity->is_deleted) { + return trans('texts.deleted'); + } elseif ($this->entity->trashed()) { + return trans('texts.archived'); + } else { + $status = $this->entity->invoice_status ? $this->entity->invoice_status->name : 'draft'; + $status = strtolower($status); + return trans("texts.status_{$status}"); + } } public function invoice_date() @@ -55,4 +62,24 @@ class InvoicePresenter extends Presenter { return Utils::fromSqlDate($this->entity->due_date); } + public function frequency() + { + return $this->entity->frequency ? $this->entity->frequency->name : ''; + } + + public function url() + { + return URL::to('/invoices/' . $this->entity->public_id); + } + + public function link() + { + return link_to('/invoices/' . $this->entity->public_id, $this->entity->invoice_number); + } + + public function email() + { + $client = $this->entity->client; + return count($client->contacts) ? $client->contacts[0]->email : ''; + } } \ No newline at end of file diff --git a/app/Ninja/Presenters/PaymentPresenter.php b/app/Ninja/Presenters/PaymentPresenter.php index a0a58663e5a7..a1c3692991fe 100644 --- a/app/Ninja/Presenters/PaymentPresenter.php +++ b/app/Ninja/Presenters/PaymentPresenter.php @@ -1,5 +1,6 @@ entity->public_id . '/edit'); + } + + public function link() + { + return link_to('/payments/' . $this->entity->public_id . '/edit', $this->entity->getDisplayName()); + } + } \ No newline at end of file diff --git a/app/Ninja/Presenters/VendorPresenter.php b/app/Ninja/Presenters/VendorPresenter.php index b3da402bec40..d0bef4e0c828 100644 --- a/app/Ninja/Presenters/VendorPresenter.php +++ b/app/Ninja/Presenters/VendorPresenter.php @@ -9,4 +9,9 @@ class VendorPresenter extends Presenter { { return $this->entity->country ? $this->entity->country->name : ''; } + + public function link() + { + return link_to('/vendors/' . $this->entity->public_id, $this->entity->name); + } } \ No newline at end of file diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index 62c924d545da..0e08fbba5d06 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -5,6 +5,7 @@ use Request; use Session; use Utils; use DB; +use URL; use stdClass; use Validator; use Schema; @@ -58,7 +59,7 @@ class AccountRepository } $user->confirmed = !Utils::isNinja(); - $user->registered = !Utils::isNinja() && $user->email; + $user->registered = !Utils::isNinja() || $email; if (!$user->confirmed) { $user->confirmation_code = str_random(RANDOM_KEY_LENGTH); @@ -69,49 +70,135 @@ class AccountRepository return $account; } - public function getSearchData() + public function getSearchData($account) { - $clients = \DB::table('clients') - ->where('clients.deleted_at', '=', null) - ->where('clients.account_id', '=', \Auth::user()->account_id) - ->whereRaw("clients.name <> ''") - ->select(\DB::raw("'clients' as type, '" . trans('texts.clients') . "' as trans_type, clients.public_id, clients.name, '' as token")); + $data = $this->getAccountSearchData($account); - $contacts = \DB::table('clients') - ->join('contacts', 'contacts.client_id', '=', 'clients.id') - ->where('clients.deleted_at', '=', null) - ->where('clients.account_id', '=', \Auth::user()->account_id) - ->whereRaw("CONCAT(contacts.first_name, contacts.last_name, contacts.email) <> ''") - ->select(\DB::raw("'clients' as type, '" . trans('texts.contacts') . "' as trans_type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ' ', contacts.email) as name, '' as token")); + $data['navigation'] = $this->getNavigationSearchData(); - $invoices = \DB::table('clients') - ->join('invoices', 'invoices.client_id', '=', 'clients.id') - ->where('clients.account_id', '=', \Auth::user()->account_id) - ->where('clients.deleted_at', '=', null) - ->where('invoices.deleted_at', '=', null) - ->select(\DB::raw("'invoices' as type, '" . trans('texts.invoices') . "' as trans_type, invoices.public_id, CONCAT(invoices.invoice_number, ': ', clients.name) as name, invoices.invoice_number as token")); + return $data; + } - $data = []; + private function getAccountSearchData($account) + { + $data = [ + 'clients' => [], + 'contacts' => [], + 'invoices' => [], + 'quotes' => [], + ]; - foreach ($clients->union($contacts)->union($invoices)->get() as $row) { - $type = $row->trans_type; + // include custom client fields in search + if ($account->custom_client_label1) { + $data[$account->custom_client_label1] = []; + } + if ($account->custom_client_label2) { + $data[$account->custom_client_label2] = []; + } - if (!isset($data[$type])) { - $data[$type] = []; + $clients = Client::scope() + ->with('contacts', 'invoices') + ->get(); + + foreach ($clients as $client) { + if ($client->name) { + $data['clients'][] = [ + 'value' => $client->name, + 'tokens' => $client->name, + 'url' => $client->present()->url, + ]; + } + + if ($client->custom_value1) { + $data[$account->custom_client_label1][] = [ + 'value' => "{$client->custom_value1}: " . $client->getDisplayName(), + 'tokens' => $client->custom_value1, + 'url' => $client->present()->url, + ]; + } + if ($client->custom_value2) { + $data[$account->custom_client_label2][] = [ + 'value' => "{$client->custom_value2}: " . $client->getDisplayName(), + 'tokens' => $client->custom_value2, + 'url' => $client->present()->url, + ]; } - $tokens = explode(' ', $row->name); - $tokens[] = $type; - - if ($type == 'Invoices') { - $tokens[] = intVal($row->token).''; + 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[$type][] = [ - 'value' => $row->name, - 'public_id' => $row->public_id, - 'tokens' => $tokens, - 'entity_type' => $row->type, + foreach ($client->invoices as $invoice) { + $entityType = $invoice->getEntityType(); + $data["{$entityType}s"][] = [ + 'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), + 'tokens' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), + 'url' => $invoice->present()->url, + ]; + } + } + + return $data; + } + + private function getNavigationSearchData() + { + $entityTypes = [ + ENTITY_INVOICE, + ENTITY_CLIENT, + ENTITY_QUOTE, + ENTITY_TASK, + ENTITY_EXPENSE, + ENTITY_RECURRING_INVOICE, + ENTITY_PAYMENT, + ENTITY_CREDIT + ]; + + foreach ($entityTypes as $entityType) { + $features[] = [ + "new_{$entityType}", + "/{$entityType}s/create", + ]; + $features[] = [ + "list_{$entityType}s", + "/{$entityType}s", + ]; + } + + $features[] = ['dashboard', '/dashboard']; + $features[] = ['customize_design', '/settings/customize_design']; + $features[] = ['new_tax_rate', '/tax_rates/create']; + $features[] = ['new_product', '/products/create']; + $features[] = ['new_user', '/users/create']; + $features[] = ['custom_fields', '/settings/invoice_settings']; + + $settings = array_merge(Account::$basicSettings, Account::$advancedSettings); + + foreach ($settings as $setting) { + $features[] = [ + $setting, + "/settings/{$setting}", + ]; + } + + foreach ($features as $feature) { + $data[] = [ + 'value' => trans('texts.' . $feature[0]), + 'tokens' => trans('texts.' . $feature[0]), + 'url' => URL::to($feature[1]) ]; } @@ -127,8 +214,10 @@ class AccountRepository $account = Auth::user()->account; $client = $this->getNinjaClient($account); $invitation = $this->createNinjaInvoice($client, $account); + return $invitation; } + public function createNinjaInvoice($client, $clientAccount) { $account = $this->getNinjaAccount(); diff --git a/app/Ninja/Repositories/ClientRepository.php b/app/Ninja/Repositories/ClientRepository.php index 324d62a45943..9df81663a5e5 100644 --- a/app/Ninja/Repositories/ClientRepository.php +++ b/app/Ninja/Repositories/ClientRepository.php @@ -1,6 +1,7 @@ with('contacts')->firstOrFail(); } + // convert currency code to id + if (isset($data['currency_code'])) { + $currencyCode = strtolower($data['currency_code']); + $currency = Cache::get('currencies')->filter(function($item) use ($currencyCode) { + return strtolower($item->code) == $currencyCode; + })->first(); + if ($currency) { + $data['currency_id'] = $currency->id; + } + } + $client->fill($data); $client->save(); diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php index 1c33cb19e41d..9803b4628af8 100644 --- a/app/Ninja/Repositories/CreditRepository.php +++ b/app/Ninja/Repositories/CreditRepository.php @@ -29,6 +29,7 @@ class CreditRepository extends BaseRepository 'credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'credits.amount', 'credits.balance', 'credits.credit_date', @@ -37,7 +38,8 @@ class CreditRepository extends BaseRepository 'contacts.email', 'credits.private_notes', 'credits.deleted_at', - 'credits.is_deleted' + 'credits.is_deleted', + 'credits.user_id' ); if ($clientPublicId) { diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index cfd312622ad3..49988435e598 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -40,7 +40,8 @@ class ExpenseRepository extends BaseRepository 'expenses.public_id', 'expenses.deleted_at', 'expenses.should_be_invoiced', - 'expenses.created_at' + 'expenses.created_at', + 'expenses.user_id' ); return $query; @@ -80,11 +81,15 @@ class ExpenseRepository extends BaseRepository 'expenses.vendor_id', 'expenses.expense_currency_id', 'expenses.invoice_currency_id', + 'expenses.user_id', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'vendors.name as vendor_name', 'vendors.public_id as vendor_public_id', + 'vendors.user_id as vendor_user_id', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 3c4072122d1f..67c0bab31080 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -49,6 +49,7 @@ class InvoiceRepository extends BaseRepository DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'invoice_number', 'invoice_status_id', 'clients.name as client_name', @@ -65,7 +66,8 @@ class InvoiceRepository extends BaseRepository 'invoices.quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted', - 'invoices.partial' + 'invoices.partial', + 'invoices.user_id' ); if (!\Session::get('show_trash:'.$entityType)) { @@ -170,7 +172,7 @@ class InvoiceRepository extends BaseRepository ); $table = \Datatable::query($query) - ->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number); }) + ->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml(); }) ->addColumn('invoice_date', function ($model) { return Utils::fromSqlDate($model->invoice_date); }) ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }); @@ -189,7 +191,7 @@ class InvoiceRepository extends BaseRepository ->make(); } - public function save($data) + public function save($data, $checkSubPermissions = false) { $account = \Auth::user()->account; $publicId = isset($data['public_id']) ? $data['public_id'] : false; @@ -398,36 +400,47 @@ 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; } $task = false; if (isset($item['task_public_id']) && $item['task_public_id']) { $task = Task::scope($item['task_public_id'])->where('invoice_id', '=', null)->firstOrFail(); - $task->invoice_id = $invoice->id; - $task->client_id = $invoice->client_id; - $task->save(); + if(!$checkSubPermissions || $task->canEdit()){ + $task->invoice_id = $invoice->id; + $task->client_id = $invoice->client_id; + $task->save(); + } } $expense = false; if (isset($item['expense_public_id']) && $item['expense_public_id']) { $expense = Expense::scope($item['expense_public_id'])->where('invoice_id', '=', null)->firstOrFail(); - $expense->invoice_id = $invoice->id; - $expense->client_id = $invoice->client_id; - $expense->save(); + if(!$checkSubPermissions || $expense->canEdit()){ + $expense->invoice_id = $invoice->id; + $expense->client_id = $invoice->client_id; + $expense->save(); + } } if ($productKey = trim($item['product_key'])) { if (\Auth::user()->account->update_products && ! strtotime($productKey)) { $product = Product::findProductByKey($productKey); if (!$product) { - $product = Product::createNew(); - $product->product_key = trim($item['product_key']); + if(!$checkSubPermissions || Product::canCreate()){ + $product = Product::createNew(); + $product->product_key = trim($item['product_key']); + } + else{ + $product = null; + } + } + if($product && (!$checkSubPermissions || $product->canEdit())){ + $product->notes = ($task || $expense) ? '' : $item['notes']; + $product->cost = $expense ? 0 : $item['cost']; + $product->save(); } - $product->notes = ($task || $expense) ? '' : $item['notes']; - $product->cost = $expense ? 0 : $item['cost']; - $product->save(); } } @@ -439,6 +452,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']); @@ -603,7 +623,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/app/Ninja/Repositories/PaymentRepository.php b/app/Ninja/Repositories/PaymentRepository.php index a080dd89c3d9..a027cb62aedb 100644 --- a/app/Ninja/Repositories/PaymentRepository.php +++ b/app/Ninja/Repositories/PaymentRepository.php @@ -36,9 +36,11 @@ class PaymentRepository extends BaseRepository 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'invoices.invoice_number', 'contacts.first_name', 'contacts.last_name', @@ -47,6 +49,7 @@ class PaymentRepository extends BaseRepository 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', + 'payments.user_id', 'invoices.is_deleted as invoice_is_deleted', 'gateways.name as gateway_name' ); @@ -116,7 +119,7 @@ class PaymentRepository extends BaseRepository public function save($input) { $publicId = isset($input['public_id']) ? $input['public_id'] : false; - + if ($publicId) { $payment = Payment::scope($publicId)->firstOrFail(); } else { @@ -136,7 +139,7 @@ class PaymentRepository extends BaseRepository } else { $payment->payment_date = date('Y-m-d'); } - + if (isset($input['transaction_reference'])) { $payment->transaction_reference = trim($input['transaction_reference']); } diff --git a/app/Ninja/Repositories/TaskRepository.php b/app/Ninja/Repositories/TaskRepository.php index 47a052378ff1..a7655a8abd9e 100644 --- a/app/Ninja/Repositories/TaskRepository.php +++ b/app/Ninja/Repositories/TaskRepository.php @@ -27,6 +27,7 @@ class TaskRepository 'tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', @@ -36,9 +37,11 @@ class TaskRepository 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', + 'invoices.user_id as invoice_user_id', 'tasks.is_running', 'tasks.time_log', - 'tasks.created_at' + 'tasks.created_at', + 'tasks.user_id' ); if ($clientPublicId) { diff --git a/app/Ninja/Repositories/UserRepository.php b/app/Ninja/Repositories/UserRepository.php index 5675a161a6ff..ca5878b0c188 100644 --- a/app/Ninja/Repositories/UserRepository.php +++ b/app/Ninja/Repositories/UserRepository.php @@ -22,7 +22,7 @@ class UserRepository extends BaseRepository $query->where('users.deleted_at', '=', null); } - $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at'); + $query->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at', 'users.is_admin', 'users.permissions'); return $query; } @@ -34,5 +34,4 @@ class UserRepository extends BaseRepository return $user; } - } diff --git a/app/Ninja/Repositories/VendorRepository.php b/app/Ninja/Repositories/VendorRepository.php index 81fc8fc7f47b..df885f62e12e 100644 --- a/app/Ninja/Repositories/VendorRepository.php +++ b/app/Ninja/Repositories/VendorRepository.php @@ -42,7 +42,8 @@ class VendorRepository extends BaseRepository 'vendors.city', 'vendor_contacts.email', 'vendors.deleted_at', - 'vendors.is_deleted' + 'vendors.is_deleted', + 'vendors.user_id' ); if (!\Session::get('show_trash:vendor')) { diff --git a/app/Ninja/Transformers/UserTransformer.php b/app/Ninja/Transformers/UserTransformer.php index d700c579baca..532c1f6fa4f2 100644 --- a/app/Ninja/Transformers/UserTransformer.php +++ b/app/Ninja/Transformers/UserTransformer.php @@ -21,7 +21,11 @@ class UserTransformer extends EntityTransformer 'registered' => (bool) $user->registered, 'confirmed' => (bool) $user->confirmed, 'oauth_user_id' => $user->oauth_user_id, - 'oauth_provider_id' => $user->oauth_provider_id + 'oauth_provider_id' => $user->oauth_provider_id, + 'notify_sent' => (bool) $user->notify_sent, + 'notify_viewed' => (bool) $user->notify_viewed, + 'notify_paid' => (bool) $user->notify_paid, + 'notify_approved' => (bool) $user->notify_approved, ]; } } \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0309b6a8fe83..3cd691098747 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,9 +4,13 @@ use Session; use Auth; use Utils; use HTML; +use Form; use URL; use Request; use Validator; +use App\Models\Credit; +use App\Models\Invoice; +use App\Models\Vendor; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { @@ -18,62 +22,74 @@ class AppServiceProvider extends ServiceProvider { */ public function boot() { - HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { + Form::macro('image_data', function($imagePath) { + return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath)); + }); + + Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { + $capitalize = config('former.capitalize_translations'); $class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2.'/*') ) ? ' class="active"' : ''; - $title = ucwords(trans("texts.$text")) . Utils::getProLabel($text); + if ($capitalize) { + $title = ucwords(trans("texts.$text")) . Utils::getProLabel($text); + } else { + $title = trans("texts.$text") . Utils::getProLabel($text); + } return ''.$title.''; }); - HTML::macro('tab_link', function($url, $text, $active = false) { + Form::macro('tab_link', function($url, $text, $active = false) { $class = $active ? ' class="active"' : ''; return ''.$text.''; }); - HTML::macro('menu_link', function($type) { + Form::macro('menu_link', function($type) { $types = $type.'s'; $Type = ucfirst($type); $Types = ucfirst($types); $class = ( Request::is($types) || Request::is('*'.$type.'*')) && !Request::is('*settings*') ? ' active' : ''; $str = '

'; + $str .= ''; return $str; }); - HTML::macro('image_data', function($imagePath) { - return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath)); - }); - - HTML::macro('flatButton', function($label, $color) { + Form::macro('flatButton', function($label, $color) { return ''; }); - HTML::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) { + Form::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) { return view('partials.email_button') ->with([ 'link' => $link, @@ -83,7 +99,7 @@ class AppServiceProvider extends ServiceProvider { ->render(); }); - HTML::macro('emailPaymentButton', function($link = '#') { + Form::macro('emailPaymentButton', function($link = '#') { return view('partials.email_button') ->with([ 'link' => $link, @@ -93,7 +109,7 @@ class AppServiceProvider extends ServiceProvider { ->render(); }); - HTML::macro('breadcrumbs', function($status = false) { + Form::macro('breadcrumbs', function($status = false) { $str = ''; }); - + Validator::extend('positive', function($attribute, $value, $parameters) { return Utils::parseFloat($value) >= 0; }); @@ -223,11 +239,6 @@ class AppServiceProvider extends ServiceProvider { 'Illuminate\Contracts\Auth\Registrar', 'App\Services\Registrar' ); - - $this->app->bind( - 'App\Ninja\Import\DataImporterServiceInterface', - 'App\Ninja\Import\FreshBooks\FreshBooksDataImporterService' - ); } } diff --git a/app/Services/AccountGatewayService.php b/app/Services/AccountGatewayService.php index 61d5a9a80e1c..bd5635e0dd52 100644 --- a/app/Services/AccountGatewayService.php +++ b/app/Services/AccountGatewayService.php @@ -41,7 +41,7 @@ class AccountGatewayService extends BaseService [ 'name', function ($model) { - return link_to("gateways/{$model->public_id}/edit", $model->name); + return link_to("gateways/{$model->public_id}/edit", $model->name)->toHtml(); } ], [ diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index 8718f3ee72b9..d3b08b997189 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -38,11 +38,11 @@ class ActivityService extends BaseService 'activity_type_id', function ($model) { $data = [ - 'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model)), - 'user' => $model->is_system ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), - 'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice) : null, - 'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice) : null, - 'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model)) : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), + 'client' => link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml(), + 'user' => $model->is_system ? '' . trans('texts.system') . '' : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), + 'invoice' => $model->invoice ? link_to('/invoices/' . $model->invoice_public_id, $model->is_recurring ? trans('texts.recurring_invoice') : $model->invoice)->toHtml() : null, + 'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null, + 'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email), 'payment' => $model->payment ?: '', 'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) ]; diff --git a/app/Services/BankAccountService.php b/app/Services/BankAccountService.php index 622c759c8216..72aada6e3ff9 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) { @@ -214,8 +228,8 @@ class BankAccountService extends BaseService [ 'bank_name', function ($model) { - return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name); - } + return link_to("bank_accounts/{$model->public_id}/edit", $model->bank_name)->toHtml(); + }, ], [ '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/BaseService.php b/app/Services/BaseService.php index 4705417e613e..b68b06482a04 100644 --- a/app/Services/BaseService.php +++ b/app/Services/BaseService.php @@ -1,11 +1,11 @@ getRepo()->findByPublicIdsWithTrashed($ids); foreach ($entities as $entity) { - $this->getRepo()->$action($entity); + if($entity->canEdit()){ + $this->getRepo()->$action($entity); + } } return count($entities); diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index d7cb08a77d86..662fc8eab9e8 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -4,6 +4,12 @@ use Utils; use URL; use Auth; use App\Services\BaseService; +use App\Models\Client; +use App\Models\Invoice; +use App\Models\Credit; +use App\Models\Expense; +use App\Models\Payment; +use App\Models\Task; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\NinjaRepository; @@ -37,6 +43,10 @@ class ClientService extends BaseService { $query = $this->clientRepo->find($search); + if(!Utils::hasPermission('view_all')){ + $query->where('clients.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_CLIENT, $query); } @@ -46,19 +56,19 @@ class ClientService extends BaseService [ 'name', function ($model) { - return link_to("clients/{$model->public_id}", $model->name ?: ''); + return link_to("clients/{$model->public_id}", $model->name ?: '')->toHtml(); } ], [ 'first_name', function ($model) { - return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name); + return link_to("clients/{$model->public_id}", $model->first_name.' '.$model->last_name)->toHtml(); } ], [ 'email', function ($model) { - return link_to("clients/{$model->public_id}", $model->email ?: ''); + return link_to("clients/{$model->public_id}", $model->email ?: '')->toHtml(); } ], [ @@ -89,19 +99,33 @@ class ClientService extends BaseService trans('texts.edit_client'), function ($model) { return URL::to("clients/{$model->public_id}/edit"); + }, + function ($model) { + return Client::canEditItem($model); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return Client::canEditItem($model) && (Task::canCreate() || Invoice::canCreate()); } ], - [], [ trans('texts.new_task'), function ($model) { return URL::to("tasks/create/{$model->public_id}"); + }, + function ($model) { + return Task::canCreate(); } ], [ trans('texts.new_invoice'), function ($model) { return URL::to("invoices/create/{$model->public_id}"); + }, + function ($model) { + return Invoice::canCreate(); } ], [ @@ -110,26 +134,40 @@ class ClientService extends BaseService return URL::to("quotes/create/{$model->public_id}"); }, function ($model) { - return Auth::user()->isPro(); + return Auth::user()->isPro() && Invoice::canCreate(); + } + ], + [ + '--divider--', function(){return false;}, + function ($model) { + return (Task::canCreate() || Invoice::canCreate()) && (Payment::canCreate() || Credit::canCreate() || Expense::canCreate()); } ], - [], [ trans('texts.enter_payment'), function ($model) { return URL::to("payments/create/{$model->public_id}"); + }, + function ($model) { + return Payment::canCreate(); } ], [ trans('texts.enter_credit'), function ($model) { return URL::to("credits/create/{$model->public_id}"); + }, + function ($model) { + return Credit::canCreate(); } ], [ trans('texts.enter_expense'), function ($model) { return URL::to("expenses/create/0/{$model->public_id}"); + }, + function ($model) { + return Expense::canCreate(); } ] ]; diff --git a/app/Services/CreditService.php b/app/Services/CreditService.php index 87aa872c5dea..2e9220ad0544 100644 --- a/app/Services/CreditService.php +++ b/app/Services/CreditService.php @@ -2,7 +2,10 @@ use Utils; use URL; +use Auth; use App\Services\BaseService; +use App\Models\Client; +use App\Models\Payment; use App\Ninja\Repositories\CreditRepository; @@ -30,6 +33,10 @@ class CreditService extends BaseService public function getDatatable($clientPublicId, $search) { $query = $this->creditRepo->find($clientPublicId, $search); + + if(!Utils::hasPermission('view_all')){ + $query->where('credits.user_id', '=', Auth::user()->id); + } return $this->createDatatable(ENTITY_CREDIT, $query, !$clientPublicId); } @@ -40,7 +47,11 @@ class CreditService extends BaseService [ 'client_name', function ($model) { - return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)) : ''; + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient ], @@ -78,6 +89,9 @@ class CreditService extends BaseService trans('texts.apply_credit'), function ($model) { return URL::to("payments/create/{$model->client_public_id}") . '?paymentTypeId=1'; + }, + function ($model) { + return Payment::canCreate(); } ] ]; diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 63fe47005bab..2f9af2856cf3 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -1,7 +1,9 @@ addColumn('checkbox', function ($model) { - return ''; }); } @@ -44,6 +48,8 @@ class DatatableService $hasAction = false; $str = '
'; + $can_edit = Auth::user()->hasPermission('edit_all') || (isset($model->user_id) && Auth::user()->id == $model->user_id); + if (property_exists($model, 'is_deleted') && $model->is_deleted) { $str .= ''; } elseif ($model->deleted_at && $model->deleted_at !== '0000-00-00') { @@ -69,9 +75,15 @@ class DatatableService } list($value, $url, $visible) = $action; if ($visible($model)) { - $str .= "
  • {$value}
  • "; - $lastIsDivider = false; - $hasAction = true; + if($value == '--divider--'){ + $str .= "
  • "; + $lastIsDivider = true; + } + else { + $str .= "
  • {$value}
  • "; + $hasAction = true; + $lastIsDivider = false; + } } } elseif ( ! $lastIsDivider) { $str .= "
  • "; @@ -83,20 +95,20 @@ class DatatableService return ''; } - if ( ! $lastIsDivider) { + if ( $can_edit && ! $lastIsDivider) { $str .= "
  • "; } - if ($entityType != ENTITY_USER || $model->public_id) { + if (($entityType != ENTITY_USER || $model->public_id) && $can_edit) { $str .= "
  • public_id})\">" . trans("texts.archive_{$entityType}") . "
  • "; } - } else { + } else if($can_edit) { $str .= "
  • public_id})\">" . trans("texts.restore_{$entityType}") . "
  • "; } - if (property_exists($model, 'is_deleted') && !$model->is_deleted) { + if (property_exists($model, 'is_deleted') && !$model->is_deleted && $can_edit) { $str .= "
  • public_id})\">" . trans("texts.delete_{$entityType}") . "
  • "; } diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index e8f4d591d75d..2fc2afbc84b1 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -1,10 +1,13 @@ expenseRepo->save($data); } @@ -42,6 +45,10 @@ class ExpenseService extends BaseService { $query = $this->expenseRepo->find($search); + if(!Utils::hasPermission('view_all')){ + $query->where('expenses.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_EXPENSE, $query); } @@ -63,7 +70,11 @@ class ExpenseService extends BaseService function ($model) { if ($model->vendor_public_id) { - return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name); + if(!Vendor::canViewItemByOwner($model->vendor_user_id)){ + return $model->vendor_name; + } + + return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml(); } else { return ''; } @@ -74,7 +85,11 @@ class ExpenseService extends BaseService function ($model) { if ($model->client_public_id) { - return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)); + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); } else { return ''; } @@ -83,7 +98,11 @@ class ExpenseService extends BaseService [ 'expense_date', function ($model) { - return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date)); + if(!Expense::canEditItemByOwner($model->user_id)){ + return Utils::fromSqlDate($model->expense_date); + } + + return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date))->toHtml(); } ], [ @@ -92,7 +111,7 @@ class ExpenseService extends BaseService // show both the amount and the converted amount if ($model->exchange_rate != 1) { $converted = round($model->amount * $model->exchange_rate, 2); - return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' . + return Utils::formatMoney($model->amount, $model->expense_currency_id) . ' | ' . Utils::formatMoney($converted, $model->invoice_currency_id); } else { return Utils::formatMoney($model->amount, $model->expense_currency_id); @@ -151,6 +170,9 @@ class ExpenseService extends BaseService trans('texts.edit_expense'), function ($model) { return URL::to("expenses/{$model->public_id}/edit") ; + }, + function ($model) { + return Expense::canEditItem($model); } ], [ @@ -159,7 +181,7 @@ class ExpenseService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_public_id; + return $model->invoice_public_id && Invoice::canEditItemByOwner($model->invoice_user_id); } ], [ @@ -168,7 +190,7 @@ class ExpenseService extends BaseService return "javascript:invoiceEntity({$model->public_id})"; }, function ($model) { - return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); + return ! $model->invoice_id && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate(); } ], ]; @@ -178,7 +200,7 @@ class ExpenseService extends BaseService { return []; } - + private function getStatusLabel($invoiceId, $shouldBeInvoiced) { if ($invoiceId) { diff --git a/app/Services/InvoiceService.php b/app/Services/InvoiceService.php index dc712011d5d8..c7c0be72acc0 100644 --- a/app/Services/InvoiceService.php +++ b/app/Services/InvoiceService.php @@ -8,6 +8,9 @@ use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\ClientRepository; use App\Events\QuoteInvitationWasApproved; use App\Models\Invitation; +use App\Models\Invoice; +use App\Models\Client; +use App\Models\Payment; class InvoiceService extends BaseService { @@ -27,15 +30,27 @@ class InvoiceService extends BaseService return $this->invoiceRepo; } - public function save($data) + public function save($data, $checkSubPermissions = false) { if (isset($data['client'])) { - $client = $this->clientRepo->save($data['client']); - $data['client_id'] = $client->id; + $can_save_client = !$checkSubPermissions; + if(!$can_save_client){ + if(empty($data['client']['public_id']) || $data['client']['public_id']=='-1'){ + $can_save_client = Client::canCreate(); + } + else{ + $can_save_client = Client::wherePublicId($data['client']['public_id'])->first()->canEdit(); + } + } + + if($can_save_client){ + $client = $this->clientRepo->save($data['client']); + $data['client_id'] = $client->id; + } } - $invoice = $this->invoiceRepo->save($data); - + $invoice = $this->invoiceRepo->save($data, $checkSubPermissions); + $client = $invoice->client; $client->load('contacts'); $sendInvoiceIds = []; @@ -45,7 +60,7 @@ class InvoiceService extends BaseService $sendInvoiceIds[] = $contact->id; } } - + foreach ($client->contacts as $contact) { $invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first(); @@ -69,7 +84,7 @@ class InvoiceService extends BaseService if (!$invitation) { return $invoice; } - + foreach ($invoice->invitations as $invoiceInvitation) { if ($invitation->contact_id == $invoiceInvitation->contact_id) { return $invoiceInvitation->invitation_key; @@ -79,11 +94,12 @@ class InvoiceService extends BaseService public function approveQuote($quote, $invitation = null) { - $account = Auth::user()->account; + $account = $quote->account; + if (!$quote->is_quote || $quote->quote_invoice_id) { return null; } - + if ($account->auto_convert_quote || ! $account->isPro()) { $invoice = $this->convertQuote($quote, $invitation); @@ -94,7 +110,7 @@ class InvoiceService extends BaseService $quote->markApproved(); event(new QuoteInvitationWasApproved($quote, null, $invitation)); - + foreach ($quote->invitations as $invoiceInvitation) { if ($invitation->contact_id == $invoiceInvitation->contact_id) { return $invoiceInvitation->invitation_key; @@ -108,6 +124,10 @@ class InvoiceService extends BaseService $query = $this->invoiceRepo->getInvoices($accountId, $clientPublicId, $entityType, $search) ->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false); + if(!Utils::hasPermission('view_all')){ + $query->where('invoices.user_id', '=', Auth::user()->id); + } + return $this->createDatatable($entityType, $query, !$clientPublicId); } @@ -117,13 +137,20 @@ class InvoiceService extends BaseService [ 'invoice_number', function ($model) use ($entityType) { - return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]); + if(!Invoice::canEditItem($model)){ + return $model->invoice_number; + } + + return link_to("{$entityType}s/{$model->public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); } ], [ 'client_name', function ($model) { - return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)); + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); }, ! $hideClient ], @@ -159,8 +186,8 @@ class InvoiceService extends BaseService ], [ 'invoice_status_name', - function ($model) { - return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted')) : self::getStatusLabel($model); + function ($model) use ($entityType) { + return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted'))->toHtml() : self::getStatusLabel($entityType, $model); } ] ]; @@ -173,12 +200,18 @@ class InvoiceService extends BaseService trans("texts.edit_{$entityType}"), function ($model) use ($entityType) { return URL::to("{$entityType}s/{$model->public_id}/edit"); + }, + function ($model) { + return Invoice::canEditItem($model); } ], [ trans("texts.clone_{$entityType}"), function ($model) use ($entityType) { return URL::to("{$entityType}s/{$model->public_id}/clone"); + }, + function ($model) { + return Invoice::canCreate(); } ], [ @@ -187,14 +220,19 @@ class InvoiceService extends BaseService return URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}"); } ], - [], + [ + '--divider--', function(){return false;}, + function ($model) { + return Invoice::canEditItem($model) || Payment::canCreate(); + } + ], [ trans("texts.mark_sent"), function ($model) { return "javascript:markEntity({$model->public_id})"; }, function ($model) { - return $model->invoice_status_id < INVOICE_STATUS_SENT; + return $model->invoice_status_id < INVOICE_STATUS_SENT && Invoice::canEditItem($model); } ], [ @@ -203,7 +241,7 @@ class InvoiceService extends BaseService return URL::to("payments/create/{$model->client_public_id}/{$model->public_id}"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->balance > 0; + return $entityType == ENTITY_INVOICE && $model->balance > 0 && Payment::canCreate(); } ], [ @@ -212,7 +250,7 @@ class InvoiceService extends BaseService return URL::to("quotes/{$model->quote_id}/edit"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_INVOICE && $model->quote_id; + return $entityType == ENTITY_INVOICE && $model->quote_id && Invoice::canEditItem($model); } ], [ @@ -221,7 +259,7 @@ class InvoiceService extends BaseService return URL::to("invoices/{$model->quote_invoice_id}/edit"); }, function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && $model->quote_invoice_id; + return $entityType == ENTITY_QUOTE && $model->quote_invoice_id && Invoice::canEditItem($model); } ], [ @@ -230,18 +268,19 @@ class InvoiceService extends BaseService return "javascript:convertEntity({$model->public_id})"; }, function ($model) use ($entityType) { - return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id; + return $entityType == ENTITY_QUOTE && ! $model->quote_invoice_id && Invoice::canEditItem($model); } ] ]; } - private function getStatusLabel($model) + private function getStatusLabel($entityType, $model) { // 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")) { - return "

    ".trans('texts.overdue')."

    "; + $label = $entityType == ENTITY_INVOICE ? trans('texts.overdue') : trans('texts.expired'); + return "

    " . $label . "

    "; } } @@ -266,5 +305,5 @@ class InvoiceService extends BaseService } return "

    $label

    "; } - + } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 66e94e43dced..3bf26d8ba206 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -1,6 +1,7 @@ isRedirect()) { $token = $response->getTransactionReference(); } - + Session::set($invitation->id . 'payment_type', PAYMENT_TYPE_CREDIT_CARD); return $token; @@ -273,7 +276,7 @@ class PaymentService extends BaseService // submit purchase/get response $response = $gateway->purchase($details)->send(); - + if ($response->isSuccessful()) { $ref = $response->getTransactionReference(); return $this->createPayment($invitation, $accountGateway, $ref); @@ -286,6 +289,10 @@ class PaymentService extends BaseService { $query = $this->paymentRepo->find($clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('payments.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId); } @@ -295,13 +302,21 @@ class PaymentService extends BaseService [ 'invoice_number', function ($model) { - return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]); + if(!Invoice::canEditItemByOwner($model->invoice_user_id)){ + return $model->invoice_number; + } + + return link_to("invoices/{$model->invoice_public_id}/edit", $model->invoice_number, ['class' => Utils::getEntityRowClass($model)])->toHtml(); } ], [ 'client_name', function ($model) { - return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)) : ''; + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient ], @@ -339,6 +354,9 @@ class PaymentService extends BaseService trans('texts.edit_payment'), function ($model) { return URL::to("payments/{$model->public_id}/edit"); + }, + function ($model) { + return Payment::canEditItem($model); } ] ]; diff --git a/app/Services/PaymentTermService.php b/app/Services/PaymentTermService.php index bfcf670475b9..1371d20c2c43 100644 --- a/app/Services/PaymentTermService.php +++ b/app/Services/PaymentTermService.php @@ -34,7 +34,7 @@ class PaymentTermService extends BaseService [ 'name', function ($model) { - return link_to("payment_terms/{$model->public_id}/edit", $model->name); + return link_to("payment_terms/{$model->public_id}/edit", $model->name)->toHtml(); } ], [ diff --git a/app/Services/ProductService.php b/app/Services/ProductService.php index a8307f0104cd..f8ec6e1131d5 100644 --- a/app/Services/ProductService.php +++ b/app/Services/ProductService.php @@ -44,7 +44,7 @@ class ProductService extends BaseService [ 'product_key', function ($model) { - return link_to('products/'.$model->public_id.'/edit', $model->product_key); + return link_to('products/'.$model->public_id.'/edit', $model->product_key)->toHtml(); } ], [ diff --git a/app/Services/PushService.php b/app/Services/PushService.php new file mode 100644 index 000000000000..17151f93af3f --- /dev/null +++ b/app/Services/PushService.php @@ -0,0 +1,171 @@ +devices Definition + * + * @param string token (push notification device token) + * @param string email (user email address - required for use as key) + * @param string device (ios, gcm etc etc) + * @param bool notify_sent + * @param bool notify_paid + * @param bool notify_approved + * @param bool notify_viewed + */ + +class PushService +{ + protected $pushFactory; + + /** + * @param PushFactory $pushFactory + */ + public function __construct(PushFactory $pushFactory) + { + $this->pushFactory = $pushFactory; + } + + /** + * @param $invoice - Invoice object + * @param $type - Type of notification, ie. Quote APPROVED, Invoice PAID, Invoice/Quote SENT, Invoice/Quote VIEWED + */ + + public function sendNotification($invoice, $type) + { + //check user has registered for push notifications + if(!$this->checkDeviceExists($invoice->account)) + return; + + //Harvest an array of devices that are registered for this notification type + $devices = json_decode($invoice->account->devices, TRUE); + + foreach($devices as $device) + { + if(($device["notify_{$type}"] == TRUE) && ($device['device'] == 'ios')) + $this->pushMessage($invoice, $device['token'], $type); + } + + + } + + + /** + * pushMessage function + * + * method to dispatch iOS notifications + * + * @param $invoice + * @param $token + * @param $type + */ + private function pushMessage($invoice, $token, $type) + { + $this->pushFactory->message($token, $this->messageType($invoice, $type)); + } + + + /** + * checkDeviceExists function + * + * Returns a boolean if this account has devices registered for PUSH notifications + * + * @param $account + * @return bool + */ + private function checkDeviceExists($account) + { + $devices = json_decode($account->devices, TRUE); + + if(count($devices) >= 1) + return TRUE; + else + return FALSE; + } + + /** + * messageType function + * + * method which formats an appropriate message depending on message type + * + * @param $invoice + * @param $type + * @return string + */ + private function messageType($invoice, $type) + { + switch($type) + { + case 'sent': + return $this->entitySentMessage($invoice); + break; + + case 'paid': + return $this->invoicePaidMessage($invoice); + break; + + case 'approved': + return $this->quoteApprovedMessage($invoice); + break; + + case 'viewed': + return $this->entityViewedMessage($invoice); + break; + } + } + + /** + * @param $invoice + * @return string + */ + private function entitySentMessage($invoice) + { + if($invoice->is_quote) + return trans("texts.notification_quote_sent_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); + else + return trans("texts.notification_invoice_sent_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); + + } + + /** + * @param $invoice + * @return string + */ + private function invoicePaidMessage($invoice) + { + return trans("texts.notification_invoice_paid_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); + } + + /** + * @param $invoice + * @return string + */ + private function quoteApprovedMessage($invoice) + { + return trans("texts.notification_quote_approved_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); + } + + /** + * @param $invoice + * @return string + */ + private function entityViewedMessage($invoice) + { + if($invoice->is_quote) + return trans("texts.notification_quote_viewed_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); + else + return trans("texts.notification_invoice_viewed_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->name]); + + } + + + +} \ No newline at end of file diff --git a/app/Services/RecurringInvoiceService.php b/app/Services/RecurringInvoiceService.php index 9adb344a137c..2786ccfc6869 100644 --- a/app/Services/RecurringInvoiceService.php +++ b/app/Services/RecurringInvoiceService.php @@ -1,7 +1,9 @@ invoiceRepo->getRecurringInvoices($accountId, $clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('invoices.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_RECURRING_INVOICE, $query, !$clientPublicId); } @@ -28,13 +34,13 @@ class RecurringInvoiceService extends BaseService [ 'frequency', function ($model) { - return link_to("invoices/{$model->public_id}", $model->frequency); + return link_to("invoices/{$model->public_id}", $model->frequency)->toHtml(); } ], [ 'client_name', function ($model) { - return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)); + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); }, ! $hideClient ], @@ -66,6 +72,9 @@ class RecurringInvoiceService extends BaseService trans('texts.edit_invoice'), function ($model) { return URL::to("invoices/{$model->public_id}/edit"); + }, + function ($model) { + return Invoice::canEditItem($model); } ] ]; diff --git a/app/Services/Registrar.php b/app/Services/Registrar.php deleted file mode 100644 index cf19d6f4b70e..000000000000 --- a/app/Services/Registrar.php +++ /dev/null @@ -1,39 +0,0 @@ - 'required|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|confirmed|min:6', - ]); - } - - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * @return User - */ - public function create(array $data) - { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => bcrypt($data['password']), - ]); - } - -} diff --git a/app/Services/TaskService.php b/app/Services/TaskService.php index 5eded5db6e26..70e7e22c7c88 100644 --- a/app/Services/TaskService.php +++ b/app/Services/TaskService.php @@ -1,8 +1,11 @@ taskRepo->find($clientPublicId, $search); + if(!Utils::hasPermission('view_all')){ + $query->where('tasks.user_id', '=', Auth::user()->id); + } + return $this->createDatatable(ENTITY_TASK, $query, !$clientPublicId); } @@ -42,14 +49,18 @@ class TaskService extends BaseService [ 'client_name', function ($model) { - return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model)) : ''; + if(!Client::canViewItemByOwner($model->client_user_id)){ + return Utils::getClientDisplayName($model); + } + + return $model->client_public_id ? link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml() : ''; }, ! $hideClient ], [ 'created_at', function ($model) { - return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model)); + return link_to("tasks/{$model->public_id}/edit", Task::calcStartTime($model))->toHtml(); } ], [ @@ -82,7 +93,7 @@ class TaskService extends BaseService return URL::to('tasks/'.$model->public_id.'/edit'); }, function ($model) { - return !$model->deleted_at || $model->deleted_at == '0000-00-00'; + return (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Task::canEditItem($model); } ], [ @@ -91,7 +102,7 @@ class TaskService extends BaseService return URL::to("/invoices/{$model->invoice_public_id}/edit"); }, function ($model) { - return $model->invoice_number; + return $model->invoice_number && Invoice::canEditItemByOwner($model->invoice_user_id); } ], [ @@ -100,7 +111,7 @@ class TaskService extends BaseService return "javascript:stopTask({$model->public_id})"; }, function ($model) { - return $model->is_running; + return $model->is_running && Task::canEditItem($model); } ], [ @@ -109,7 +120,7 @@ class TaskService extends BaseService return "javascript:invoiceEntity({$model->public_id})"; }, function ($model) { - return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00'); + return ! $model->invoice_number && (!$model->deleted_at || $model->deleted_at == '0000-00-00') && Invoice::canCreate(); } ] ]; diff --git a/app/Services/TaxRateService.php b/app/Services/TaxRateService.php index 38d2867f275c..db5b041395f0 100644 --- a/app/Services/TaxRateService.php +++ b/app/Services/TaxRateService.php @@ -41,7 +41,7 @@ class TaxRateService extends BaseService [ 'name', function ($model) { - return link_to("tax_rates/{$model->public_id}/edit", $model->name); + return link_to("tax_rates/{$model->public_id}/edit", $model->name)->toHtml(); } ], [ diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php index 5a687a0552aa..8b428f0eb199 100644 --- a/app/Services/TokenService.php +++ b/app/Services/TokenService.php @@ -40,7 +40,7 @@ class TokenService extends BaseService [ 'name', function ($model) { - return link_to("tokens/{$model->public_id}/edit", $model->name); + return link_to("tokens/{$model->public_id}/edit", $model->name)->toHtml(); } ], [ diff --git a/app/Services/UserService.php b/app/Services/UserService.php index a8b9bc569102..8e31b4c30d2d 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -40,7 +40,7 @@ class UserService extends BaseService [ 'first_name', function ($model) { - return $model->public_id ? link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name) : ($model->first_name.' '.$model->last_name); + return $model->public_id ? link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name)->toHtml() : ($model->first_name.' '.$model->last_name); } ], [ @@ -53,11 +53,15 @@ class UserService extends BaseService 'confirmed', function ($model) { if (!$model->public_id) { - return self::getStatusLabel(USER_STATE_ADMIN); + return self::getStatusLabel(USER_STATE_OWNER); } elseif ($model->deleted_at) { return self::getStatusLabel(USER_STATE_DISABLED); } elseif ($model->confirmed) { - return self::getStatusLabel(USER_STATE_ACTIVE); + if($model->is_admin){ + return self::getStatusLabel(USER_STATE_ADMIN); + } else { + return self::getStatusLabel(USER_STATE_ACTIVE); + } } else { return self::getStatusLabel(USER_STATE_PENDING); } @@ -96,17 +100,20 @@ class UserService extends BaseService $class = 'default'; switch ($state) { case USER_STATE_PENDING: - $class = 'info'; + $class = 'default'; break; case USER_STATE_ACTIVE: - $class = 'primary'; + $class = 'info'; break; case USER_STATE_DISABLED: $class = 'warning'; break; - case USER_STATE_ADMIN: + case USER_STATE_OWNER: $class = 'success'; break; + case USER_STATE_ADMIN: + $class = 'primary'; + break; } return "

    $label

    "; } diff --git a/app/Services/VendorService.php b/app/Services/VendorService.php index 155443c8e089..6f9b4420d772 100644 --- a/app/Services/VendorService.php +++ b/app/Services/VendorService.php @@ -3,6 +3,8 @@ use Utils; use URL; use Auth; +use App\Models\Vendor; +use App\Models\Expense; use App\Services\BaseService; use App\Ninja\Repositories\VendorRepository; use App\Ninja\Repositories\NinjaRepository; @@ -36,6 +38,10 @@ class VendorService extends BaseService public function getDatatable($search) { $query = $this->vendorRepo->find($search); + + if(!Utils::hasPermission('view_all')){ + $query->where('vendors.user_id', '=', Auth::user()->id); + } return $this->createDatatable(ENTITY_VENDOR, $query); } @@ -46,7 +52,7 @@ class VendorService extends BaseService [ 'name', function ($model) { - return link_to("vendors/{$model->public_id}", $model->name ?: ''); + return link_to("vendors/{$model->public_id}", $model->name ?: '')->toHtml(); } ], [ @@ -64,7 +70,7 @@ class VendorService extends BaseService [ 'email', function ($model) { - return link_to("vendors/{$model->public_id}", $model->email ?: ''); + return link_to("vendors/{$model->public_id}", $model->email ?: '')->toHtml(); } ], [ @@ -83,13 +89,25 @@ class VendorService extends BaseService trans('texts.edit_vendor'), function ($model) { return URL::to("vendors/{$model->public_id}/edit"); + }, + function ($model) { + return Vendor::canEditItem($model); } ], - [], + [ + '--divider--', function(){return false;}, + function ($model) { + return Vendor::canEditItem($model) && Expense::canCreate(); + } + + ], [ trans('texts.enter_expense'), function ($model) { return URL::to("expenses/create/{$model->public_id}"); + }, + function ($model) { + return Expense::canCreate(); } ] ]; diff --git a/bootstrap/app.php b/bootstrap/app.php old mode 100644 new mode 100755 index 354e5dd90538..71f392315f59 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -58,4 +58,9 @@ if (strstr($_SERVER['HTTP_USER_AGENT'], 'PhantomJS') && Utils::isNinjaDev()) { } */ +// Write info messages to a separate file +$app->configureMonologUsing(function($monolog) { + $monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-info.log', Monolog\Logger::INFO, false)); +}); + return $app; diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php old mode 100644 new mode 100755 index f6387198c299..d1a406d25936 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -30,7 +30,7 @@ require __DIR__.'/../vendor/autoload.php'; | */ -$compiledPath = __DIR__.'/../vendor/compiled.php'; +$compiledPath = __DIR__.'/cache/compiled.php'; if (file_exists($compiledPath)) { diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100755 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/environment.php b/bootstrap/environment.php old mode 100644 new mode 100755 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/composer.json b/composer.json index 68d48cd64803..45e6e1872640 100644 --- a/composer.json +++ b/composer.json @@ -10,15 +10,19 @@ } ], "require": { + "turbo124/laravel-push-notification": "dev-laravel5", "omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525", "omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "omnipay/gocardless": "dev-master", "omnipay/stripe": "2.3.0", - "laravel/framework": "5.0.*", + "laravel/framework": "5.2.*", + "laravelcollective/html": "5.2.*", + "laravelcollective/bus": "5.2.*", + "symfony/css-selector": "~3.0", "patricktalmadge/bootstrapper": "5.5.x", "anahkiasen/former": "4.0.*@dev", - "barryvdh/laravel-debugbar": "~2.0.2", - "chumper/datatable": "dev-develop#7fa47cb", + "barryvdh/laravel-debugbar": "~2.0", + "chumper/datatable": "dev-develop#04ef2bf", "omnipay/omnipay": "~2.3.0", "intervention/image": "dev-master", "webpatser/laravel-countries": "dev-master", @@ -27,7 +31,6 @@ "jsanc623/phpbenchtime": "2.x", "lokielse/omnipay-alipay": "dev-master", "coatesap/omnipay-datacash": "~2.0", - "alfaproject/omnipay-neteller": "1.0.*@dev", "mfauveau/omnipay-pacnet": "~2.0", "coatesap/omnipay-paymentsense": "2.0.0", "coatesap/omnipay-realex": "~2.0", @@ -35,7 +38,6 @@ "alfaproject/omnipay-skrill": "dev-master", "omnipay/bitpay": "dev-master", "guzzlehttp/guzzle": "~6.0", - "laravelcollective/html": "~5.0", "wildbit/laravel-postmark-provider": "2.0", "Dwolla/omnipay-dwolla": "dev-master", "laravel/socialite": "~2.0", @@ -71,7 +73,8 @@ "phpspec/phpspec": "~2.1", "codeception/codeception": "*", "codeception/c3": "~2.0", - "fzaninotto/faker": "^1.5" + "fzaninotto/faker": "^1.5", + "symfony/dom-crawler": "~3.0" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 59574931678e..6ce01e65ba67 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": "fceb9a043eac244cb01d8e8378e6d66a", - "content-hash": "f717dc8e67caa65002f0f0689d4a5478", + "hash": "e5e8524886bd38794a15e406acc3745a", + "content-hash": "6b3f343959ba3f330c425325574dfe28", "packages": [ { "name": "agmscode/omnipay-agms", @@ -63,60 +63,6 @@ ], "time": "2015-03-21 20:06:25" }, - { - "name": "alfaproject/omnipay-neteller", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/alfaproject/omnipay-neteller.git", - "reference": "e2b9129936cf45bf10e0dd406f897d2852e3d567" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/alfaproject/omnipay-neteller/zipball/e2b9129936cf45bf10e0dd406f897d2852e3d567", - "reference": "e2b9129936cf45bf10e0dd406f897d2852e3d567", - "shasum": "" - }, - "require": { - "omnipay/common": "~2.0" - }, - "require-dev": { - "omnipay/tests": "~2.0", - "satooshi/php-coveralls": "dev-master" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Omnipay\\Neteller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joao Dias", - "email": "joao.dias@cherrygroup.com" - } - ], - "description": "Neteller driver for the Omnipay payment processing library", - "homepage": "https://github.com/alfaproject/omnipay-neteller", - "keywords": [ - "gateway", - "merchant", - "neteller", - "omnipay", - "pay", - "payment" - ], - "time": "2014-02-25 10:03:25" - }, { "name": "alfaproject/omnipay-skrill", "version": "dev-master", @@ -177,12 +123,12 @@ "source": { "type": "git", "url": "https://github.com/formers/former.git", - "reference": "795f7b9b200a4ff4a33b37a96eaaab0229e36325" + "reference": "d97f907741323b390f43954a90a227921ecc6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/formers/former/zipball/795f7b9b200a4ff4a33b37a96eaaab0229e36325", - "reference": "795f7b9b200a4ff4a33b37a96eaaab0229e36325", + "url": "https://api.github.com/repos/formers/former/zipball/d97f907741323b390f43954a90a227921ecc6b96", + "reference": "d97f907741323b390f43954a90a227921ecc6b96", "shasum": "" }, "require": { @@ -228,7 +174,7 @@ "foundation", "laravel" ], - "time": "2015-11-05 15:53:52" + "time": "2016-03-16 01:43:45" }, { "name": "anahkiasen/html-object", @@ -377,28 +323,28 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v2.0.6", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c" + "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c", - "reference": "23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/13b7058d2120c8d5af7f1ada21b7c44dd87b666a", + "reference": "13b7058d2120c8d5af7f1ada21b7c44dd87b666a", "shasum": "" }, "require": { - "illuminate/support": "~5.0.17|5.1.*", - "maximebf/debugbar": "~1.10.2", - "php": ">=5.4.0", - "symfony/finder": "~2.6" + "illuminate/support": "5.1.*|5.2.*", + "maximebf/debugbar": "~1.11.0", + "php": ">=5.5.9", + "symfony/finder": "~2.7|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -427,7 +373,7 @@ "profiler", "webprofiler" ], - "time": "2015-09-09 11:39:27" + "time": "2016-02-17 08:32:21" }, { "name": "barryvdh/laravel-ide-helper", @@ -435,12 +381,12 @@ "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "4b8ba85b346fc9962521661aa639911535b2bb3f" + "reference": "e97ed532f09e290b91ff7713b785ed7ab11d0812" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/4b8ba85b346fc9962521661aa639911535b2bb3f", - "reference": "4b8ba85b346fc9962521661aa639911535b2bb3f", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e97ed532f09e290b91ff7713b785ed7ab11d0812", + "reference": "e97ed532f09e290b91ff7713b785ed7ab11d0812", "shasum": "" }, "require": { @@ -448,7 +394,7 @@ "illuminate/filesystem": "5.0.x|5.1.x|5.2.x", "illuminate/support": "5.0.x|5.1.x|5.2.x", "php": ">=5.4.0", - "phpdocumentor/reflection-docblock": "2.0.4", + "phpdocumentor/reflection-docblock": "^2.0.4", "symfony/class-loader": "~2.3|~3.0" }, "require-dev": { @@ -490,7 +436,7 @@ "phpstorm", "sublime" ], - "time": "2016-01-28 08:19:58" + "time": "2016-03-03 14:38:04" }, { "name": "cardgate/omnipay-cardgate", @@ -587,12 +533,12 @@ "source": { "type": "git", "url": "https://github.com/Chumper/Datatable.git", - "reference": "7fa47cb" + "reference": "04ef2bf" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Chumper/Datatable/zipball/546e8768d7987b0d9a8501e67432309349ef5504", - "reference": "7fa47cb", + "reference": "04ef2bf", "shasum": "" }, "require": { @@ -633,39 +579,33 @@ "laravel" ], "abandoned": "OpenSkill/Datatable", - "time": "2015-11-23 21:33:41" + "time": "2015-04-29 07:00:36" }, { "name": "classpreloader/classpreloader", - "version": "1.4.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/ClassPreloader/ClassPreloader.git", - "reference": "b76f3f4f603ebbe7e64351a7ef973431ddaf7b27" + "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/b76f3f4f603ebbe7e64351a7ef973431ddaf7b27", - "reference": "b76f3f4f603ebbe7e64351a7ef973431ddaf7b27", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/9b10b913c2bdf90c3d2e0d726b454fb7f77c552a", + "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a", "shasum": "" }, "require": { - "nikic/php-parser": "~1.3", - "php": ">=5.3.3", - "symfony/console": "~2.1", - "symfony/filesystem": "~2.1", - "symfony/finder": "~2.1" + "nikic/php-parser": "^1.0|^2.0", + "php": ">=5.5.9" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.8|^5.0" }, - "bin": [ - "classpreloader.php" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -684,7 +624,7 @@ }, { "name": "Graham Campbell", - "email": "graham@cachethq.io" + "email": "graham@alt-three.com" } ], "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", @@ -693,7 +633,7 @@ "class", "preload" ], - "time": "2015-05-26 10:57:51" + "time": "2015-11-09 22:51:51" }, { "name": "coatesap/omnipay-datacash", @@ -908,60 +848,31 @@ "time": "2015-12-15 20:31:39" }, { - "name": "danielstjules/stringy", - "version": "1.10.0", + "name": "container-interop/container-interop", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/danielstjules/Stringy.git", - "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b" + "url": "https://github.com/container-interop/container-interop.git", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danielstjules/Stringy/zipball/4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", - "reference": "4749c205db47ee5b32e8d1adf6d9aff8db6caf3b", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", "shasum": "" }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, "type": "library", "autoload": { "psr-4": { - "Stringy\\": "src/" - }, - "files": [ - "src/Create.php" - ] + "Interop\\Container\\": "src/Interop/Container/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Daniel St. Jules", - "email": "danielst.jules@gmail.com", - "homepage": "http://www.danielstjules.com" - } - ], - "description": "A string manipulation library with multibyte support", - "homepage": "https://github.com/danielstjules/Stringy", - "keywords": [ - "UTF", - "helpers", - "manipulation", - "methods", - "multibyte", - "string", - "utf-8", - "utility", - "utils" - ], - "time": "2015-07-23 00:54:12" + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "time": "2014-12-30 15:22:37" }, { "name": "delatbabel/omnipay-fatzebra", @@ -969,12 +880,12 @@ "source": { "type": "git", "url": "https://github.com/delatbabel/omnipay-fatzebra.git", - "reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc" + "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc", - "reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc", + "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/d0a56a8704357d91457672741a48a4cb6c7ecd53", + "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53", "shasum": "" }, "require": { @@ -1018,7 +929,7 @@ "payment", "paystream" ], - "time": "2015-02-15 11:27:23" + "time": "2016-03-21 09:21:14" }, { "name": "dercoder/omnipay-ecopayz", @@ -1128,12 +1039,12 @@ "source": { "type": "git", "url": "https://github.com/descubraomundo/omnipay-pagarme.git", - "reference": "528953568929b57189de16fa7431eaab75d61840" + "reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/528953568929b57189de16fa7431eaab75d61840", - "reference": "528953568929b57189de16fa7431eaab75d61840", + "url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/8571396139eb1fb1a7011450714a5e8d8d604d8c", + "reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c", "shasum": "" }, "require": { @@ -1170,7 +1081,7 @@ "pay", "payment" ], - "time": "2015-10-27 19:17:20" + "time": "2016-03-18 19:37:37" }, { "name": "dioscouri/omnipay-cybersource", @@ -1935,22 +1846,22 @@ }, { "name": "guzzle/guzzle", - "version": "v3.9.3", + "version": "v3.8.1", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle3.git", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", "shasum": "" }, "require": { "ext-curl": "*", "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" + "symfony/event-dispatcher": ">=2.1" }, "replace": { "guzzle/batch": "self.version", @@ -1977,21 +1888,18 @@ "guzzle/stream": "self.version" }, "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", + "doctrine/cache": "*", + "monolog/monolog": "1.*", "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.9-dev" + "dev-master": "3.8-dev" } }, "autoload": { @@ -2015,7 +1923,7 @@ "homepage": "https://github.com/guzzle/guzzle/contributors" } ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", "homepage": "http://guzzlephp.org/", "keywords": [ "client", @@ -2026,20 +1934,20 @@ "rest", "web service" ], - "time": "2015-03-18 18:23:50" + "time": "2014-01-28 22:29:15" }, { "name": "guzzlehttp/guzzle", - "version": "6.1.1", + "version": "6.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" + "reference": "d094e337976dff9d8e2424e8485872194e768662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", + "reference": "d094e337976dff9d8e2424e8485872194e768662", "shasum": "" }, "require": { @@ -2055,7 +1963,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -2088,20 +1996,20 @@ "rest", "web service" ], - "time": "2015-11-23 00:47:50" + "time": "2016-03-21 20:02:09" }, { "name": "guzzlehttp/promises", - "version": "1.0.3", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea" + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea", - "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8", + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8", "shasum": "" }, "require": { @@ -2139,20 +2047,20 @@ "keywords": [ "promise" ], - "time": "2015-10-15 22:28:00" + "time": "2016-03-08 01:15:46" }, { "name": "guzzlehttp/psr7", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb" + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb", - "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b", + "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b", "shasum": "" }, "require": { @@ -2197,7 +2105,7 @@ "stream", "uri" ], - "time": "2016-01-23 01:23:02" + "time": "2016-02-18 21:54:00" }, { "name": "illuminate/html", @@ -2306,12 +2214,12 @@ "source": { "type": "git", "url": "https://github.com/Intervention/image.git", - "reference": "86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931" + "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931", - "reference": "86dfe2f2a95aa9eed58faf1cc1dae6534a6ce931", + "url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6", + "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6", "shasum": "" }, "require": { @@ -2360,7 +2268,7 @@ "thumbnail", "watermark" ], - "time": "2016-01-10 11:20:02" + "time": "2016-02-26 18:18:19" }, { "name": "ircmaxell/password-compat", @@ -2695,12 +2603,12 @@ "source": { "type": "git", "url": "https://github.com/labs7in0/omnipay-wechat.git", - "reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a" + "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", - "reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", + "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e", + "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "shasum": "" }, "require": { @@ -2736,7 +2644,7 @@ "purchase", "wechat" ], - "time": "2015-11-16 11:04:21" + "time": "2016-03-18 09:59:11" }, { "name": "laracasts/presenter", @@ -2786,48 +2694,47 @@ }, { "name": "laravel/framework", - "version": "v5.0.35", + "version": "v5.2.24", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "37151cf533f468e2227605e4b9ac596154f6b92b" + "reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/37151cf533f468e2227605e4b9ac596154f6b92b", - "reference": "37151cf533f468e2227605e4b9ac596154f6b92b", + "url": "https://api.github.com/repos/laravel/framework/zipball/396297a5fd3c70c2fc1af68f09ee574a2380175c", + "reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c", "shasum": "" }, "require": { - "classpreloader/classpreloader": "~1.2", - "danielstjules/stringy": "~1.8", + "classpreloader/classpreloader": "~3.0", "doctrine/inflector": "~1.0", "ext-mbstring": "*", - "ext-mcrypt": "*", "ext-openssl": "*", - "ircmaxell/password-compat": "~1.0", - "jeremeamia/superclosure": "~2.0", + "jeremeamia/superclosure": "~2.2", "league/flysystem": "~1.0", "monolog/monolog": "~1.11", "mtdowling/cron-expression": "~1.0", - "nesbot/carbon": "~1.0", - "php": ">=5.4.0", - "psy/psysh": "0.4.*", + "nesbot/carbon": "~1.20", + "paragonie/random_compat": "~1.4", + "php": ">=5.5.9", + "psy/psysh": "0.7.*", "swiftmailer/swiftmailer": "~5.1", - "symfony/console": "2.6.*", - "symfony/debug": "2.6.*", - "symfony/finder": "2.6.*", - "symfony/http-foundation": "2.6.*", - "symfony/http-kernel": "2.6.*", - "symfony/process": "2.6.*", - "symfony/routing": "2.6.*", - "symfony/security-core": "2.6.*", - "symfony/translation": "2.6.*", - "symfony/var-dumper": "2.6.*", - "vlucas/phpdotenv": "~1.0" + "symfony/console": "2.8.*|3.0.*", + "symfony/debug": "2.8.*|3.0.*", + "symfony/finder": "2.8.*|3.0.*", + "symfony/http-foundation": "2.8.*|3.0.*", + "symfony/http-kernel": "2.8.*|3.0.*", + "symfony/polyfill-php56": "~1.0", + "symfony/process": "2.8.*|3.0.*", + "symfony/routing": "2.8.*|3.0.*", + "symfony/translation": "2.8.*|3.0.*", + "symfony/var-dumper": "2.8.*|3.0.*", + "vlucas/phpdotenv": "~2.2" }, "replace": { "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/config": "self.version", @@ -2840,7 +2747,6 @@ "illuminate/events": "self.version", "illuminate/exception": "self.version", "illuminate/filesystem": "self.version", - "illuminate/foundation": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", "illuminate/log": "self.version", @@ -2857,27 +2763,31 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "~2.4", - "iron-io/iron_mq": "~1.5", - "mockery/mockery": "~0.9", + "aws/aws-sdk-php": "~3.0", + "mockery/mockery": "~0.9.2", "pda/pheanstalk": "~3.0", - "phpunit/phpunit": "~4.0", - "predis/predis": "~1.0" + "phpunit/phpunit": "~4.1", + "predis/predis": "~1.0", + "symfony/css-selector": "2.8.*|3.0.*", + "symfony/dom-crawler": "2.8.*|3.0.*" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~2.4).", + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).", - "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers (~5.0).", - "iron-io/iron_mq": "Required to use the iron queue driver (~1.5).", - "league/flysystem-aws-s3-v2": "Required to use the Flysystem S3 driver (~1.0).", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", - "predis/predis": "Required to use the redis cache and queue drivers (~1.0)." + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "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.*)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.2-dev" } }, "autoload": { @@ -2908,7 +2818,7 @@ "framework", "laravel" ], - "time": "2016-02-02 14:55:52" + "time": "2016-03-22 13:45:19" }, { "name": "laravel/socialite", @@ -2965,31 +2875,78 @@ "time": "2015-10-16 15:39:46" }, { - "name": "laravelcollective/html", - "version": "v5.0.4", + "name": "laravelcollective/bus", + "version": "v5.2", "source": { "type": "git", - "url": "https://github.com/LaravelCollective/html.git", - "reference": "c55fda58b1a9a1b58bd04f97e0fb9ebc238a0a94" + "url": "https://github.com/LaravelCollective/bus.git", + "reference": "e48b4d44d49f820e1b85ff16b9402e01c770c83a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/c55fda58b1a9a1b58bd04f97e0fb9ebc238a0a94", - "reference": "c55fda58b1a9a1b58bd04f97e0fb9ebc238a0a94", + "url": "https://api.github.com/repos/LaravelCollective/bus/zipball/e48b4d44d49f820e1b85ff16b9402e01c770c83a", + "reference": "e48b4d44d49f820e1b85ff16b9402e01c770c83a", "shasum": "" }, "require": { - "illuminate/http": "~5.0", - "illuminate/routing": "~5.0", - "illuminate/session": "~5.0", - "illuminate/support": "~5.0", - "php": ">=5.4.0" + "illuminate/container": "5.2.*", + "illuminate/contracts": "5.2.*", + "illuminate/pipeline": "5.2.*", + "illuminate/support": "5.2.*", + "php": ">=5.5.9" }, "require-dev": { "mockery/mockery": "~0.9", "phpunit/phpunit": "~4.0" }, "type": "library", + "autoload": { + "psr-4": { + "Collective\\Bus\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "The Laravel Bus (5.1) package for use in Laravel 5.2.", + "homepage": "http://laravelcollective.com", + "time": "2015-12-23 07:43:33" + }, + { + "name": "laravelcollective/html", + "version": "v5.2.4", + "source": { + "type": "git", + "url": "https://github.com/LaravelCollective/html.git", + "reference": "3a312d39ffe37da0f57b602618b61fd07c1fcec5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/LaravelCollective/html/zipball/3a312d39ffe37da0f57b602618b61fd07c1fcec5", + "reference": "3a312d39ffe37da0f57b602618b61fd07c1fcec5", + "shasum": "" + }, + "require": { + "illuminate/http": "5.2.*", + "illuminate/routing": "5.2.*", + "illuminate/session": "5.2.*", + "illuminate/support": "5.2.*", + "illuminate/view": "5.2.*", + "php": ">=5.5.9" + }, + "require-dev": { + "illuminate/database": "5.2.*", + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0" + }, + "type": "library", "autoload": { "psr-4": { "Collective\\Html\\": "src/" @@ -3012,20 +2969,22 @@ "email": "adam@laravelcollective.com" } ], - "time": "2015-05-06 14:23:37" + "description": "HTML and Form Builders for the Laravel Framework", + "homepage": "http://laravelcollective.com", + "time": "2016-01-27 22:29:54" }, { "name": "league/flysystem", - "version": "1.0.16", + "version": "1.0.20", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031" + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/183e1a610664baf6dcd6fceda415baf43cbdc031", - "reference": "183e1a610664baf6dcd6fceda415baf43cbdc031", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e87a786e3ae12a25cf78a71bb07b4b384bfaa83a", + "reference": "e87a786e3ae12a25cf78a71bb07b4b384bfaa83a", "shasum": "" }, "require": { @@ -3038,8 +2997,7 @@ "ext-fileinfo": "*", "mockery/mockery": "~0.9", "phpspec/phpspec": "^2.2", - "phpspec/prophecy-phpunit": "~1.0", - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "~4.8 || ~5.0" }, "suggest": { "ext-fileinfo": "Required for MimeType", @@ -3096,7 +3054,7 @@ "sftp", "storage" ], - "time": "2015-12-19 20:16:43" + "time": "2016-03-14 21:54:11" }, { "name": "league/fractal", @@ -3329,16 +3287,16 @@ }, { "name": "maatwebsite/excel", - "version": "v2.1.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/Maatwebsite/Laravel-Excel.git", - "reference": "4c89f4cb1d30090a256530e1dce0ed3765eca7fa" + "reference": "aae17dbd6f39606cf8f588359a7a79438ab5aa53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/4c89f4cb1d30090a256530e1dce0ed3765eca7fa", - "reference": "4c89f4cb1d30090a256530e1dce0ed3765eca7fa", + "url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/aae17dbd6f39606cf8f588359a7a79438ab5aa53", + "reference": "aae17dbd6f39606cf8f588359a7a79438ab5aa53", "shasum": "" }, "require": { @@ -3393,29 +3351,29 @@ "import", "laravel" ], - "time": "2015-12-15 20:14:50" + "time": "2016-03-01 17:45:44" }, { "name": "maximebf/debugbar", - "version": "v1.10.5", + "version": "v1.11.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636" + "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e53e8a28284b69dd223c9f5ee8957befd72636", - "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f", + "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f", "shasum": "" }, "require": { "php": ">=5.3.0", - "psr/log": "~1.0", - "symfony/var-dumper": "~2.6" + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.0|^5.0" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -3425,12 +3383,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-master": "1.11-dev" } }, "autoload": { - "psr-0": { - "DebugBar": "src/" + "psr-4": { + "DebugBar\\": "src/DebugBar/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3442,14 +3400,19 @@ "name": "Maxime Bouroumeau-Fuseau", "email": "maxime.bouroumeau@gmail.com", "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" } ], "description": "Debug bar in the browser for php application", "homepage": "https://github.com/maximebf/php-debugbar", "keywords": [ - "debug" + "debug", + "debugbar" ], - "time": "2015-10-19 20:35:12" + "time": "2016-01-22 12:22:23" }, { "name": "meebio/omnipay-creditcall", @@ -3624,16 +3587,16 @@ }, { "name": "monolog/monolog", - "version": "1.17.2", + "version": "1.18.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" + "reference": "a5f2734e8c16f3aa21b3da09715d10e15b4d2d45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", - "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a5f2734e8c16f3aa21b3da09715d10e15b4d2d45", + "reference": "a5f2734e8c16f3aa21b3da09715d10e15b4d2d45", "shasum": "" }, "require": { @@ -3662,6 +3625,7 @@ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", "ext-mongo": "Allow sending log messages to a MongoDB server", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", "php-console/php-console": "Allow sending log messages to Google Chrome", "raven/raven": "Allow sending log messages to a Sentry server", "rollbar/rollbar": "Allow sending log messages to Rollbar", @@ -3671,7 +3635,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.16.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -3697,7 +3661,7 @@ "logging", "psr-3" ], - "time": "2015-10-14 12:51:02" + "time": "2016-03-13 16:08:35" }, { "name": "mtdowling/cron-expression", @@ -3792,32 +3756,38 @@ }, { "name": "nikic/php-parser", - "version": "v1.4.1", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" + "reference": "ce5be709d59b32dd8a88c80259028759991a4206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ce5be709d59b32dd8a88c80259028759991a4206", + "reference": "ce5be709d59b32dd8a88c80259028759991a4206", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3" + "php": ">=5.4" }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { - "files": [ - "lib/bootstrap.php" - ] + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3833,7 +3803,7 @@ "parser", "php" ], - "time": "2015-09-19 14:15:08" + "time": "2016-02-28 19:48:28" }, { "name": "omnipay/2checkout", @@ -3845,7 +3815,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/31394ce58d5999b6f49b321cb3547747837c1297", + "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/77b316bd08c6b7a1e93721f15d1bfbd21a62ba6b", "reference": "e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "shasum": "" }, @@ -3896,16 +3866,16 @@ }, { "name": "omnipay/authorizenet", - "version": "v2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-authorizenet.git", - "reference": "142a95f550a5320db09e66019ecf5c8b8c3885b9" + "reference": "e2e813b0b6306ef97b8763037f05476456546b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-authorizenet/zipball/142a95f550a5320db09e66019ecf5c8b8c3885b9", - "reference": "142a95f550a5320db09e66019ecf5c8b8c3885b9", + "url": "https://api.github.com/repos/thephpleague/omnipay-authorizenet/zipball/e2e813b0b6306ef97b8763037f05476456546b3e", + "reference": "e2e813b0b6306ef97b8763037f05476456546b3e", "shasum": "" }, "require": { @@ -3951,7 +3921,7 @@ "pay", "payment" ], - "time": "2015-07-15 18:11:17" + "time": "2016-03-10 11:35:24" }, { "name": "omnipay/bitpay", @@ -3959,12 +3929,12 @@ "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-bitpay.git", - "reference": "e659f0e993c586cb36acafaf50835570b4a16eb2" + "reference": "cf813f1d5436a1d2f942d3df6666695d1e2b5280" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/e659f0e993c586cb36acafaf50835570b4a16eb2", - "reference": "e659f0e993c586cb36acafaf50835570b4a16eb2", + "url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/cf813f1d5436a1d2f942d3df6666695d1e2b5280", + "reference": "cf813f1d5436a1d2f942d3df6666695d1e2b5280", "shasum": "" }, "require": { @@ -4009,7 +3979,7 @@ "pay", "payment" ], - "time": "2015-03-23 14:18:26" + "time": "2016-03-10 03:16:04" }, { "name": "omnipay/buckaroo", @@ -4185,20 +4155,20 @@ }, { "name": "omnipay/common", - "version": "v2.3.4", + "version": "v2.3.3", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-common.git", - "reference": "fcd5a606713d11536c89315a5ae02d965a737c21" + "reference": "e4c54a314a2529c1008ad3f77e9eef26ed1f311b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/fcd5a606713d11536c89315a5ae02d965a737c21", - "reference": "fcd5a606713d11536c89315a5ae02d965a737c21", + "url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/e4c54a314a2529c1008ad3f77e9eef26ed1f311b", + "reference": "e4c54a314a2529c1008ad3f77e9eef26ed1f311b", "shasum": "" }, "require": { - "guzzle/guzzle": "~3.9", + "guzzle/http": "~3.1", "php": ">=5.3.2", "symfony/http-foundation": "~2.1" }, @@ -4297,7 +4267,7 @@ "payment", "purchase" ], - "time": "2015-03-30 14:34:46" + "time": "2015-01-11 04:54:29" }, { "name": "omnipay/dummy", @@ -4358,16 +4328,16 @@ }, { "name": "omnipay/eway", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-eway.git", - "reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16" + "reference": "1c953630f7097bfdeed200b17a847015a4df5607" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/0dcf28596f0382fbfc3ee229e98e60798675ed16", - "reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16", + "url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/1c953630f7097bfdeed200b17a847015a4df5607", + "reference": "1c953630f7097bfdeed200b17a847015a4df5607", "shasum": "" }, "require": { @@ -4411,7 +4381,7 @@ "pay", "payment" ], - "time": "2015-03-30 00:28:33" + "time": "2016-03-22 01:11:02" }, { "name": "omnipay/firstdata", @@ -4654,7 +4624,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-mollie/zipball/a89cb0d15447023b24c03f86873c1c1489cd021b", + "url": "https://api.github.com/repos/thephpleague/omnipay-mollie/zipball/efd491fdac7d1243e2dd1da5a964514e3aab2a5a", "reference": "22956c1a62a9662afa5f5d119723b413770ac525", "shasum": "" }, @@ -4703,16 +4673,16 @@ }, { "name": "omnipay/multisafepay", - "version": "V2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-multisafepay.git", - "reference": "a0f09abf404ca0dd21b553578d7f95df2bfa5318" + "reference": "342d0a3ba1a5ef0d788f20d23d0c70ce04ec3de1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-multisafepay/zipball/a0f09abf404ca0dd21b553578d7f95df2bfa5318", - "reference": "a0f09abf404ca0dd21b553578d7f95df2bfa5318", + "url": "https://api.github.com/repos/thephpleague/omnipay-multisafepay/zipball/342d0a3ba1a5ef0d788f20d23d0c70ce04ec3de1", + "reference": "342d0a3ba1a5ef0d788f20d23d0c70ce04ec3de1", "shasum": "" }, "require": { @@ -4757,7 +4727,7 @@ "pay", "payment" ], - "time": "2015-01-14 04:01:43" + "time": "2016-02-18 00:06:08" }, { "name": "omnipay/netaxept", @@ -5164,16 +5134,16 @@ }, { "name": "omnipay/paypal", - "version": "v2.5.1", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-paypal.git", - "reference": "b546d24241725061d44e60516f0fbce202336963" + "reference": "97fc3b1ff43e130ee911b35e139dcc853488d07a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/b546d24241725061d44e60516f0fbce202336963", - "reference": "b546d24241725061d44e60516f0fbce202336963", + "url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/97fc3b1ff43e130ee911b35e139dcc853488d07a", + "reference": "97fc3b1ff43e130ee911b35e139dcc853488d07a", "shasum": "" }, "require": { @@ -5218,7 +5188,7 @@ "paypal", "purchase" ], - "time": "2016-01-13 07:03:27" + "time": "2016-02-29 00:06:43" }, { "name": "omnipay/pin", @@ -5566,16 +5536,16 @@ }, { "name": "paragonie/random_compat", - "version": "v1.2.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "b0e69d10852716b2ccbdff69c75c477637220790" + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790", - "reference": "b0e69d10852716b2ccbdff69c75c477637220790", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774", + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774", "shasum": "" }, "require": { @@ -5610,7 +5580,7 @@ "pseudorandom", "random" ], - "time": "2016-02-06 03:52:05" + "time": "2016-03-18 20:34:03" }, { "name": "patricktalmadge/bootstrapper", @@ -5866,28 +5836,29 @@ }, { "name": "psy/psysh", - "version": "v0.4.4", + "version": "v0.7.2", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "489816db71649bd95b416e3ed9062d40528ab0ac" + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/489816db71649bd95b416e3ed9062d40528ab0ac", - "reference": "489816db71649bd95b416e3ed9062d40528ab0ac", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280", + "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280", "shasum": "" }, "require": { "dnoegel/php-xdg-base-dir": "0.1", "jakub-onderka/php-console-highlighter": "0.3.*", - "nikic/php-parser": "~1.0", - "php": ">=5.3.0", - "symfony/console": "~2.3.10|~2.4.2|~2.5" + "nikic/php-parser": "^1.2.1|~2.0", + "php": ">=5.3.9", + "symfony/console": "~2.3.10|^2.4.2|~3.0", + "symfony/var-dumper": "~2.7|~3.0" }, "require-dev": { "fabpot/php-cs-fixer": "~1.5", - "phpunit/phpunit": "~3.7|~4.0", + "phpunit/phpunit": "~3.7|~4.0|~5.0", "squizlabs/php_codesniffer": "~2.0", "symfony/finder": "~2.1|~3.0" }, @@ -5903,15 +5874,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.4.x-dev" + "dev-develop": "0.8.x-dev" } }, "autoload": { "files": [ "src/Psy/functions.php" ], - "psr-0": { - "Psy\\": "src/" + "psr-4": { + "Psy\\": "src/Psy/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5933,20 +5904,20 @@ "interactive", "shell" ], - "time": "2015-03-26 18:43:54" + "time": "2016-03-09 05:03:14" }, { "name": "samvaughton/omnipay-barclays-epdq", - "version": "2.1.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/samvaughton/omnipay-barclays-epdq.git", - "reference": "f971de37aa40c72cc58f02d05f540a93b2c5958e" + "reference": "b7f9263afa73b8e6c3c5e8bb2bf04a82548a41da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/samvaughton/omnipay-barclays-epdq/zipball/f971de37aa40c72cc58f02d05f540a93b2c5958e", - "reference": "f971de37aa40c72cc58f02d05f540a93b2c5958e", + "url": "https://api.github.com/repos/samvaughton/omnipay-barclays-epdq/zipball/b7f9263afa73b8e6c3c5e8bb2bf04a82548a41da", + "reference": "b7f9263afa73b8e6c3c5e8bb2bf04a82548a41da", "shasum": "" }, "require": { @@ -5995,7 +5966,7 @@ "pay", "payment" ], - "time": "2015-05-07 14:45:43" + "time": "2016-03-03 14:40:27" }, { "name": "simshaun/recurr", @@ -6003,12 +5974,12 @@ "source": { "type": "git", "url": "https://github.com/simshaun/recurr.git", - "reference": "53c1ddacc7c91b25cc21983731bc2c8137d143f6" + "reference": "4d875b0237dcef75ee0de80d9bd63609349568d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/simshaun/recurr/zipball/53c1ddacc7c91b25cc21983731bc2c8137d143f6", - "reference": "53c1ddacc7c91b25cc21983731bc2c8137d143f6", + "url": "https://api.github.com/repos/simshaun/recurr/zipball/4d875b0237dcef75ee0de80d9bd63609349568d4", + "reference": "4d875b0237dcef75ee0de80d9bd63609349568d4", "shasum": "" }, "require": { @@ -6049,7 +6020,69 @@ "recurring", "rrule" ], - "time": "2015-12-10 10:27:22" + "time": "2016-02-22 17:40:00" + }, + { + "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" }, { "name": "softcommerce/omnipay-paytrace", @@ -6158,7 +6191,7 @@ }, { "name": "symfony/class-loader", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -6214,27 +6247,26 @@ }, { "name": "symfony/console", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Console", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359" + "reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", - "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359", + "url": "https://api.github.com/repos/symfony/console/zipball/2ed5e2706ce92313d120b8fe50d1063bcfd12e04", + "reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/phpunit-bridge": "~2.7", - "symfony/process": "~2.1" + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" }, "suggest": { "psr/log": "For using the console logger", @@ -6244,13 +6276,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\Console\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6268,11 +6303,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-07-26 09:08:40" + "time": "2016-02-28 16:24:34" }, { "name": "symfony/css-selector", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -6325,46 +6360,42 @@ }, { "name": "symfony/debug", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Debug", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "fca5696e0c9787722baa8f2ad6940dfd7a6a6941" + "reference": "29606049ced1ec715475f88d1bbe587252a3476e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/fca5696e0c9787722baa8f2ad6940dfd7a6a6941", - "reference": "fca5696e0c9787722baa8f2ad6940dfd7a6a6941", + "url": "https://api.github.com/repos/symfony/debug/zipball/29606049ced1ec715475f88d1bbe587252a3476e", + "reference": "29606049ced1ec715475f88d1bbe587252a3476e", "shasum": "" }, "require": { - "php": ">=5.3.3", + "php": ">=5.5.9", "psr/log": "~1.0" }, "conflict": { "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" }, "require-dev": { - "symfony/class-loader": "~2.2", - "symfony/http-foundation": "~2.1", - "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2", - "symfony/phpunit-bridge": "~2.7" - }, - "suggest": { - "symfony/http-foundation": "", - "symfony/http-kernel": "" + "symfony/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\Debug\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6382,31 +6413,31 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2015-07-08 05:59:48" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "ee278f7c851533e58ca307f66305ccb9188aceda" + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", - "reference": "ee278f7c851533e58ca307f66305ccb9188aceda", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", + "reference": "4dd5df31a28c0f82b41cb1e1599b74b5dcdbdafa", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" }, "suggest": { "symfony/dependency-injection": "", @@ -6415,7 +6446,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -6442,34 +6473,34 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-01-13 10:28:07" + "time": "2016-01-27 05:14:46" }, { - "name": "symfony/filesystem", - "version": "v2.8.2", + "name": "symfony/finder", + "version": "v3.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c" + "url": "https://github.com/symfony/finder.git", + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/637b64d0ee10f44ae98dbad651b1ecdf35a11e8c", - "reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c", + "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723", + "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\Finder\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6489,94 +6520,44 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "time": "2016-01-13 10:28:07" - }, - { - "name": "symfony/finder", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Finder", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "203a10f928ae30176deeba33512999233181dd28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/203a10f928ae30176deeba33512999233181dd28", - "reference": "203a10f928ae30176deeba33512999233181dd28", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Finder\\": "" - } - }, - "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 Finder Component", "homepage": "https://symfony.com", - "time": "2015-07-09 16:02:48" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/http-foundation", - "version": "v2.6.13", - "target-dir": "Symfony/Component/HttpFoundation", + "version": "v2.8.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c" + "reference": "6f4e41c41e7d352ed9adf71ff6f2ec1756490a1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", - "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6f4e41c41e7d352ed9adf71ff6f2ec1756490a1b", + "reference": "6f4e41c41e7d352ed9adf71ff6f2ec1756490a1b", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.3.9", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0" }, "require-dev": { - "symfony/expression-language": "~2.4", - "symfony/phpunit-bridge": "~2.7" + "symfony/expression-language": "~2.4|~3.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.8-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, - "classmap": [ - "Symfony/Component/HttpFoundation/Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6595,47 +6576,48 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2015-07-22 10:08:40" + "time": "2016-02-28 16:20:50" }, { "name": "symfony/http-kernel", - "version": "v2.6.13", - "target-dir": "Symfony/Component/HttpKernel", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "cdd991d304fed833514dc44d6aafcf19397c26cb" + "reference": "59c0a1972e9aad87b7a56bbe1ccee26b7535a0db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cdd991d304fed833514dc44d6aafcf19397c26cb", - "reference": "cdd991d304fed833514dc44d6aafcf19397c26cb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/59c0a1972e9aad87b7a56bbe1ccee26b7535a0db", + "reference": "59c0a1972e9aad87b7a56bbe1ccee26b7535a0db", "shasum": "" }, "require": { - "php": ">=5.3.3", + "php": ">=5.5.9", "psr/log": "~1.0", - "symfony/debug": "~2.6,>=2.6.2", - "symfony/event-dispatcher": "~2.6,>=2.6.7", - "symfony/http-foundation": "~2.5,>=2.5.4" + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0" + }, + "conflict": { + "symfony/config": "<2.8" }, "require-dev": { - "symfony/browser-kit": "~2.3", - "symfony/class-loader": "~2.1", - "symfony/config": "~2.0,>=2.0.5", - "symfony/console": "~2.3", - "symfony/css-selector": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.2", - "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/expression-language": "~2.4", - "symfony/finder": "~2.0,>=2.0.5", - "symfony/phpunit-bridge": "~2.7", - "symfony/process": "~2.0,>=2.0.5", - "symfony/routing": "~2.2", - "symfony/stopwatch": "~2.3", - "symfony/templating": "~2.2", - "symfony/translation": "~2.0,>=2.0.5", - "symfony/var-dumper": "~2.6" + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~2.8|~3.0" }, "suggest": { "symfony/browser-kit": "", @@ -6649,13 +6631,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\HttpKernel\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6673,11 +6658,238 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2016-01-14 10:11:16" + "time": "2016-02-28 21:33:13" + }, + { + "name": "symfony/options-resolver", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "bc7ee3ef3905f7c21fbed4e921cb39d6518b1300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/bc7ee3ef3905f7c21fbed4e921cb39d6518b1300", + "reference": "bc7ee3ef3905f7c21fbed4e921cb39d6518b1300", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "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 OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2016-01-21 09:38:31" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "1289d16209491b584839022f29257ad859b8532d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d", + "reference": "1289d16209491b584839022f29257ad859b8532d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/polyfill-php54", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php54.git", + "reference": "9ba741ca01c77282ecf5796c2c1d667f03454ffb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/9ba741ca01c77282ecf5796c2c1d667f03454ffb", + "reference": "9ba741ca01c77282ecf5796c2c1d667f03454ffb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php54\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-25 19:13:00" + }, + { + "name": "symfony/polyfill-php55", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php55.git", + "reference": "b4f3f07d91702f8f926339fc4fcf81671d8c27e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/b4f3f07d91702f8f926339fc4fcf81671d8c27e6", + "reference": "b4f3f07d91702f8f926339fc4fcf81671d8c27e6", + "shasum": "" + }, + "require": { + "ircmaxell/password-compat": "~1.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php55\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" }, { "name": "symfony/polyfill-php56", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", @@ -6733,7 +6945,7 @@ }, { "name": "symfony/polyfill-util", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", @@ -6785,35 +6997,34 @@ }, { "name": "symfony/process", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Process", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "57f1e88bb5dafa449b83f9f265b11d52d517b3e9" + "reference": "dfecef47506179db2501430e732adbf3793099c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/57f1e88bb5dafa449b83f9f265b11d52d517b3e9", - "reference": "57f1e88bb5dafa449b83f9f265b11d52d517b3e9", + "url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8", + "reference": "dfecef47506179db2501430e732adbf3793099c8", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\Process\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6831,52 +7042,58 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-06-30 16:10:16" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/routing", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Routing", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0a1764d41bbb54f3864808c50569ac382b44d128" + "reference": "fa1e9a8173cf0077dd995205da453eacd758fdf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0a1764d41bbb54f3864808c50569ac382b44d128", - "reference": "0a1764d41bbb54f3864808c50569ac382b44d128", + "url": "https://api.github.com/repos/symfony/routing/zipball/fa1e9a8173cf0077dd995205da453eacd758fdf6", + "reference": "fa1e9a8173cf0077dd995205da453eacd758fdf6", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8" }, "require-dev": { "doctrine/annotations": "~1.0", "doctrine/common": "~2.2", "psr/log": "~1.0", - "symfony/config": "~2.2", - "symfony/expression-language": "~2.4", - "symfony/http-foundation": "~2.3", - "symfony/phpunit-bridge": "~2.7", - "symfony/yaml": "~2.0,>=2.0.5" + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\Routing\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6900,97 +7117,34 @@ "uri", "url" ], - "time": "2015-07-09 16:02:48" - }, - { - "name": "symfony/security-core", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Security/Core", - "source": { - "type": "git", - "url": "https://github.com/symfony/security-core.git", - "reference": "813cf2aaacccbbe1a4705aef8d4ac0d79d993a76" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/813cf2aaacccbbe1a4705aef8d4ac0d79d993a76", - "reference": "813cf2aaacccbbe1a4705aef8d4ac0d79d993a76", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "~1.0", - "php": ">=5.3.3" - }, - "require-dev": { - "ircmaxell/password-compat": "1.0.*", - "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/expression-language": "~2.6", - "symfony/http-foundation": "~2.4", - "symfony/phpunit-bridge": "~2.7", - "symfony/translation": "~2.0,>=2.0.5", - "symfony/validator": "~2.5,>=2.5.5" - }, - "suggest": { - "ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5", - "symfony/event-dispatcher": "", - "symfony/expression-language": "For using the expression voter", - "symfony/http-foundation": "", - "symfony/validator": "For using the user password constraint" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Security\\Core\\": "" - } - }, - "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 Security Component - Core Library", - "homepage": "https://symfony.com", - "time": "2016-01-14 09:04:34" + "time": "2016-02-04 13:53:13" }, { "name": "symfony/translation", - "version": "v2.6.13", - "target-dir": "Symfony/Component/Translation", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "d84291215b5892834dd8ca8ee52f9cbdb8274904" + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/d84291215b5892834dd8ca8ee52f9cbdb8274904", - "reference": "d84291215b5892834dd8ca8ee52f9cbdb8274904", + "url": "https://api.github.com/repos/symfony/translation/zipball/2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", + "reference": "2de0b6f7ebe43cffd8a06996ebec6aab79ea9e91", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.3,>=2.3.12", - "symfony/intl": "~2.3", - "symfony/phpunit-bridge": "~2.7", - "symfony/yaml": "~2.2" + "symfony/config": "~2.8|~3.0", + "symfony/intl": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" }, "suggest": { "psr/log": "To use logging capability in translator", @@ -7000,13 +7154,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Symfony\\Component\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7024,28 +7181,28 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2015-07-08 05:59:48" + "time": "2016-02-02 13:44:19" }, { "name": "symfony/var-dumper", - "version": "v2.6.13", - "target-dir": "Symfony/Component/VarDumper", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5fba957a30161d8724aade093593cd22f815bea2" + "reference": "9a6a883c48acb215d4825ce9de61dccf93d62074" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5fba957a30161d8724aade093593cd22f815bea2", - "reference": "5fba957a30161d8724aade093593cd22f815bea2", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9a6a883c48acb215d4825ce9de61dccf93d62074", + "reference": "9a6a883c48acb215d4825ce9de61dccf93d62074", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "twig/twig": "~1.20|~2.0" }, "suggest": { "ext-symfony_debug": "" @@ -7053,16 +7210,19 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "3.0-dev" } }, "autoload": { "files": [ "Resources/functions/dump.php" ], - "psr-0": { + "psr-4": { "Symfony\\Component\\VarDumper\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7084,7 +7244,7 @@ "debug", "dump" ], - "time": "2015-07-01 10:03:42" + "time": "2016-02-13 09:23:44" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7179,6 +7339,48 @@ ], "time": "2016-01-07 17:12:58" }, + { + "name": "turbo124/laravel-push-notification", + "version": "dev-laravel5", + "source": { + "type": "git", + "url": "https://github.com/turbo124/laravel-push-notification.git", + "reference": "b703b0fe02719a540139f5d26b73ac53a198b50d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/turbo124/laravel-push-notification/zipball/b703b0fe02719a540139f5d26b73ac53a198b50d", + "reference": "b703b0fe02719a540139f5d26b73ac53a198b50d", + "shasum": "" + }, + "require": { + "illuminate/support": "5.*", + "php": ">=5.3.0", + "sly/notification-pusher": "2.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Davibennun\\LaravelPushNotification": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "authors": [ + { + "name": "DaviBenNun", + "email": "davi@andradenunes.org" + } + ], + "description": "Laravel package to send push notifications to mobile devices (apns, gcm)", + "keywords": [ + "apns", + "gcm", + "laravel", + "notification", + "push" + ], + "time": "2015-06-15 13:11:17" + }, { "name": "twbs/bootstrap", "version": "v3.3.6", @@ -7280,28 +7482,33 @@ }, { "name": "vlucas/phpdotenv", - "version": "v1.1.1", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa" + "reference": "9caf304153dc2288e4970caec6f1f3b3bc205412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", - "reference": "0cac554ce06277e33ddf9f0b7ade4b8bbf2af3fa", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/9caf304153dc2288e4970caec6f1f3b3bc205412", + "reference": "9caf304153dc2288e4970caec6f1f3b3bc205412", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.8|^5.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, "autoload": { - "psr-0": { - "Dotenv": "src/" + "psr-4": { + "Dotenv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -7322,7 +7529,7 @@ "env", "environment" ], - "time": "2015-05-30 15:59:26" + "time": "2015-12-29 15:10:30" }, { "name": "webpatser/laravel-countries", @@ -7330,12 +7537,12 @@ "source": { "type": "git", "url": "https://github.com/webpatser/laravel-countries.git", - "reference": "e29dcce821f2c4a522e35483c38632ca534db4ee" + "reference": "2a310c0ff1b112d3710223d3bd51e8a30c5a304d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webpatser/laravel-countries/zipball/e29dcce821f2c4a522e35483c38632ca534db4ee", - "reference": "e29dcce821f2c4a522e35483c38632ca534db4ee", + "url": "https://api.github.com/repos/webpatser/laravel-countries/zipball/2a310c0ff1b112d3710223d3bd51e8a30c5a304d", + "reference": "2a310c0ff1b112d3710223d3bd51e8a30c5a304d", "shasum": "" }, "require": { @@ -7374,7 +7581,7 @@ "iso_3166_3", "laravel" ], - "time": "2015-08-21 12:12:14" + "time": "2016-02-25 10:29:59" }, { "name": "wildbit/laravel-postmark-provider", @@ -7450,6 +7657,445 @@ "description": "A Swiftmailer Transport for Postmark.", "time": "2015-11-30 18:23:03" }, + { + "name": "zendframework/zend-escaper", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-escaper.git", + "reference": "a4b227d8a477f4e7e9073f8e0a7ae7dbd3104a73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/a4b227d8a477f4e7e9073f8e0a7ae7dbd3104a73", + "reference": "a4b227d8a477f4e7e9073f8e0a7ae7dbd3104a73", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-escaper", + "keywords": [ + "escaper", + "zf2" + ], + "time": "2015-06-03 14:05:37" + }, + { + "name": "zendframework/zend-http", + "version": "2.5.4", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-http.git", + "reference": "7b920b4ec34b5ee58f76eb4e8c408b083121953c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/7b920b4ec34b5ee58f76eb4e8c408b083121953c", + "reference": "7b920b4ec34b5ee58f76eb4e8c408b083121953c", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "zendframework/zend-loader": "^2.5", + "zendframework/zend-stdlib": "^2.5 || ^3.0", + "zendframework/zend-uri": "^2.5", + "zendframework/zend-validator": "^2.5" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "^4.0", + "zendframework/zend-config": "^2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "homepage": "https://github.com/zendframework/zend-http", + "keywords": [ + "http", + "zf2" + ], + "time": "2016-02-04 20:36:48" + }, + { + "name": "zendframework/zend-json", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-json.git", + "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "zendframework/zend-http": "^2.5.4", + "zendframework/zend-server": "^2.6.1", + "zendframework/zend-stdlib": "^2.5 || ^3.0", + "zendframework/zendxml": "^1.0.2" + }, + "suggest": { + "zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server", + "zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server", + "zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses", + "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Json\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", + "homepage": "https://github.com/zendframework/zend-json", + "keywords": [ + "json", + "zf2" + ], + "time": "2016-02-04 21:20:26" + }, + { + "name": "zendframework/zend-loader", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-loader.git", + "reference": "c5fd2f071bde071f4363def7dea8dec7393e135c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/c5fd2f071bde071f4363def7dea8dec7393e135c", + "reference": "c5fd2f071bde071f4363def7dea8dec7393e135c", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-loader", + "keywords": [ + "loader", + "zf2" + ], + "time": "2015-06-03 14:05:47" + }, + { + "name": "zendframework/zend-stdlib", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "22eb098958980fbbe6b9a06f209f5a4b496cc0c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/22eb098958980fbbe6b9a06f209f5a4b496cc0c1", + "reference": "22eb098958980fbbe6b9a06f209f5a4b496cc0c1", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "~0.1", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev", + "dev-develop": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-stdlib", + "keywords": [ + "stdlib", + "zf2" + ], + "time": "2016-02-03 16:53:37" + }, + { + "name": "zendframework/zend-uri", + "version": "2.5.2", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-uri.git", + "reference": "0bf717a239432b1a1675ae314f7c4acd742749ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/0bf717a239432b1a1675ae314f7c4acd742749ed", + "reference": "0bf717a239432b1a1675ae314f7c4acd742749ed", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "zendframework/zend-escaper": "^2.5", + "zendframework/zend-validator": "^2.5" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "a component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "homepage": "https://github.com/zendframework/zend-uri", + "keywords": [ + "uri", + "zf2" + ], + "time": "2016-02-17 22:38:51" + }, + { + "name": "zendframework/zend-validator", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-validator.git", + "reference": "1315fead53358054e3f5fcf440c1a4cd5f0724db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/1315fead53358054e3f5fcf440c1a4cd5f0724db", + "reference": "1315fead53358054e3f5fcf440c1a4cd5f0724db", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "php": "^5.5 || ^7.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "^4.0", + "zendframework/zend-cache": "^2.6.1", + "zendframework/zend-config": "^2.6", + "zendframework/zend-db": "^2.5", + "zendframework/zend-filter": "^2.6", + "zendframework/zend-http": "^2.5.4", + "zendframework/zend-i18n": "^2.6", + "zendframework/zend-math": "^2.6", + "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", + "zendframework/zend-session": "^2.5", + "zendframework/zend-uri": "^2.5" + }, + "suggest": { + "zendframework/zend-db": "Zend\\Db component", + "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", + "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages as well as to use the various Date validators", + "zendframework/zend-i18n-resources": "Translations of validator messages", + "zendframework/zend-math": "Zend\\Math component", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "zendframework/zend-session": "Zend\\Session component", + "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a set of commonly needed validators", + "homepage": "https://github.com/zendframework/zend-validator", + "keywords": [ + "validator", + "zf2" + ], + "time": "2016-02-17 17:59:34" + }, + { + "name": "zendframework/zendservice-apple-apns", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/ZendService_Apple_Apns.git", + "reference": "ee2c44ee833206c1eb95b2fb7be1e2335e7570eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/ZendService_Apple_Apns/zipball/ee2c44ee833206c1eb95b2fb7be1e2335e7570eb", + "reference": "ee2c44ee833206c1eb95b2fb7be1e2335e7570eb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-json": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "ZendService\\Apple\\Apns\\": "library/", + "ZendService\\Apple\\Exception\\": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "OOP Zend Framework 2 wrapper for Apple Push Notification Service", + "homepage": "http://packages.zendframework.com/", + "keywords": [ + "apns", + "apple", + "notification", + "push", + "zf2" + ], + "time": "2015-12-09 22:55:07" + }, + { + "name": "zendframework/zendservice-google-gcm", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/zendframework/ZendService_Google_Gcm.git", + "reference": "86d16e9dcb4d41677e6f691642856b3eb95a1073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/ZendService_Google_Gcm/zipball/86d16e9dcb4d41677e6f691642856b3eb95a1073", + "reference": "86d16e9dcb4d41677e6f691642856b3eb95a1073", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-http": ">=2.0.0", + "zendframework/zend-json": ">=2.0.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "ZendService\\Google\\Gcm\\": "library/", + "ZendService\\Google\\Exception\\": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "OOP wrapper for Google Cloud Messaging", + "homepage": "http://packages.zendframework.com/", + "keywords": [ + "gcm", + "google", + "notification", + "push", + "zf2" + ], + "time": "2015-10-14 03:18:56" + }, { "name": "zircote/swagger-php", "version": "2.0.6", @@ -7565,41 +8211,42 @@ }, { "name": "codeception/codeception", - "version": "2.1.6", + "version": "2.1.7", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89" + "reference": "65971b0dee4972710365b6102154cd412a9bf7b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b199941f5e59d1e7fd32d78296c8ab98db873d89", - "reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/65971b0dee4972710365b6102154cd412a9bf7b1", + "reference": "65971b0dee4972710365b6102154cd412a9bf7b1", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "facebook/webdriver": ">=1.0.1", + "facebook/webdriver": ">=1.0.1 <2.0", "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", - "php": ">=5.4.0", - "phpunit/phpunit": "~4.8.0", - "symfony/browser-kit": ">=2.4|<3.1", - "symfony/console": ">=2.4|<3.1", - "symfony/css-selector": ">=2.4|<3.1", - "symfony/dom-crawler": ">=2.4|<3.1", - "symfony/event-dispatcher": ">=2.4|<3.1", - "symfony/finder": ">=2.4|<3.1", - "symfony/yaml": ">=2.4|<3.1" + "php": ">=5.4.0 <8.0", + "phpunit/php-code-coverage": ">=2.1.3", + "phpunit/phpunit": ">4.8.20 <6.0", + "symfony/browser-kit": ">=2.5 <3.1", + "symfony/console": ">=2.5 <3.1", + "symfony/css-selector": ">=2.5 <3.1", + "symfony/dom-crawler": ">=2.5 <3.1", + "symfony/event-dispatcher": ">=2.5 <3.1", + "symfony/finder": ">=2.5 <3.1", + "symfony/yaml": ">=2.5 <3.1" }, "require-dev": { "codeception/specify": "~0.3", - "facebook/php-sdk-v4": "~4.0", + "facebook/php-sdk-v4": "~5.0", "flow/jsonpath": "~0.2", "monolog/monolog": "~1.8", "pda/pheanstalk": "~2.0", - "videlalvaro/php-amqplib": "~2.4" + "php-amqplib/php-amqplib": "~2.4" }, "suggest": { "codeception/phpbuiltinserver": "Extension to start and stop PHP built-in web server for your tests", @@ -7641,7 +8288,7 @@ "functional testing", "unit testing" ], - "time": "2016-02-09 22:27:48" + "time": "2016-03-12 01:15:25" }, { "name": "doctrine/instantiator", @@ -7828,16 +8475,16 @@ }, { "name": "phpspec/phpspec", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/phpspec/phpspec.git", - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" + "reference": "385ecb015e97c13818074f1517928b24d4a26067" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067", + "reference": "385ecb015e97c13818074f1517928b24d4a26067", "shasum": "" }, "require": { @@ -7902,7 +8549,7 @@ "testing", "tests" ], - "time": "2016-01-01 10:17:54" + "time": "2016-03-20 20:34:32" }, { "name": "phpspec/prophecy", @@ -8208,16 +8855,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.23", + "version": "4.8.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483" + "reference": "a1066c562c52900a142a0e2bbf0582994671385e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483", - "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e", + "reference": "a1066c562c52900a142a0e2bbf0582994671385e", "shasum": "" }, "require": { @@ -8276,7 +8923,7 @@ "testing", "xunit" ], - "time": "2016-02-11 14:56:33" + "time": "2016-03-14 06:16:08" }, { "name": "phpunit/phpunit-mock-objects", @@ -8452,16 +9099,16 @@ }, { "name": "sebastian/environment", - "version": "1.3.3", + "version": "1.3.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6e7133793a8e5a5714a551a8324337374be209df" + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df", - "reference": "6e7133793a8e5a5714a551a8324337374be209df", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", "shasum": "" }, "require": { @@ -8498,7 +9145,7 @@ "environment", "hhvm" ], - "time": "2015-12-02 08:37:27" + "time": "2016-02-26 18:40:46" }, { "name": "sebastian/exporter", @@ -8707,7 +9354,7 @@ }, { "name": "symfony/browser-kit", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", @@ -8764,16 +9411,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b693a9650aa004576b593ff2e91ae749dc90123d" + "reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d", - "reference": "b693a9650aa004576b593ff2e91ae749dc90123d", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/981c8edb4538f88ba976ed44bdcaa683fce3d6c6", + "reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6", "shasum": "" }, "require": { @@ -8816,79 +9463,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-01-25 09:56:57" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "1289d16209491b584839022f29257ad859b8532d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d", - "reference": "1289d16209491b584839022f29257ad859b8532d", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2016-01-20 09:13:37" + "time": "2016-02-28 16:24:34" }, { "name": "symfony/yaml", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a" + "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a", - "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c", + "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c", "shasum": "" }, "require": { @@ -8924,12 +9512,13 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-02-02 13:44:19" + "time": "2016-02-23 15:16:06" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": { + "turbo124/laravel-push-notification": 20, "omnipay/mollie": 20, "omnipay/2checkout": 20, "omnipay/gocardless": 20, @@ -8939,7 +9528,6 @@ "webpatser/laravel-countries": 20, "barryvdh/laravel-ide-helper": 20, "lokielse/omnipay-alipay": 20, - "alfaproject/omnipay-neteller": 20, "alfaproject/omnipay-skrill": 20, "omnipay/bitpay": 20, "dwolla/omnipay-dwolla": 20, diff --git a/config/app.php b/config/app.php index cd55e8ee965a..c86362a9afea 100644 --- a/config/app.php +++ b/config/app.php @@ -17,6 +17,8 @@ return [ 'debug' => env('APP_DEBUG', ''), + 'env' => env('APP_ENV', 'production'), + /* |-------------------------------------------------------------------------- | Application URL @@ -115,12 +117,11 @@ return [ /* * Laravel Framework Service Providers... */ - 'Illuminate\Foundation\Providers\ArtisanServiceProvider', 'Illuminate\Auth\AuthServiceProvider', - 'Illuminate\Bus\BusServiceProvider', + 'Collective\Html\HtmlServiceProvider', + 'Collective\Bus\BusServiceProvider', 'Illuminate\Cache\CacheServiceProvider', 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider', - 'Illuminate\Routing\ControllerServiceProvider', 'Illuminate\Cookie\CookieServiceProvider', 'Illuminate\Database\DatabaseServiceProvider', 'Illuminate\Encryption\EncryptionServiceProvider', @@ -137,7 +138,8 @@ return [ 'Illuminate\Translation\TranslationServiceProvider', 'Illuminate\Validation\ValidationServiceProvider', 'Illuminate\View\ViewServiceProvider', - + 'Illuminate\Broadcasting\BroadcastServiceProvider', + /* * Additional Providers */ @@ -148,7 +150,6 @@ return [ 'Intervention\Image\ImageServiceProvider', 'Webpatser\Countries\CountriesServiceProvider', 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', - 'Illuminate\Html\HtmlServiceProvider', 'Laravel\Socialite\SocialiteServiceProvider', 'Jlapp\Swaggervel\SwaggervelServiceProvider', 'Maatwebsite\Excel\ExcelServiceProvider', @@ -157,12 +158,13 @@ return [ * Application Service Providers... */ 'App\Providers\AppServiceProvider', - 'App\Providers\BusServiceProvider', + //'App\Providers\BusServiceProvider', 'App\Providers\ConfigServiceProvider', 'App\Providers\EventServiceProvider', 'App\Providers\RouteServiceProvider', 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', + 'Davibennun\LaravelPushNotification\LaravelPushNotificationServiceProvider', ], /* @@ -248,6 +250,7 @@ return [ 'Rocketeer' => 'Rocketeer\Facades\Rocketeer', 'Socialite' => 'Laravel\Socialite\Facades\Socialite', 'Excel' => 'Maatwebsite\Excel\Facades\Excel', + 'PushNotification' => 'Davibennun\LaravelPushNotification\Facades\PushNotification', ], diff --git a/config/auth.php b/config/auth.php index 1ce67b55a13e..7b08fd473147 100644 --- a/config/auth.php +++ b/config/auth.php @@ -1,67 +1,117 @@ [ + 'guard' => 'user', + 'passwords' => 'users', + ], - 'driver' => 'eloquent', + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session", "token" + | + */ - /* - |-------------------------------------------------------------------------- - | Authentication Model - |-------------------------------------------------------------------------- - | - | When using the "Eloquent" authentication driver, we need to know which - | Eloquent model should be used to retrieve your users. Of course, it - | is often just the "User" model but you may use whatever you like. - | - */ + 'guards' => [ + 'user' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + 'client' => [ + 'driver' => 'session', + 'provider' => 'client', + ], - 'model' => 'App\Models\User', + 'api' => [ + 'driver' => 'token', + 'provider' => 'users', + ], + ], - /* - |-------------------------------------------------------------------------- - | Authentication Table - |-------------------------------------------------------------------------- - | - | When using the "Database" authentication driver, we need to know which - | table should be used to retrieve your users. We have chosen a basic - | default value but you may easily change it to any table you like. - | - */ + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ - 'table' => 'users', + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ], + + 'client' => [ + 'driver' => 'eloquent', + 'model' => App\Models\Contact::class, + ] + ], - /* - |-------------------------------------------------------------------------- - | Password Reset Settings - |-------------------------------------------------------------------------- - | - | Here you may set the options for resetting passwords including the view - | that is your password reset e-mail. You can also set the name of the - | table that maintains all of the reset tokens for your application. - | - | The expire time is the number of minutes that the reset token should be - | considered valid. This security feature keeps tokens short-lived so - | they have less time to be guessed. You may change this as needed. - | - */ + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | Here you may set the options for resetting passwords including the view + | that is your password reset e-mail. You may also set the name of the + | table that maintains all of the reset tokens for your application. + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expire time is the number of minutes that the reset token should be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + */ - 'password' => [ - 'email' => 'emails.password', - 'table' => 'password_resets', - 'expire' => 60, - ], + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'email' => 'emails.password', + 'table' => 'password_resets', + 'expire' => 60, + ], + 'client' => [ + 'provider' => 'client', + 'email' => 'emails.client_password', + 'table' => 'password_resets', + 'expire' => 60, + ], + ], -]; +]; \ No newline at end of file diff --git a/config/former.php b/config/former.php index 7279aaf78aa6..b9c729e2f9ae 100644 --- a/config/former.php +++ b/config/former.php @@ -49,7 +49,7 @@ // Whether text that comes out of the translated // should be capitalized (ex: email => Email) automatically - 'capitalize_translations' => true, + 'capitalize_translations' => false, // An array of attributes to automatically translate 'translatable' => array( diff --git a/config/mail.php b/config/mail.php index 91d6e8fbcd8b..6b08e8efb333 100644 --- a/config/mail.php +++ b/config/mail.php @@ -108,17 +108,4 @@ return [ 'sendmail' => '/usr/sbin/sendmail -bs', - /* - |-------------------------------------------------------------------------- - | Mail "Pretend" - |-------------------------------------------------------------------------- - | - | When this option is enabled, e-mail will not actually be sent over the - | web and will instead be written to your application's logs files so - | you may inspect the message. This is great for local development. - | - */ - - 'pretend' => env('MAIL_PRETEND'), - ]; diff --git a/config/push-notification.php b/config/push-notification.php new file mode 100644 index 000000000000..89dd441ab531 --- /dev/null +++ b/config/push-notification.php @@ -0,0 +1,23 @@ + [ + 'environment' =>'development', + 'certificate'=>app_path().'/certs/ninjaIOS.pem', + 'passPhrase' =>'', + 'service' =>'apns' + ], + 'ninjaIOS' => [ + 'environment' =>'production', + 'certificate'=>app_path().'/certs/productionNinjaIOS.pem', + 'passPhrase' =>'', + 'service' =>'apns' + ], + 'ninjaAndroid' => [ + 'environment' =>'production', + 'apiKey' =>'yourAPIKey', + 'service' =>'gcm' + ] + +]; \ No newline at end of file diff --git a/database/migrations/2016_02_25_152948_add_client_password.php b/database/migrations/2016_02_25_152948_add_client_password.php new file mode 100644 index 000000000000..49fcae7fcbe4 --- /dev/null +++ b/database/migrations/2016_02_25_152948_add_client_password.php @@ -0,0 +1,46 @@ +boolean('enable_portal_password')->default(0); + $table->boolean('send_portal_password')->default(0); + }); + + Schema::table('contacts', function ($table) { + $table->string('password', 255)->nullable(); + $table->boolean('confirmation_code', 255)->nullable(); + $table->boolean('remember_token', 100)->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function ($table) { + $table->dropColumn('enable_portal_password'); + $table->dropColumn('send_portal_password'); + }); + + Schema::table('contacts', function ($table) { + $table->dropColumn('password'); + $table->dropColumn('confirmation_code'); + $table->dropColumn('remember_token'); + }); + } + +} 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..cda210fdf5fc --- /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('invoice_items', function($table) { + $table->dropColumn('custom_value1'); + $table->dropColumn('custom_value2'); + }); + } +} diff --git a/database/migrations/2016_03_14_066181_add_user_permissions.php b/database/migrations/2016_03_14_066181_add_user_permissions.php new file mode 100644 index 000000000000..2cdd5994825d --- /dev/null +++ b/database/migrations/2016_03_14_066181_add_user_permissions.php @@ -0,0 +1,33 @@ +boolean('is_admin')->default(true); + $table->unsignedInteger('permissions')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function($table) { + $table->dropColumn('is_admin'); + $table->dropColumn('permissions'); + }); + } +} diff --git a/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php b/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php new file mode 100644 index 000000000000..af35fc927564 --- /dev/null +++ b/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php @@ -0,0 +1,28 @@ +decimal('rate', 13, 3)->change(); + }); + } + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tax_rates', function($table) { + $table->decimal('rate', 13, 2)->change(); + }); + } +} diff --git a/database/seeds/ConstantsSeeder.php b/database/seeds/ConstantsSeeder.php index ec2a94d49164..e02741684318 100644 --- a/database/seeds/ConstantsSeeder.php +++ b/database/seeds/ConstantsSeeder.php @@ -177,7 +177,7 @@ class ConstantsSeeder extends Seeder 'America/Santiago' => "(GMT-04:00) Santiago", 'Canada/Newfoundland' => "(GMT-03:30) Newfoundland", 'America/Buenos_Aires' => "(GMT-03:00) Buenos Aires", - 'Greenland' => "(GMT-03:00) Greenland", + 'America/Godthab' => "(GMT-03:00) Greenland", 'Atlantic/Stanley' => "(GMT-02:00) Stanley", 'Atlantic/Azores' => "(GMT-01:00) Azores", 'Atlantic/Cape_Verde' => "(GMT-01:00) Cape Verde Is.", diff --git a/database/seeds/CountriesSeeder.php b/database/seeds/CountriesSeeder.php index 7fe9c9e3fb05..570cb51e6c0b 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, + ], + 'JP' => [ // Japan + '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/CurrenciesSeeder.php b/database/seeds/CurrenciesSeeder.php new file mode 100644 index 000000000000..9a8304b181f6 --- /dev/null +++ b/database/seeds/CurrenciesSeeder.php @@ -0,0 +1,73 @@ + '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' => '.'], + ['name' => 'Croatian Kuna', 'code' => 'HKR', 'symbol' => 'kn', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Saudi Riyal', 'code' => 'SAR', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Japanese Yen', 'code' => 'JPY', 'symbol' => '¥', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['name' => 'Maldivian Rufiyaa', 'code' => 'MVR', '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/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/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/FontsSeeder.php b/database/seeds/FontsSeeder.php index 5db76296a5c5..6f27ffa0be02 100644 --- a/database/seeds/FontsSeeder.php +++ b/database/seeds/FontsSeeder.php @@ -220,6 +220,28 @@ class FontsSeeder extends Seeder 'bolditalics' => 'UKai.ttf', 'sort_order' => 1800, ], + [ + 'folder' => 'gensha_gothic_p', + 'name' => 'GenshinGothic P - Japanese', + 'css_stack' => '', + 'google_font' => '', + 'normal' => 'GenShinGothic-P-Regular.ttf', + 'bold' => 'GenShinGothic-P-Regular.ttf', + 'italics' => 'GenShinGothic-P-Regular.ttf', + 'bolditalics' => 'GenShinGothic-P-Regular.ttf', + 'sort_order' => 1800, + ], + [ + 'folder' => 'gensha_gothic', + 'name' => 'GenshinGothic - Japanese', + 'css_stack' => '', + 'google_font' => '', + 'normal' => 'GenShinGothic-Regular.ttf', + 'bold' => 'GenShinGothic-Regular.ttf', + 'italics' => 'GenShinGothic-Regular.ttf', + 'bolditalics' => 'GenShinGothic-Regular.ttf', + 'sort_order' => 1800, + ], ]; foreach ($fonts as $font) { 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/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index 8710357a36c2..654ad9fe0e43 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/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); + } + } + } + +} diff --git a/public/built.js b/public/built.js index ec11a94ceb11..9fccf73e0bff 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