diff --git a/.env.example b/.env.example
index 1ed1ec049cbb..ec281e283601 100644
--- a/.env.example
+++ b/.env.example
@@ -3,6 +3,7 @@ APP_DEBUG=false
APP_URL=http://ninja.dev
APP_KEY=SomeRandomStringSomeRandomString
APP_CIPHER=AES-256-CBC
+APP_LOCALE=en
DB_TYPE=mysql
DB_STRICT=false
@@ -90,7 +91,8 @@ WEPAY_ENVIRONMENT=production # production or stage
WEPAY_AUTO_UPDATE=true # Requires permission from WePay
WEPAY_ENABLE_CANADA=true
WEPAY_FEE_PAYER=payee
-WEPAY_APP_FEE_MULTIPLIER=0.002
+WEPAY_APP_FEE_CC_MULTIPLIER=0
+WEPAY_APP_FEE_ACH_MULTIPLIER=0
WEPAY_APP_FEE_FIXED=0
WEPAY_THEME='{"name":"Invoice Ninja","primary_color":"0b4d78","secondary_color":"0b4d78","background_color":"f8f8f8","button_color":"33b753"}' # See https://www.wepay.com/developer/reference/structures#theme
diff --git a/.travis.yml b/.travis.yml
index 92bf4064b384..067ed7c1530e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -50,6 +50,7 @@ before_script:
- sed -i 's/APP_ENV=production/APP_ENV=development/g' .env
- sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env
- sed -i 's/MAIL_DRIVER=smtp/MAIL_DRIVER=log/g' .env
+ - sed -i 's/PHANTOMJS_CLOUD_KEY/#PHANTOMJS_CLOUD_KEY/g' .env
- sed -i '$a NINJA_DEV=true' .env
- sed -i '$a TRAVIS=true' .env
# create the database and user
@@ -58,7 +59,6 @@ before_script:
# migrate and seed the database
- php artisan migrate --no-interaction
- php artisan db:seed --no-interaction # default seed
- - php artisan db:seed --no-interaction --class=UserTableSeeder # development seed
# Start webserver on ninja.dev:8000
- php artisan serve --host=ninja.dev --port=8000 & # '&' allows to run in background
# Start PhantomJS
@@ -67,10 +67,10 @@ before_script:
- sleep 5
# Make sure the app is up-to-date
- curl -L http://ninja.dev:8000/update
- #- php artisan ninja:create-test-data 25
+ - php artisan ninja:create-test-data 4 true
+ - php artisan db:seed --no-interaction --class=UserTableSeeder # development seed
script:
- - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php
@@ -83,23 +83,29 @@ script:
- php ./vendor/codeception/codeception/codecept run --debug acceptance OnlinePaymentCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php
- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php
+ - php ./vendor/codeception/codeception/codecept run --debug acceptance GatewayFeesCest.php
+ - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php
#- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env
#- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php
after_script:
+ - php artisan ninja:check-data --no-interaction
- cat .env
- mysql -u root -e 'select * from accounts;' ninja
+ - mysql -u root -e 'select * from users;' ninja
- mysql -u root -e 'select * from account_gateways;' ninja
- mysql -u root -e 'select * from clients;' ninja
+ - mysql -u root -e 'select * from contacts;' ninja
- mysql -u root -e 'select * from invoices;' ninja
- mysql -u root -e 'select * from invoice_items;' ninja
+ - mysql -u root -e 'select * from invitations;' ninja
- mysql -u root -e 'select * from payments;' ninja
- mysql -u root -e 'select * from credits;' ninja
- mysql -u root -e 'select * from expenses;' ninja
- cat storage/logs/laravel-error.log
- cat storage/logs/laravel-info.log
- - FILES=$(find tests/_output -type f -name '*.png')
+ - FILES=$(find tests/_output -type f -name '*.png' | sort -nr)
- for i in $FILES; do echo $i; base64 "$i"; break; done
notifications:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0c2d0bbb7327..0fad62197099 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,6 +4,8 @@ Thanks for your contributions!
## Submit bug reports or feature requests
+Please discuss the changes with us ahead of time to ensure they will be merged.
+
### Submit pull requests
* [Fork](https://github.com/invoiceninja/invoiceninja#fork-destination-box) the [Invoice Ninja repository](https://github.com/invoiceninja/invoiceninja)
* Create a new branch with the name `#issue_number-Short-description`
@@ -11,7 +13,7 @@ Thanks for your contributions!
* Make your changes and commit
* Check if your branch is still in sync with the repositorys **`develop`** branch
* _Read:_ [Syncing a fork](https://help.github.com/articles/syncing-a-fork/)
- * _Also read:_ [How to rebase a pull request](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request)
+ * _Also read:_ [How to rebase a pull request](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request)
* Push your branch and create a PR against the Invoice Ninja **`develop`** branch
* Update the [Changelog](CHANGELOG.md)
@@ -21,7 +23,7 @@ To make the contribution process nice and easy for anyone, please follow some ru
to give a more detailed explanation.
* Only one feature/bugfix per issue. If you want to submit more, create multiple issues.
* Only one feature/bugfix per PR(pull request). Split more changes into multiple PRs.
-
+
#### Coding Style
Try to follow the [PSR-2 guidlines](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
@@ -29,7 +31,7 @@ _Example styling:_
```php
/**
* Gets a preview of the email
- *
+ *
* @param TemplateService $templateService
*
* @return \Illuminate\Http\Response
diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php
index dca1327f40ac..de02d0d47094 100644
--- a/app/Console/Commands/CheckData.php
+++ b/app/Console/Commands/CheckData.php
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Carbon;
use DB;
+use Exception;
use Illuminate\Console\Command;
use Mail;
use Symfony\Component\Console\Input\InputOption;
@@ -83,6 +84,8 @@ class CheckData extends Command
->from(CONTACT_EMAIL)
->subject('Check-Data: ' . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE));
});
+ } elseif (! $this->isValid) {
+ throw new Exception('Check data failed!!');
}
}
@@ -157,9 +160,15 @@ class CheckData extends Command
'products' => [
ENTITY_USER,
],
+ 'vendors' => [
+ ENTITY_USER,
+ ],
'expense_categories' => [
ENTITY_USER,
],
+ 'payment_terms' => [
+ ENTITY_USER,
+ ],
'projects' => [
ENTITY_USER,
ENTITY_CLIENT,
diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php
index 7e339643e182..1606e5eec5ec 100644
--- a/app/Console/Commands/CreateTestData.php
+++ b/app/Console/Commands/CreateTestData.php
@@ -2,6 +2,7 @@
namespace App\Console\Commands;
+use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Repositories\InvoiceRepository;
@@ -25,7 +26,7 @@ class CreateTestData extends Command
/**
* @var string
*/
- protected $signature = 'ninja:create-test-data {count=1}';
+ protected $signature = 'ninja:create-test-data {count=1} {create_account=false}';
/**
* @var
@@ -40,13 +41,15 @@ class CreateTestData extends Command
* @param PaymentRepository $paymentRepo
* @param VendorRepository $vendorRepo
* @param ExpenseRepository $expenseRepo
+ * @param AccountRepository $accountRepo
*/
public function __construct(
ClientRepository $clientRepo,
InvoiceRepository $invoiceRepo,
PaymentRepository $paymentRepo,
VendorRepository $vendorRepo,
- ExpenseRepository $expenseRepo)
+ ExpenseRepository $expenseRepo,
+ AccountRepository $accountRepo)
{
parent::__construct();
@@ -57,6 +60,7 @@ class CreateTestData extends Command
$this->paymentRepo = $paymentRepo;
$this->vendorRepo = $vendorRepo;
$this->expenseRepo = $expenseRepo;
+ $this->accountRepo = $accountRepo;
}
/**
@@ -69,10 +73,21 @@ class CreateTestData extends Command
}
$this->info(date('Y-m-d').' Running CreateTestData...');
-
- Auth::loginUsingId(1);
$this->count = $this->argument('count');
+ if (filter_var($this->argument('create_account'), FILTER_VALIDATE_BOOLEAN)) {
+ $this->info('Creating new account...');
+ $account = $this->accountRepo->create(
+ $this->faker->firstName,
+ $this->faker->lastName,
+ $this->faker->safeEmail
+ );
+ Auth::login($account->users[0]);
+ } else {
+ $this->info('Using first account...');
+ Auth::loginUsingId(1);
+ }
+
$this->createClients();
$this->createVendors();
@@ -182,7 +197,7 @@ class CreateTestData extends Command
'vendor_id' => $vendor->id,
'amount' => $this->faker->randomFloat(2, 1, 10),
'expense_date' => null,
- 'public_notes' => null,
+ 'public_notes' => '',
];
$expense = $this->expenseRepo->save($data);
diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php
index 88b317df5a69..0a62ca626fbf 100644
--- a/app/Console/Commands/SendRecurringInvoices.php
+++ b/app/Console/Commands/SendRecurringInvoices.php
@@ -2,6 +2,7 @@
namespace App\Console\Commands;
+use App\Models\Account;
use App\Models\Invoice;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
@@ -57,9 +58,18 @@ class SendRecurringInvoices extends Command
public function fire()
{
- $this->info(date('Y-m-d').' Running SendRecurringInvoices...');
+ $this->info(date('Y-m-d H:i:s') . ' Running SendRecurringInvoices...');
$today = new DateTime();
+ // check for counter resets
+ $accounts = Account::where('reset_counter_frequency_id', '>', 0)
+ ->orderBy('id', 'asc')
+ ->get();
+
+ foreach ($accounts as $account) {
+ $account->checkCounterReset();
+ }
+
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today])
->orderBy('id', 'asc')
@@ -74,7 +84,8 @@ class SendRecurringInvoices extends Command
continue;
}
- $recurInvoice->account->loadLocalizationSettings($recurInvoice->client);
+ $account = $recurInvoice->account;
+ $account->loadLocalizationSettings($recurInvoice->client);
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
if ($invoice && ! $invoice->isPaid()) {
@@ -103,7 +114,7 @@ class SendRecurringInvoices extends Command
}
}
- $this->info('Done');
+ $this->info(date('Y-m-d H:i:s') . ' Done');
}
/**
diff --git a/app/Console/Commands/stubs/api-controller.stub b/app/Console/Commands/stubs/api-controller.stub
index 8a047352ca8d..bd7a2bc30ead 100644
--- a/app/Console/Commands/stubs/api-controller.stub
+++ b/app/Console/Commands/stubs/api-controller.stub
@@ -23,11 +23,12 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
/**
* @SWG\Get(
* path="/$LOWER_NAME$",
- * summary="List of $LOWER_NAME$",
+ * summary="List $LOWER_NAME$",
+ * operationId="list$STUDLY_NAME$s",
* tags={"$LOWER_NAME$"},
* @SWG\Response(
* response=200,
- * description="A list with $LOWER_NAME$",
+ * description="A list of $LOWER_NAME$",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
@@ -47,7 +48,14 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
* @SWG\Get(
* path="/$LOWER_NAME$/{$LOWER_NAME$_id}",
* summary="Individual $STUDLY_NAME$",
+ * operationId="get$STUDLY_NAME$",
* tags={"$LOWER_NAME$"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="$LOWER_NAME$_id",
+ * type="integer",
+ * required=true
+ * ),
* @SWG\Response(
* response=200,
* description="A single $LOWER_NAME$",
@@ -59,7 +67,6 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
* )
* )
*/
-
public function show($STUDLY_NAME$Request $request)
{
return $this->itemResponse($request->entity());
@@ -71,11 +78,12 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
/**
* @SWG\Post(
* path="/$LOWER_NAME$",
- * tags={"$LOWER_NAME$"},
* summary="Create a $LOWER_NAME$",
+ * operationId="create$STUDLY_NAME$",
+ * tags={"$LOWER_NAME$"},
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="$LOWER_NAME$",
* @SWG\Schema(ref="#/definitions/$STUDLY_NAME$")
* ),
* @SWG\Response(
@@ -99,16 +107,23 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
/**
* @SWG\Put(
* path="/$LOWER_NAME$/{$LOWER_NAME$_id}",
- * tags={"$LOWER_NAME$"},
* summary="Update a $LOWER_NAME$",
+ * operationId="update$STUDLY_NAME$",
+ * tags={"$LOWER_NAME$"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="$LOWER_NAME$_id",
+ * type="integer",
+ * required=true
+ * ),
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="$LOWER_NAME$",
* @SWG\Schema(ref="#/definitions/$STUDLY_NAME$")
* ),
* @SWG\Response(
* response=200,
- * description="Update $LOWER_NAME$",
+ * description="Updated $LOWER_NAME$",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
@@ -117,7 +132,6 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
* )
* )
*/
-
public function update(Update$STUDLY_NAME$Request $request, $publicId)
{
if ($request->action) {
@@ -133,16 +147,18 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
/**
* @SWG\Delete(
* path="/$LOWER_NAME$/{$LOWER_NAME$_id}",
- * tags={"$LOWER_NAME$"},
* summary="Delete a $LOWER_NAME$",
+ * operationId="delete$STUDLY_NAME$",
+ * tags={"$LOWER_NAME$"},
* @SWG\Parameter(
- * in="body",
- * name="body",
- * @SWG\Schema(ref="#/definitions/$STUDLY_NAME$")
+ * in="path",
+ * name="$LOWER_NAME$_id",
+ * type="integer",
+ * required=true
* ),
* @SWG\Response(
* response=200,
- * description="Delete $LOWER_NAME$",
+ * description="Deleted $LOWER_NAME$",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/$STUDLY_NAME$"))
* ),
* @SWG\Response(
@@ -151,7 +167,6 @@ class $STUDLY_NAME$ApiController extends BaseAPIController
* )
* )
*/
-
public function destroy(Update$STUDLY_NAME$Request $request)
{
$$LOWER_NAME$ = $request->entity();
diff --git a/app/Console/Commands/stubs/transformer.stub b/app/Console/Commands/stubs/transformer.stub
index 27636445b238..e5dc615bb6f6 100644
--- a/app/Console/Commands/stubs/transformer.stub
+++ b/app/Console/Commands/stubs/transformer.stub
@@ -15,8 +15,8 @@ class $STUDLY_NAME$Transformer extends EntityTransformer
* @SWG\Property(property="id", type="integer", example=1, readOnly=true)
* @SWG\Property(property="user_id", type="integer", example=1)
* @SWG\Property(property="account_key", type="string", example="123456")
- * @SWG\Property(property="updated_at", type="timestamp", example="")
- * @SWG\Property(property="archived_at", type="timestamp", example="1451160233")
+ * @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true)
+ * @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true)
*/
/**
diff --git a/app/Constants.php b/app/Constants.php
index 1a568596fc9f..75634ad9da1e 100644
--- a/app/Constants.php
+++ b/app/Constants.php
@@ -41,6 +41,11 @@ if (! defined('APP_NAME')) {
define('INVOICE_TYPE_STANDARD', 1);
define('INVOICE_TYPE_QUOTE', 2);
+ define('INVOICE_ITEM_TYPE_STANDARD', 1);
+ define('INVOICE_ITEM_TYPE_TASK', 2);
+ define('INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE', 3);
+ define('INVOICE_ITEM_TYPE_PAID_GATEWAY_FEE', 4);
+
define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user');
define('PERSON_VENDOR_CONTACT', 'vendorcontact');
@@ -283,7 +288,6 @@ if (! defined('APP_NAME')) {
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID');
- define('PREV_USER_ID', 'PREV_USER_ID');
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_LICENSE_ACCOUNT_KEY', 'AsFmBAeLXF0IKf7tmi0eiyZfmWW9hxMT');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
@@ -292,7 +296,7 @@ if (! defined('APP_NAME')) {
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'http://docs.invoiceninja.com/en/latest'));
define('NINJA_DATE', '2000-01-01');
- define('NINJA_VERSION', '3.1.3' . env('NINJA_VERSION_SUFFIX'));
+ define('NINJA_VERSION', '3.2.0' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index f5567383c745..9ed644515c34 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -10,6 +10,7 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Foundation\Validation\ValidationException;
use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Support\Facades\Response;
+use Illuminate\Session\TokenMismatchException;
use Redirect;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -26,10 +27,11 @@ class Handler extends ExceptionHandler
* @var array
*/
protected $dontReport = [
- AuthorizationException::class,
- HttpException::class,
+ TokenMismatchException::class,
ModelNotFoundException::class,
- ValidationException::class,
+ //AuthorizationException::class,
+ //HttpException::class,
+ //ValidationException::class,
];
/**
@@ -43,11 +45,20 @@ class Handler extends ExceptionHandler
*/
public function report(Exception $e)
{
+ if (! $this->shouldReport($e)) {
+ return false;
+ }
+
// don't show these errors in the logs
if ($e instanceof NotFoundHttpException) {
if (Crawler::isCrawler()) {
return false;
}
+ // The logo can take a few seconds to get synced between servers
+ // TODO: remove once we're using cloud storage for logos
+ if (Utils::isNinja() && strpos(request()->url(), '/logo/') !== false) {
+ return false;
+ }
} elseif ($e instanceof HttpResponseException) {
return false;
}
@@ -74,9 +85,9 @@ class Handler extends ExceptionHandler
if ($e instanceof ModelNotFoundException) {
return Redirect::to('/');
}
- if ($e instanceof \Illuminate\Session\TokenMismatchException) {
- // prevent loop since the page auto-submits
- if ($request->path() != 'get_started') {
+
+ if ($e instanceof TokenMismatchException) {
+ if (! in_array($request->path(), ['get_started', 'save_sidebar_state'])) {
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
return redirect()
->back()
diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php
index cceb58a2502f..cc20a725c211 100644
--- a/app/Http/Controllers/AccountApiController.php
+++ b/app/Http/Controllers/AccountApiController.php
@@ -6,6 +6,7 @@ use App\Events\UserSignedUp;
use App\Http\Requests\RegisterRequest;
use App\Http\Requests\UpdateAccountRequest;
use App\Models\Account;
+use App\Ninja\OAuth\OAuth;
use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Transformers\AccountTransformer;
use App\Ninja\Transformers\UserAccountTransformer;
@@ -121,6 +122,7 @@ class AccountApiController extends BaseAPIController
for ($x = 0; $x < count($devices); $x++) {
if ($devices[$x]['email'] == Auth::user()->username) {
$devices[$x]['token'] = $request->token; //update
+ $devices[$x]['device'] = $request->device;
$account->devices = json_encode($devices);
$account->save();
$devices[$x]['account_key'] = $account->account_key;
@@ -187,25 +189,15 @@ class AccountApiController extends BaseAPIController
$token = $request->input('token');
$provider = $request->input('provider');
- try {
- $user = Socialite::driver($provider)->stateless()->userFromToken($token);
- } catch (Exception $exception) {
- return $this->errorResponse(['message' => $exception->getMessage()], 401);
- }
+ $oAuth = new OAuth();
+ $user = $oAuth->getProvider($provider)->getTokenResponse($token);
- if ($user) {
- $providerId = AuthService::getProviderId($provider);
- $user = $this->accountRepo->findUserByOauth($providerId, $user->id);
- }
-
- if ($user) {
+ if($user) {
Auth::login($user);
-
return $this->processLogin($request);
- } else {
- sleep(ERROR_DELAY);
-
- return $this->errorResponse(['message' => 'Invalid credentials'], 401);
}
+ else
+ return $this->errorResponse(['message' => 'Invalid credentials'], 401);
+
}
}
diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
index c3d65cf0a50a..e7e04b15921d 100644
--- a/app/Http/Controllers/AccountController.php
+++ b/app/Http/Controllers/AccountController.php
@@ -9,7 +9,6 @@ use App\Http\Requests\SaveEmailSettings;
use App\Http\Requests\UpdateAccountRequest;
use App\Models\Account;
use App\Models\AccountGateway;
-use App\Models\AccountGatewaySettings;
use App\Models\Affiliate;
use App\Models\Document;
use App\Models\Gateway;
@@ -38,6 +37,7 @@ use Request;
use Response;
use Session;
use stdClass;
+use Exception;
use URL;
use Utils;
@@ -123,17 +123,16 @@ class AccountController extends BaseController
{
$user = false;
$guestKey = Input::get('guest_key'); // local storage key to login until registered
- $prevUserId = Session::pull(PREV_USER_ID); // last user id used to link to new account
if (Auth::check()) {
return Redirect::to('invoices/create');
}
- if (! Utils::isNinja() && (Account::count() > 0 && ! $prevUserId)) {
+ if (! Utils::isNinja() && Account::count() > 0) {
return Redirect::to('/login');
}
- if ($guestKey && ! $prevUserId) {
+ if ($guestKey) {
$user = User::where('password', '=', $guestKey)->first();
if ($user && $user->registered) {
@@ -144,11 +143,6 @@ class AccountController extends BaseController
if (! $user) {
$account = $this->accountRepo->create();
$user = $account->users()->first();
-
- if ($prevUserId) {
- $users = $this->accountRepo->associateAccounts($user->id, $prevUserId);
- Session::put(SESSION_USER_ACCOUNTS, $users);
- }
}
Auth::login($user, true);
@@ -186,22 +180,8 @@ class AccountController extends BaseController
$newPlan['price'] = Utils::getPlanPrice($newPlan);
$credit = 0;
- if (! empty($planDetails['started']) && $plan == PLAN_FREE) {
- // Downgrade
- $refund_deadline = clone $planDetails['started'];
- $refund_deadline->modify('+30 days');
-
- if ($plan == PLAN_FREE && $refund_deadline >= date_create()) {
- if ($payment = $account->company->payment) {
- $ninjaAccount = $this->accountRepo->getNinjaAccount();
- $paymentDriver = $ninjaAccount->paymentDriver();
- $paymentDriver->refundPayment($payment);
- Session::flash('message', trans('texts.plan_refunded'));
- \Log::info("Refunded Plan Payment: {$account->name} - {$user->email} - Deadline: {$refund_deadline->format('Y-m-d')}");
- } else {
- Session::flash('message', trans('texts.updated_plan'));
- }
- }
+ if ($plan == PLAN_FREE && $company->processRefund(Auth::user())) {
+ Session::flash('warning', trans('texts.plan_refunded'));
}
$hasPaid = false;
@@ -241,6 +221,8 @@ class AccountController extends BaseController
$company->plan = $plan;
$company->save();
+ Session::flash('message', trans('texts.updated_plan'));
+
return Redirect::to('settings/account_management');
}
}
@@ -488,23 +470,19 @@ class AccountController extends BaseController
}
}
- if ($trashedCount == 0) {
- return Redirect::to('gateways/create');
- } else {
- $tokenBillingOptions = [];
- for ($i = 1; $i <= 4; $i++) {
- $tokenBillingOptions[$i] = trans("texts.token_billing_{$i}");
- }
-
- return View::make('accounts.payments', [
- 'showAdd' => $count < count(Gateway::$alternate) + 1,
- 'title' => trans('texts.online_payments'),
- 'tokenBillingOptions' => $tokenBillingOptions,
- 'currency' => Utils::getFromCache(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY),
- 'currencies'),
- 'account' => $account,
- ]);
+ $tokenBillingOptions = [];
+ for ($i = 1; $i <= 4; $i++) {
+ $tokenBillingOptions[$i] = trans("texts.token_billing_{$i}");
}
+
+ return View::make('accounts.payments', [
+ 'showAdd' => $count < count(Gateway::$alternate) + 1,
+ 'title' => trans('texts.online_payments'),
+ 'tokenBillingOptions' => $tokenBillingOptions,
+ 'currency' => Utils::getFromCache(Session::get(SESSION_CURRENCY, DEFAULT_CURRENCY), 'currencies'),
+ 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('rate')->get(['public_id', 'name', 'rate']),
+ 'account' => $account,
+ ]);
}
/**
@@ -812,9 +790,12 @@ class AccountController extends BaseController
{
$account = $request->user()->account;
$account->fill($request->all());
- $account->bcc_email = $request->bcc_email;
$account->save();
+ $settings = $account->account_email_settings;
+ $settings->fill($request->all());
+ $settings->save();
+
return redirect('settings/' . ACCOUNT_EMAIL_SETTINGS)
->with('message', trans('texts.updated_settings'));
}
@@ -830,11 +811,11 @@ class AccountController extends BaseController
foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) {
$subjectField = "email_subject_{$type}";
$subject = Input::get($subjectField, $account->getEmailSubject($type));
- $account->$subjectField = ($subject == $account->getDefaultEmailSubject($type) ? null : $subject);
+ $account->account_email_settings->$subjectField = ($subject == $account->getDefaultEmailSubject($type) ? null : $subject);
$bodyField = "email_template_{$type}";
$body = Input::get($bodyField, $account->getEmailTemplate($type));
- $account->$bodyField = ($body == $account->getDefaultEmailTemplate($type) ? null : $body);
+ $account->account_email_settings->$bodyField = ($body == $account->getDefaultEmailTemplate($type) ? null : $body);
}
foreach ([REMINDER1, REMINDER2, REMINDER3] as $type) {
@@ -846,6 +827,7 @@ class AccountController extends BaseController
}
$account->save();
+ $account->account_email_settings->save();
Session::flash('message', trans('texts.updated_settings'));
}
@@ -932,6 +914,8 @@ class AccountController extends BaseController
$account->client_number_prefix = trim(Input::get('client_number_prefix'));
$account->client_number_pattern = trim(Input::get('client_number_pattern'));
$account->client_number_counter = Input::get('client_number_counter');
+ $account->reset_counter_frequency_id = Input::get('reset_counter_frequency_id');
+ $account->reset_counter_date = $account->reset_counter_frequency_id ? Utils::toSqlDate(Input::get('reset_counter_date')) : null;
if (Input::has('recurring_hour')) {
$account->recurring_hour = Input::get('recurring_hour');
@@ -1054,28 +1038,32 @@ class AccountController extends BaseController
$size = filesize($filePath);
if ($size / 1000 > MAX_DOCUMENT_SIZE) {
- Session::flash('warning', 'File too large');
+ Session::flash('warning', trans('texts.logo_warning_too_large'));
} else {
if ($documentType != 'gif') {
$account->logo = $account->account_key.'.'.$documentType;
- $imageSize = getimagesize($filePath);
- $account->logo_width = $imageSize[0];
- $account->logo_height = $imageSize[1];
- $account->logo_size = $size;
+ try {
+ $imageSize = getimagesize($filePath);
+ $account->logo_width = $imageSize[0];
+ $account->logo_height = $imageSize[1];
+ $account->logo_size = $size;
- // make sure image isn't interlaced
- if (extension_loaded('fileinfo')) {
- $image = Image::make($path);
- $image->interlace(false);
- $imageStr = (string) $image->encode($documentType);
- $disk->put($account->logo, $imageStr);
+ // make sure image isn't interlaced
+ if (extension_loaded('fileinfo')) {
+ $image = Image::make($path);
+ $image->interlace(false);
+ $imageStr = (string) $image->encode($documentType);
+ $disk->put($account->logo, $imageStr);
- $account->logo_size = strlen($imageStr);
- } else {
- $stream = fopen($filePath, 'r');
- $disk->getDriver()->putStream($account->logo, $stream, ['mimetype' => $documentTypeData['mime']]);
- fclose($stream);
+ $account->logo_size = strlen($imageStr);
+ } else {
+ $stream = fopen($filePath, 'r');
+ $disk->getDriver()->putStream($account->logo, $stream, ['mimetype' => $documentTypeData['mime']]);
+ fclose($stream);
+ }
+ } catch (Exception $exception) {
+ Session::flash('warning', trans('texts.logo_warning_invalid'));
}
} else {
if (extension_loaded('fileinfo')) {
@@ -1093,7 +1081,7 @@ class AccountController extends BaseController
$account->logo_width = $image->width();
$account->logo_height = $image->height();
} else {
- Session::flash('warning', 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.');
+ Session::flash('warning', trans('texts.logo_warning_fileinfo'));
}
}
}
@@ -1142,9 +1130,6 @@ class AccountController extends BaseController
$user->referral_code = $this->accountRepo->getReferralCode();
}
}
- if (Utils::isNinjaDev()) {
- $user->dark_mode = Input::get('dark_mode') ? true : false;
- }
$user->save();
@@ -1189,6 +1174,8 @@ class AccountController extends BaseController
$account = Auth::user()->account;
$account->token_billing_type_id = Input::get('token_billing_type_id');
$account->auto_bill_on_due_date = boolval(Input::get('auto_bill_on_due_date'));
+ $account->gateway_fee_enabled = boolval(Input::get('gateway_fee_enabled'));
+
$account->save();
event(new UserSettingsChanged());
@@ -1198,35 +1185,6 @@ class AccountController extends BaseController
return Redirect::to('settings/'.ACCOUNT_PAYMENTS);
}
- /**
- * @return \Illuminate\Http\RedirectResponse
- */
- public function savePaymentGatewayLimits()
- {
- $gateway_type_id = intval(Input::get('gateway_type_id'));
- $gateway_settings = AccountGatewaySettings::scope()->where('gateway_type_id', '=', $gateway_type_id)->first();
-
- if (! $gateway_settings) {
- $gateway_settings = AccountGatewaySettings::createNew();
- $gateway_settings->gateway_type_id = $gateway_type_id;
- }
-
- $gateway_settings->min_limit = Input::get('limit_min_enable') ? intval(Input::get('limit_min')) : null;
- $gateway_settings->max_limit = Input::get('limit_max_enable') ? intval(Input::get('limit_max')) : null;
-
- if ($gateway_settings->max_limit !== null && $gateway_settings->min_limit > $gateway_settings->max_limit) {
- $gateway_settings->max_limit = $gateway_settings->min_limit;
- }
-
- $gateway_settings->save();
-
- event(new UserSettingsChanged());
-
- Session::flash('message', trans('texts.updated_settings'));
-
- return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
- }
-
/**
* @return \Illuminate\Http\RedirectResponse
*/
@@ -1255,7 +1213,7 @@ class AccountController extends BaseController
public function checkEmail()
{
$email = User::withTrashed()->where('email', '=', Input::get('email'))
- ->where('id', '<>', Auth::user()->id)
+ ->where('id', '<>', Auth::user()->registered ? 0 : Auth::user()->id)
->first();
if ($email) {
@@ -1270,36 +1228,58 @@ class AccountController extends BaseController
*/
public function submitSignup()
{
+ $user = Auth::user();
+ $account = $user->account;
+
$rules = [
'new_first_name' => 'required',
'new_last_name' => 'required',
'new_password' => 'required|min:6',
- 'new_email' => 'email|required|unique:users,email,'.Auth::user()->id.',id',
+ 'new_email' => 'email|required|unique:users,email',
];
+ if (! $user->registered) {
+ $rules['new_email'] .= ',' . Auth::user()->id . ',id';
+ }
+
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return '';
}
- /** @var \App\Models\User $user */
- $user = Auth::user();
- $user->first_name = trim(Input::get('new_first_name'));
- $user->last_name = trim(Input::get('new_last_name'));
- $user->email = trim(strtolower(Input::get('new_email')));
- $user->username = $user->email;
- $user->password = bcrypt(trim(Input::get('new_password')));
- $user->registered = true;
- $user->save();
+ $firstName = trim(Input::get('new_first_name'));
+ $lastName = trim(Input::get('new_last_name'));
+ $email = trim(strtolower(Input::get('new_email')));
+ $password = trim(Input::get('new_password'));
- $user->account->startTrial(PLAN_PRO);
+ if ($user->registered) {
+ $newAccount = $this->accountRepo->create($firstName, $lastName, $email, $password, $account->company);
+ $newUser = $newAccount->users()->first();
+ $users = $this->accountRepo->associateAccounts($user->id, $newUser->id);
- if (Input::get('go_pro') == 'true') {
- Session::set(REQUESTED_PRO_PLAN, true);
+ Session::flash('message', trans('texts.created_new_company'));
+ Session::put(SESSION_USER_ACCOUNTS, $users);
+ Auth::loginUsingId($newUser->id);
+
+ return RESULT_SUCCESS;
+ } else {
+ $user->first_name = $firstName;
+ $user->last_name = $lastName;
+ $user->email = $email;
+ $user->username = $user->email;
+ $user->password = bcrypt($password);
+ $user->registered = true;
+ $user->save();
+
+ $user->account->startTrial(PLAN_PRO);
+
+ if (Input::get('go_pro') == 'true') {
+ Session::set(REQUESTED_PRO_PLAN, true);
+ }
+
+ return "{$user->first_name} {$user->last_name}";
}
-
- return "{$user->first_name} {$user->last_name}";
}
/**
@@ -1328,6 +1308,16 @@ class AccountController extends BaseController
return RESULT_SUCCESS;
}
+ /**
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function purgeData()
+ {
+ $this->dispatch(new \App\Jobs\PurgeAccountData());
+
+ return redirect('/settings/account_management')->withMessage(trans('texts.purge_successful'));
+ }
+
/**
* @return \Illuminate\Http\RedirectResponse
*/
@@ -1350,6 +1340,9 @@ class AccountController extends BaseController
$account = Auth::user()->account;
\Log::info("Canceled Account: {$account->name} - {$user->email}");
+ $company = $account->company;
+ $refunded = $company->processRefund(Auth::user());
+
Document::scope()->each(function ($item, $key) {
$item->delete();
});
@@ -1365,6 +1358,10 @@ class AccountController extends BaseController
Auth::logout();
Session::flush();
+ if ($refunded) {
+ Session::flash('warning', trans('texts.plan_refunded'));
+ }
+
return Redirect::to('/')->with('clearGuestKey', true);
}
@@ -1414,18 +1411,17 @@ class AccountController extends BaseController
public function previewEmail(TemplateService $templateService)
{
$template = Input::get('template');
- $invoice = Invoice::scope()
- ->invoices()
- ->withTrashed()
- ->first();
+ $invitation = \App\Models\Invitation::scope()
+ ->with('invoice.client.contacts')
+ ->first();
- if (! $invoice) {
+ if (! $invitation) {
return trans('texts.create_invoice_for_sample');
}
/** @var \App\Models\Account $account */
$account = Auth::user()->account;
- $invitation = $invoice->invitations->first();
+ $invoice = $invitation->invoice;
// replace the variables with sample data
$data = [
diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php
index 56f2dc5d746a..44bbd40006ba 100644
--- a/app/Http/Controllers/AccountGatewayController.php
+++ b/app/Http/Controllers/AccountGatewayController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Account;
+use App\Models\AccountGatewaySettings;
use App\Models\AccountGateway;
use App\Models\Gateway;
use App\Services\AccountGatewayService;
@@ -131,6 +132,10 @@ class AccountGatewayController extends BaseController
$currentGateways = $account->account_gateways;
$gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name')->get();
+ if ($accountGateway) {
+ $accountGateway->fields = [];
+ }
+
foreach ($gateways as $gateway) {
$fields = $gateway->getFields();
if (! $gateway->isCustom()) {
@@ -372,7 +377,7 @@ class AccountGatewayController extends BaseController
'tos_agree' => 'required',
'first_name' => 'required',
'last_name' => 'required',
- 'email' => 'required',
+ 'email' => 'required|email',
];
if (WEPAY_ENABLE_CANADA) {
@@ -387,6 +392,13 @@ class AccountGatewayController extends BaseController
->withInput();
}
+ if (! $user->email) {
+ $user->email = trim(Input::get('email'));
+ $user->first_name = trim(Input::get('first_name'));
+ $user->last_name = trim(Input::get('last_name'));
+ $user->save();
+ }
+
try {
$wepay = Utils::setupWePay();
@@ -494,4 +506,33 @@ class AccountGatewayController extends BaseController
return Redirect::to("gateways/{$accountGateway->public_id}/edit");
}
+
+ /**
+ * @return \Illuminate\Http\RedirectResponse
+ */
+ public function savePaymentGatewayLimits()
+ {
+ $gateway_type_id = intval(Input::get('gateway_type_id'));
+ $gateway_settings = AccountGatewaySettings::scope()->where('gateway_type_id', '=', $gateway_type_id)->first();
+
+ if (! $gateway_settings) {
+ $gateway_settings = AccountGatewaySettings::createNew();
+ $gateway_settings->gateway_type_id = $gateway_type_id;
+ }
+
+ $gateway_settings->min_limit = Input::get('limit_min_enable') ? intval(Input::get('limit_min')) : null;
+ $gateway_settings->max_limit = Input::get('limit_max_enable') ? intval(Input::get('limit_max')) : null;
+
+ if ($gateway_settings->max_limit !== null && $gateway_settings->min_limit > $gateway_settings->max_limit) {
+ $gateway_settings->max_limit = $gateway_settings->min_limit;
+ }
+
+ $gateway_settings->fill(Input::all());
+ $gateway_settings->save();
+
+ Session::flash('message', trans('texts.updated_settings'));
+
+ return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
+ }
+
}
diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php
index c87b333fe20c..13e96ae7443b 100644
--- a/app/Http/Controllers/AppController.php
+++ b/app/Http/Controllers/AppController.php
@@ -56,7 +56,7 @@ class AppController extends BaseController
$test = Input::get('test');
$app = Input::get('app');
- $app['key'] = env('APP_KEY') ?: str_random(RANDOM_KEY_LENGTH);
+ $app['key'] = env('APP_KEY') ?: strtolower(str_random(RANDOM_KEY_LENGTH));
$app['debug'] = Input::get('debug') ? 'true' : 'false';
$app['https'] = Input::get('https') ? 'true' : 'false';
@@ -101,7 +101,7 @@ class AppController extends BaseController
$_ENV['MAIL_FROM_ADDRESS'] = $mail['from']['address'];
$_ENV['MAIL_PASSWORD'] = $mail['password'];
$_ENV['PHANTOMJS_CLOUD_KEY'] = 'a-demo-key-with-low-quota-per-ip-address';
- $_ENV['PHANTOMJS_SECRET'] = str_random(RANDOM_KEY_LENGTH);
+ $_ENV['PHANTOMJS_SECRET'] = strtolower(str_random(RANDOM_KEY_LENGTH));
$_ENV['MAILGUN_DOMAIN'] = $mail['mailgun_domain'];
$_ENV['MAILGUN_SECRET'] = $mail['mailgun_secret'];
@@ -191,7 +191,8 @@ class AppController extends BaseController
$config .= "{$key}={$val}\n";
}
- $fp = fopen(base_path().'/.env', 'w');
+ $filePath = base_path().'/.env';
+ $fp = fopen($filePath, 'w');
fwrite($fp, $config);
fclose($fp);
@@ -345,6 +346,16 @@ class AppController extends BaseController
return RESULT_SUCCESS;
}
+ public function checkData()
+ {
+ try {
+ Artisan::call('ninja:check-data');
+ return RESULT_SUCCESS;
+ } catch (Exception $exception) {
+ return RESULT_FAILURE;
+ }
+ }
+
public function stats()
{
if (! hash_equals(Input::get('password'), env('RESELLER_PASSWORD'))) {
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
index c5fbccb78790..efe471557917 100644
--- a/app/Http/Controllers/Auth/AuthController.php
+++ b/app/Http/Controllers/Auth/AuthController.php
@@ -173,13 +173,17 @@ class AuthController extends Controller
public function getLogoutWrapper()
{
if (Auth::check() && ! Auth::user()->registered) {
- $account = Auth::user()->account;
- $this->accountRepo->unlinkAccount($account);
+ if (request()->force_logout) {
+ $account = Auth::user()->account;
+ $this->accountRepo->unlinkAccount($account);
- if (! $account->hasMultipleAccounts()) {
- $account->company->forceDelete();
+ if (! $account->hasMultipleAccounts()) {
+ $account->company->forceDelete();
+ }
+ $account->forceDelete();
+ } else {
+ return redirect('/');
}
- $account->forceDelete();
}
$response = self::getLogout();
diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php
index 0a07f8b0b5bd..ef7460b6480c 100644
--- a/app/Http/Controllers/BaseAPIController.php
+++ b/app/Http/Controllers/BaseAPIController.php
@@ -20,6 +20,7 @@ use Utils;
* schemes={"http","https"},
* host="ninja.dev",
* basePath="/api/v1",
+ * produces={"application/json"},
* @SWG\Info(
* version="1.0.0",
* title="Invoice Ninja API",
@@ -37,11 +38,12 @@ use Utils;
* description="Find out more about Invoice Ninja",
* url="https://www.invoiceninja.com"
* ),
+ * security={"api_key": {}},
* @SWG\SecurityScheme(
* securityDefinition="api_key",
* type="apiKey",
* in="header",
- * name="TOKEN"
+ * name="X-Ninja-Token"
* )
* )
*/
diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php
index 90e85c312f78..55ebaeb559db 100644
--- a/app/Http/Controllers/BaseController.php
+++ b/app/Http/Controllers/BaseController.php
@@ -37,7 +37,7 @@ class BaseController extends Controller
// when restoring redirect to entity
if ($action == 'restore' && count($ids) == 1) {
- return redirect("{$entityTypes}/" . $ids[0]);
+ return redirect("{$entityTypes}/" . $ids[0] . '/edit');
// when viewing from a datatable list
} elseif (strpos($referer, '/clients/')) {
return redirect($referer);
@@ -45,7 +45,7 @@ class BaseController extends Controller
return redirect("{$entityTypes}");
// when viewing individual entity
} elseif (count($ids)) {
- return redirect("{$entityTypes}/" . $ids[0]);
+ return redirect("{$entityTypes}/" . $ids[0] . '/edit');
} else {
return redirect("{$entityTypes}");
}
diff --git a/app/Http/Controllers/ClientApiController.php b/app/Http/Controllers/ClientApiController.php
index 928fe68b8c7c..d32968eacbf9 100644
--- a/app/Http/Controllers/ClientApiController.php
+++ b/app/Http/Controllers/ClientApiController.php
@@ -26,11 +26,12 @@ class ClientApiController extends BaseAPIController
/**
* @SWG\Get(
* path="/clients",
- * summary="List of clients",
+ * summary="List clients",
+ * operationId="listClients",
* tags={"client"},
* @SWG\Response(
* response=200,
- * description="A list with clients",
+ * description="A list of clients",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Client"))
* ),
* @SWG\Response(
@@ -45,11 +46,12 @@ class ClientApiController extends BaseAPIController
->orderBy('created_at', 'desc')
->withTrashed();
- // Filter by email
if ($email = Input::get('email')) {
$clients = $clients->whereHas('contacts', function ($query) use ($email) {
$query->where('email', $email);
});
+ } elseif ($idNumber = Input::get('id_number')) {
+ $clients = $clients->whereIdNumber($idNumber);
}
return $this->listResponse($clients);
@@ -58,8 +60,15 @@ class ClientApiController extends BaseAPIController
/**
* @SWG\Get(
* path="/clients/{client_id}",
- * summary="Individual Client",
+ * summary="Retrieve a client",
+ * operationId="getClient",
* tags={"client"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="client_id",
+ * type="integer",
+ * required=true
+ * ),
* @SWG\Response(
* response=200,
* description="A single client",
@@ -79,11 +88,12 @@ class ClientApiController extends BaseAPIController
/**
* @SWG\Post(
* path="/clients",
- * tags={"client"},
* summary="Create a client",
+ * operationId="createClient",
+ * tags={"client"},
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="client",
* @SWG\Schema(ref="#/definitions/Client")
* ),
* @SWG\Response(
@@ -107,16 +117,23 @@ class ClientApiController extends BaseAPIController
/**
* @SWG\Put(
* path="/clients/{client_id}",
- * tags={"client"},
* summary="Update a client",
+ * operationId="updateClient",
+ * tags={"client"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="client_id",
+ * type="integer",
+ * required=true
+ * ),
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="client",
* @SWG\Schema(ref="#/definitions/Client")
* ),
* @SWG\Response(
* response=200,
- * description="Update client",
+ * description="Updated client",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client"))
* ),
* @SWG\Response(
@@ -145,16 +162,18 @@ class ClientApiController extends BaseAPIController
/**
* @SWG\Delete(
* path="/clients/{client_id}",
- * tags={"client"},
* summary="Delete a client",
+ * operationId="deleteClient",
+ * tags={"client"},
* @SWG\Parameter(
- * in="body",
- * name="body",
- * @SWG\Schema(ref="#/definitions/Client")
+ * in="path",
+ * name="client_id",
+ * type="integer",
+ * required=true
* ),
* @SWG\Response(
* response=200,
- * description="Delete client",
+ * description="Deleted client",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client"))
* ),
* @SWG\Response(
diff --git a/app/Http/Controllers/ClientPortalController.php b/app/Http/Controllers/ClientPortalController.php
index 40a46176fc59..c583835fd49d 100644
--- a/app/Http/Controllers/ClientPortalController.php
+++ b/app/Http/Controllers/ClientPortalController.php
@@ -68,6 +68,7 @@ class ClientPortalController extends BaseController
}
$account->loadLocalizationSettings($client);
+ $this->invoiceRepo->clearGatewayFee($invoice);
if (! Input::has('phantomjs') && ! session('silent:' . $client->id) && ! Session::has($invitation->invitation_key)
&& (! Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
@@ -146,6 +147,7 @@ class ClientPortalController extends BaseController
'paymentTypes' => $paymentTypes,
'paymentURL' => $paymentURL,
'phantomjs' => Input::has('phantomjs'),
+ 'gatewayTypeId' => count($paymentTypes) == 1 ? $paymentTypes[0]['gatewayTypeId'] : false,
];
if ($paymentDriver = $account->paymentDriver($invitation, GATEWAY_TYPE_CREDIT_CARD)) {
@@ -521,7 +523,7 @@ class ClientPortalController extends BaseController
'account' => $account,
'title' => trans('texts.credits'),
'entityType' => ENTITY_CREDIT,
- 'columns' => Utils::trans(['credit_date', 'credit_amount', 'credit_balance']),
+ 'columns' => Utils::trans(['credit_date', 'credit_amount', 'credit_balance', 'notes']),
];
return response()->view('public_list', $data);
diff --git a/app/Http/Controllers/ContactApiController.php b/app/Http/Controllers/ContactApiController.php
new file mode 100644
index 000000000000..a74658077f59
--- /dev/null
+++ b/app/Http/Controllers/ContactApiController.php
@@ -0,0 +1,182 @@
+contactRepo = $contactRepo;
+ $this->contactService = $contactService;
+ }
+
+ /**
+ * @SWG\Get(
+ * path="/contacts",
+ * summary="List contacts",
+ * tags={"contact"},
+ * @SWG\Response(
+ * response=200,
+ * description="A list of contacts",
+ * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Contact"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function index()
+ {
+ $contacts = Contact::scope()
+ ->withTrashed()
+ ->orderBy('created_at', 'desc');
+
+ return $this->listResponse($contacts);
+ }
+
+ /**
+ * @SWG\Get(
+ * path="/contacts/{contact_id}",
+ * summary="Retrieve a contact",
+ * tags={"contact"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="contact_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="A single contact",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function show(ContactRequest $request)
+ {
+ return $this->itemResponse($request->entity());
+ }
+
+ /**
+ * @SWG\Post(
+ * path="/contacts",
+ * tags={"contact"},
+ * summary="Create a contact",
+ * @SWG\Parameter(
+ * in="body",
+ * name="contact",
+ * @SWG\Schema(ref="#/definitions/Contact")
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="New contact",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function store(CreateContactRequest $request)
+ {
+ $contact = $this->contactService->save($request->input());
+
+ return $this->itemResponse($contact);
+ }
+
+ /**
+ * @SWG\Put(
+ * path="/contacts/{contact_id}",
+ * tags={"contact"},
+ * summary="Update a contact",
+ * @SWG\Parameter(
+ * in="path",
+ * name="contact_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Parameter(
+ * in="body",
+ * name="contact",
+ * @SWG\Schema(ref="#/definitions/Contact")
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="Updated contact",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ *
+ * @param mixed $publicId
+ */
+ public function update(UpdateContactRequest $request, $publicId)
+ {
+ if ($request->action) {
+ return $this->handleAction($request);
+ }
+
+ $data = $request->input();
+ $data['public_id'] = $publicId;
+ $contact = $this->contactService->save($data, $request->entity());
+
+ return $this->itemResponse($contact);
+ }
+
+ /**
+ * @SWG\Delete(
+ * path="/contacts/{contact_id}",
+ * tags={"contact"},
+ * summary="Delete a contact",
+ * @SWG\Parameter(
+ * in="path",
+ * name="contact_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="Deleted contact",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Contact"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function destroy(UpdateContactRequest $request)
+ {
+ $contact = $request->entity();
+
+ $this->contactRepo->delete($contact);
+
+ return $this->itemResponse($contact);
+ }
+}
diff --git a/app/Http/Controllers/DocumentAPIController.php b/app/Http/Controllers/DocumentAPIController.php
index fb85331eb1e2..df3691599535 100644
--- a/app/Http/Controllers/DocumentAPIController.php
+++ b/app/Http/Controllers/DocumentAPIController.php
@@ -2,8 +2,9 @@
namespace App\Http\Controllers;
-use App\Http\Requests\CreateDocumentRequest;
use App\Http\Requests\DocumentRequest;
+use App\Http\Requests\CreateDocumentRequest;
+use App\Http\Requests\UpdateDocumentRequest;
use App\Models\Document;
use App\Ninja\Repositories\DocumentRepository;
@@ -37,11 +38,12 @@ class DocumentAPIController extends BaseAPIController
/**
* @SWG\Get(
* path="/documents",
- * summary="List of document",
+ * summary="List document",
+ * operationId="listDocuments",
* tags={"document"},
* @SWG\Response(
* response=200,
- * description="A list with documents",
+ * description="A list of documents",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Document"))
* ),
* @SWG\Response(
@@ -61,6 +63,29 @@ class DocumentAPIController extends BaseAPIController
* @param DocumentRequest $request
*
* @return \Illuminate\Http\Response|\Redirect|\Symfony\Component\HttpFoundation\StreamedResponse
+ *
+ * @SWG\Get(
+ * path="/documents/{document_id}",
+ * summary="Download a document",
+ * operationId="getDocument",
+ * tags={"document"},
+ * produces={"application/octet-stream"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="document_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="A file",
+ * @SWG\Schema(type="file")
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
*/
public function show(DocumentRequest $request)
{
@@ -76,11 +101,12 @@ class DocumentAPIController extends BaseAPIController
/**
* @SWG\Post(
* path="/documents",
- * tags={"document"},
* summary="Create a document",
+ * operationId="createDocument",
+ * tags={"document"},
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="document",
* @SWG\Schema(ref="#/definitions/Document")
* ),
* @SWG\Response(
@@ -100,4 +126,36 @@ class DocumentAPIController extends BaseAPIController
return $this->itemResponse($document);
}
+
+ /**
+ * @SWG\Delete(
+ * path="/documents/{document_id}",
+ * summary="Delete a document",
+ * operationId="deleteDocument",
+ * tags={"document"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="document_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="Deleted document",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Document"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function destroy(UpdateDocumentRequest $request)
+ {
+ $entity = $request->entity();
+
+ $this->documentRepo->delete($entity);
+
+ return $this->itemResponse($entity);
+ }
}
diff --git a/app/Http/Controllers/ExpenseApiController.php b/app/Http/Controllers/ExpenseApiController.php
index 07bbcb995fce..1b67c1e8d1f1 100644
--- a/app/Http/Controllers/ExpenseApiController.php
+++ b/app/Http/Controllers/ExpenseApiController.php
@@ -2,8 +2,8 @@
namespace App\Http\Controllers;
-use App\Http\Requests\CreateExpenseRequest;
use App\Http\Requests\ExpenseRequest;
+use App\Http\Requests\CreateExpenseRequest;
use App\Http\Requests\UpdateExpenseRequest;
use App\Models\Expense;
use App\Ninja\Repositories\ExpenseRepository;
@@ -28,11 +28,12 @@ class ExpenseApiController extends BaseAPIController
/**
* @SWG\Get(
* path="/expenses",
- * summary="List of expenses",
+ * summary="List expenses",
+ * operationId="listExpenses",
* tags={"expense"},
* @SWG\Response(
* response=200,
- * description="A list with expenses",
+ * description="A list of expenses",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Expense"))
* ),
* @SWG\Response(
@@ -51,14 +52,43 @@ class ExpenseApiController extends BaseAPIController
return $this->listResponse($expenses);
}
+ /**
+ * @SWG\Get(
+ * path="/expenses/{expense_id}",
+ * summary="Retrieve an expense",
+ * operationId="getExpense",
+ * tags={"expense"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="expense_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="A single expense",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function show(ExpenseRequest $request)
+ {
+ return $this->itemResponse($request->entity());
+ }
+
/**
* @SWG\Post(
* path="/expenses",
+ * summary="Create an expense",
+ * operationId="createExpense",
* tags={"expense"},
- * summary="Create a expense",
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="expense",
* @SWG\Schema(ref="#/definitions/Expense")
* ),
* @SWG\Response(
@@ -86,16 +116,23 @@ class ExpenseApiController extends BaseAPIController
/**
* @SWG\Put(
* path="/expenses/{expense_id}",
+ * summary="Update an expense",
+ * operationId="updateExpense",
* tags={"expense"},
- * summary="Update a expense",
+ * @SWG\Parameter(
+ * in="path",
+ * name="expense_id",
+ * type="integer",
+ * required=true
+ * ),
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="expense",
* @SWG\Schema(ref="#/definitions/Expense")
* ),
* @SWG\Response(
* response=200,
- * description="Update expense",
+ * description="Updated expense",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense"))
* ),
* @SWG\Response(
@@ -122,16 +159,18 @@ class ExpenseApiController extends BaseAPIController
/**
* @SWG\Delete(
* path="/expenses/{expense_id}",
+ * summary="Delete an expense",
+ * operationId="deleteExpense",
* tags={"expense"},
- * summary="Delete a expense",
* @SWG\Parameter(
- * in="body",
- * name="body",
- * @SWG\Schema(ref="#/definitions/Expense")
+ * in="path",
+ * name="expense_id",
+ * type="integer",
+ * required=true
* ),
* @SWG\Response(
* response=200,
- * description="Delete expense",
+ * description="Deleted expense",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Expense"))
* ),
* @SWG\Response(
@@ -140,7 +179,7 @@ class ExpenseApiController extends BaseAPIController
* )
* )
*/
- public function destroy(ExpenseRequest $request)
+ public function destroy(UpdateExpenseRequest $request)
{
$expense = $request->entity();
diff --git a/app/Http/Controllers/ExpenseCategoryApiController.php b/app/Http/Controllers/ExpenseCategoryApiController.php
index cbdcb3f5e1a4..1a2dea026ca4 100644
--- a/app/Http/Controllers/ExpenseCategoryApiController.php
+++ b/app/Http/Controllers/ExpenseCategoryApiController.php
@@ -2,8 +2,10 @@
namespace App\Http\Controllers;
+use App\Http\Requests\ExpenseCategoryRequest;
use App\Http\Requests\CreateExpenseCategoryRequest;
use App\Http\Requests\UpdateExpenseCategoryRequest;
+use App\Models\ExpenseCategory;
use App\Ninja\Repositories\ExpenseCategoryRepository;
use App\Services\ExpenseCategoryService;
use Input;
@@ -22,14 +24,69 @@ class ExpenseCategoryApiController extends BaseAPIController
$this->categoryService = $categoryService;
}
+ /**
+ * @SWG\Get(
+ * path="/expense_categories",
+ * summary="List expense categories",
+ * operationId="listExpenseCategories",
+ * tags={"expense_category"},
+ * @SWG\Response(
+ * response=200,
+ * description="A list of expense categories",
+ * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/ExpenseCategory"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function index()
+ {
+ $clients = ExpenseCategory::scope()
+ ->orderBy('created_at', 'desc')
+ ->withTrashed();
+
+ return $this->listResponse($clients);
+ }
+
+ /**
+ * @SWG\Get(
+ * path="/expense_categories/{expense_category_id}",
+ * summary="Retrieve an Expense Category",
+ * operationId="getExpenseCategory",
+ * tags={"expense_category"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="expense_category_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="A single expense categroy",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function show(ExpenseCategory $request)
+ {
+ return $this->itemResponse($request->entity());
+ }
+
/**
* @SWG\Post(
* path="/expense_categories",
- * tags={"expense_category"},
* summary="Create an expense category",
+ * operationId="createExpenseCategory",
+ * tags={"expense_category"},
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="expense_category",
* @SWG\Schema(ref="#/definitions/ExpenseCategory")
* ),
* @SWG\Response(
@@ -53,16 +110,23 @@ class ExpenseCategoryApiController extends BaseAPIController
/**
* @SWG\Put(
* path="/expense_categories/{expense_category_id}",
- * tags={"expense_category"},
* summary="Update an expense category",
+ * operationId="updateExpenseCategory",
+ * tags={"expense_category"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="expense_category_id",
+ * type="integer",
+ * required=true
+ * ),
* @SWG\Parameter(
* in="body",
- * name="body",
+ * name="expense_category",
* @SWG\Schema(ref="#/definitions/ExpenseCategory")
* ),
* @SWG\Response(
* response=200,
- * description="Update expense category",
+ * description="Updated expense category",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory"))
* ),
* @SWG\Response(
@@ -77,4 +141,36 @@ class ExpenseCategoryApiController extends BaseAPIController
return $this->itemResponse($category);
}
+
+ /**
+ * @SWG\Delete(
+ * path="/expense_categories/{expense_category_id}",
+ * summary="Delete an expense category",
+ * operationId="deleteExpenseCategory",
+ * tags={"expense_category"},
+ * @SWG\Parameter(
+ * in="path",
+ * name="expense_category_id",
+ * type="integer",
+ * required=true
+ * ),
+ * @SWG\Response(
+ * response=200,
+ * description="Deleted expense category",
+ * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/ExpenseCategory"))
+ * ),
+ * @SWG\Response(
+ * response="default",
+ * description="an ""unexpected"" error"
+ * )
+ * )
+ */
+ public function destroy(UpdateExpenseCategoryRequest $request)
+ {
+ $entity = $request->entity();
+
+ $this->expenseCategoryRepo->delete($entity);
+
+ return $this->itemResponse($entity);
+ }
}
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index 7f26529fe06d..7948d210e27b 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -65,12 +65,6 @@ class HomeController extends BaseController
*/
public function invoiceNow()
{
- if (Auth::check() && Input::get('new_company')) {
- Session::put(PREV_USER_ID, Auth::user()->id);
- Auth::user()->clearSession();
- Auth::logout();
- }
-
// Track the referral/campaign code
if (Input::has('rc')) {
Session::set(SESSION_REFERRAL_CODE, Input::get('rc'));
diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php
index 6fde46cd1613..49618a7abc72 100644
--- a/app/Http/Controllers/ImportController.php
+++ b/app/Http/Controllers/ImportController.php
@@ -2,57 +2,92 @@
namespace App\Http\Controllers;
+use Illuminate\Http\Request;
use App\Services\ImportService;
+use App\Jobs\ImportData;
use Exception;
use Input;
use Redirect;
use Session;
use Utils;
use View;
+use Auth;
class ImportController extends BaseController
{
public function __construct(ImportService $importService)
{
- //parent::__construct();
-
$this->importService = $importService;
}
- public function doImport()
+ public function doImport(Request $request)
{
+ if (! Auth::user()->confirmed) {
+ return redirect('/settings/' . ACCOUNT_IMPORT_EXPORT)->withError(trans('texts.confirm_account_to_import'));
+ }
+
$source = Input::get('source');
$files = [];
+ $timestamp = time();
foreach (ImportService::$entityTypes as $entityType) {
- if (Input::file("{$entityType}_file")) {
- $files[$entityType] = Input::file("{$entityType}_file")->getRealPath();
- if ($source === IMPORT_CSV) {
- Session::forget("{$entityType}-data");
+ $fileName = $entityType;
+ if ($request->hasFile($fileName)) {
+ $file = $request->file($fileName);
+ $destinationPath = storage_path() . '/import';
+ $extension = $file->getClientOriginalExtension();
+
+ if (! in_array($extension, ['csv', 'xls', 'xlsx', 'json'])) {
+ continue;
}
+
+ $newFileName = sprintf('%s_%s_%s.%s', Auth::user()->account_id, $timestamp, $fileName, $extension);
+ $file->move($destinationPath, $newFileName);
+ $files[$entityType] = $newFileName;
}
}
if (! count($files)) {
Session::flash('error', trans('texts.select_file'));
-
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
}
try {
if ($source === IMPORT_CSV) {
$data = $this->importService->mapCSV($files);
-
- return View::make('accounts.import_map', ['data' => $data]);
+ return View::make('accounts.import_map', [
+ 'data' => $data,
+ 'timestamp' => $timestamp,
+ ]);
} elseif ($source === IMPORT_JSON) {
- $results = $this->importService->importJSON($files[IMPORT_JSON]);
-
- return $this->showResult($results);
+ $includeData = filter_var(Input::get('data'), FILTER_VALIDATE_BOOLEAN);
+ $includeSettings = filter_var(Input::get('settings'), FILTER_VALIDATE_BOOLEAN);
+ if (config('queue.default') === 'sync') {
+ $results = $this->importService->importJSON($files[IMPORT_JSON], $includeData, $includeSettings);
+ $message = $this->importService->presentResults($results, $includeSettings);
+ } else {
+ $settings = [
+ 'files' => $files,
+ 'include_data' => $includeData,
+ 'include_settings' => $includeSettings,
+ ];
+ $this->dispatch(new ImportData(Auth::user(), IMPORT_JSON, $settings));
+ $message = trans('texts.import_started');
+ }
} else {
- $results = $this->importService->importFiles($source, $files);
-
- return $this->showResult($results);
+ if (config('queue.default') === 'sync') {
+ $results = $this->importService->importFiles($source, $files);
+ $message = $this->importService->presentResults($results);
+ } else {
+ $settings = [
+ 'files' => $files,
+ 'source' => $source,
+ ];
+ $this->dispatch(new ImportData(Auth::user(), false, $settings));
+ $message = trans('texts.import_started');
+ }
}
+ return redirect('/settings/' . ACCOUNT_IMPORT_EXPORT)->withWarning($message);
} catch (Exception $exception) {
Utils::logError($exception);
Session::flash('error', $exception->getMessage());
@@ -63,13 +98,24 @@ class ImportController extends BaseController
public function doImportCSV()
{
- $map = Input::get('map');
- $headers = Input::get('headers');
-
try {
- $results = $this->importService->importCSV($map, $headers);
+ $map = Input::get('map');
+ $headers = Input::get('headers');
+ $timestamp = Input::get('timestamp');
+ if (config('queue.default') === 'sync') {
+ $results = $this->importService->importCSV($map, $headers, $timestamp);
+ $message = $this->importService->presentResults($results);
+ } else {
+ $settings = [
+ 'timestamp' => $timestamp,
+ 'map' => $map,
+ 'headers' => $headers,
+ ];
+ $this->dispatch(new ImportData(Auth::user(), IMPORT_CSV, $settings));
+ $message = trans('texts.import_started');
+ }
- return $this->showResult($results);
+ return redirect('/settings/' . ACCOUNT_IMPORT_EXPORT)->withWarning($message);
} catch (Exception $exception) {
Utils::logError($exception);
Session::flash('error', $exception->getMessage());
@@ -77,32 +123,4 @@ class ImportController extends BaseController
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
}
}
-
- private function showResult($results)
- {
- $message = '';
- $skipped = [];
-
- foreach ($results as $entityType => $entityResults) {
- if ($count = count($entityResults[RESULT_SUCCESS])) {
- $message .= trans("texts.created_{$entityType}s", ['count' => $count]) . '
';
- }
- if (count($entityResults[RESULT_FAILURE])) {
- $skipped = array_merge($skipped, $entityResults[RESULT_FAILURE]);
- }
- }
-
- if (count($skipped)) {
- $message .= '
t |
p,v=i.minHeight&&i.minHeight>f;i.grid=c,b&&(p+=l),v&&(f+=u),m&&(p-=l),g&&(f-=u),/^(se|s|e)$/.test(r)?(n.size.width=p,n.size.height=f):/^(ne)$/.test(r)?(n.size.width=p,n.size.height=f,n.position.top=s.top-d):/^(sw)$/.test(r)?(n.size.width=p,n.size.height=f,n.position.left=s.left-h):((f-u<=0||p-l<=0)&&(e=n._getPaddingPlusBorderDimensions(this)),f-u>0?(n.size.height=f,n.position.top=s.top-d):(f=u-e.height,n.size.height=f,n.position.top=s.top+a.height-f),p-l>0?(n.size.width=p,n.position.left=s.left-h):(p=u-e.height,n.size.width=p,n.position.left=s.left+a.width-p))}});t.ui.resizable,t.widget("ui.dialog",{version:"1.11.2",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"Close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var n=t(this).css(e).offset().top;n<0&&t(this).css("top",e.top-n)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog},disable:t.noop,enable:t.noop,close:function(e){var n,i=this;if(this._isOpen&&this._trigger("beforeClose",e)!==!1){if(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),!this.opener.filter(":focusable").focus().length)try{n=this.document[0].activeElement,n&&"body"!==n.nodeName.toLowerCase()&&t(n).blur()}catch(o){}this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,n){var i=!1,o=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),a=Math.max.apply(null,o);return a>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",a+1),i=!0),i&&!n&&this._trigger("focus",e),i},open:function(){var e=this;return this._isOpen?void(this._moveToTop()&&this._focusTabbable()):(this._isOpen=!0,this.opener=t(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),void this._trigger("open"))},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).focus()},_keepFocus:function(e){function n(){var e=this.document[0].activeElement,n=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);n||this._focusTabbable()}e.preventDefault(),n.call(this),this._delay(n)},_createWrapper:function(){this.uiDialog=t("
1&&M.splice.apply(M,[1,0].concat(M.splice(y,f+1))),s.dequeue()},t.effects.effect.clip=function(e,n){var i,o,a,s=t(this),r=["position","top","bottom","left","right","height","width"],c=t.effects.setMode(s,e.mode||"hide"),l="show"===c,u=e.direction||"vertical",h="vertical"===u,d=h?"height":"width",p=h?"top":"left",f={};t.effects.save(s,r),s.show(),i=t.effects.createWrapper(s).css({overflow:"hidden"}),o="IMG"===s[0].tagName?i:s,a=o[d](),l&&(o.css(d,0),o.css(p,a/2)),f[d]=l?a:0,f[p]=l?0:a/2,o.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){l||s.hide(),t.effects.restore(s,r),t.effects.removeWrapper(s),n()}})},t.effects.effect.drop=function(e,n){var i,o=t(this),a=["position","top","bottom","left","right","opacity","height","width"],s=t.effects.setMode(o,e.mode||"hide"),r="show"===s,c=e.direction||"left",l="up"===c||"down"===c?"top":"left",u="up"===c||"left"===c?"pos":"neg",h={opacity:r?1:0};t.effects.save(o,a),o.show(),t.effects.createWrapper(o),i=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&o.css("opacity",0).css(l,"pos"===u?-i:i),h[l]=(r?"pos"===u?"+=":"-=":"pos"===u?"-=":"+=")+i,o.animate(h,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===s&&o.hide(),t.effects.restore(o,a),t.effects.removeWrapper(o),n()}})},t.effects.effect.explode=function(e,n){function i(){M.push(this),M.length===h*d&&o()}function o(){p.css({visibility:"visible"}),t(M).remove(),m||p.hide(),n()}var a,s,r,c,l,u,h=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=h,p=t(this),f=t.effects.setMode(p,e.mode||"hide"),m="show"===f,g=p.show().css("visibility","hidden").offset(),b=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/h),M=[];for(a=0;a
t<"F"ip>'),C.renderer?i.isPlainObject(C.renderer)&&!C.renderer.header&&(C.renderer.header="jqueryui"):C.renderer="jqueryui"):i.extend(O,Yt.ext.classes,m.oClasses),i(this).addClass(O.sTable),""===C.oScroll.sX&&""===C.oScroll.sY||(C.oScroll.iBarWidth=wt()),C.oScroll.sX===!0&&(C.oScroll.sX="100%"),C.iInitDisplayStart===n&&(C.iInitDisplayStart=m.iDisplayStart,C._iDisplayStart=m.iDisplayStart),null!==m.iDeferLoading){C.bDeferLoading=!0;var N=i.isArray(m.iDeferLoading);C._iRecordsDisplay=N?m.iDeferLoading[0]:m.iDeferLoading,C._iRecordsTotal=N?m.iDeferLoading[1]:m.iDeferLoading}var S=C.oLanguage;i.extend(!0,S,m.oLanguage),""!==S.sUrl&&(i.ajax({dataType:"json",url:S.sUrl,success:function(t){s(t),a(z.oLanguage,t),i.extend(!0,S,t),rt(C)},error:function(){rt(C)}}),v=!0),null===m.asStripeClasses&&(C.asStripeClasses=[O.sStripeOdd,O.sStripeEven]);var x=C.asStripeClasses,L=i("tbody tr:eq(0)",this);i.inArray(!0,i.map(x,function(t,e){return L.hasClass(t)}))!==-1&&(i("tbody tr",this).removeClass(x.join(" ")),C.asDestroyStripes=x.slice());var D,q=[],W=this.getElementsByTagName("thead");if(0!==W.length&&(R(C.aoHeader,W[0]),q=F(C)),null===m.aoColumns)for(D=[],g=0,p=q.length;g
").appendTo(this)),C.nTHead=P[0];var H=i(this).children("tbody");0===H.length&&(H=i("
").appendTo(this)),C.nTBody=H[0];var j=i(this).children("tfoot");if(0===j.length&&X.length>0&&(""!==C.oScroll.sX||""!==C.oScroll.sY)&&(j=i("").appendTo(this)),0===j.length||0===j.children().length?i(this).addClass(O.sNoFooter):j.length>0&&(C.nTFoot=j[0],R(C.aoFooter,C.nTFoot)),m.aaData)for(g=0;g