diff --git a/.env.example b/.env.example index c955b9de3a71..d103500d8a51 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ APP_KEY=SomeRandomString APP_TIMEZONE DB_TYPE=mysql +DB_STRICT=false DB_HOST=localhost DB_DATABASE=ninja DB_USERNAME @@ -22,6 +23,7 @@ MAIL_PASSWORD PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' LOG=single +REQUIRE_HTTPS=false GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index 75ef6ac8a383..9c51c40dcd3c 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -51,26 +51,21 @@ class AccountApiController extends BaseAPIController $this->accountRepo->createTokens($user, $request->token_name); $users = $this->accountRepo->findUsers($user, 'account.account_tokens'); - $data = $this->createCollection($users, new UserAccountTransformer($user->account, $request->token_name)); + $transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name); + $data = $this->createCollection($users, $transformer, 'user_account'); - $response = [ - 'user_accounts' => $data - ]; - - return $this->response($response); + return $this->response($data); } - public function show() + public function show(Request $request) { $account = Auth::user()->account; $account->loadAllData(); - $account = $this->createItem($account, new AccountTransformer); - $response = [ - 'account' => $account - ]; + $transformer = new AccountTransformer(null, $request->serializer); + $account = $this->createItem($account, $transformer, 'account'); - return $this->response($response); + return $this->response($account); } public function getStaticData() diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 05da6eea544b..d7722136d2de 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -212,13 +212,8 @@ class AccountController extends BaseController { // check that logo is less than the max file size $account = Auth::user()->account; - if ($account->hasLogo()) { - $filename = $account->getLogoPath(); - $bytes = File::size($filename); - if ($bytes > MAX_LOGO_FILE_SIZE * 1000) { - $bytes /= 1000; - Session::flash('warning', trans('texts.logo_too_large', ['size' => round($bytes) . 'KB'])); - } + if ($account->isLogoTooLarge()) { + Session::flash('warning', trans('texts.logo_too_large', ['size' => $account->getLogoSize() . 'KB'])); } $data = [ @@ -272,6 +267,12 @@ class AccountController extends BaseController $account->load('account_gateways'); $count = count($account->account_gateways); + if ($accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE)) { + if ( ! $accountGateway->getPublishableStripeKey()) { + Session::flash('warning', trans('texts.missing_publishable_key')); + } + } + if ($count == 0) { return Redirect::to('gateways/create'); } else { diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 56f8fd662375..d481e1b029d8 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -236,6 +236,13 @@ class AccountGatewayController extends BaseController } } + $publishableKey = Input::get('publishable_key'); + if ($publishableKey = str_replace('*', '', $publishableKey)) { + $config->publishableKey = $publishableKey; + } elseif ($oldConfig && property_exists($oldConfig, 'publishableKey')) { + $config->publishableKey = $oldConfig->publishableKey; + } + $cardCount = 0; if ($creditcards) { foreach ($creditcards as $card => $value) { diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php index ac10d6678622..e5878e22ab55 100644 --- a/app/Http/Controllers/BaseAPIController.php +++ b/app/Http/Controllers/BaseAPIController.php @@ -1,12 +1,16 @@ manager = new Manager(); - $this->manager->setSerializer(new ArraySerializer()); + + if ($include = Request::get('include')) { + $this->manager->parseIncludes($include); + } + + $this->serializer = Request::get('serializer') ?: API_SERIALIZER_ARRAY; + + if ($this->serializer === API_SERIALIZER_JSON) { + $this->manager->setSerializer(new JsonApiSerializer()); + } else { + $this->manager->setSerializer(new ArraySerializer()); + } } - protected function createItem($data, $transformer) + protected function createItem($data, $transformer, $entityType) { - $resource = new Item($data, $transformer); + if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) { + $entityType = null; + } + + $resource = new Item($data, $transformer, $entityType); return $this->manager->createData($resource)->toArray(); } - protected function createCollection($data, $transformer) + protected function createCollection($data, $transformer, $entityType, $paginator = false) { - $resource = new Collection($data, $transformer); + if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) { + $entityType = null; + } + + $resource = new Collection($data, $transformer, $entityType); + + if ($paginator) { + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + } + return $this->manager->createData($resource)->toArray(); } protected function response($response) { + $index = Request::get('index') ?: 'data'; + $meta = isset($response['meta']) ? $response['meta'] : null; + $response = [ + $index => $response + ]; + if ($meta) { + $response['meta'] = $meta; + unset($response[$index]['meta']); + } + $response = json_encode($response, JSON_PRETTY_PRINT); $headers = Utils::getApiHeaders(); return Response::make($response, 200, $headers); } + protected function getIncluded() + { + $data = ['user']; + + $included = Request::get('include'); + $included = explode(',', $included); + + foreach ($included as $include) { + if ($include == 'invoices') { + $data[] = 'invoices.invoice_items'; + $data[] = 'invoices.user'; + } elseif ($include == 'clients') { + $data[] = 'clients.contacts'; + $data[] = 'clients.user'; + } elseif ($include) { + $data[] = $include; + } + } + + return $data; + } + } diff --git a/app/Http/Controllers/ClientApiController.php b/app/Http/Controllers/ClientApiController.php index c4cce4d570ad..bbe0f9fde298 100644 --- a/app/Http/Controllers/ClientApiController.php +++ b/app/Http/Controllers/ClientApiController.php @@ -3,16 +3,21 @@ use Utils; use Response; use Input; +use Auth; use App\Models\Client; use App\Ninja\Repositories\ClientRepository; use App\Http\Requests\CreateClientRequest; +use App\Http\Controllers\BaseAPIController; +use App\Ninja\Transformers\ClientTransformer; -class ClientApiController extends Controller +class ClientApiController extends BaseAPIController { protected $clientRepo; public function __construct(ClientRepository $clientRepo) { + parent::__construct(); + $this->clientRepo = $clientRepo; } @@ -42,15 +47,16 @@ class ClientApiController extends Controller public function index() { $clients = Client::scope() - ->with('country', 'contacts', 'industry', 'size', 'currency') + ->with($this->getIncluded()) ->orderBy('created_at', 'desc') - ->get(); - $clients = Utils::remapPublicIds($clients); + ->paginate(); - $response = json_encode($clients, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(count($clients)); + $transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer')); + $paginator = Client::scope()->paginate(); - return Response::make($response, 200, $headers); + $data = $this->createCollection($clients, $transformer, ENTITY_CLIENT, $paginator); + + return $this->response($data); } /** @@ -77,12 +83,14 @@ class ClientApiController extends Controller public function store(CreateClientRequest $request) { $client = $this->clientRepo->save($request->input()); + + $client = Client::scope($client->public_id) + ->with('country', 'contacts', 'industry', 'size', 'currency') + ->first(); - $client = Client::scope($client->public_id)->with('country', 'contacts', 'industry', 'size', 'currency')->first(); - $client = Utils::remapPublicIds([$client]); - $response = json_encode($client, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(); + $transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($client, $transformer, ENTITY_CLIENT); - return Response::make($response, 200, $headers); + return $this->response($data); } } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 6504d54ed90f..6080a70331f2 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -60,7 +60,7 @@ class ClientController extends BaseController 'date_created', 'last_login', 'balance', - 'action' + '' ]), )); } diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index e2d5ad78c554..e08c136b5267 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -44,7 +44,7 @@ class CreditController extends BaseController 'credit_balance', 'credit_date', 'private_notes', - 'action' + '' ]), )); } diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 7573e72e7087..53c25ed5d78a 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -21,41 +21,56 @@ class ImportController extends BaseController public function doImport() { $source = Input::get('source'); + $files = []; + $skipped = []; - if ($source === IMPORT_CSV) { - $filename = Input::file('client_file')->getRealPath(); - $data = $this->importService->mapFile($filename); + foreach (ImportService::$entityTypes as $entityType) { + if (Input::file("{$entityType}_file")) { + $files[$entityType] = Input::file("{$entityType}_file")->getRealPath(); + } + } - return View::make('accounts.import_map', $data); - } else { - $files = []; - foreach (ImportService::$entityTypes as $entityType) { - if (Input::file("{$entityType}_file")) { - $files[$entityType] = Input::file("{$entityType}_file")->getRealPath(); + try { + if ($source === IMPORT_CSV) { + $data = $this->importService->mapCSV($files); + return View::make('accounts.import_map', ['data' => $data]); + } else { + $skipped = $this->importService->import($source, $files); + if (count($skipped)) { + $message = trans('texts.failed_to_import'); + foreach ($skipped as $skip) { + $message .= '
' . json_encode($skip); + } + Session::flash('warning', $message); + } else { + Session::flash('message', trans('texts.imported_file')); } } - - try { - $result = $this->importService->import($source, $files); - Session::flash('message', trans('texts.imported_file') . ' - ' . $result); - } catch (Exception $exception) { - Session::flash('error', $exception->getMessage()); - } - - return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); + } catch (Exception $exception) { + Session::flash('error', $exception->getMessage()); } + + return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT); } public function doImportCSV() { $map = Input::get('map'); - $hasHeaders = Input::get('header_checkbox'); + $headers = Input::get('headers'); + $skipped = []; try { - $count = $this->importService->importCSV($map, $hasHeaders); - $message = Utils::pluralize('created_client', $count); + $skipped = $this->importService->importCSV($map, $headers); - Session::flash('message', $message); + if (count($skipped)) { + $message = trans('texts.failed_to_import'); + foreach ($skipped as $skip) { + $message .= '
' . json_encode($skip); + } + Session::flash('warning', $message); + } else { + Session::flash('message', trans('texts.imported_file')); + } } catch (Exception $exception) { Session::flash('error', $exception->getMessage()); } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index b5f92d9a0eed..a85a1b622cd2 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -13,13 +13,17 @@ use App\Models\Invitation; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Mailers\ContactMailer as Mailer; +use App\Http\Controllers\BaseAPIController; +use App\Ninja\Transformers\InvoiceTransformer; -class InvoiceApiController extends Controller +class InvoiceApiController extends BaseAPIController { protected $invoiceRepo; public function __construct(InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, Mailer $mailer) { + parent::__construct(); + $this->invoiceRepo = $invoiceRepo; $this->clientRepo = $clientRepo; $this->mailer = $mailer; @@ -41,20 +45,24 @@ class InvoiceApiController extends Controller * ) * ) */ - public function index($clientPublicId = false) + public function index() { + $paginator = Invoice::scope(); $invoices = Invoice::scope() - ->with('client', 'invitations.account') + ->with(array_merge(['invoice_items'], $this->getIncluded())) ->where('invoices.is_quote', '=', false); - if ($clientPublicId) { - $invoices->whereHas('client', function($query) use ($clientPublicId) { + if ($clientPublicId = Input::get('client_id')) { + $filter = function($query) use ($clientPublicId) { $query->where('public_id', '=', $clientPublicId); - }); + }; + $invoices->whereHas('client', $filter); + $paginator->whereHas('client', $filter); } - $invoices = $invoices->orderBy('created_at', 'desc')->get(); + $invoices = $invoices->orderBy('created_at', 'desc')->paginate(); + /* // Add the first invitation link to the data foreach ($invoices as $key => $invoice) { foreach ($invoice->invitations as $subKey => $invitation) { @@ -62,13 +70,14 @@ class InvoiceApiController extends Controller } unset($invoice['invitations']); } + */ - $invoices = Utils::remapPublicIds($invoices); - - $response = json_encode($invoices, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(count($invoices)); + $transformer = new InvoiceTransformer(Auth::user()->account, Input::get('serializer')); + $paginator = $paginator->paginate(); - return Response::make($response, 200, $headers); + $data = $this->createCollection($invoices, $transformer, 'invoices', $paginator); + + return $this->response($data); } @@ -145,14 +154,14 @@ class InvoiceApiController extends Controller if (!$error) { if (!isset($data['client_id']) && !isset($data['email'])) { - $error = trans('validation.', ['attribute' => 'client_id or email']); + $error = trans('validation.required_without', ['attribute' => 'client_id', 'values' => 'email']); } else if (!$client) { $error = trans('validation.not_in', ['attribute' => 'client_id']); } } if ($error) { - $response = json_encode($error, JSON_PRETTY_PRINT); + return $error; } else { $data = self::prepareData($data, $client); $data['client_id'] = $client->id; @@ -170,16 +179,12 @@ class InvoiceApiController extends Controller $this->mailer->sendInvoice($invoice); } - // prepare the return data $invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->first(); - $invoice = Utils::remapPublicIds([$invoice]); + $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($invoice, $transformer, 'invoice'); - $response = json_encode($invoice, JSON_PRETTY_PRINT); + return $this->response($data); } - - $headers = Utils::getApiHeaders(); - - return Response::make($response, $error ? 400 : 200, $headers); } private function prepareData($data, $client) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index ea870d0f411d..7b04edb733fe 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -69,7 +69,7 @@ class InvoiceController extends BaseController 'balance_due', 'due_date', 'status', - 'action' + '' ]), ]; @@ -326,7 +326,7 @@ class InvoiceController extends BaseController if ($clientPublicId) { $clientId = Client::getPrivateId($clientPublicId); } - + $invoice = $account->createInvoice($entityType, $clientId); $invoice->public_id = 0; diff --git a/app/Http/Controllers/PaymentApiController.php b/app/Http/Controllers/PaymentApiController.php index f23a26969fb0..b271d4c99996 100644 --- a/app/Http/Controllers/PaymentApiController.php +++ b/app/Http/Controllers/PaymentApiController.php @@ -1,18 +1,23 @@ paymentRepo = $paymentRepo; } @@ -32,27 +37,29 @@ class PaymentApiController extends Controller * ) * ) */ - public function index($clientPublicId = false) + public function index() { + $paginator = Payment::scope(); $payments = Payment::scope() - ->with('client', 'contact', 'invitation', 'user', 'invoice'); + ->with('client.contacts', 'invitation', 'user', 'invoice'); - if ($clientPublicId) { - $payments->whereHas('client', function($query) use ($clientPublicId) { + if ($clientPublicId = Input::get('client_id')) { + $filter = function($query) use ($clientPublicId) { $query->where('public_id', '=', $clientPublicId); - }); + }; + $payments->whereHas('client', $filter); + $paginator->whereHas('client', $filter); } - $payments = $payments->orderBy('created_at', 'desc')->get(); - $payments = Utils::remapPublicIds($payments); + $payments = $payments->orderBy('created_at', 'desc')->paginate(); + $paginator = $paginator->paginate(); + $transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer')); - $response = json_encode($payments, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(count($payments)); + $data = $this->createCollection($payments, $transformer, 'payments', $paginator); - return Response::make($response, 200, $headers); + return $this->response($data); } - /** * @SWG\Post( * path="/payments", @@ -96,15 +103,17 @@ class PaymentApiController extends Controller $data['transaction_reference'] = ''; } - if (!$error) { - $payment = $this->paymentRepo->save($data); - $payment = Payment::scope($payment->public_id)->with('client', 'contact', 'user', 'invoice')->first(); - - $payment = Utils::remapPublicIds([$payment]); + if ($error) { + return $error; } - $response = json_encode($error ?: $payment, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(); - return Response::make($response, 200, $headers); + + $payment = $this->paymentRepo->save($data); + $payment = Payment::scope($payment->public_id)->with('client', 'contact', 'user', 'invoice')->first(); + + $transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($payment, $transformer, 'payment'); + + return $this->response($data); } } diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 1c881384ceb0..61e8487f53c5 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -54,7 +54,7 @@ class PaymentController extends BaseController 'method', 'payment_amount', 'payment_date', - 'action' + '' ]), )); } @@ -168,6 +168,7 @@ class PaymentController extends BaseController 'client' => $client, 'contact' => $invitation->contact, 'gateway' => $gateway, + 'accountGateway' => $accountGateway, 'acceptedCreditCardTypes' => $acceptedCreditCardTypes, 'countries' => Cache::get('countries'), 'currencyId' => $client->getCurrencyId(), @@ -349,12 +350,20 @@ class PaymentController extends BaseController $rules = [ 'first_name' => 'required', 'last_name' => 'required', - 'card_number' => 'required', - 'expiration_month' => 'required', - 'expiration_year' => 'required', - 'cvv' => 'required', ]; + if ( ! Input::get('stripeToken')) { + $rules = array_merge( + $rules, + [ + 'card_number' => 'required', + 'expiration_month' => 'required', + 'expiration_year' => 'required', + 'cvv' => 'required', + ] + ); + } + if ($accountGateway->show_address) { $rules = array_merge($rules, [ 'address1' => 'required', @@ -399,12 +408,17 @@ class PaymentController extends BaseController // check if we're creating/using a billing token if ($accountGateway->gateway_id == GATEWAY_STRIPE) { + if ($token = Input::get('stripeToken')) { + $details['token'] = $token; + unset($details['card']); + } + if ($useToken) { - $details['cardReference'] = $client->getGatewayToken(); + $details['customerReference'] = $client->getGatewayToken(); } elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) { $token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id); if ($token) { - $details['cardReference'] = $token; + $details['customerReference'] = $token; } else { $this->error('Token-No-Ref', $this->paymentService->lastError, $accountGateway); return Redirect::to('payment/'.$invitationKey)->withInput(); diff --git a/app/Http/Controllers/QuoteApiController.php b/app/Http/Controllers/QuoteApiController.php index 055f718fb179..3e3cfa580c23 100644 --- a/app/Http/Controllers/QuoteApiController.php +++ b/app/Http/Controllers/QuoteApiController.php @@ -1,16 +1,22 @@ invoiceRepo = $invoiceRepo; } @@ -30,25 +36,29 @@ class QuoteApiController extends Controller * ) * ) */ - public function index($clientPublicId = false) + public function index() { + $paginator = Invoice::scope(); $invoices = Invoice::scope() - ->with('client', 'user') + ->with('client', 'invitations', 'user', 'invoice_items') ->where('invoices.is_quote', '=', true); - if ($clientPublicId) { - $invoices->whereHas('client', function($query) use ($clientPublicId) { + if ($clientPublicId = Input::get('client_id')) { + $filter = function($query) use ($clientPublicId) { $query->where('public_id', '=', $clientPublicId); - }); + }; + $invoices->whereHas('client', $filter); + $paginator->whereHas('client', $filter); } - $invoices = $invoices->orderBy('created_at', 'desc')->get(); - $invoices = Utils::remapPublicIds($invoices); + $invoices = $invoices->orderBy('created_at', 'desc')->paginate(); + + $transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer')); + $paginator = $paginator->paginate(); - $response = json_encode($invoices, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(count($invoices)); + $data = $this->createCollection($invoices, $transformer, 'quotes', $paginator); - return Response::make($response, 200, $headers); + return $this->response($data); } /* diff --git a/app/Http/Controllers/TaskApiController.php b/app/Http/Controllers/TaskApiController.php index ddbd11f7ae92..a302944d2b62 100644 --- a/app/Http/Controllers/TaskApiController.php +++ b/app/Http/Controllers/TaskApiController.php @@ -1,17 +1,22 @@ taskRepo = $taskRepo; } @@ -31,23 +36,27 @@ class TaskApiController extends Controller * ) * ) */ - public function index($clientPublicId = false) + public function index() { - $tasks = Task::scope()->with('client'); + $paginator = Task::scope(); + $tasks = Task::scope() + ->with($this->getIncluded()); - if ($clientPublicId) { - $tasks->whereHas('client', function($query) use ($clientPublicId) { + if ($clientPublicId = Input::get('client_id')) { + $filter = function($query) use ($clientPublicId) { $query->where('public_id', '=', $clientPublicId); - }); + }; + $tasks->whereHas('client', $filter); + $paginator->whereHas('client', $filter); } - $tasks = $tasks->orderBy('created_at', 'desc')->get(); - $tasks = Utils::remapPublicIds($tasks); + $tasks = $tasks->orderBy('created_at', 'desc')->paginate(); + $paginator = $paginator->paginate(); + $transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer')); - $response = json_encode($tasks, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(count($tasks)); + $data = $this->createCollection($tasks, $transformer, 'tasks', $paginator); - return Response::make($response, 200, $headers); + return $this->response($data); } /** @@ -82,12 +91,11 @@ class TaskApiController extends Controller $task = $this->taskRepo->save($taskId, $data); $task = Task::scope($task->public_id)->with('client')->first(); - $task = Utils::remapPublicIds([$task]); - $response = json_encode($task, JSON_PRETTY_PRINT); - $headers = Utils::getApiHeaders(); + $transformer = new TaskTransformer(Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($task, $transformer, 'task'); - return Response::make($response, 200, $headers); + return $this->response($data); } } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 3d4ab08935fe..c2ec5f6d96ff 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -50,7 +50,7 @@ class TaskController extends BaseController 'duration', 'description', 'status', - 'action' + '' ]), )); } diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index 08b586b80fc1..5bc4d543204a 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -33,7 +33,7 @@ class StartupCheck } // Ensure all request are over HTTPS in production - if (Utils::isNinjaProd() && !Request::secure()) { + if (Utils::requireHTTPS() && !Request::secure()) { return Redirect::secure(Request::getRequestUri()); } diff --git a/app/Http/routes.php b/app/Http/routes.php index f58d5269b522..eed60982811f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -199,13 +199,13 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::get('static', 'AccountApiController@getStaticData'); Route::get('accounts', 'AccountApiController@show'); Route::resource('clients', 'ClientApiController'); - Route::get('quotes/{client_id?}', 'QuoteApiController@index'); + Route::get('quotes', 'QuoteApiController@index'); Route::resource('quotes', 'QuoteApiController'); - Route::get('invoices/{client_id?}', 'InvoiceApiController@index'); + Route::get('invoices', 'InvoiceApiController@index'); Route::resource('invoices', 'InvoiceApiController'); - Route::get('payments/{client_id?}', 'PaymentApiController@index'); + Route::get('payments', 'PaymentApiController@index'); Route::resource('payments', 'PaymentApiController'); - Route::get('tasks/{client_id?}', 'TaskApiController@index'); + Route::get('tasks', 'TaskApiController@index'); Route::resource('tasks', 'TaskApiController'); Route::post('hooks', 'IntegrationController@subscribe'); Route::post('email_invoice', 'InvoiceApiController@emailInvoice'); @@ -250,7 +250,9 @@ if (!defined('CONTACT_EMAIL')) { define('RECENTLY_VIEWED', 'RECENTLY_VIEWED'); define('ENTITY_CLIENT', 'client'); + define('ENTITY_CONTACT', 'contact'); define('ENTITY_INVOICE', 'invoice'); + define('ENTITY_INVOICE_ITEMS', 'invoice_items'); define('ENTITY_RECURRING_INVOICE', 'recurring_invoice'); define('ENTITY_PAYMENT', 'payment'); define('ENTITY_CREDIT', 'credit'); @@ -433,7 +435,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.4.6'); + define('NINJA_VERSION', '2.4.7'); define('NINJA_DATE', '2000-01-01'); define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'); @@ -441,7 +443,7 @@ if (!defined('CONTACT_EMAIL')) { define('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'); define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'); define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'); - define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/'); + define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/'); define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'); define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/'); @@ -492,7 +494,9 @@ if (!defined('CONTACT_EMAIL')) { define('USER_STATE_PENDING', 'pending'); define('USER_STATE_DISABLED', 'disabled'); define('USER_STATE_ADMIN', 'admin'); - + + define('API_SERIALIZER_ARRAY', 'array'); + define('API_SERIALIZER_JSON', 'json'); $creditCards = [ 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], @@ -542,8 +546,8 @@ if (!defined('CONTACT_EMAIL')) { } } -/* // Log all SQL queries to laravel.log +/* if (Utils::isNinjaDev()) { Event::listen('illuminate.query', function($query, $bindings, $time, $name) { $data = compact('bindings', 'time', 'name'); diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index a8c74d2b64a5..4af034825d0c 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -60,6 +60,11 @@ class Utils return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true'; } + public static function requireHTTPS() + { + return Utils::isNinjaProd() || (isset($_ENV['REQUIRE_HTTPS']) && $_ENV['REQUIRE_HTTPS'] == 'true'); + } + public static function isOAuthEnabled() { $providers = [ @@ -227,6 +232,13 @@ class Utils return floatval($value); } + public static function parseInt($value) + { + $value = preg_replace('/[^0-9]/', '', $value); + + return intval($value); + } + public static function formatMoney($value, $currencyId = false, $showSymbol = true) { if (!$currencyId) { @@ -585,18 +597,6 @@ class Utils } } - - public static function remapPublicIds($items) - { - $return = []; - - foreach ($items as $item) { - $return[] = $item->toPublicArray(); - } - - return $return; - } - public static function hideIds($data, $mapped = false) { $publicId = null; diff --git a/app/Listeners/HandleUserLoggedIn.php b/app/Listeners/HandleUserLoggedIn.php index 5e65af0397f8..bf39d7e34e11 100644 --- a/app/Listeners/HandleUserLoggedIn.php +++ b/app/Listeners/HandleUserLoggedIn.php @@ -45,6 +45,14 @@ class HandleUserLoggedIn { Session::put(SESSION_USER_ACCOUNTS, $users); $account->loadLocalizationSettings(); + + // if they're using Stripe make sure they're using Stripe.js + $accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE); + if ($accountGateway && ! $accountGateway->getPublishableStripeKey()) { + Session::flash('warning', trans('texts.missing_publishable_key')); + } elseif ($account->isLogoTooLarge()) { + Session::flash('warning', trans('texts.logo_too_large', ['size' => $account->getLogoSize() . 'KB'])); + } } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 7d9d15d992aa..0842f36434da 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -6,6 +6,7 @@ use Session; use DateTime; use Event; use App; +use File; use App\Events\UserSettingsChanged; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; @@ -304,11 +305,13 @@ class Account extends Eloquent { $invoice = Invoice::createNew(); + $invoice->is_recurring = false; + $invoice->is_quote = false; $invoice->invoice_date = Utils::today(); $invoice->start_date = Utils::today(); $invoice->invoice_design_id = $this->invoice_design_id; $invoice->client_id = $clientId; - + if ($entityType === ENTITY_RECURRING_INVOICE) { $invoice->invoice_number = microtime(true); $invoice->is_recurring = true; @@ -441,7 +444,14 @@ class Account extends Eloquent if ($invoice->is_quote && !$this->share_counter) { $this->quote_number_counter += 1; } else { - $this->invoice_number_counter += 1; + $default = $this->invoice_number_counter; + $actual = Utils::parseInt($invoice->invoice_number); + + if ( ! $this->isPro() && $default != $actual) { + $this->invoice_number_counter = $actual + 1; + } else { + $this->invoice_number_counter += 1; + } } $this->save(); @@ -572,6 +582,21 @@ class Account extends Eloquent } } + public function getLogoSize() + { + if (!$this->hasLogo()) { + return 0; + } + + $filename = $this->getLogoPath(); + return round(File::size($filename) / 1000); + } + + public function isLogoTooLarge() + { + return $this->getLogoSize() > MAX_LOGO_FILE_SIZE; + } + public function getSubscription($eventId) { return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first(); diff --git a/app/Models/AccountGateway.php b/app/Models/AccountGateway.php index e27a98d772bb..a4027db6ef36 100644 --- a/app/Models/AccountGateway.php +++ b/app/Models/AccountGateway.php @@ -57,5 +57,25 @@ class AccountGateway extends EntityModel { return json_decode(Crypt::decrypt($this->config)); } + + public function getConfigField($field) + { + $config = $this->getConfig(); + + if (!$field || !property_exists($config, $field)) { + return false; + } + + return $config->$field; + } + + public function getPublishableStripeKey() + { + if ( ! $this->isGateway(GATEWAY_STRIPE)) { + return false; + } + + return $this->getConfigField('publishableKey'); + } } diff --git a/app/Models/Client.php b/app/Models/Client.php index 389fad250557..9399d2d91c34 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -39,15 +39,52 @@ class Client extends EntityModel 'website', ]; - public static $fieldName = 'Client - Name'; - public static $fieldPhone = 'Client - Phone'; - public static $fieldAddress1 = 'Client - Street'; - public static $fieldAddress2 = 'Client - Apt/Floor'; - public static $fieldCity = 'Client - City'; - public static $fieldState = 'Client - State'; - public static $fieldPostalCode = 'Client - Postal Code'; - public static $fieldNotes = 'Client - Notes'; - public static $fieldCountry = 'Client - Country'; + public static $fieldName = 'name'; + public static $fieldPhone = 'work_phone'; + public static $fieldAddress1 = 'address1'; + public static $fieldAddress2 = 'address2'; + public static $fieldCity = 'city'; + public static $fieldState = 'state'; + public static $fieldPostalCode = 'postal_code'; + public static $fieldNotes = 'notes'; + public static $fieldCountry = 'country'; + + public static function getImportColumns() + { + return [ + Client::$fieldName, + Client::$fieldPhone, + Client::$fieldAddress1, + Client::$fieldAddress2, + Client::$fieldCity, + Client::$fieldState, + Client::$fieldPostalCode, + Client::$fieldCountry, + Client::$fieldNotes, + Contact::$fieldFirstName, + Contact::$fieldLastName, + Contact::$fieldPhone, + Contact::$fieldEmail, + ]; + } + + public static function getImportMap() + { + return [ + 'first' => 'first_name', + 'last' => 'last_name', + 'email' => 'email', + 'mobile|phone' => 'phone', + 'name|organization' => 'name', + 'street2|address2' => 'address2', + 'street|address|address1' => 'address1', + 'city' => 'city', + 'state|province' => 'state', + 'zip|postal|code' => 'postal_code', + 'country' => 'country', + 'note' => 'notes', + ]; + } public function account() { diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 23faf964315e..a95f40bab059 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -17,10 +17,10 @@ class Contact extends EntityModel 'send_invoice', ]; - public static $fieldFirstName = 'Contact - First Name'; - public static $fieldLastName = 'Contact - Last Name'; - public static $fieldEmail = 'Contact - Email'; - public static $fieldPhone = 'Contact - Phone'; + public static $fieldFirstName = 'first_name'; + public static $fieldLastName = 'last_name'; + public static $fieldEmail = 'email'; + public static $fieldPhone = 'phone'; public function account() { diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 97079eb42416..ac354d15bb6d 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -5,6 +5,7 @@ use DateTime; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; use App\Models\BalanceAffecting; +use App\Models\Client; use App\Events\QuoteWasCreated; use App\Events\QuoteWasUpdated; use App\Events\InvoiceWasCreated; @@ -29,6 +30,7 @@ class Invoice extends EntityModel implements BalanceAffecting 'auto_bill' => 'boolean', ]; + // used for custom invoice numbers public static $patternFields = [ 'counter', 'custom1', @@ -38,6 +40,40 @@ class Invoice extends EntityModel implements BalanceAffecting 'date:', ]; + public static $fieldInvoiceNumber = 'invoice_number'; + public static $fieldInvoiceDate = 'invoice_date'; + public static $fieldDueDate = 'due_date'; + public static $fieldAmount = 'amount'; + public static $fieldPaid = 'paid'; + public static $fieldNotes = 'notes'; + public static $fieldTerms = 'terms'; + + public static function getImportColumns() + { + return [ + Client::$fieldName, + Invoice::$fieldInvoiceNumber, + Invoice::$fieldInvoiceDate, + Invoice::$fieldDueDate, + Invoice::$fieldAmount, + Invoice::$fieldPaid, + Invoice::$fieldNotes, + Invoice::$fieldTerms, + ]; + } + + public static function getImportMap() + { + return [ + 'number^po' => 'invoice_number', + 'amount' => 'amount', + 'organization' => 'name', + 'paid^date' => 'paid', + 'invoice_date|create_date' => 'invoice_date', + 'terms' => 'terms', + 'notes' => 'notes', + ]; + } public function getRoute() { $entityType = $this->getEntityType(); @@ -576,21 +612,28 @@ class Invoice extends EntityModel implements BalanceAffecting $invitation = $this->invitations[0]; $link = $invitation->getLink(); - $curl = curl_init(); + $jsonEncodedData = json_encode([ - 'targetUrl' => "{$link}?phantomjs=true", - 'requestType' => 'raw', - 'delayTime' => 1000, + 'url' => "{$link}?phantomjs=true", + 'renderType' => 'html', + 'outputAsJson' => false, + 'renderSettings' => [ + 'passThroughHeaders' => true, + ], + // 'delayTime' => 1000, ]); $opts = [ - CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY'), + CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY') . '/', CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $jsonEncodedData, - CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)], + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Content-Length: '.strlen($jsonEncodedData) + ], ]; curl_setopt_array($curl, $opts); diff --git a/app/Ninja/Import/CSV/ClientTransformer.php b/app/Ninja/Import/CSV/ClientTransformer.php new file mode 100644 index 000000000000..8a0d4ab0130b --- /dev/null +++ b/app/Ninja/Import/CSV/ClientTransformer.php @@ -0,0 +1,40 @@ +name])) { + return false; + } + + if (isset($maps['countries'][$data->country])) { + $data->country_id = $maps['countries'][$data->country]; + } + + return new Item($data, function ($data) use ($maps) { + return [ + 'name' => isset($data->name) ? $data->name : null, + 'work_phone' => isset($data->work_phone) ? $data->work_phone : null, + 'address1' => isset($data->address1) ? $data->address1 : null, + 'city' => isset($data->city) ? $data->city : null, + 'state' => isset($data->state) ? $data->state : null, + 'postal_code' => isset($data->postal_code) ? $data->postal_code : null, + 'private_notes' => isset($data->notes) ? $data->notes : null, + 'contacts' => [ + [ + 'first_name' => isset($data->first_name) ? $data->first_name : null, + 'last_name' => isset($data->last_name) ? $data->last_name : null, + 'email' => isset($data->email) ? $data->email : null, + 'phone' => isset($data->phone) ? $data->phone : null, + ], + ], + 'country_id' => isset($data->country_id) ? $data->country_id : null, + ]; + }); + } +} diff --git a/app/Ninja/Import/CSV/InvoiceTransformer.php b/app/Ninja/Import/CSV/InvoiceTransformer.php new file mode 100644 index 000000000000..d18d4d7b1559 --- /dev/null +++ b/app/Ninja/Import/CSV/InvoiceTransformer.php @@ -0,0 +1,40 @@ +invoice_number])) { + return false; + } + + if (isset($maps[ENTITY_CLIENT][$data->name])) { + $data->client_id = $maps[ENTITY_CLIENT][$data->name]; + } else { + return false; + } + + return new Item($data, function ($data) use ($maps) { + return [ + 'invoice_number' => isset($data->invoice_number) ? $data->invoice_number : null, + 'paid' => isset($data->paid) ? (float) $data->paid : null, + 'client_id' => (int) $data->client_id, + 'po_number' => isset($data->po_number) ? $data->po_number : null, + 'terms' => isset($data->terms) ? $data->terms : null, + 'public_notes' => isset($data->notes) ? $data->notes : null, + 'invoice_date_sql' => isset($data->invoice_date) ? $data->invoice_date : null, + 'invoice_items' => [ + [ + 'notes' => isset($data->notes) ? $data->notes : null, + 'cost' => isset($data->amount) ? (float) $data->amount : null, + 'qty' => 1, + ] + ], + ]; + }); + } +} \ No newline at end of file diff --git a/app/Ninja/Import/CSV/PaymentTransformer.php b/app/Ninja/Import/CSV/PaymentTransformer.php new file mode 100644 index 000000000000..87936ccbc65c --- /dev/null +++ b/app/Ninja/Import/CSV/PaymentTransformer.php @@ -0,0 +1,19 @@ + $data->paid, + 'payment_date_sql' => isset($data->invoice_date) ? $data->invoice_date : null, + 'client_id' => $data->client_id, + 'invoice_id' => $data->invoice_id, + ]; + }); + } +} \ No newline at end of file diff --git a/app/Ninja/Repositories/CreditRepository.php b/app/Ninja/Repositories/CreditRepository.php index 1ac543c5b281..4fdace91eddb 100644 --- a/app/Ninja/Repositories/CreditRepository.php +++ b/app/Ninja/Repositories/CreditRepository.php @@ -19,6 +19,7 @@ class CreditRepository extends BaseRepository ->join('contacts', 'contacts.client_id', '=', 'clients.id') ->where('clients.account_id', '=', \Auth::user()->account_id) ->where('clients.deleted_at', '=', null) + ->where('contacts.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.balance', 'credits.credit_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'credits.private_notes', 'credits.deleted_at', 'credits.is_deleted'); diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index be6e625b1f73..7ab30fee05bf 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -138,13 +138,13 @@ class InvoiceRepository extends BaseRepository if ($isNew) { $entityType = ENTITY_INVOICE; - if (isset($data['is_recurring']) && $data['is_recurring']) { + if (isset($data['is_recurring']) && filter_var($data['is_recurring'], FILTER_VALIDATE_BOOLEAN)) { $entityType = ENTITY_RECURRING_INVOICE; - } elseif (isset($data['is_quote']) && $data['is_quote']) { + } elseif (isset($data['is_quote']) && filter_var($data['is_quote'], FILTER_VALIDATE_BOOLEAN)) { $entityType = ENTITY_QUOTE; } $invoice = $account->createInvoice($entityType, $data['client_id']); - if (isset($data['has_tasks']) && $data['has_tasks']) { + if (isset($data['has_tasks']) && filter_var($data['has_tasks'], FILTER_VALIDATE_BOOLEAN)) { $invoice->has_tasks = true; } } else { diff --git a/app/Ninja/Transformers/AccountTransformer.php b/app/Ninja/Transformers/AccountTransformer.php index 7e50baff9e6a..d812d243628f 100644 --- a/app/Ninja/Transformers/AccountTransformer.php +++ b/app/Ninja/Transformers/AccountTransformer.php @@ -7,7 +7,7 @@ use App\Models\Product; use League\Fractal; use League\Fractal\TransformerAbstract; -class AccountTransformer extends TransformerAbstract +class AccountTransformer extends EntityTransformer { protected $defaultIncludes = [ 'users', @@ -19,27 +19,32 @@ class AccountTransformer extends TransformerAbstract public function includeUsers(Account $account) { - return $this->collection($account->users, new UserTransformer($account)); + $transformer = new UserTransformer($account, $this->serializer); + return $this->includeCollection($account->users, $transformer, 'users'); } public function includeClients(Account $account) { - return $this->collection($account->clients, new ClientTransformer($account)); + $transformer = new ClientTransformer($account, $this->serializer); + return $this->includeCollection($account->clients, $transformer, 'clients'); } public function includeInvoices(Account $account) { - return $this->collection($account->invoices, new InvoiceTransformer($account)); + $transformer = new InvoiceTransformer($account, $this->serializer); + return $this->includeCollection($account->invoices, $transformer, 'invoices'); } public function includeContacts(Account $account) { - return $this->collection($account->contacts, new ContactTransformer($account)); + $transformer = new ContactTransformer($account, $this->serializer); + return $this->includeCollection($account->contacts, $transformer, 'contacts'); } public function includeProducts(Account $account) { - return $this->collection($account->products, new ProductTransformer($account)); + $transformer = new ProductTransformer($account, $this->serializer); + return $this->includeCollection($account->products, $transformer, 'products'); } public function transform(Account $account) diff --git a/app/Ninja/Transformers/ClientTransformer.php b/app/Ninja/Transformers/ClientTransformer.php index 2f4a8cdf44aa..8423786fd7d4 100644 --- a/app/Ninja/Transformers/ClientTransformer.php +++ b/app/Ninja/Transformers/ClientTransformer.php @@ -40,25 +40,28 @@ class ClientTransformer extends EntityTransformer * @SWG\Property(property="language_id", type="integer", example=1) */ - protected $defaultIncludes = [ - // 'contacts', - // 'invoices', - // 'quotes', + protected $availableIncludes = [ + 'contacts', + 'invoices', + 'quotes', ]; public function includeContacts(Client $client) { - return $this->collection($client->contacts, new ContactTransformer($this->account)); + $transformer = new ContactTransformer($this->account, $this->serializer); + return $this->includeCollection($client->contacts, $transformer, ENTITY_CONTACT); } public function includeInvoices(Client $client) { - return $this->collection($client->getInvoices, new InvoiceTransformer($this->account, $client)); + $transformer = new InvoiceTransformer($this->account, $this->serializer); + return $this->includeCollection($client->getInvoices, $transformer, ENTITY_INVOICE); } public function includeQuotes(Client $client) { - return $this->collection($client->getQuotes, new QuoteTransformer($this->account, $client)); + $transformer = new QuoteTransformer($this->account, $this->serializer); + return $this->includeCollection($client->getQuotes, $transformer, ENTITY_QUOTE); } public function transform(Client $client) @@ -68,7 +71,7 @@ class ClientTransformer extends EntityTransformer 'name' => $client->name, 'balance' => (float) $client->balance, 'paid_to_date' => (float) $client->paid_to_date, - 'user_id' => (int) $client->user->public_id+1, + 'user_id' => (int) $client->user->public_id + 1, 'account_key' => $this->account->account_key, 'updated_at' => $client->updated_at, 'deleted_at' => $client->deleted_at, diff --git a/app/Ninja/Transformers/ContactTransformer.php b/app/Ninja/Transformers/ContactTransformer.php index 5c5cd7d4b00d..51114da5aee4 100644 --- a/app/Ninja/Transformers/ContactTransformer.php +++ b/app/Ninja/Transformers/ContactTransformer.php @@ -20,7 +20,6 @@ class ContactTransformer extends EntityTransformer 'phone' => $contact->phone, 'last_login' => $contact->last_login, 'account_key' => $this->account->account_key, - 'client_id' => $contact->client->public_id ]; } } \ No newline at end of file diff --git a/app/Ninja/Transformers/EntityTransformer.php b/app/Ninja/Transformers/EntityTransformer.php index 11ba0d1fc356..f3d61f94de8d 100644 --- a/app/Ninja/Transformers/EntityTransformer.php +++ b/app/Ninja/Transformers/EntityTransformer.php @@ -1,14 +1,35 @@ account = $account; + $this->serializer = $serializer; + } + + protected function includeCollection($data, $transformer, $entityType) + { + if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) { + $entityType = null; + } + + return $this->collection($data, $transformer, $entityType); + } + + protected function includeItem($data, $transformer, $entityType) + { + if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) { + $entityType = null; + } + + return $this->item($data, $transformer, $entityType); } } diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index 0024e44117d4..8415374d4e00 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -20,20 +20,14 @@ class InvoiceTransformer extends EntityTransformer * @SWG\Property(property="invoice_status_id", type="integer", example=1) */ - - public function __construct(Account $account) - { - parent::__construct($account); - - } - protected $defaultIncludes = [ 'invoice_items', ]; - + public function includeInvoiceItems(Invoice $invoice) { - return $this->collection($invoice->invoice_items, new InvoiceItemTransformer($this->account)); + $transformer = new InvoiceItemTransformer($this->account, $this->serializer); + return $this->includeCollection($invoice->invoice_items, $transformer, ENTITY_INVOICE_ITEMS); } public function transform(Invoice $invoice) @@ -70,7 +64,7 @@ class InvoiceTransformer extends EntityTransformer 'has_tasks' => (bool) $invoice->has_tasks, 'auto_bill' => (bool) $invoice->auto_bill, 'account_key' => $this->account->account_key, - 'user_id' => (int) $invoice->user->public_id+1 + 'user_id' => (int) $invoice->user->public_id + 1 ]; } } \ No newline at end of file diff --git a/app/Ninja/Transformers/PaymentTransformer.php b/app/Ninja/Transformers/PaymentTransformer.php new file mode 100644 index 000000000000..6ec0b13da78e --- /dev/null +++ b/app/Ninja/Transformers/PaymentTransformer.php @@ -0,0 +1,55 @@ +account, $this->serializer); + return $this->includeItem($payment->invoice, $transformer, 'invoice'); + } + + public function includeClient(Payment $payment) + { + $transformer = new ClientTransformer($this->account, $this->serializer); + return $this->includeItem($payment->client, $transformer, 'client'); + } + + public function transform(Payment $payment) + { + return [ + 'id' => (int) $payment->public_id, + 'amount' => (float) $payment->amount, + 'account_key' => $this->account->account_key, + 'user_id' => (int) $payment->user->public_id + 1, + 'transaction_reference' => $payment->transaction_reference, + ]; + } +} \ No newline at end of file diff --git a/app/Ninja/Transformers/QuoteTransformer.php b/app/Ninja/Transformers/QuoteTransformer.php index 716b29656a59..d012aceb45b8 100644 --- a/app/Ninja/Transformers/QuoteTransformer.php +++ b/app/Ninja/Transformers/QuoteTransformer.php @@ -11,7 +11,8 @@ class QuoteTransformer extends EntityTransformer public function includeInvoiceItems($invoice) { - return $this->collection($invoice->invoice_items, new InvoiceItemTransformer($this->account)); + $transformer = new InvoiceItemTransformer($this->account, $this->serializer); + return $this->includeCollection($invoice->invoice_items, $transformer, 'invoice_items'); } public function transform(Invoice $invoice) diff --git a/app/Ninja/Transformers/TaskTransformer.php b/app/Ninja/Transformers/TaskTransformer.php new file mode 100644 index 000000000000..908a8118aaea --- /dev/null +++ b/app/Ninja/Transformers/TaskTransformer.php @@ -0,0 +1,50 @@ +client) { + $transformer = new ClientTransformer($this->account, $this->serializer); + return $this->includeItem($task->client, $transformer, 'client'); + } else { + return null; + } + } + + public function transform(Task $task) + { + return [ + 'id' => (int) $task->public_id, + 'account_key' => $this->account->account_key, + 'user_id' => (int) $task->user->public_id + 1, + 'description' => $task->description, + 'duration' => $task->getDuration() + ]; + } +} \ No newline at end of file diff --git a/app/Ninja/Transformers/UserAccountTransformer.php b/app/Ninja/Transformers/UserAccountTransformer.php index 18992ae1706f..bc49a96c546a 100644 --- a/app/Ninja/Transformers/UserAccountTransformer.php +++ b/app/Ninja/Transformers/UserAccountTransformer.php @@ -14,16 +14,17 @@ class UserAccountTransformer extends EntityTransformer protected $tokenName; - public function __construct(Account $account, $tokenName) + public function __construct(Account $account, $serializer, $tokenName) { - parent::__construct($account); + parent::__construct($account, $serializer); $this->tokenName = $tokenName; } public function includeUser(User $user) { - return $this->item($user, new UserTransformer($this->account)); + $transformer = new UserTransformer($this->account, $this->serializer); + return $this->includeItem($user, $transformer, 'user'); } public function transform(User $user) diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 32d5909800f2..67636ebc96ad 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -12,7 +12,8 @@ class DatatableService if ($actions && $showCheckbox) { $table->addColumn('checkbox', function ($model) { - return ''; + return ''; }); } @@ -81,14 +82,17 @@ class DatatableService } if ($entityType != ENTITY_USER || $model->public_id) { - $str .= "
  • public_id})\">" . trans("texts.archive_{$entityType}") . "
  • "; + $str .= "
  • public_id})\">" + . trans("texts.archive_{$entityType}") . "
  • "; } } else { - $str .= "
  • public_id})\">" . trans("texts.restore_{$entityType}") . "
  • "; + $str .= "
  • public_id})\">" + . trans("texts.restore_{$entityType}") . "
  • "; } if (property_exists($model, 'is_deleted') && !$model->is_deleted) { - $str .= "
  • public_id})\">" . trans("texts.delete_{$entityType}") . "
  • "; + $str .= "
  • public_id})\">" + . trans("texts.delete_{$entityType}") . "
  • "; } return $str.''; diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index ebab4b8780b1..796c9815debe 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -1,10 +1,10 @@ - $file) { $this->execute($source, $entityType, $file); } @@ -61,67 +61,117 @@ class ImportService private function execute($source, $entityType, $file) { - $transformerClassName = $this->getTransformerClassName($source, $entityType); - $transformer = new $transformerClassName; - - Excel::load($file, function($reader) use ($source, $entityType, $transformer) { - - if ($entityType === ENTITY_CLIENT) { - $totalClients = count($reader->all()) + Client::scope()->withTrashed()->count(); - if ($totalClients > Auth::user()->getMaxNumClients()) { - throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); - } - } + $skipped = []; + Excel::load($file, function ($reader) use ($source, $entityType, $skipped) { + $this->checkData($entityType, count($reader->all())); $maps = $this->createMaps(); - - $reader->each(function($row) use ($source, $entityType, $transformer, $maps) { - if ($resource = $transformer->transform($row, $maps)) { - $data = $this->fractal->createData($resource)->toArray(); - if ($this->validate($data, $entityType) !== true) { - return; - } + $reader->each(function ($row) use ($source, $entityType, $maps) { + $result = $this->saveData($source, $entityType, $row, $maps); - $entity = $this->{"{$entityType}Repo"}->save($data); - - // if the invoice is paid we'll also create a payment record - if ($entityType === ENTITY_INVOICE && isset($data['paid']) && $data['paid']) { - $class = self::getTransformerClassName($source, ENTITY_PAYMENT); - $paymentTransformer = new $class; - $row->client_id = $data['client_id']; - $row->invoice_id = $entity->public_id; - if ($resource = $paymentTransformer->transform($row, $maps)) { - $data = $this->fractal->createData($resource)->toArray(); - $this->paymentRepo->save($data); - } - } + if ( ! $result) { + $skipped[] = $row; } }); }); + + return $skipped; + } + + private function saveData($source, $entityType, $row, $maps) + { + $transformer = $this->getTransformer($source, $entityType); + $resource = $transformer->transform($row, $maps); + + if (!$resource) { + return; + } + + $data = $this->fractal->createData($resource)->toArray(); + + // if the invoice number is blank we'll assign it + if ($entityType == ENTITY_INVOICE && !$data['invoice_number']) { + $account = Auth::user()->account; + $invoice = Invoice::createNew(); + $data['invoice_number'] = $account->getNextInvoiceNumber($invoice); + } + + if ($this->validate($data, $entityType) !== true) { + return; + } + + $entity = $this->{"{$entityType}Repo"}->save($data); + + // if the invoice is paid we'll also create a payment record + if ($entityType === ENTITY_INVOICE && isset($row->paid) && $row->paid) { + $this->createPayment($source, $row, $maps, $data['client_id'], $entity->public_id); + } + } + + private function checkData($entityType, $count) + { + if ($entityType === ENTITY_CLIENT) { + $this->checkClientCount($count); + } + } + + private function checkClientCount($count) + { + $totalClients = $count + Client::scope()->withTrashed()->count(); + if ($totalClients > Auth::user()->getMaxNumClients()) { + throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); + } + } + + public static function getTransformerClassName($source, $entityType) + { + return 'App\\Ninja\\Import\\'.$source.'\\'.ucwords($entityType).'Transformer'; + } + + public static function getTransformer($source, $entityType) + { + $className = self::getTransformerClassName($source, $entityType); + + return new $className(); + } + + private function createPayment($source, $data, $maps, $clientId, $invoiceId) + { + $paymentTransformer = $this->getTransformer($source, ENTITY_PAYMENT); + + $data->client_id = $clientId; + $data->invoice_id = $invoiceId; + + if ($resource = $paymentTransformer->transform($data, $maps)) { + $data = $this->fractal->createData($resource)->toArray(); + $this->paymentRepo->save($data); + } } - // looking for a better solution... - // http://stackoverflow.com/questions/33781567/how-can-i-re-use-the-validation-code-in-my-laravel-formrequest-classes private function validate($data, $entityType) { if ($entityType === ENTITY_CLIENT) { $rules = [ 'contacts' => 'valid_contacts', ]; - } if ($entityType === ENTITY_INVOICE) { + } + if ($entityType === ENTITY_INVOICE) { $rules = [ 'client.contacts' => 'valid_contacts', 'invoice_items' => 'valid_invoice_items', 'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id, 'discount' => 'positive', ]; + } else { + return true; } $validator = Validator::make($data, $rules); if ($validator->fails()) { $messages = $validator->messages(); + return $messages->first(); } else { return true; @@ -155,42 +205,50 @@ class ImportService ]; } - public static function getTransformerClassName($source, $entityType) + public function mapCSV($files) { - return 'App\\Ninja\\Import\\' . $source . '\\' . ucwords($entityType) . 'Transformer'; + $data = []; + + foreach ($files as $entityType => $filename) { + if ($entityType === ENTITY_CLIENT) { + $columns = Client::getImportColumns(); + $map = Client::getImportMap(); + } else { + $columns = Invoice::getImportColumns(); + $map = Invoice::getImportMap(); + } + + // Lookup field translations + foreach ($columns as $key => $value) { + unset($columns[$key]); + $columns[$value] = trans("texts.{$value}"); + } + array_unshift($columns, ' '); + + $data[$entityType] = $this->mapFile($entityType, $filename, $columns, $map); + + if ($entityType === ENTITY_CLIENT) { + if (count($data[$entityType]['data']) + Client::scope()->count() > Auth::user()->getMaxNumClients()) { + throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); + } + } + } + + return $data; } - public function mapFile($filename) + public function mapFile($entityType, $filename, $columns, $map) { require_once app_path().'/Includes/parsecsv.lib.php'; $csv = new parseCSV(); $csv->heading = false; $csv->auto($filename); - if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) { - throw new Exception(trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()])); - } - - Session::put('data', $csv->data); + Session::put("{$entityType}-data", $csv->data); $headers = false; $hasHeaders = false; $mapped = array(); - $columns = array('', - Client::$fieldName, - Client::$fieldPhone, - Client::$fieldAddress1, - Client::$fieldAddress2, - Client::$fieldCity, - Client::$fieldState, - Client::$fieldPostalCode, - Client::$fieldCountry, - Client::$fieldNotes, - Contact::$fieldFirstName, - Contact::$fieldLastName, - Contact::$fieldPhone, - Contact::$fieldEmail, - ); if (count($csv->data) > 0) { $headers = $csv->data[0]; @@ -206,32 +264,10 @@ class ImportService $mapped[$i] = ''; if ($hasHeaders) { - $map = array( - 'first' => Contact::$fieldFirstName, - 'last' => Contact::$fieldLastName, - 'email' => Contact::$fieldEmail, - 'mobile' => Contact::$fieldPhone, - 'phone' => Client::$fieldPhone, - 'name|organization' => Client::$fieldName, - 'street|address|address1' => Client::$fieldAddress1, - 'street2|address2' => Client::$fieldAddress2, - 'city' => Client::$fieldCity, - 'state|province' => Client::$fieldState, - 'zip|postal|code' => Client::$fieldPostalCode, - 'country' => Client::$fieldCountry, - 'note' => Client::$fieldNotes, - ); - foreach ($map as $search => $column) { - foreach (explode("|", $search) as $string) { - if (strpos($title, 'sec') === 0) { - continue; - } - - if (strpos($title, $string) !== false) { - $mapped[$i] = $column; - break(2); - } + if ($this->checkForMatch($title, $search)) { + $mapped[$i] = $column; + break; } } } @@ -239,6 +275,7 @@ class ImportService } $data = array( + 'entityType' => $entityType, 'data' => $csv->data, 'headers' => $headers, 'hasHeaders' => $hasHeaders, @@ -249,78 +286,105 @@ class ImportService return $data; } - public function importCSV($map, $hasHeaders) + private function checkForMatch($column, $pattern) { - $count = 0; - $data = Session::get('data'); - $countries = Cache::get('countries'); - $countryMap = []; - - foreach ($countries as $country) { - $countryMap[strtolower($country->name)] = $country->id; + if (strpos($column, 'sec') === 0) { + return false; } + if (strpos($pattern, '^')) { + list($include, $exclude) = explode('^', $pattern); + $includes = explode('|', $include); + $excludes = explode('|', $exclude); + } else { + $includes = explode('|', $pattern); + $excludes = []; + } + + foreach ($includes as $string) { + if (strpos($column, $string) !== false) { + $excluded = false; + foreach ($excludes as $exclude) { + if (strpos($column, $exclude) !== false) { + $excluded = true; + break; + } + } + if (!$excluded) { + return true; + } + } + } + + return false; + } + + public function importCSV($maps, $headers) + { + $skipped = []; + + foreach ($maps as $entityType => $map) { + $result = $this->executeCSV($entityType, $map, $headers[$entityType]); + $skipped = array_merge($skipped, $result); + } + + return $skipped; + } + + private function executeCSV($entityType, $map, $hasHeaders) + { + $skipped = []; + $source = IMPORT_CSV; + + $data = Session::get("{$entityType}-data"); + $this->checkData($entityType, count($data)); + $maps = $this->createMaps(); + foreach ($data as $row) { if ($hasHeaders) { $hasHeaders = false; continue; } - $data = [ - 'contacts' => [[]] - ]; + $row = $this->convertToObject($entityType, $row, $map); + $result = $this->saveData($source, $entityType, $row, $maps); - foreach ($row as $index => $value) { - $field = $map[$index]; - if ( ! $value = trim($value)) { - continue; - } - - if ($field == Client::$fieldName) { - $data['name'] = $value; - } elseif ($field == Client::$fieldPhone) { - $data['work_phone'] = $value; - } elseif ($field == Client::$fieldAddress1) { - $data['address1'] = $value; - } elseif ($field == Client::$fieldAddress2) { - $data['address2'] = $value; - } elseif ($field == Client::$fieldCity) { - $data['city'] = $value; - } elseif ($field == Client::$fieldState) { - $data['state'] = $value; - } elseif ($field == Client::$fieldPostalCode) { - $data['postal_code'] = $value; - } elseif ($field == Client::$fieldCountry) { - $value = strtolower($value); - $data['country_id'] = isset($countryMap[$value]) ? $countryMap[$value] : null; - } elseif ($field == Client::$fieldNotes) { - $data['private_notes'] = $value; - } elseif ($field == Contact::$fieldFirstName) { - $data['contacts'][0]['first_name'] = $value; - } elseif ($field == Contact::$fieldLastName) { - $data['contacts'][0]['last_name'] = $value; - } elseif ($field == Contact::$fieldPhone) { - $data['contacts'][0]['phone'] = $value; - } elseif ($field == Contact::$fieldEmail) { - $data['contacts'][0]['email'] = strtolower($value); - } + if ( ! $result) { + $skipped[] = $row; } + } - $rules = [ - 'contacts' => 'valid_contacts', - ]; - $validator = Validator::make($data, $rules); - if ($validator->fails()) { + Session::forget("{$entityType}-data"); + + return $skipped; + } + + private function convertToObject($entityType, $data, $map) + { + $obj = new stdClass(); + + if ($entityType === ENTITY_CLIENT) { + $columns = Client::getImportColumns(); + } else { + $columns = Invoice::getImportColumns(); + } + + foreach ($columns as $column) { + $obj->$column = false; + } + + foreach ($map as $index => $field) { + if (! $field) { continue; } - $this->clientRepo->save($data); - $count++; + if (isset($obj->$field) && $obj->$field) { + continue; + } + + $obj->$field = $data[$index]; } - Session::forget('data'); - - return $count; + return $obj; } - } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 19c95a23bfbf..eee8c814f268 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -44,7 +44,9 @@ class PaymentService extends BaseService } $function = "set".ucfirst($key); - $gateway->$function($val); + if (method_exists($gateway, $function)) { + $gateway->$function($val); + } } if ($accountGateway->isGateway(GATEWAY_DWOLLA)) { @@ -100,10 +102,10 @@ class PaymentService extends BaseService $data = [ 'firstName' => $input['first_name'], 'lastName' => $input['last_name'], - 'number' => $input['card_number'], - 'expiryMonth' => $input['expiration_month'], - 'expiryYear' => $input['expiration_year'], - 'cvv' => $input['cvv'], + 'number' => isset($input['card_number']) ? $input['card_number'] : null, + 'expiryMonth' => isset($input['expiration_month']) ? $input['expiration_month'] : null, + 'expiryYear' => isset($input['expiration_year']) ? $input['expiration_year'] : null, + 'cvv' => isset($input['cvv']) ? $input['cvv'] : '', ]; if (isset($input['country_id'])) { @@ -159,7 +161,7 @@ class PaymentService extends BaseService public function createToken($gateway, $details, $accountGateway, $client, $contactId) { $tokenResponse = $gateway->createCard($details)->send(); - $cardReference = $tokenResponse->getCardReference(); + $cardReference = $tokenResponse->getCustomerReference(); if ($cardReference) { $token = AccountGatewayToken::where('client_id', '=', $client->id) @@ -237,7 +239,7 @@ class PaymentService extends BaseService // setup the gateway/payment info $gateway = $this->createGateway($accountGateway); $details = $this->getPaymentDetails($invitation, $accountGateway); - $details['cardReference'] = $token; + $details['customerReference'] = $token; // submit purchase/get response $response = $gateway->purchase($details)->send(); diff --git a/c3.php b/c3.php index 96720d95adc9..cadb3a399b95 100755 --- a/c3.php +++ b/c3.php @@ -33,7 +33,14 @@ if (!array_key_exists('HTTP_X_CODECEPTION_CODECOVERAGE', $_SERVER)) { if (!function_exists('__c3_error')) { function __c3_error($message) { - file_put_contents(C3_CODECOVERAGE_MEDIATE_STORAGE . DIRECTORY_SEPARATOR . 'error.txt', $message); + $errorLogFile = defined('C3_CODECOVERAGE_ERROR_LOG_FILE') ? + C3_CODECOVERAGE_ERROR_LOG_FILE : + C3_CODECOVERAGE_MEDIATE_STORAGE . DIRECTORY_SEPARATOR . 'error.txt'; + if (is_writable($errorLogFile)) { + file_put_contents($errorLogFile, $message); + }else{ + $message = "Could not write error to log file ($errorLogFile), original message: $message"; + } if (!headers_sent()) { header('X-Codeception-CodeCoverage-Error: ' . str_replace("\n", ' ', $message), true, 500); } @@ -43,10 +50,14 @@ if (!function_exists('__c3_error')) { // Autoload Codeception classes if (!class_exists('\\Codeception\\Codecept')) { - if (stream_resolve_include_path(__DIR__ . '/vendor/autoload.php')) { - require_once __DIR__ . '/vendor/autoload.php'; - } elseif (file_exists(__DIR__ . '/codecept.phar')) { + if (file_exists(__DIR__ . '/codecept.phar')) { require_once 'phar://'.__DIR__ . '/codecept.phar/autoload.php'; + } elseif (stream_resolve_include_path(__DIR__ . '/vendor/autoload.php')) { + require_once __DIR__ . '/vendor/autoload.php'; + // Required to load some methods only available at codeception/autoload.php + if (stream_resolve_include_path(__DIR__ . '/vendor/codeception/codeception/autoload.php')) { + require_once __DIR__ . '/vendor/codeception/codeception/autoload.php'; + } } elseif (stream_resolve_include_path('Codeception/autoload.php')) { require_once 'Codeception/autoload.php'; } else { @@ -55,11 +66,18 @@ if (!class_exists('\\Codeception\\Codecept')) { } // Load Codeception Config +$config_dist_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.dist.yml'; $config_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . 'codeception.yml'; + if (isset($_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG'])) { $config_file = realpath(__DIR__) . DIRECTORY_SEPARATOR . $_SERVER['HTTP_X_CODECEPTION_CODECOVERAGE_CONFIG']; } -if (!file_exists($config_file)) { +if (file_exists($config_file)) { + // Use codeception.yml for configuration. +} elseif (file_exists($config_dist_file)) { + // Use codeception.dist.yml for configuration. + $config_file = $config_dist_file; +} else { __c3_error(sprintf("Codeception config file '%s' not found", $config_file)); } try { @@ -237,4 +255,4 @@ if ($requested_c3_report) { } } -// @codeCoverageIgnoreEnd \ No newline at end of file +// @codeCoverageIgnoreEnd diff --git a/composer.json b/composer.json index 85579d8c9b44..9f128c3f76a7 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "require-dev": { "phpunit/phpunit": "~4.0", "phpspec/phpspec": "~2.1", - "codeception/codeception": "~2.0", + "codeception/codeception": "2.1.2", "codeception/c3": "~2.0", "fzaninotto/faker": "^1.5" }, diff --git a/composer.lock b/composer.lock index e532d0739db5..7066d4070c87 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "b6c2660a613f4e94f13f226ec19c66eb", + "hash": "6966ff410ab9fb5745347a70ab9d85ca", "packages": [ { "name": "agmscode/omnipay-agms", @@ -779,16 +779,16 @@ }, { "name": "collizo4sky/omnipay-wepay", - "version": "1.0", + "version": "1.1", "source": { "type": "git", - "url": "https://github.com/Collizo4sky/omnipay-wepay.git", - "reference": "360abf8c4bb4a926c39846abde970ab7dad93cb2" + "url": "https://github.com/collizo4sky/omnipay-wepay.git", + "reference": "bcc235113915c1547f62b8f02019f6289869c95c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Collizo4sky/omnipay-wepay/zipball/360abf8c4bb4a926c39846abde970ab7dad93cb2", - "reference": "360abf8c4bb4a926c39846abde970ab7dad93cb2", + "url": "https://api.github.com/repos/collizo4sky/omnipay-wepay/zipball/bcc235113915c1547f62b8f02019f6289869c95c", + "reference": "bcc235113915c1547f62b8f02019f6289869c95c", "shasum": "" }, "require": { @@ -800,7 +800,7 @@ }, "type": "library", "autoload": { - "psr-0": { + "psr-4": { "Omnipay\\WePay\\": "src/" } }, @@ -809,7 +809,7 @@ "MIT" ], "description": "WePay driver for the Omnipay payment processing library", - "homepage": "https://github.com/Collizo4sky/omnipay-wepay", + "homepage": "https://github.com/collizo4sky/omnipay-wepay", "keywords": [ "gateway", "merchant", @@ -818,7 +818,7 @@ "payment", "wepay" ], - "time": "2015-10-08 14:12:18" + "time": "2015-11-13 13:19:25" }, { "name": "danielstjules/stringy", @@ -2610,12 +2610,12 @@ "source": { "type": "git", "url": "https://github.com/labs7in0/omnipay-wechat.git", - "reference": "d4c46d37f438c48510840aa3e5b806d4280c6b40" + "reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/d4c46d37f438c48510840aa3e5b806d4280c6b40", - "reference": "d4c46d37f438c48510840aa3e5b806d4280c6b40", + "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", + "reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", "shasum": "" }, "require": { @@ -2651,7 +2651,7 @@ "purchase", "wechat" ], - "time": "2015-10-27 05:12:08" + "time": "2015-11-16 11:04:21" }, { "name": "laracasts/presenter", @@ -5309,16 +5309,16 @@ }, { "name": "omnipay/stripe", - "version": "v2.2.1", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-stripe.git", - "reference": "906377e50045dc2ba9c612aa1f6924157e1f750e" + "reference": "54b816a5e95e34c988d71fb805b0232cfd7c1ce5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/906377e50045dc2ba9c612aa1f6924157e1f750e", - "reference": "906377e50045dc2ba9c612aa1f6924157e1f750e", + "url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/54b816a5e95e34c988d71fb805b0232cfd7c1ce5", + "reference": "54b816a5e95e34c988d71fb805b0232cfd7c1ce5", "shasum": "" }, "require": { @@ -5362,7 +5362,7 @@ "payment", "stripe" ], - "time": "2015-04-14 18:55:56" + "time": "2015-11-10 16:17:35" }, { "name": "omnipay/targetpay", @@ -6068,16 +6068,16 @@ }, { "name": "symfony/class-loader", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "320f8d2a9cdbcbeb24be602c124aae9d998474a4" + "reference": "9d8359ca865621ba7f43ac6a3455d064d064bed7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/320f8d2a9cdbcbeb24be602c124aae9d998474a4", - "reference": "320f8d2a9cdbcbeb24be602c124aae9d998474a4", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/9d8359ca865621ba7f43ac6a3455d064d064bed7", + "reference": "9d8359ca865621ba7f43ac6a3455d064d064bed7", "shasum": "" }, "require": { @@ -6095,7 +6095,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\ClassLoader\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6113,20 +6116,20 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2015-10-23 14:47:27" + "time": "2015-10-30 20:10:21" }, { "name": "symfony/console", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Console", "source": { "type": "git", - "url": "https://github.com/symfony/Console.git", + "url": "https://github.com/symfony/console.git", "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", + "url": "https://api.github.com/repos/symfony/console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359", "shasum": "" }, @@ -6175,16 +6178,16 @@ }, { "name": "symfony/css-selector", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "e1b865b26be4a56d22a8dee398375044a80c865b" + "reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/e1b865b26be4a56d22a8dee398375044a80c865b", - "reference": "e1b865b26be4a56d22a8dee398375044a80c865b", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/abb47717fb88aebd9437da2fc8bb01a50a36679f", + "reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f", "shasum": "" }, "require": { @@ -6199,7 +6202,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\CssSelector\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6221,20 +6227,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2015-10-11 09:39:48" + "time": "2015-10-30 20:10:21" }, { "name": "symfony/debug", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Debug", "source": { "type": "git", - "url": "https://github.com/symfony/Debug.git", + "url": "https://github.com/symfony/debug.git", "reference": "fca5696e0c9787722baa8f2ad6940dfd7a6a6941" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Debug/zipball/fca5696e0c9787722baa8f2ad6940dfd7a6a6941", + "url": "https://api.github.com/repos/symfony/debug/zipball/fca5696e0c9787722baa8f2ad6940dfd7a6a6941", "reference": "fca5696e0c9787722baa8f2ad6940dfd7a6a6941", "shasum": "" }, @@ -6286,16 +6292,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8" + "reference": "7e2f9c31645680026c2372edf66f863fc7757af5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87a5db5ea887763fa3a31a5471b512ff1596d9b8", - "reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7e2f9c31645680026c2372edf66f863fc7757af5", + "reference": "7e2f9c31645680026c2372edf66f863fc7757af5", "shasum": "" }, "require": { @@ -6321,7 +6327,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6339,20 +6348,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-11 09:39:48" + "time": "2015-10-30 20:10:21" }, { "name": "symfony/filesystem", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "56fd6df73be859323ff97418d97edc1d756df6df" + "reference": "8e173509d7fdbbba3cf34d6d072f2073c0210c1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/56fd6df73be859323ff97418d97edc1d756df6df", - "reference": "56fd6df73be859323ff97418d97edc1d756df6df", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/8e173509d7fdbbba3cf34d6d072f2073c0210c1d", + "reference": "8e173509d7fdbbba3cf34d6d072f2073c0210c1d", "shasum": "" }, "require": { @@ -6367,7 +6376,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6385,20 +6397,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2015-10-18 20:23:18" + "time": "2015-11-18 13:41:01" }, { "name": "symfony/finder", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", - "url": "https://github.com/symfony/Finder.git", + "url": "https://github.com/symfony/finder.git", "reference": "203a10f928ae30176deeba33512999233181dd28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/203a10f928ae30176deeba33512999233181dd28", + "url": "https://api.github.com/repos/symfony/finder/zipball/203a10f928ae30176deeba33512999233181dd28", "reference": "203a10f928ae30176deeba33512999233181dd28", "shasum": "" }, @@ -6439,16 +6451,16 @@ }, { "name": "symfony/http-foundation", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/HttpFoundation", "source": { "type": "git", - "url": "https://github.com/symfony/HttpFoundation.git", + "url": "https://github.com/symfony/http-foundation.git", "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", "shasum": "" }, @@ -6493,17 +6505,17 @@ }, { "name": "symfony/http-kernel", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/HttpKernel", "source": { "type": "git", - "url": "https://github.com/symfony/HttpKernel.git", - "reference": "a3f0ed713255c0400a2db38b3ed01989ef4b7322" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/a3f0ed713255c0400a2db38b3ed01989ef4b7322", - "reference": "a3f0ed713255c0400a2db38b3ed01989ef4b7322", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6", + "reference": "498866a8ca0bcbcd3f3824b1520fa568ff7ca3b6", "shasum": "" }, "require": { @@ -6567,20 +6579,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2015-07-26 10:44:22" + "time": "2015-11-23 11:37:53" }, { "name": "symfony/process", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Process", "source": { "type": "git", - "url": "https://github.com/symfony/Process.git", + "url": "https://github.com/symfony/process.git", "reference": "57f1e88bb5dafa449b83f9f265b11d52d517b3e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/57f1e88bb5dafa449b83f9f265b11d52d517b3e9", + "url": "https://api.github.com/repos/symfony/process/zipball/57f1e88bb5dafa449b83f9f265b11d52d517b3e9", "reference": "57f1e88bb5dafa449b83f9f265b11d52d517b3e9", "shasum": "" }, @@ -6621,16 +6633,16 @@ }, { "name": "symfony/routing", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Routing", "source": { "type": "git", - "url": "https://github.com/symfony/Routing.git", + "url": "https://github.com/symfony/routing.git", "reference": "0a1764d41bbb54f3864808c50569ac382b44d128" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Routing/zipball/0a1764d41bbb54f3864808c50569ac382b44d128", + "url": "https://api.github.com/repos/symfony/routing/zipball/0a1764d41bbb54f3864808c50569ac382b44d128", "reference": "0a1764d41bbb54f3864808c50569ac382b44d128", "shasum": "" }, @@ -6690,7 +6702,7 @@ }, { "name": "symfony/security-core", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Security/Core", "source": { "type": "git", @@ -6754,16 +6766,16 @@ }, { "name": "symfony/translation", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/Translation", "source": { "type": "git", - "url": "https://github.com/symfony/Translation.git", + "url": "https://github.com/symfony/translation.git", "reference": "d84291215b5892834dd8ca8ee52f9cbdb8274904" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Translation/zipball/d84291215b5892834dd8ca8ee52f9cbdb8274904", + "url": "https://api.github.com/repos/symfony/translation/zipball/d84291215b5892834dd8ca8ee52f9cbdb8274904", "reference": "d84291215b5892834dd8ca8ee52f9cbdb8274904", "shasum": "" }, @@ -6813,7 +6825,7 @@ }, { "name": "symfony/var-dumper", - "version": "v2.6.11", + "version": "v2.6.12", "target-dir": "Symfony/Component/VarDumper", "source": { "type": "git", @@ -6966,16 +6978,16 @@ }, { "name": "twbs/bootstrap", - "version": "v3.3.5", + "version": "v3.3.6", "source": { "type": "git", "url": "https://github.com/twbs/bootstrap.git", - "reference": "16b48259a62f576e52c903c476bd42b90ab22482" + "reference": "81df608a40bf0629a1dc08e584849bb1e43e0b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twbs/bootstrap/zipball/16b48259a62f576e52c903c476bd42b90ab22482", - "reference": "16b48259a62f576e52c903c476bd42b90ab22482", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/81df608a40bf0629a1dc08e584849bb1e43e0b7a", + "reference": "81df608a40bf0629a1dc08e584849bb1e43e0b7a", "shasum": "" }, "replace": { @@ -7013,7 +7025,7 @@ "responsive", "web" ], - "time": "2015-06-16 16:13:22" + "time": "2015-11-24 19:37:05" }, { "name": "vink/omnipay-komoju", @@ -7236,16 +7248,16 @@ }, { "name": "zircote/swagger-php", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "f6624cc067d7894ec32943f5b94cf282c683f7c7" + "reference": "be5d96e56c23cbe52c5bc5e267851323d95c57cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/f6624cc067d7894ec32943f5b94cf282c683f7c7", - "reference": "f6624cc067d7894ec32943f5b94cf282c683f7c7", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/be5d96e56c23cbe52c5bc5e267851323d95c57cd", + "reference": "be5d96e56c23cbe52c5bc5e267851323d95c57cd", "shasum": "" }, "require": { @@ -7292,28 +7304,32 @@ "rest", "service discovery" ], - "time": "2015-10-18 13:05:54" + "time": "2015-11-13 13:50:11" } ], "packages-dev": [ { "name": "codeception/c3", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/Codeception/c3.git", - "reference": "30321efb2421c5d201d02e2cb8da1a1ca96e4a38" + "reference": "bc22b4f6cd1a7e74a98dbff541c055dbf0f6f7c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/c3/zipball/30321efb2421c5d201d02e2cb8da1a1ca96e4a38", - "reference": "30321efb2421c5d201d02e2cb8da1a1ca96e4a38", + "url": "https://api.github.com/repos/Codeception/c3/zipball/bc22b4f6cd1a7e74a98dbff541c055dbf0f6f7c8", + "reference": "bc22b4f6cd1a7e74a98dbff541c055dbf0f6f7c8", "shasum": "" }, "require": { + "composer-plugin-api": "1.0.0", "php": ">=5.4.0" }, - "type": "library", + "type": "composer-plugin", + "extra": { + "class": "Codeception\\c3\\Installer" + }, "autoload": { "psr-4": { "Codeception\\c3\\": "." @@ -7324,9 +7340,13 @@ "MIT" ], "authors": [ + { + "name": "Tiger Seo", + "email": "tiger.seo@gmail.com" + }, { "name": "Michael Bodnarchuk", - "email": "davert.php@resend.cc", + "email": "davert.php@codegyre.com", "homepage": "http://codegyre.com" } ], @@ -7336,27 +7356,27 @@ "code coverage", "codecoverage" ], - "time": "2014-11-18 22:06:45" + "time": "2015-11-25 04:03:09" }, { "name": "codeception/codeception", - "version": "2.1.4", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "6a812e8a0d1b1db939a29b4dc14cb398b21b6112" + "reference": "521adbb2ee34e9debdd8508a2c41ab2b5c2f042b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/6a812e8a0d1b1db939a29b4dc14cb398b21b6112", - "reference": "6a812e8a0d1b1db939a29b4dc14cb398b21b6112", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/521adbb2ee34e9debdd8508a2c41ab2b5c2f042b", + "reference": "521adbb2ee34e9debdd8508a2c41ab2b5c2f042b", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "facebook/webdriver": ">=1.0.1", - "guzzlehttp/guzzle": ">=4.1.4 <7.0", + "guzzlehttp/guzzle": ">=4.0|<7.0", "guzzlehttp/psr7": "~1.0", "php": ">=5.4.0", "phpunit/phpunit": "~4.8.0", @@ -7416,7 +7436,7 @@ "functional testing", "unit testing" ], - "time": "2015-11-12 03:57:06" + "time": "2015-08-09 13:48:55" }, { "name": "doctrine/instantiator", @@ -8479,16 +8499,16 @@ }, { "name": "symfony/browser-kit", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367" + "reference": "bd28847ea2193916074c7b11d4fdd78570049694" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/07d664a052572ccc28eb2ab7dbbe82155b1ad367", - "reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/bd28847ea2193916074c7b11d4fdd78570049694", + "reference": "bd28847ea2193916074c7b11d4fdd78570049694", "shasum": "" }, "require": { @@ -8511,7 +8531,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\BrowserKit\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8529,20 +8552,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2015-10-23 14:47:27" + "time": "2015-11-02 20:20:53" }, { "name": "symfony/dom-crawler", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612" + "reference": "b33593cbfe1d81b50d48353f338aca76a08658d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5fef7d8b80d8f9992df99d8ee283f420484c9612", - "reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b33593cbfe1d81b50d48353f338aca76a08658d8", + "reference": "b33593cbfe1d81b50d48353f338aca76a08658d8", "shasum": "" }, "require": { @@ -8563,7 +8586,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\DomCrawler\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8581,20 +8607,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2015-10-11 09:39:48" + "time": "2015-11-02 20:20:53" }, { "name": "symfony/yaml", - "version": "v2.7.6", + "version": "v2.7.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "eca9019c88fbe250164affd107bc8057771f3f4d" + "reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d", - "reference": "eca9019c88fbe250164affd107bc8057771f3f4d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4cfcd7a9fceba662b3c036b7d9a91f6197af046c", + "reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c", "shasum": "" }, "require": { @@ -8609,7 +8635,10 @@ "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8627,7 +8656,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-10-11 09:39:48" + "time": "2015-11-18 13:41:01" } ], "aliases": [], diff --git a/config/database.php b/config/database.php index d6b202f23b88..082117ea767f 100644 --- a/config/database.php +++ b/config/database.php @@ -2,124 +2,124 @@ return [ - /* - |-------------------------------------------------------------------------- - | PDO Fetch Style - |-------------------------------------------------------------------------- - | - | By default, database results will be returned as instances of the PHP - | stdClass object; however, you may desire to retrieve records in an - | array format for simplicity. Here you can tweak the fetch style. - | - */ + /* + |-------------------------------------------------------------------------- + | PDO Fetch Style + |-------------------------------------------------------------------------- + | + | By default, database results will be returned as instances of the PHP + | stdClass object; however, you may desire to retrieve records in an + | array format for simplicity. Here you can tweak the fetch style. + | + */ - 'fetch' => PDO::FETCH_CLASS, + 'fetch' => PDO::FETCH_CLASS, - /* - |-------------------------------------------------------------------------- - | Default Database Connection Name - |-------------------------------------------------------------------------- - | - | Here you may specify which of the database connections below you wish - | to use as your default connection for all database work. Of course - | you may use many connections at once using the Database library. - | - */ + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ - 'default' => env('DB_TYPE', 'mysql'), + 'default' => env('DB_TYPE', 'mysql'), - /* - |-------------------------------------------------------------------------- - | Database Connections - |-------------------------------------------------------------------------- - | - | Here are each of the database connections setup for your application. - | Of course, examples of configuring each database platform that is - | supported by Laravel is shown below to make development simple. - | - | - | All database work in Laravel is done through the PHP PDO facilities - | so make sure you have the driver for your particular database of - | choice installed on your machine before you begin development. - | - */ + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ - 'connections' => [ + 'connections' => [ - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => storage_path().'/database.sqlite', - 'prefix' => '', - ], + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => storage_path().'/database.sqlite', + 'prefix' => '', + ], - 'mysql' => [ - 'driver' => 'mysql', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'strict' => false, - ], + 'mysql' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', 'localhost'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => env('DB_STRICT', false), + ], - 'pgsql' => [ - 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'schema' => 'public', - ], + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', 'localhost'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ], - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'host' => env('DB_HOST', 'localhost'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'prefix' => '', - ], + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'host' => env('DB_HOST', 'localhost'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'prefix' => '', + ], - ], + ], - /* - |-------------------------------------------------------------------------- - | Migration Repository Table - |-------------------------------------------------------------------------- - | - | This table keeps track of all the migrations that have already run for - | your application. Using this information, we can determine which of - | the migrations on disk haven't actually been run in the database. - | - */ + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ - 'migrations' => 'migrations', + 'migrations' => 'migrations', - /* - |-------------------------------------------------------------------------- - | Redis Databases - |-------------------------------------------------------------------------- - | - | Redis is an open source, fast, and advanced key-value store that also - | provides a richer set of commands than a typical key-value systems - | such as APC or Memcached. Laravel makes it easy to dig right in. - | - */ + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ - 'redis' => [ + 'redis' => [ - 'cluster' => false, + 'cluster' => false, - 'default' => [ - 'host' => '127.0.0.1', - 'port' => 6379, - 'database' => 0, - ], + 'default' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 0, + ], - ], + ], ]; diff --git a/public/favicon-v2.png b/public/favicon-v2.png new file mode 100644 index 000000000000..9fdfcb9aae66 Binary files /dev/null and b/public/favicon-v2.png differ diff --git a/public/js/built.js b/public/js/built.js index 46de4c96d734..73deb11cdd9f 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -31755,6 +31755,8 @@ NINJA.decodeJavascript = function(invoice, javascript) if (match.indexOf('?') < 0 || value) { if (invoice.partial && field == 'balance_due') { field = 'amount_due'; + } else if (invoice.is_quote && field == 'your_invoice') { + field = 'your_quote'; } var label = invoiceLabels[field]; if (match.indexOf('UC') >= 0) { diff --git a/public/js/pdf.pdfmake.js b/public/js/pdf.pdfmake.js index fd57a6bf6270..49c0b579cd5e 100644 --- a/public/js/pdf.pdfmake.js +++ b/public/js/pdf.pdfmake.js @@ -168,8 +168,10 @@ NINJA.decodeJavascript = function(invoice, javascript) if (match.indexOf('?') < 0 || value) { if (invoice.partial && field == 'balance_due') { field = 'amount_due'; + } else if (invoice.is_quote && field == 'your_invoice') { + field = 'your_quote'; } - var label = invoiceLabels[field]; + var label = invoiceLabels[field]; if (match.indexOf('UC') >= 0) { label = label.toUpperCase(); } diff --git a/readme.md b/readme.md index cb3a30fa2953..3bb80b1a58a7 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,13 @@ [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -If you'd like to use our code to sell your own invoicing app email us for details about our affiliate program. +### Referral Program +* $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) + +### Reseller Program +There are two options: +* 10% of revenue +* $1,000 for a site limited to 1,000 accounts ### Installation Options * [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free @@ -15,6 +21,11 @@ If you'd like to use our code to sell your own invoicing app email us for detail * [Bitnami](https://bitnami.com/stack/invoice-ninja) - Free * [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja) - $30 +### Requirements +* PHP >= 5.4.0 +* MCrypt Extension +* MySQL + ### Features * Built using Laravel 5 * Live PDF generation using [pdfmake](http://pdfmake.org/) diff --git a/resources/lang/da/texts.php b/resources/lang/da/texts.php index 6fa11cd5d2a6..8cac5e56ecea 100644 --- a/resources/lang/da/texts.php +++ b/resources/lang/da/texts.php @@ -447,7 +447,6 @@ 'gateway_help_1' => ':link til at registrere dig hos Authorize.net.', 'gateway_help_2' => ':link til at registrere dig hos Authorize.net.', 'gateway_help_17' => ':link til at hente din PayPal API signatur.', - 'gateway_help_23' => 'Note: brug din hemmelige API nøgle, IKKE din publicerede API nøgle.', 'gateway_help_27' => ':link til at registrere dig hos TwoCheckout.', 'more_designs' => 'Flere designs', @@ -918,5 +917,38 @@ 'country' => 'Country', 'include' => 'Include', + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + ); diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index ff6a6da5ac22..eae5510b0bcc 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -448,7 +448,6 @@ return array( 'gateway_help_1' => ':link um sich bei Authorize.net anzumelden.', 'gateway_help_2' => ':link um sich bei Authorize.net anzumelden.', 'gateway_help_17' => ':link um deine PayPal API-Signatur zu erhalten.', - 'gateway_help_23' => 'Anmerkung: benutze deinen secret API key, nicht deinen publishable API key.', 'gateway_help_27' => ':link um sich bei TwoCheckout anzumelden.', 'more_designs' => 'Weitere Designs', @@ -920,4 +919,37 @@ return array( 'country' => 'Land', 'include' => 'Hinzufügen', + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + ); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 82941ea6298a..bee2302645dc 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -87,7 +87,7 @@ return array( 'guest' => 'Guest', 'company_details' => 'Company Details', 'online_payments' => 'Online Payments', - 'notifications' => 'Notifications', + 'notifications' => 'Email Notifications', 'import_export' => 'Import/Export', 'done' => 'Done', 'save' => 'Save', @@ -334,7 +334,7 @@ return array( // product management 'product_library' => 'Product Library', 'product' => 'Product', - 'products' => 'Products', + 'products' => 'Product Library', 'fill_products' => 'Auto-fill products', 'fill_products_help' => 'Selecting a product will automatically fill in the description and cost', 'update_products' => 'Auto-update products', @@ -450,7 +450,6 @@ return array( 'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_17' => ':link to get your PayPal API signature.', - 'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.', 'gateway_help_27' => ':link to sign up for TwoCheckout.', 'more_designs' => 'More designs', @@ -673,7 +672,7 @@ return array( 'counter' => 'Counter', 'payment_type_dwolla' => 'Dwolla', - 'gateway_help_43' => ':link to sign up for Dwolla.
    Note: remove dashes from the Destination/Dwolla Id', + 'gateway_help_43' => ':link to sign up for Dwolla', 'partial_value' => 'Must be greater than zero and less than the total', 'more_actions' => 'More Actions', @@ -921,7 +920,7 @@ return array( 'country' => 'Country', 'include' => 'Include', - 'logo_too_large' => 'Your logo is :size, for better performance we suggest uploading an image file less than 200KB', + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', 'import_freshbooks' => 'Import From FreshBooks', 'import_data' => 'Import Data', 'source' => 'Source', @@ -945,4 +944,14 @@ return array( 'admin' => 'Admin', 'disabled' => 'Disabled', 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + + ); diff --git a/resources/lang/es/texts.php b/resources/lang/es/texts.php index 0fb7664d7d67..f5236749c113 100644 --- a/resources/lang/es/texts.php +++ b/resources/lang/es/texts.php @@ -420,7 +420,6 @@ return array( 'gateway_help_1' => ':link para registrarse con Authorize.net.', 'gateway_help_2' => ':link para registrarse con Authorize.net.', 'gateway_help_17' => ':link para obtener su firma del API de PayPal.', - 'gateway_help_23' => 'Nota: use use llave secreta del API, no la llave pública.', 'gateway_help_27' => ':link para registrarse con TwoCheckout.', 'more_designs' => 'Más diseños', @@ -896,5 +895,38 @@ return array( 'country' => 'Country', 'include' => 'Include', + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + ); diff --git a/resources/lang/es_ES/texts.php b/resources/lang/es_ES/texts.php index 4a6214f10233..ea2922644209 100644 --- a/resources/lang/es_ES/texts.php +++ b/resources/lang/es_ES/texts.php @@ -438,7 +438,6 @@ return array( 'gateway_help_1' => ':link para registrarse en Authorize.net.', 'gateway_help_2' => ':link para registrarse en Authorize.net.', 'gateway_help_17' => ':link para obtener su firma API de PayPal.', - 'gateway_help_23' => 'Nota: utilizar su clave de API secreta, no es su clave de API publica.', 'gateway_help_27' => ':link para registrarse en TwoCheckout.', 'more_designs' => 'Más diseños', @@ -917,4 +916,37 @@ return array( 'country' => 'Country', 'include' => 'Include', + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + ); diff --git a/resources/lang/fr/texts.php b/resources/lang/fr/texts.php index 22890d9b6050..76b4ee253113 100644 --- a/resources/lang/fr/texts.php +++ b/resources/lang/fr/texts.php @@ -441,7 +441,6 @@ return array( 'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_17' => ':link pour obtenir votre signature PayPal API.', - 'gateway_help_23' => 'Note: uutilisez votre Secret API et non votre clé publiable.', 'gateway_help_27' => ':link pour vous enregistrer sur TwoCheckout.', 'more_designs' => 'Plus de modèles', @@ -910,5 +909,38 @@ return array( 'user' => 'Utilisateur', 'country' => 'Pays', 'include' => 'Inclure', + + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', ); diff --git a/resources/lang/fr_CA/texts.php b/resources/lang/fr_CA/texts.php index 13d3e367d999..4f94ef98f65d 100644 --- a/resources/lang/fr_CA/texts.php +++ b/resources/lang/fr_CA/texts.php @@ -441,7 +441,6 @@ return array( 'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_17' => ':link to get your PayPal API signature.', - 'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.', 'gateway_help_27' => ':link to sign up for TwoCheckout.', 'more_designs' => 'Plus de modèles', @@ -911,4 +910,37 @@ return array( 'country' => 'Country', 'include' => 'Include', + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + ); diff --git a/resources/lang/it/texts.php b/resources/lang/it/texts.php index a35a42cbb2ed..a73598fa3e98 100644 --- a/resources/lang/it/texts.php +++ b/resources/lang/it/texts.php @@ -441,7 +441,6 @@ return array( 'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_17' => ':link to get your PayPal API signature.', - 'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.', 'gateway_help_27' => ':link to sign up for TwoCheckout.', 'more_designs' => 'More designs', @@ -912,5 +911,38 @@ return array( 'user' => 'User', 'country' => 'Country', 'include' => 'Include', + + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', ); diff --git a/resources/lang/lt/texts.php b/resources/lang/lt/texts.php index 9aee3cd86e2a..efb4248a6a3e 100644 --- a/resources/lang/lt/texts.php +++ b/resources/lang/lt/texts.php @@ -449,7 +449,6 @@ return array( 'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_17' => ':link to get your PayPal API signature.', - 'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.', 'gateway_help_27' => ':link to sign up for TwoCheckout.', 'more_designs' => 'More designs', @@ -919,6 +918,39 @@ return array( 'user' => 'User', 'country' => 'Country', 'include' => 'Include', + + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', ); diff --git a/resources/lang/nb_NO/texts.php b/resources/lang/nb_NO/texts.php index 3c50871fa650..43002f7f5bcc 100644 --- a/resources/lang/nb_NO/texts.php +++ b/resources/lang/nb_NO/texts.php @@ -447,7 +447,6 @@ return array( 'gateway_help_1' => ':link for å lage en konto for Authorize.net.', 'gateway_help_2' => ':link for å lage en konto for Authorize.net.', 'gateway_help_17' => ':link for å få din PayPal API signatur.', - 'gateway_help_23' => 'Info: bruk din hemmelige API nøkkel, ikke din offentlige API nøkkel.', 'gateway_help_27' => ':link for å lage en konto for TwoCheckout.', 'more_designs' => 'Flere design', @@ -917,5 +916,38 @@ return array( 'user' => 'User', 'country' => 'Country', 'include' => 'Include', + + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', ); diff --git a/resources/lang/nl/texts.php b/resources/lang/nl/texts.php index a071965d5eeb..e366bf5eca5c 100644 --- a/resources/lang/nl/texts.php +++ b/resources/lang/nl/texts.php @@ -443,7 +443,6 @@ return array( 'gateway_help_1' => ':link om in te schrijven voor Authorize.net.', 'gateway_help_2' => ':link om in te schrijven voor Authorize.net.', 'gateway_help_17' => ':link om uw PayPal API signature te krijgen.', - 'gateway_help_23' => 'Opmerking: gebruik uw gehieme API key, niet uw publiceerbare API key.', 'gateway_help_27' => ':link om in te schrijven voor TwoCheckout.', 'more_designs' => 'Meer ontwerpen', @@ -913,5 +912,38 @@ return array( 'user' => 'User', 'country' => 'Country', 'include' => 'Include', + + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', ); diff --git a/resources/lang/pt_BR/texts.php b/resources/lang/pt_BR/texts.php index d673bc6b9c08..70c01675d321 100644 --- a/resources/lang/pt_BR/texts.php +++ b/resources/lang/pt_BR/texts.php @@ -441,7 +441,6 @@ return array( 'gateway_help_1' => ':link para acessar Authorize.net.', 'gateway_help_2' => ':link para acessar Authorize.net.', 'gateway_help_17' => ':link para adquirir sua "PayPal API signature".', - 'gateway_help_23' => 'Aviso: use sua "Secret API Key", não a "Publishable API Key".', 'gateway_help_27' => ':link para acessar TwoCheckout.', 'more_designs' => 'Mais Modelos', @@ -918,7 +917,28 @@ return array( 'user_unregistered' => 'Registre sua conta para enviar e-mails', 'user_unconfirmed' => 'Confirme sua conta para enviar e-mails', 'invalid_contact_email' => 'E-mail do contato inválido', - ] + ], - 'client_portal' => 'Portal do Cliente', + 'import_freshbooks' => 'Importar de FreshBooks', + 'import_data' => 'Importar Dados', + 'source' => 'Fonte', + 'csv' => 'CSV', + 'client_file' => 'Arquivo de Clientes', + 'invoice_file' => 'Arquivo de Faturas', + 'task_file' => 'Arquivo de Tarefas', + 'no_mapper' => 'Mapeamento inválido', + 'invalid_csv_header' => 'CSV com cabeçalho inválido', + + 'client_portal' => 'Portal do Cliente', + 'admin' => 'Admin', + 'disabled' => 'Disabilitado', + 'show_archived_users' => 'Mostrar usuários arquivados', + 'notes' => 'Observações', + 'invoice_will_create' => 'cliente será criado', + 'invoices_will_create' => 'faturas serão criadas', + 'failed_to_import' => 'A importação dos seguintes registros falhou', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', ); diff --git a/resources/lang/sv/texts.php b/resources/lang/sv/texts.php index 7401020eb6d7..230eafb8ab26 100644 --- a/resources/lang/sv/texts.php +++ b/resources/lang/sv/texts.php @@ -447,7 +447,6 @@ return array( 'gateway_help_1' => ':link för att registrera dig på Authorize.net.', 'gateway_help_2' => ':link för att registrera dig på Authorize.net.', 'gateway_help_17' => ':link för att hämta din PayPal API-nyckel.', - 'gateway_help_23' => 'Observera: använd din hemliga API-nyckel, inte den publika.', 'gateway_help_27' => ':link för att registrera dig för TwoCheckout.', 'more_designs' => 'Fler fakturalayouter', @@ -915,5 +914,39 @@ return array( 'user' => 'User', 'country' => 'Country', 'include' => 'Include', + + 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', + 'import_freshbooks' => 'Import From FreshBooks', + 'import_data' => 'Import Data', + 'source' => 'Source', + 'csv' => 'CSV', + 'client_file' => 'Client File', + 'invoice_file' => 'Invoice File', + 'task_file' => 'Task File', + 'no_mapper' => 'No valid mapping for file', + 'invalid_csv_header' => 'Invalid CSV Header', + + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], + + 'client_portal' => 'Client Portal', + 'admin' => 'Admin', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import', + + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + ); diff --git a/resources/views/accounts/account_gateway.blade.php b/resources/views/accounts/account_gateway.blade.php index dfb47aaf9670..9c1f09cec351 100644 --- a/resources/views/accounts/account_gateway.blade.php +++ b/resources/views/accounts/account_gateway.blade.php @@ -19,8 +19,9 @@ {!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!} {!! Former::populateField('payment_type_id', $paymentTypeId) !!} {!! Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) !!} - {!! Former::populateField('show_address', intval($accountGateway->show_address)) !!} + {!! Former::populateField('show_address', intval($accountGateway->show_address)) !!} {!! Former::populateField('update_address', intval($accountGateway->update_address)) !!} + {!! Former::populateField('publishable_key', $accountGateway->getPublishableStripeKey() ? str_repeat('*', strlen($accountGateway->getPublishableStripeKey())) : '') !!} @if ($config) @foreach ($accountGateway->fields as $field => $junk) @@ -63,7 +64,7 @@ @elseif ($field == 'username' || $field == 'password') {!! Former::text($gateway->id.'_'.$field)->label('API '. ucfirst(Utils::toSpaceCase($field))) !!} @else - {!! Former::text($gateway->id.'_'.$field)->label(Utils::toSpaceCase($field)) !!} + {!! Former::text($gateway->id.'_'.$field)->label($gateway->id == GATEWAY_STRIPE ? trans('texts.secret_key') : Utils::toSpaceCase($field)) !!} @endif @endforeach @@ -78,6 +79,8 @@ @endif @if ($gateway->id == GATEWAY_STRIPE) + {!! Former::text('publishable_key') !!} + {!! Former::select('token_billing_type_id') ->options($tokenBillingOptions) ->help(trans('texts.token_billing_help')) !!} diff --git a/resources/views/accounts/import_export.blade.php b/resources/views/accounts/import_export.blade.php index cab0b31b9d51..92f3c231508f 100644 --- a/resources/views/accounts/import_export.blade.php +++ b/resources/views/accounts/import_export.blade.php @@ -4,7 +4,6 @@ @parent +@include('payments.payment_css') {!! Former::vertical_open($url) ->autocomplete('on') + ->addClass('payment-form') ->rules(array( 'first_name' => 'required', 'last_name' => 'required', @@ -172,6 +91,8 @@ header h3 em {

     

    + +
    @@ -279,14 +200,14 @@ header h3 em {

    {{ trans('texts.billing_method') }}

    - {!! Former::text($gateway->isGateway(GATEWAY_STRIPE) ? 'card_number' : 'card_number') + {!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'card_number') ->placeholder(trans('texts.card_number')) ->autocomplete('cc-number') ->data_stripe('number') ->label('') !!}
    - {!! Former::text($gateway->isGateway(GATEWAY_STRIPE) ? 'cvv' : 'cvv') + {!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'cvv') ->placeholder(trans('texts.cvv')) ->autocomplete('off') ->data_stripe('cvc') @@ -295,7 +216,7 @@ header h3 em {
    - {!! Former::select($gateway->isGateway(GATEWAY_STRIPE) ? 'expiration_month' : 'expiration_month') + {!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_month') ->autocomplete('cc-exp-month') ->data_stripe('exp-month') ->placeholder(trans('texts.expiration_month')) @@ -314,7 +235,7 @@ header h3 em { !!}
    - {!! Former::select($gateway->isGateway(GATEWAY_STRIPE) ? 'expiration_year' : 'expiration_year') + {!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_year') ->autocomplete('cc-exp-year') ->data_stripe('exp-year') ->placeholder(trans('texts.expiration_year')) @@ -336,15 +257,15 @@ header h3 em {
    - @if ($client && $account->showTokenCheckbox()) + @if ($client && $account->showTokenCheckbox()) selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top"> {!! trans('texts.token_billing_secure', ['stripe_link' => link_to('https://stripe.com/', 'Stripe.com', ['target' => '_blank'])]) !!} - @endif + @endif
    -
    - @if (isset($acceptedCreditCardTypes)) +
    + @if (isset($acceptedCreditCardTypes))
    @foreach ($acceptedCreditCardTypes as $card) {{ $card['alt'] }} diff --git a/resources/views/payments/payment_css.blade.php b/resources/views/payments/payment_css.blade.php new file mode 100644 index 000000000000..e22a15464561 --- /dev/null +++ b/resources/views/payments/payment_css.blade.php @@ -0,0 +1,130 @@ + diff --git a/tests/_support/_generated/AcceptanceTesterActions.php b/tests/_support/_generated/AcceptanceTesterActions.php index 94bb9b320601..3135d9aa0510 100644 --- a/tests/_support/_generated/AcceptanceTesterActions.php +++ b/tests/_support/_generated/AcceptanceTesterActions.php @@ -1,4 +1,4 @@ -getScenario()->runStep(new \Codeception\Step\Action('debugWebDriverLogs', func_get_args())); - } - - /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -260,6 +249,7 @@ trait AcceptanceTesterActions * $I->amOnPage('/'); * // opens /register page * $I->amOnPage('/register'); + * ?> * ``` * * @param $page @@ -273,31 +263,16 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page contains the given string (case insensitive). - * - * You can specify a specific HTML element (via CSS or XPath) as the second - * parameter to only search within that element. + * Checks that the current page contains the given string. + * Specify a locator as the second parameter to match a specific region. * * ``` php * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page - * $I->see('Sign Up', '//body/h1'); // with XPath + * $I->see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up','h1'); // I can suppose it's a signup page + * $I->see('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->see('strong')` will return true for strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will *not* be true for strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -310,31 +285,16 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page contains the given string (case insensitive). - * - * You can specify a specific HTML element (via CSS or XPath) as the second - * parameter to only search within that element. + * Checks that the current page contains the given string. + * Specify a locator as the second parameter to match a specific region. * * ``` php * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page - * $I->see('Sign Up', '//body/h1'); // with XPath + * $I->see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up','h1'); // I can suppose it's a signup page + * $I->see('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->see('strong')` will return true for strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will *not* be true for strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -348,29 +308,16 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page doesn't contain the text specified (case insensitive). + * Checks that the current page doesn't contain the text specified. * Give a locator as the second parameter to match a specific region. * * ```php * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->dontSee('strong')` will fail on strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will ignore strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -383,29 +330,16 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page doesn't contain the text specified (case insensitive). + * Checks that the current page doesn't contain the text specified. * Give a locator as the second parameter to match a specific region. * * ```php * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->dontSee('strong')` will fail on strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will ignore strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -416,80 +350,6 @@ trait AcceptanceTesterActions } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ``` php - * seeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\WebDriver::seeInSource() - */ - public function canSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInSource', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ``` php - * seeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * @see \Codeception\Module\WebDriver::seeInSource() - */ - public function seeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInSource', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ```php - * dontSeeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\WebDriver::dontSeeInSource() - */ - public function cantSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInSource', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ```php - * dontSeeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * @see \Codeception\Module\WebDriver::dontSeeInSource() - */ - public function dontSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInSource', func_get_args())); - } - - /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -1565,28 +1425,7 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Grabs either the text content, or attribute values, of nodes - * matched by $cssOrXpath and returns them as an array. - * - * ```html - * First - * Second - * Third - * ``` - * - * ```php - * grabMultiple('a'); - * - * // would return ['#first', '#second', '#third'] - * $aLinks = $I->grabMultiple('a', 'href'); - * ?> - * ``` - * - * @param $cssOrXpath - * @param $attribute - * @return string[] + * * @see \Codeception\Module\WebDriver::grabMultiple() */ public function grabMultiple($cssOrXpath, $attribute = null) { @@ -1801,27 +1640,6 @@ trait AcceptanceTesterActions } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Module\WebDriver::seeNumberOfElementsInDOM() - */ - public function canSeeNumberOfElementsInDOM($selector, $expected) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberOfElementsInDOM', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * - * @see \Codeception\Module\WebDriver::seeNumberOfElementsInDOM() - */ - public function seeNumberOfElementsInDOM($selector, $expected) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberOfElementsInDOM', func_get_args())); - } - - /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -2139,13 +1957,13 @@ trait AcceptanceTesterActions * ``` * Note that "2" will be the submitted value for the "plan" field, as it is * the selected option. - * + * * Also note that this differs from PhpBrowser, in that * ```'user' => [ 'login' => 'Davert' ]``` is not supported at the moment. * Named array keys *must* be included in the name as above. - * + * * Pair this with seeInFormFields for quick testing magic. - * + * * ``` php * submitForm('#my-form', [ * 'field[]' => 'value', * 'field[]' => 'another value', // 'field[]' is already a defined key * ]); * ``` - * + * * The solution is to pass an array value: - * + * * ```php * // this way both values are submitted * $I->submitForm('#my-form', [ @@ -2645,7 +2463,34 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * @param string $name + * Saves current cookies into named snapshot in order to restore them in other tests + * This is useful to save session state between tests. + * For example, if user needs log in to site for each test this scenario can be executed once + * while other tests can just restore saved cookies. + * + * ``` php + * loadSessionSnapshot('login')) return; + * + * // logging in + * $I->amOnPage('/login'); + * $I->fillField('name', 'jon'); + * $I->fillField('password', '123345'); + * $I->click('Login'); + * + * // saving snapshot + * $I->saveSessionSnapshot('login'); + * } + * ?> + * ``` + * + * @param $name + * @return mixed * @see \Codeception\Module\WebDriver::saveSessionSnapshot() */ public function saveSessionSnapshot($name) { @@ -2656,8 +2501,11 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * @param string $name - * @return bool + * Loads cookies from saved snapshot. + * + * @param $name + * @see saveSessionSnapshot + * @return mixed * @see \Codeception\Module\WebDriver::loadSessionSnapshot() */ public function loadSessionSnapshot($name) { @@ -2668,7 +2516,7 @@ trait AcceptanceTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Inserts an SQL record into a database. This record will be erased after the test. + * Inserts SQL record into database. This record will be erased after the test. * * ``` php * * ``` * - * @param int $expectedNumber Expected number + * @param int $num Expected number * @param string $table Table name * @param array $criteria Search criteria [Optional] * Conditional Assertion: Test won't be stopped on fail * @see \Codeception\Module\Db::seeNumRecords() */ - public function canSeeNumRecords($expectedNumber, $table, $criteria = null) { + public function canSeeNumRecords($num, $table, $criteria = null) { return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumRecords', func_get_args())); } /** * [!] Method is generated. Documentation taken from corresponding module. * - * Asserts that the given number of records were found in the database. + * Asserts that found number of records in database * * ``` php * * ``` * - * @param int $expectedNumber Expected number + * @param int $num Expected number * @param string $table Table name * @param array $criteria Search criteria [Optional] * @see \Codeception\Module\Db::seeNumRecords() */ - public function seeNumRecords($expectedNumber, $table, $criteria = null) { + public function seeNumRecords($num, $table, $criteria = null) { return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumRecords', func_get_args())); } @@ -2790,7 +2638,7 @@ trait AcceptanceTesterActions * * Effect is opposite to ->seeInDatabase * - * Asserts that there is no record with the given column values in a database. + * Checks if there is no record with such column values in database. * Provide table name and column values. * * Example: @@ -2820,7 +2668,7 @@ trait AcceptanceTesterActions * * Effect is opposite to ->seeInDatabase * - * Asserts that there is no record with the given column values in a database. + * Checks if there is no record with such column values in database. * Provide table name and column values. * * Example: diff --git a/tests/_support/_generated/FunctionalTesterActions.php b/tests/_support/_generated/FunctionalTesterActions.php index a3980a806784..edf786bc5c7e 100644 --- a/tests/_support/_generated/FunctionalTesterActions.php +++ b/tests/_support/_generated/FunctionalTesterActions.php @@ -1,4 +1,4 @@ -amOnPage('/'); * // opens /register page * $I->amOnPage('/register'); + * ?> * ``` * * @param $page @@ -216,31 +217,16 @@ trait FunctionalTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page contains the given string (case insensitive). - * - * You can specify a specific HTML element (via CSS or XPath) as the second - * parameter to only search within that element. + * Checks that the current page contains the given string. + * Specify a locator as the second parameter to match a specific region. * * ``` php * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page - * $I->see('Sign Up', '//body/h1'); // with XPath + * $I->see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up','h1'); // I can suppose it's a signup page + * $I->see('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->see('strong')` will return true for strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will *not* be true for strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -253,31 +239,16 @@ trait FunctionalTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page contains the given string (case insensitive). - * - * You can specify a specific HTML element (via CSS or XPath) as the second - * parameter to only search within that element. + * Checks that the current page contains the given string. + * Specify a locator as the second parameter to match a specific region. * * ``` php * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page - * $I->see('Sign Up', '//body/h1'); // with XPath + * $I->see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up','h1'); // I can suppose it's a signup page + * $I->see('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->see('strong')` will return true for strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will *not* be true for strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -291,29 +262,16 @@ trait FunctionalTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page doesn't contain the text specified (case insensitive). + * Checks that the current page doesn't contain the text specified. * Give a locator as the second parameter to match a specific region. * * ```php * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->dontSee('strong')` will fail on strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will ignore strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -326,29 +284,16 @@ trait FunctionalTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that the current page doesn't contain the text specified (case insensitive). + * Checks that the current page doesn't contain the text specified. * Give a locator as the second parameter to match a specific region. * * ```php * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * ?> * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->dontSee('strong')` will fail on strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will ignore strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. * * @param $text * @param null $selector @@ -359,80 +304,6 @@ trait FunctionalTesterActions } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ``` php - * seeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInSource() - */ - public function canSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInSource', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ``` php - * seeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * @see \Codeception\Lib\InnerBrowser::seeInSource() - */ - public function seeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInSource', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ```php - * dontSeeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInSource() - */ - public function cantSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInSource', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ```php - * dontSeeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * @see \Codeception\Lib\InnerBrowser::dontSeeInSource() - */ - public function dontSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInSource', func_get_args())); - } - - /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -1205,25 +1076,13 @@ trait FunctionalTesterActions * Submits the given form on the page, optionally with the given form * values. Give the form fields values as an array. * - * Although this function can be used as a short-hand version of - * `fillField()`, `selectOption()`, `click()` etc. it has some important - * differences: - * - * * Only field *names* may be used, not CSS/XPath selectors nor field labels - * * If a field is sent to this function that does *not* exist on the page, - * it will silently be added to the HTTP request. This is helpful for testing - * some types of forms, but be aware that you will *not* get an exception - * like you would if you called `fillField()` or `selectOption()` with - * a missing field. - * - * Fields that are not provided will be filled by their values from the page, - * or from any previous calls to `fillField()`, `selectOption()` etc. + * Skipped fields will be filled by their values from the page. * You don't need to click the 'Submit' button afterwards. * This command itself triggers the request to form's action. * - * You can optionally specify which button's value to include - * in the request with the last parameter (as an alternative to - * explicitly setting its value in the second parameter), as + * You can optionally specify what button's value to include + * in the request with the last parameter as an alternative to + * explicitly setting its value in the second parameter, as * button values are not otherwise included in the request. * * Examples: @@ -1297,8 +1156,7 @@ trait FunctionalTesterActions * ); * ``` * - * This function works well when paired with `seeInFormFields()` - * for quickly testing CRUD interfaces and form validation logic. + * Pair this with seeInFormFields for quick testing magic. * * ``` php * true, * // ... * ]; - * $I->submitForm('#my-form', $form, 'submitButton'); + * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); * // $I->amOnPage('/path/to/form-page') may be needed - * $I->seeInFormFields('#my-form', $form); + * $I->seeInFormFields('//form[@id=my-form]', $form); + * ?> * ``` * * Parameter values can be set to arrays for multiple input fields * of the same name, or multi-select combo boxes. For checkboxes, - * you can use either the string value or boolean `true`/`false` which will + * either the string value can be used, or boolean values which will * be replaced by the checkbox's value in the DOM. * * ``` php @@ -1324,7 +1183,7 @@ trait FunctionalTesterActions * 'field1' => 'value', * 'checkbox' => [ * 'value of first checkbox', - * 'value of second checkbox', + * 'value of second checkbox, * ], * 'otherCheckboxes' => [ * true, @@ -1336,29 +1195,27 @@ trait FunctionalTesterActions * 'second option value' * ] * ]); + * ?> * ``` * * Mixing string and boolean values for a checkbox's value is not supported * and may produce unexpected results. * - * Field names ending in `[]` must be passed without the trailing square + * Field names ending in "[]" must be passed without the trailing square * bracket characters, and must contain an array for its value. This allows * submitting multiple values with the same name, consider: * * ```php - * submitForm('#my-form', [ * 'field[]' => 'value', - * 'field[]' => 'another value', // 'field[]' is already a defined key + * 'field[]' => 'another value', // 'field[]' is already a defined key * ]); * ``` * * The solution is to pass an array value: * * ```php - * submitForm('#my-form', [ * 'field' => [ * 'value', @@ -1609,28 +1466,7 @@ trait FunctionalTesterActions /** * [!] Method is generated. Documentation taken from corresponding module. * - * Grabs either the text content, or attribute values, of nodes - * matched by $cssOrXpath and returns them as an array. - * - * ```html - * First - * Second - * Third - * ``` - * - * ```php - * grabMultiple('a'); - * - * // would return ['#first', '#second', '#third'] - * $aLinks = $I->grabMultiple('a', 'href'); - * ?> - * ``` - * - * @param $cssOrXpath - * @param $attribute - * @return string[] + * * @see \Codeception\Lib\InnerBrowser::grabMultiple() */ public function grabMultiple($cssOrXpath, $attribute = null) { @@ -2136,43 +1972,6 @@ trait FunctionalTesterActions } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Switch to iframe or frame on the page. - * - * Example: - * ``` html - *