Merge branch 'release-2.5.2'

Conflicts:
	app/Http/Controllers/AppController.php
	app/Http/routes.php
	app/Models/Account.php
	app/Models/Invitation.php
	composer.json
	composer.lock
	resources/lang/de/texts.php
	resources/lang/fr/texts.php
	resources/views/header.blade.php
This commit is contained in:
Hillel Coren 2016-05-10 21:41:13 +03:00
commit da911d090e
299 changed files with 13312 additions and 4155 deletions

View File

@ -43,4 +43,28 @@ API_SECRET=password
#GOOGLE_CLIENT_SECRET=
#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
#GOOGLE_MAPS_API_KEY=
#GOOGLE_MAPS_API_KEY=
#S3_KEY=
#S3_SECRET=
#S3_REGION=
#S3_BUCKET=
#RACKSPACE_USERNAME=
#RACKSPACE_KEY=
#RACKSPACE_CONTAINER=
#RACKSPACE_REGION=
#RACKSPACE_TEMP_URL_SECRET=
# If this is set to anything, the URL secret will be set the next
# time a file is downloaded through the client portal.
# Only set this temporarily, as it slows things down.
#RACKSPACE_TEMP_URL_SECRET_SET=
#DOCUMENT_FILESYSTEM=
#MAX_DOCUMENT_SIZE # KB
#MAX_EMAIL_DOCUMENTS_SIZE # Total KB
#MAX_ZIP_DOCUMENTS_SIZE # Total KB (uncompressed)
#DOCUMENT_PREVIEW_SIZE # Pixels

138
.htaccess
View File

@ -6,3 +6,141 @@
# https://coderwall.com/p/erbaig/laravel-s-htaccess-to-remove-public-from-url
# RewriteRule ^(.*)$ public/$1 [L]
</IfModule>
# https://github.com/h5bp/server-configs-apache/blob/master/dist/.htaccess
# ######################################################################
# # INTERNET EXPLORER #
# ######################################################################
# ----------------------------------------------------------------------
# | Iframes cookies |
# ----------------------------------------------------------------------
# Allow cookies to be set from iframes in Internet Explorer.
#
# https://msdn.microsoft.com/en-us/library/ms537343.aspx
# http://www.w3.org/TR/2000/CR-P3P-20001215/
<IfModule mod_headers.c>
Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
</IfModule>
# ######################################################################
# # MEDIA TYPES AND CHARACTER ENCODINGS #
# ######################################################################
# ----------------------------------------------------------------------
# | Character encodings |
# ----------------------------------------------------------------------
# Serve all resources labeled as `text/html` or `text/plain`
# with the media type `charset` parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
AddDefaultCharset utf-8
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Serve the following file types with the media type `charset`
# parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
<IfModule mod_mime.c>
AddCharset utf-8 .atom \
.bbaw \
.css \
.geojson \
.js \
.json \
.jsonld \
.manifest \
.rdf \
.rss \
.topojson \
.vtt \
.webapp \
.webmanifest \
.xloc \
.xml
</IfModule>
# ######################################################################
# # WEB PERFORMANCE #
# ######################################################################
# ----------------------------------------------------------------------
# | Compression |
# ----------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force compression for mangled headers.
# https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Map certain file types to the specified encoding type in order to
# make Apache serve them with the appropriate `Content-Encoding` HTTP
# response header (this will NOT make Apache compress them!).
# If the following file types wouldn't be served without the appropriate
# `Content-Enable` HTTP response header, client applications (e.g.:
# browsers) wouldn't know that they first need to uncompress the response,
# and thus, wouldn't be able to understand the content.
# http://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
<IfModule mod_mime.c>
AddEncoding gzip svgz
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Compress all output labeled with one of the following media types.
# IMPORTANT: For Apache versions below 2.3.7 you don't need to enable
# `mod_filter` and can remove the `<IfModule mod_filter.c>` & `</IfModule>`
# lines as `AddOutputFilterByType` is still in the core directives.
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE "application/atom+xml" \
"application/javascript" \
"application/json" \
"application/ld+json" \
"application/manifest+json" \
"application/rdf+xml" \
"application/rss+xml" \
"application/schema+json" \
"application/vnd.geo+json" \
"application/vnd.ms-fontobject" \
"application/x-font-ttf" \
"application/x-web-app-manifest+json" \
"application/xhtml+xml" \
"application/xml" \
"font/opentype" \
"image/svg+xml" \
"image/x-icon" \
"text/cache-manifest" \
"text/css" \
"text/html" \
"text/javascript" \
"text/plain" \
"text/vtt" \
"text/x-component" \
"text/xml"
</IfModule>
</IfModule>

View File

@ -45,7 +45,8 @@ before_script:
- php artisan key:generate --no-interaction
- sed -i 's/APP_ENV=production/APP_ENV=development/g' .env
- sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env
- sed -i 's/REQUIRE_HTTPS=false/NINJA_DEV=true/g' .env
- sed -i '$a NINJA_DEV=true' .env
- sed -i '$a TRAVIS=true' .env
# create the database and user
- mysql -u root -e "create database IF NOT EXISTS ninja;"
- mysql -u root -e "GRANT ALL PRIVILEGES ON ninja.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;"
@ -87,7 +88,10 @@ after_script:
- mysql -u root -e 'select * from clients;' ninja
- mysql -u root -e 'select * from invoices;' ninja
- mysql -u root -e 'select * from invoice_items;' ninja
- cat storage/logs/laravel.log
- mysql -u root -e 'select * from payments;' ninja
- mysql -u root -e 'select * from credits;' ninja
- cat storage/logs/laravel-error.log
- cat storage/logs/laravel-info.log
notifications:
email:

View File

@ -95,6 +95,7 @@ module.exports = function(grunt) {
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
'public/vendor/dropzone/dist/min/dropzone.min.js',
'public/vendor/typeahead.js/dist/typeahead.jquery.min.js',
'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js',
@ -137,6 +138,7 @@ module.exports = function(grunt) {
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
'public/vendor/dropzone/dist/min/dropzone.min.css',
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',
@ -169,7 +171,7 @@ module.exports = function(grunt) {
'public/js/pdf_viewer.js',
'public/js/compatibility.js',
'public/js/pdfmake.min.js',
'public/js/vfs_fonts.js',
'public/js/vfs.js',
],
dest: 'public/pdf.built.js',
nonull: true

View File

@ -1,4 +1,4 @@
<?php namespace app\Commands;
<?php namespace App\Commands;
abstract class Command
{

View File

@ -0,0 +1,172 @@
<?php namespace App\Console\Commands;
use stdClass;
use Auth;
use DB;
use Utils;
use Artisan;
use Illuminate\Console\Command;
use Faker\Factory;
use App\Models\User;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\VendorRepository;
use App\Ninja\Repositories\ExpenseRepository;
class CreateTestData extends Command
{
//protected $name = 'ninja:create-test-data';
protected $description = 'Create Test Data';
protected $signature = 'ninja:create-test-data {count=1}';
protected $token;
public function __construct(
ClientRepository $clientRepo,
InvoiceRepository $invoiceRepo,
PaymentRepository $paymentRepo,
VendorRepository $vendorRepo,
ExpenseRepository $expenseRepo)
{
parent::__construct();
$this->faker = Factory::create();
$this->clientRepo = $clientRepo;
$this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo;
$this->vendorRepo = $vendorRepo;
$this->expenseRepo = $expenseRepo;
}
public function fire()
{
if (Utils::isNinjaProd()) {
return false;
}
$this->info(date('Y-m-d').' Running CreateTestData...');
Auth::loginUsingId(1);
$this->count = $this->argument('count');
$this->createClients();
$this->createVendors();
$this->info('Done');
}
private function createClients()
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'name' => $this->faker->name,
'address1' => $this->faker->streetAddress,
'address2' => $this->faker->secondaryAddress,
'city' => $this->faker->city,
'state' => $this->faker->state,
'postal_code' => $this->faker->postcode,
'contacts' => [[
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->safeEmail,
'phone' => $this->faker->phoneNumber,
]]
];
$client = $this->clientRepo->save($data);
$this->info('Client: ' . $client->name);
$this->createInvoices($client);
}
}
private function createInvoices($client)
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'client_id' => $client->id,
'invoice_items' => [[
'product_key' => $this->faker->word,
'qty' => $this->faker->randomDigit + 1,
'cost' => $this->faker->randomFloat(2, 1, 10),
'notes' => $this->faker->text($this->faker->numberBetween(50, 300))
]]
];
$invoice = $this->invoiceRepo->save($data);
$this->info('Invoice: ' . $invoice->invoice_number);
$this->createPayment($client, $invoice);
}
}
private function createPayment($client, $invoice)
{
$data = [
'invoice_id' => $invoice->id,
'client_id' => $client->id,
'amount' => $this->faker->randomFloat(2, 0, $invoice->amount)
];
$payment = $this->paymentRepo->save($data);
$this->info('Payment: ' . $payment->amount);
}
private function createVendors()
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'name' => $this->faker->name,
'address1' => $this->faker->streetAddress,
'address2' => $this->faker->secondaryAddress,
'city' => $this->faker->city,
'state' => $this->faker->state,
'postal_code' => $this->faker->postcode,
'vendor_contacts' => [[
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->safeEmail,
'phone' => $this->faker->phoneNumber,
]]
];
$vendor = $this->vendorRepo->save($data);
$this->info('Vendor: ' . $vendor->name);
$this->createExpense($vendor);
}
}
private function createExpense($vendor)
{
for ($i=0; $i<$this->count; $i++) {
$data = [
'vendor_id' => $vendor->id,
'amount' => $this->faker->randomFloat(2, 1, 10),
'expense_date' => null,
'public_notes' => null,
];
$expense = $this->expenseRepo->save($data);
$this->info('Expense: ' . $expense->amount);
}
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Console\Commands;
<?php namespace App\Console\Commands;
use File;
use Illuminate\Console\Command;

View File

@ -0,0 +1,57 @@
<?php namespace App\Console\Commands;
use DB;
use Illuminate\Console\Command;
class PruneData extends Command
{
protected $name = 'ninja:prune-data';
protected $description = 'Delete inactive accounts';
public function fire()
{
$this->info(date('Y-m-d').' Running PruneData...');
// delete accounts who never registered, didn't create any invoices,
// hansn't logged in within the past 6 months and isn't linked to another account
$sql = 'select a.id
from (select id, last_login from accounts) a
left join users u on u.account_id = a.id and u.public_id = 0
left join invoices i on i.account_id = a.id
left join user_accounts ua1 on ua1.user_id1 = u.id
left join user_accounts ua2 on ua2.user_id2 = u.id
left join user_accounts ua3 on ua3.user_id3 = u.id
left join user_accounts ua4 on ua4.user_id4 = u.id
left join user_accounts ua5 on ua5.user_id5 = u.id
where u.registered = 0
and a.last_login < DATE_SUB(now(), INTERVAL 6 MONTH)
and (ua1.id is null and ua2.id is null and ua3.id is null and ua4.id is null and ua5.id is null)
group by a.id
having count(i.id) = 0';
$results = DB::select($sql);
foreach ($results as $result) {
$this->info("Deleting {$result->id}");
DB::table('accounts')
->where('id', '=', $result->id)
->delete();
}
$this->info('Done');
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -0,0 +1,41 @@
<?php namespace App\Console\Commands;
use DateTime;
use App\Models\Document;
use Illuminate\Console\Command;
class RemoveOrphanedDocuments extends Command
{
protected $name = 'ninja:remove-orphaned-documents';
protected $description = 'Removes old documents not associated with an expense or invoice';
public function fire()
{
$this->info(date('Y-m-d').' Running RemoveOrphanedDocuments...');
$documents = Document::whereRaw('invoice_id IS NULL AND expense_id IS NULL AND updated_at <= ?', array(new DateTime('-1 hour')))
->get();
$this->info(count($documents).' orphaned document(s) found');
foreach ($documents as $document) {
$document->delete();
}
$this->info('Done');
}
protected function getArguments()
{
return array(
//array('example', InputArgument::REQUIRED, 'An example argument.'),
);
}
protected function getOptions()
{
return array(
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
);
}
}

View File

@ -36,7 +36,7 @@ class SendReminders extends Command
$this->info(count($accounts).' accounts found');
foreach ($accounts as $account) {
if (!$account->isPro()) {
if (!$account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
continue;
}

View File

@ -5,7 +5,7 @@ use DateTime;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use App\Models\Account;
use App\Models\Company;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\AccountRepository;
@ -30,32 +30,44 @@ class SendRenewalInvoices extends Command
$today = new DateTime();
$sentTo = [];
// get all accounts with pro plans expiring in 10 days
$accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355')
// get all accounts with plans expiring in 10 days
$companies = Company::whereRaw('datediff(plan_expires, curdate()) = 10')
->orderBy('id')
->get();
$this->info(count($accounts).' accounts found');
$this->info(count($companies).' companies found');
foreach ($accounts as $account) {
// don't send multiple invoices to multi-company users
if ($userAccountId = $this->accountRepo->getUserAccountId($account)) {
if (isset($sentTo[$userAccountId])) {
continue;
} else {
$sentTo[$userAccountId] = true;
}
foreach ($companies as $company) {
if (!count($company->accounts)) {
continue;
}
$account = $company->accounts->sortBy('id')->first();
$plan = $company->plan;
$term = $company->plan_term;
if ($company->pending_plan) {
$plan = $company->pending_plan;
$term = $company->pending_term;
}
if ($plan == PLAN_FREE || !$plan || !$term ){
continue;
}
$client = $this->accountRepo->getNinjaClient($account);
$invitation = $this->accountRepo->createNinjaInvoice($client, $account);
$invitation = $this->accountRepo->createNinjaInvoice($client, $account, $plan, $term);
// set the due date to 10 days from now
$invoice = $invitation->invoice;
$invoice->due_date = date('Y-m-d', strtotime('+ 10 days'));
$invoice->save();
$this->mailer->sendInvoice($invoice);
$this->info("Sent invoice to {$client->getDisplayName()}");
if ($term == PLAN_TERM_YEARLY) {
$this->mailer->sendInvoice($invoice);
$this->info("Sent {$term}ly {$plan} invoice to {$client->getDisplayName()}");
} else {
$this->info("Created {$term}ly {$plan} invoice for {$client->getDisplayName()}");
}
}
$this->info('Done');

View File

@ -1,4 +1,4 @@
<?php namespace app\Console\Commands;
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\BankAccountService;

View File

@ -1,4 +1,4 @@
<?php namespace app\Console;
<?php namespace App\Console;
use Utils;
use Illuminate\Console\Scheduling\Schedule;
@ -13,8 +13,11 @@ class Kernel extends ConsoleKernel
*/
protected $commands = [
'App\Console\Commands\SendRecurringInvoices',
'App\Console\Commands\RemoveOrphanedDocuments',
'App\Console\Commands\ResetData',
'App\Console\Commands\CheckData',
'App\Console\Commands\PruneData',
'App\Console\Commands\CreateTestData',
'App\Console\Commands\SendRenewalInvoices',
'App\Console\Commands\ChargeRenewalInvoices',
'App\Console\Commands\SendReminders',

View File

@ -4,7 +4,11 @@ use Redirect;
use Utils;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Exception\HttpResponseException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
class Handler extends ExceptionHandler {
@ -14,7 +18,10 @@ class Handler extends ExceptionHandler {
* @var array
*/
protected $dontReport = [
'Symfony\Component\HttpKernel\Exception\HttpException'
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
];
/**
@ -27,7 +34,12 @@ class Handler extends ExceptionHandler {
*/
public function report(Exception $e)
{
if (Utils::isNinja()) {
// don't show these errors in the logs
if ($e instanceof HttpResponseException) {
return false;
}
if (Utils::isNinja() && ! Utils::isTravis()) {
Utils::logError(Utils::getErrorString($e));
return false;
} else {
@ -60,7 +72,9 @@ class Handler extends ExceptionHandler {
}
// In production, except for maintenance mode, we'll show a custom error screen
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) {
if (Utils::isNinjaProd()
&& !Utils::isDownForMaintenance()
&& !($e instanceof HttpResponseException)) {
$data = [
'error' => get_class($e),
'hideHeader' => true,

View File

@ -34,6 +34,13 @@ class AccountApiController extends BaseAPIController
$this->accountRepo = $accountRepo;
}
public function ping()
{
$headers = Utils::getApiHeaders();
return Response::make(RESULT_SUCCESS, 200, $headers);
}
public function register(RegisterRequest $request)
{

File diff suppressed because one or more lines are too long

View File

@ -83,20 +83,23 @@ class AppController extends BaseController
return Redirect::to('/');
}
$_ENV['APP_ENV']='production';
$_ENV['APP_DEBUG']=$app['debug'];
$_ENV['APP_URL']=$app['url'];
$_ENV['APP_KEY']=$app['key'];
$_ENV['DB_TYPE']=$dbType;
$_ENV['DB_HOST']=$database['type']['host'];
$_ENV['DB_DATABASE']=$database['type']['database'];
$_ENV['DB_USERNAME']=$database['type']['username'];
$_ENV['DB_PASSWORD']=$database['type']['password'];
$_ENV['MAIL_DRIVER']=$mail['driver'];
$_ENV['MAIL_PORT']=$mail['port'];
$_ENV['MAIL_ENCRYPTION']=$mail['encryption'];
$_ENV['MAIL_HOST']=$mail['host'];
$_ENV['MAIL_USERNAME']=$mail['username'];;
$_ENV['APP_ENV'] = 'production';
$_ENV['APP_DEBUG'] = $app['debug'];
$_ENV['APP_URL'] = $app['url'];
$_ENV['APP_KEY'] = $app['key'];
$_ENV['DB_TYPE'] = $dbType;
$_ENV['DB_HOST'] = $database['type']['host'];
$_ENV['DB_DATABASE'] = $database['type']['database'];
$_ENV['DB_USERNAME'] = $database['type']['username'];
$_ENV['DB_PASSWORD'] = $database['type']['password'];
$_ENV['MAIL_DRIVER'] = $mail['driver'];
$_ENV['MAIL_PORT'] = $mail['port'];
$_ENV['MAIL_ENCRYPTION'] = $mail['encryption'];
$_ENV['MAIL_HOST'] = $mail['host'];
$_ENV['MAIL_USERNAME'] = $mail['username'];
$_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
$_ENV['MAIL_PASSWORD'] = $mail['password'];
$_ENV['PHANTOMJS_CLOUD_KEY'] = 'a-demo-key-with-low-quota-per-ip-address';
$config = '';
foreach ($_ENV as $key => $val) {
@ -175,9 +178,12 @@ class AppController extends BaseController
$config = '';
foreach ($_ENV as $key => $val) {
if (preg_match('/\s/',$val)) {
$val = "'{$val}'";
}
if (is_array($val)) {
continue;
}
if (preg_match('/\s/', $val)) {
$val = "'{$val}'";
}
$config .= "{$key}={$val}\n";
}
@ -260,18 +266,7 @@ class AppController extends BaseController
Cache::flush();
Session::flush();
Artisan::call('migrate', array('--force' => true));
foreach ([
'PaymentLibraries',
'Fonts',
'Banks',
'InvoiceStatus',
'Currencies',
'DateFormats',
'InvoiceDesigns',
'PaymentTerms',
] as $seeder) {
Artisan::call('db:seed', array('--force' => true, '--class' => "{$seeder}Seeder"));
}
Artisan::call('db:seed', array('--force' => true, '--class' => "UpdateSeeder"));
Event::fire(new UserSettingsChanged());
Session::flash('message', trans('texts.processed_updates'));
} catch (Exception $e) {
@ -300,7 +295,7 @@ class AppController extends BaseController
public function stats()
{
if (Input::get('password') != env('RESELLER_PASSWORD')) {
if ( ! hash_equals(Input::get('password'), env('RESELLER_PASSWORD'))) {
sleep(3);
return '';
}
@ -323,4 +318,4 @@ class AppController extends BaseController
return json_encode($data);
}
}
}

View File

@ -133,6 +133,9 @@ class AuthController extends Controller {
if (Auth::check() && !Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete();
}

View File

@ -2,6 +2,9 @@
use Session;
use Utils;
use Auth;
use Log;
use Input;
use Response;
use Request;
use League\Fractal;
@ -9,8 +12,10 @@ use League\Fractal\Manager;
use League\Fractal\Resource\Item;
use League\Fractal\Resource\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Models\EntityModel;
use App\Ninja\Serializers\ArraySerializer;
use League\Fractal\Serializer\JsonApiSerializer;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* @SWG\Swagger(
@ -62,6 +67,74 @@ class BaseAPIController extends Controller
} else {
$this->manager->setSerializer(new ArraySerializer());
}
if (Utils::isNinjaDev()) {
\DB::enableQueryLog();
}
}
protected function handleAction($request)
{
$entity = $request->entity();
$action = $request->action;
$repo = Utils::toCamelCase($this->entityType) . 'Repo';
$this->$repo->$action($entity);
return $this->itemResponse($entity);
}
protected function listResponse($query)
{
$transformerClass = EntityModel::getTransformerName($this->entityType);
$transformer = new $transformerClass(Auth::user()->account, Input::get('serializer'));
$includes = $transformer->getDefaultIncludes();
$includes = $this->getRequestIncludes($includes);
$query->with($includes);
if ($updatedAt = Input::get('updated_at')) {
$updatedAt = date('Y-m-d H:i:s', $updatedAt);
$query->where(function($query) use ($includes, $updatedAt) {
$query->where('updated_at', '>=', $updatedAt);
foreach ($includes as $include) {
$query->orWhereHas($include, function($query) use ($updatedAt) {
$query->where('updated_at', '>=', $updatedAt);
});
}
});
}
if ($clientPublicId = Input::get('client_id')) {
$filter = function($query) use ($clientPublicId) {
$query->where('public_id', '=', $clientPublicId);
};
$query->whereHas('client', $filter);
}
if ( ! Utils::hasPermission('view_all')){
if ($this->entityType == ENTITY_USER) {
$query->where('id', '=', Auth::user()->id);
} else {
$query->where('user_id', '=', Auth::user()->id);
}
}
$data = $this->createCollection($query, $transformer, $this->entityType);
return $this->response($data);
}
protected function itemResponse($item)
{
$transformerClass = EntityModel::getTransformerName($this->entityType);
$transformer = new $transformerClass(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($item, $transformer, $this->entityType);
return $this->response($data);
}
protected function createItem($data, $transformer, $entityType)
@ -74,23 +147,31 @@ class BaseAPIController extends Controller
return $this->manager->createData($resource)->toArray();
}
protected function createCollection($data, $transformer, $entityType, $paginator = false)
protected function createCollection($query, $transformer, $entityType)
{
if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) {
$entityType = null;
}
$resource = new Collection($data, $transformer, $entityType);
if ($paginator) {
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
if (is_a($query, "Illuminate\Database\Eloquent\Builder")) {
$limit = min(MAX_API_PAGE_SIZE, Input::get('per_page', DEFAULT_API_PAGE_SIZE));
$resource = new Collection($query->get(), $transformer, $entityType);
$resource->setPaginator(new IlluminatePaginatorAdapter($query->paginate($limit)));
} else {
$resource = new Collection($query, $transformer, $entityType);
}
return $this->manager->createData($resource)->toArray();
}
protected function response($response)
{
if (Utils::isNinjaDev()) {
$count = count(\DB::getQueryLog());
Log::info(Request::method() . ' - ' . Request::url() . ": $count queries");
Log::info(json_encode(\DB::getQueryLog()));
}
$index = Request::get('index') ?: 'data';
if ($index == 'none') {
@ -123,26 +204,21 @@ class BaseAPIController extends Controller
}
protected function getIncluded()
protected function getRequestIncludes($data)
{
$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 == 'client') {
$data[] = 'client.contacts';
} elseif ($include == 'clients') {
$data[] = 'clients.contacts';
$data[] = 'clients.user';
} elseif ($include == 'vendors') {
$data[] = 'vendors.vendorcontacts';
$data[] = 'vendors.user';
}
elseif ($include) {
$data[] = 'vendors.vendor_contacts';
} elseif ($include) {
$data[] = $include;
}
}

View File

@ -2,13 +2,16 @@
use App\Http\Middleware\PermissionsRequired;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Input;
use Auth;
use Utils;
class BaseController extends Controller
{
use DispatchesJobs;
use DispatchesJobs, AuthorizesRequests;
protected $model = 'App\Models\EntityModel';
protected $entityType;
/**
* Setup the layout used by the controller.
@ -21,40 +24,4 @@ class BaseController extends Controller
$this->layout = View::make($this->layout);
}
}
protected function checkViewPermission($object, &$response = null){
if(!$object->canView()){
$response = response('Unauthorized.', 401);
return false;
}
return true;
}
protected function checkEditPermission($object, &$response = null){
if(!$object->canEdit()){
$response = response('Unauthorized.', 401);
return false;
}
return true;
}
protected function checkCreatePermission(&$response = null){
if(!call_user_func(array($this->model, 'canCreate'))){
$response = response('Unauthorized.', 401);
return false;
}
return true;
}
protected function checkUpdatePermission($input, &$response = null){
$creating = empty($input['public_id']) || $input['public_id'] == '-1';
if($creating){
return $this->checkCreatePermission($response);
}
else{
$object = call_user_func(array($this->model, 'scope'), $input['public_id'])->firstOrFail();
return $this->checkEditPermission($object, $response);
}
}
}

View File

@ -10,27 +10,19 @@ use App\Ninja\Repositories\ClientRepository;
use App\Http\Requests\CreateClientRequest;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\ClientTransformer;
use App\Services\ClientService;
use App\Http\Requests\UpdateClientRequest;
class ClientApiController extends BaseAPIController
{
protected $clientRepo;
protected $clientService;
public function __construct(ClientRepository $clientRepo, ClientService $clientService)
protected $entityType = ENTITY_CLIENT;
public function __construct(ClientRepository $clientRepo)
{
parent::__construct();
$this->clientRepo = $clientRepo;
$this->clientService = $clientService;
}
public function ping()
{
$headers = Utils::getApiHeaders();
return Response::make('', 200, $headers);
}
/**
@ -52,27 +44,17 @@ class ClientApiController extends BaseAPIController
public function index()
{
$clients = Client::scope()
->with($this->getIncluded())
->orderBy('created_at', 'desc')->withTrashed();
->orderBy('created_at', 'desc')
->withTrashed();
// Filter by email
if (Input::has('email')) {
$email = Input::get('email');
if ($email = Input::get('email')) {
$clients = $clients->whereHas('contacts', function ($query) use ($email) {
$query->where('email', $email);
});
}
$clients = $clients->paginate();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Client::scope()->withTrashed()->paginate();
$data = $this->createCollection($clients, $transformer, ENTITY_CLIENT, $paginator);
return $this->response($data);
return $this->listResponse($clients);
}
/**
@ -100,14 +82,7 @@ class ClientApiController extends BaseAPIController
{
$client = $this->clientRepo->save($request->input());
$client = Client::scope($client->public_id)
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
return $this->itemResponse($client);
}
/**
@ -134,51 +109,15 @@ class ClientApiController extends BaseAPIController
public function update(UpdateClientRequest $request, $publicId)
{
if ($request->action == ACTION_ARCHIVE) {
$client = Client::scope($publicId)->withTrashed()->first();
if(!$client)
return $this->errorResponse(['message'=>'Record not found'], 400);
$this->clientRepo->archive($client);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
if ($request->action) {
return $this->handleAction($request);
}
else if ($request->action == ACTION_RESTORE){
$client = Client::scope($publicId)->withTrashed()->first();
if(!$client)
return $this->errorResponse(['message'=>'Client not found.'], 400);
$this->clientRepo->restore($client);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
}
$data = $request->input();
$data['public_id'] = $publicId;
$this->clientRepo->save($data);
$client = $this->clientRepo->save($data, $request->entity());
$client = Client::scope($publicId)
->with('country', 'contacts', 'industry', 'size', 'currency')
->first();
if(!$client)
return $this->errorResponse(['message'=>'Client not found.'],400);
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
return $this->itemResponse($client);
}
@ -204,23 +143,13 @@ class ClientApiController extends BaseAPIController
* )
*/
public function destroy($publicId)
public function destroy(UpdateClientRequest $request)
{
$client = Client::scope($publicId)->withTrashed()->first();
$client = $request->entity();
$this->clientRepo->delete($client);
$client = Client::scope($publicId)
->with('country', 'contacts', 'industry', 'size', 'currency')
->withTrashed()
->first();
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
return $this->response($data);
return $this->itemResponse($client);
}
}
}

View File

@ -33,7 +33,7 @@ class AuthController extends Controller {
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}

View File

@ -50,7 +50,7 @@ class PasswordController extends Controller {
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}
@ -117,7 +117,7 @@ class PasswordController extends Controller {
$client = $invoice->client;
$account = $client->account;
$data['hideLogo'] = $account->isWhiteLabel();
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
$data['clientViewCSS'] = $account->clientViewCSS();
$data['clientFontUrl'] = $account->getFontsUrl();
}

View File

@ -28,6 +28,7 @@ use App\Models\Task;
use App\Ninja\Repositories\ClientRepository;
use App\Services\ClientService;
use App\Http\Requests\ClientRequest;
use App\Http\Requests\CreateClientRequest;
use App\Http\Requests\UpdateClientRequest;
@ -35,7 +36,7 @@ class ClientController extends BaseController
{
protected $clientService;
protected $clientRepo;
protected $model = 'App\Models\Client';
protected $entityType = ENTITY_CLIENT;
public function __construct(ClientRepository $clientRepo, ClientService $clientService)
{
@ -81,13 +82,7 @@ class ClientController extends BaseController
*/
public function store(CreateClientRequest $request)
{
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$client = $this->clientService->save($data);
$client = $this->clientService->save($request->input());
Session::flash('message', trans('texts.created_client'));
@ -100,38 +95,35 @@ class ClientController extends BaseController
* @param int $id
* @return Response
*/
public function show($publicId)
public function show(ClientRequest $request)
{
$client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail();
if(!$this->checkViewPermission($client, $response)){
return $response;
}
$client = $request->entity();
$user = Auth::user();
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
$actionLinks = [];
if(Task::canCreate()){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => '/tasks/create/'.$client->public_id];
if($user->can('create', ENTITY_TASK)){
$actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)];
}
if (Utils::isPro() && Invoice::canCreate()) {
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => '/quotes/create/'.$client->public_id];
if (Utils::hasFeature(FEATURE_QUOTES) && $user->can('create', ENTITY_INVOICE)) {
$actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)];
}
if(!empty($actionLinks)){
$actionLinks[] = \DropdownButton::DIVIDER;
}
if(Payment::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id];
if($user->can('create', ENTITY_PAYMENT)){
$actionLinks[] = ['label' => trans('texts.enter_payment'), 'url' => URL::to('/payments/create/'.$client->public_id)];
}
if(Credit::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id];
if($user->can('create', ENTITY_CREDIT)){
$actionLinks[] = ['label' => trans('texts.enter_credit'), 'url' => URL::to('/credits/create/'.$client->public_id)];
}
if(Expense::canCreate()){
$actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id];
if($user->can('create', ENTITY_EXPENSE)){
$actionLinks[] = ['label' => trans('texts.enter_expense'), 'url' => URL::to('/expenses/create/0/'.$client->public_id)];
}
$data = array(
@ -154,12 +146,8 @@ class ClientController extends BaseController
*
* @return Response
*/
public function create()
public function create(ClientRequest $request)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
if (Client::scope()->withTrashed()->count() > Auth::user()->getMaxNumClients()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]);
}
@ -182,18 +170,14 @@ class ClientController extends BaseController
* @param int $id
* @return Response
*/
public function edit($publicId)
public function edit(ClientRequest $request)
{
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
if(!$this->checkEditPermission($client, $response)){
return $response;
}
$client = $request->entity();
$data = [
'client' => $client,
'method' => 'PUT',
'url' => 'clients/'.$publicId,
'url' => 'clients/'.$client->public_id,
'title' => trans('texts.edit_client'),
];
@ -201,7 +185,7 @@ class ClientController extends BaseController
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
$data['planDetails'] = $account->getPlanDetails(false, false);
}
}
@ -232,13 +216,7 @@ class ClientController extends BaseController
*/
public function update(UpdateClientRequest $request)
{
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$client = $this->clientService->save($data);
$client = $this->clientService->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_client'));

View File

@ -12,12 +12,13 @@ use App\Models\Client;
use App\Services\CreditService;
use App\Ninja\Repositories\CreditRepository;
use App\Http\Requests\CreateCreditRequest;
use App\Http\Requests\CreditRequest;
class CreditController extends BaseController
{
protected $creditRepo;
protected $creditService;
protected $model = 'App\Models\Credit';
protected $entityType = ENTITY_CREDIT;
public function __construct(CreditRepository $creditRepo, CreditService $creditService)
{
@ -55,32 +56,26 @@ class CreditController extends BaseController
return $this->creditService->getDatatable($clientPublicId, Input::get('sSearch'));
}
public function create($clientPublicId = 0)
public function create(CreditRequest $request)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
$data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
//'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
'credit' => null,
'method' => 'POST',
'url' => 'credits',
'title' => trans('texts.new_credit'),
//'invoices' => Invoice::scope()->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
);
return View::make('credits.edit', $data);
}
/*
public function edit($publicId)
{
$credit = Credit::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($credit, $response)){
return $response;
}
$this->authorize('edit', $credit);
$credit->credit_date = Utils::fromSqlDate($credit->credit_date);
@ -94,7 +89,8 @@ class CreditController extends BaseController
return View::make('credit.edit', $data);
}
*/
public function store(CreateCreditRequest $request)
{
$credit = $this->creditRepo->save($request->input());

View File

@ -11,7 +11,7 @@ class DashboardController extends BaseController
{
public function index()
{
$view_all = !Auth::user()->hasPermission('view_all');
$view_all = Auth::user()->hasPermission('view_all');
$user_id = Auth::user()->id;
// total_income, billed_clients, invoice_sent and active_clients
@ -105,6 +105,7 @@ class DashboardController extends BaseController
->where('contacts.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('invoices.deleted_at', '=', null)
@ -129,6 +130,7 @@ class DashboardController extends BaseController
->where('invoices.deleted_at', '=', null)
->where('invoices.is_recurring', '=', false)
//->where('invoices.is_quote', '=', false)
->where('invoices.quote_invoice_id', '=', null)
->where('invoices.balance', '>', 0)
->where('invoices.is_deleted', '=', false)
->where('contacts.is_primary', '=', true)

View File

@ -0,0 +1,123 @@
<?php namespace App\Http\Controllers;
use Datatable;
use Input;
use Redirect;
use Session;
use URL;
use Utils;
use View;
use Validator;
use Response;
use App\Models\Document;
use App\Ninja\Repositories\DocumentRepository;
use App\Http\Requests\DocumentRequest;
use App\Http\Requests\CreateDocumentRequest;
class DocumentController extends BaseController
{
protected $documentRepo;
protected $entityType = ENTITY_DOCUMENT;
public function __construct(DocumentRepository $documentRepo)
{
// parent::__construct();
$this->documentRepo = $documentRepo;
}
public function get(DocumentRequest $request)
{
return static::getDownloadResponse($request->entity());
}
public static function getDownloadResponse($document){
$direct_url = $document->getDirectUrl();
if($direct_url){
return redirect($direct_url);
}
$stream = $document->getStream();
if($stream){
$headers = [
'Content-Type' => Document::$types[$document->type]['mime'],
'Content-Length' => $document->size,
];
$response = Response::stream(function() use ($stream) {
fpassthru($stream);
}, 200, $headers);
}
else{
$response = Response::make($document->getRaw(), 200);
$response->header('content-type', Document::$types[$document->type]['mime']);
}
return $response;
}
public function getPreview(DocumentRequest $request)
{
$document = $request->entity();
if(empty($document->preview)){
return Response::view('error', array('error'=>'Preview does not exist!'), 404);
}
$direct_url = $document->getDirectPreviewUrl();
if($direct_url){
return redirect($direct_url);
}
$previewType = pathinfo($document->preview, PATHINFO_EXTENSION);
$response = Response::make($document->getRawPreview(), 200);
$response->header('content-type', Document::$types[$previewType]['mime']);
return $response;
}
public function getVFSJS(DocumentRequest $request, $publicId, $name)
{
$document = $request->entity();
if(substr($name, -3)=='.js'){
$name = substr($name, 0, -3);
}
if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404);
}
$content = $document->preview?$document->getRawPreview():$document->getRaw();
$content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
$response = Response::make($content, 200);
$response->header('content-type', 'text/javascript');
$response->header('cache-control', 'max-age=31536000');
return $response;
}
public function postUpload(CreateDocumentRequest $request)
{
if (!Utils::hasFeature(FEATURE_DOCUMENTS)) {
return;
}
$result = $this->documentRepo->upload(Input::all()['file'], $doc_array);
if(is_string($result)){
return Response::json([
'error' => $result,
'code' => 400
], 400);
} else {
return Response::json([
'error' => false,
'document' => $doc_array,
'code' => 200
], 200);
}
}
}

View File

@ -1,5 +1,5 @@
<?php namespace App\Http\Controllers;
// vendor
use App\Models\Expense;
use app\Ninja\Repositories\ExpenseRepository;
use App\Ninja\Transformers\ExpenseTransformer;
@ -16,6 +16,8 @@ class ExpenseApiController extends BaseAPIController
protected $expenseRepo;
protected $expenseService;
protected $entityType = ENTITY_EXPENSE;
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
{
parent::__construct();
@ -26,20 +28,12 @@ class ExpenseApiController extends BaseAPIController
public function index()
{
$expenses = Expense::scope()
->withTrashed()
->with('client', 'invoice', 'vendor')
->orderBy('created_at','desc');
$expenses = $expenses->paginate();
$transformer = new ExpenseTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Expense::scope()->withTrashed()->paginate();
$data = $this->createCollection($expenses, $transformer, ENTITY_EXPENSE, $paginator);
return $this->response($data);
return $this->listResponse($expenses);
}
public function update()

View File

@ -17,6 +17,8 @@ use App\Models\Expense;
use App\Models\Client;
use App\Services\ExpenseService;
use App\Ninja\Repositories\ExpenseRepository;
use App\Http\Requests\ExpenseRequest;
use App\Http\Requests\CreateExpenseRequest;
use App\Http\Requests\UpdateExpenseRequest;
@ -25,7 +27,7 @@ class ExpenseController extends BaseController
// Expenses
protected $expenseRepo;
protected $expenseService;
protected $model = 'App\Models\Expense';
protected $entityType = ENTITY_EXPENSE;
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
{
@ -69,42 +71,35 @@ class ExpenseController extends BaseController
return $this->expenseService->getDatatableVendor($vendorPublicId);
}
public function create($vendorPublicId = null, $clientPublicId = null)
public function create(ExpenseRequest $request)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
if($vendorPublicId != 0) {
$vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail();
if ($request->vendor_id != 0) {
$vendor = Vendor::scope($request->vendor_id)->with('vendor_contacts')->firstOrFail();
} else {
$vendor = null;
}
$data = array(
'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $vendorPublicId,
'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $request->vendor_id,
'expense' => null,
'method' => 'POST',
'url' => 'expenses',
'title' => trans('texts.new_expense'),
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(),
'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(),
'vendor' => $vendor,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $clientPublicId,
);
'clientPublicId' => $request->client_id,
);
$data = array_merge($data, self::getViewModel());
return View::make('expenses.edit', $data);
}
public function edit($publicId)
public function edit(ExpenseRequest $request)
{
$expense = Expense::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($expense, $response)){
return $response;
}
$expense = $request->entity();
$expense->expense_date = Utils::fromSqlDate($expense->expense_date);
$actions = [];
@ -112,15 +107,6 @@ class ExpenseController extends BaseController
$actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
} else {
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_expense")];
/*
// check for any open invoices
$invoices = $task->client_id ? $this->invoiceRepo->findOpenInvoices($task->client_id) : [];
foreach ($invoices as $invoice) {
$actions[] = ['url' => 'javascript:submitAction("add_to_invoice", '.$invoice->public_id.')', 'label' => trans("texts.add_to_invoice", ["invoice" => $invoice->invoice_number])];
}
*/
}
$actions[] = \DropdownButton::DIVIDER;
@ -135,10 +121,10 @@ class ExpenseController extends BaseController
'vendor' => null,
'expense' => $expense,
'method' => 'PUT',
'url' => 'expenses/'.$publicId,
'url' => 'expenses/'.$expense->public_id,
'title' => 'Edit Expense',
'actions' => $actions,
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(),
'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(),
'vendorPublicId' => $expense->vendor ? $expense->vendor->public_id : null,
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
'clientPublicId' => $expense->client ? $expense->client->public_id : null,
@ -146,12 +132,6 @@ class ExpenseController extends BaseController
$data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($client->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
}
}
return View::make('expenses.edit', $data);
}
@ -163,7 +143,10 @@ class ExpenseController extends BaseController
*/
public function update(UpdateExpenseRequest $request)
{
$expense = $this->expenseService->save($request->input());
$data = $request->input();
$data['documents'] = $request->file('documents');
$expense = $this->expenseService->save($data, $request->entity());
Session::flash('message', trans('texts.updated_expense'));
@ -177,7 +160,10 @@ class ExpenseController extends BaseController
public function store(CreateExpenseRequest $request)
{
$expense = $this->expenseService->save($request->input());
$data = $request->input();
$data['documents'] = $request->file('documents');
$expense = $this->expenseService->save($data);
Session::flash('message', trans('texts.created_expense'));
@ -195,8 +181,7 @@ class ExpenseController extends BaseController
$expenses = Expense::scope($ids)->with('client')->get();
$clientPublicId = null;
$currencyId = null;
$data = [];
// Validate that either all expenses do not have a client or if there is a client, it is the same client
foreach ($expenses as $expense)
{
@ -220,19 +205,11 @@ class ExpenseController extends BaseController
Session::flash('error', trans('texts.expense_error_invoiced'));
return Redirect::to('expenses');
}
$account = Auth::user()->account;
$data[] = [
'publicId' => $expense->public_id,
'description' => $expense->public_notes,
'qty' => 1,
'cost' => $expense->present()->converted_amount,
];
}
return Redirect::to("invoices/create/{$clientPublicId}")
->with('expenseCurrencyId', $currencyId)
->with('expenses', $data);
->with('expenses', $ids);
break;
default:

View File

@ -164,12 +164,12 @@ class ExportController extends BaseController
if ($request->input(ENTITY_VENDOR)) {
$data['clients'] = Vendor::scope()
->with('user', 'vendorcontacts', 'country')
->with('user', 'vendor_contacts', 'country')
->withArchived()
->get();
$data['vendor_contacts'] = VendorContact::scope()
->with('user', 'vendor.contacts')
->with('user', 'vendor.vendor_contacts')
->withTrashed()
->get();

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Controllers;
<?php namespace App\Http\Controllers;
use Utils;
use View;

View File

@ -18,14 +18,17 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\InvoiceTransformer;
use App\Http\Requests\CreateInvoiceRequest;
use App\Http\Requests\UpdateInvoiceRequest;
use App\Http\Requests\InvoiceRequest;
use App\Http\Requests\CreateInvoiceAPIRequest;
use App\Http\Requests\UpdateInvoiceAPIRequest;
use App\Services\InvoiceService;
class InvoiceApiController extends BaseAPIController
{
protected $invoiceRepo;
protected $entityType = ENTITY_INVOICE;
public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer)
{
parent::__construct();
@ -55,36 +58,12 @@ class InvoiceApiController extends BaseAPIController
*/
public function index()
{
$paginator = Invoice::scope()->withTrashed();
$invoices = Invoice::scope()->withTrashed()
->with(array_merge(['invoice_items'], $this->getIncluded()));
$invoices = Invoice::scope()
->withTrashed()
->with('invoice_items', 'client')
->orderBy('created_at', 'desc');
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')->paginate();
/*
// Add the first invitation link to the data
foreach ($invoices as $key => $invoice) {
foreach ($invoice->invitations as $subKey => $invitation) {
$invoices[$key]['link'] = $invitation->getLink();
}
unset($invoice['invitations']);
}
*/
$transformer = new InvoiceTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = $paginator->paginate();
$data = $this->createCollection($invoices, $transformer, 'invoices', $paginator);
return $this->response($data);
return $this->listResponse($invoices);
}
/**
@ -104,18 +83,9 @@ class InvoiceApiController extends BaseAPIController
* )
*/
public function show($publicId)
public function show(InvoiceRequest $request)
{
$invoice = Invoice::scope($publicId)->withTrashed()->first();
if(!$invoice)
return $this->errorResponse(['message'=>'Invoice does not exist!'], 404);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
return $this->itemResponse($request->entity());
}
/**
@ -139,7 +109,7 @@ class InvoiceApiController extends BaseAPIController
* )
* )
*/
public function store(CreateInvoiceRequest $request)
public function store(CreateInvoiceAPIRequest $request)
{
$data = Input::all();
$error = null;
@ -166,6 +136,7 @@ class InvoiceApiController extends BaseAPIController
'state',
'postal_code',
'private_notes',
'currency_code',
] as $field) {
if (isset($data[$field])) {
$clientData[$field] = $data[$field];
@ -209,11 +180,11 @@ class InvoiceApiController extends BaseAPIController
}
}
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->first();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
$invoice = Invoice::scope($invoice->public_id)
->with('client', 'invoice_items', 'invitations')
->first();
return $this->itemResponse($invoice);
}
private function prepareData($data, $client)
@ -258,10 +229,11 @@ class InvoiceApiController extends BaseAPIController
// initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)];
// make sure the tax isn't applied twice (for the invoice and the line item)
unset($data['invoice_items'][0]['tax_name']);
unset($data['invoice_items'][0]['tax_rate']);
unset($data['invoice_items'][0]['tax_name1']);
unset($data['invoice_items'][0]['tax_rate1']);
unset($data['invoice_items'][0]['tax_name2']);
unset($data['invoice_items'][0]['tax_rate2']);
} else {
foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item);
@ -298,36 +270,21 @@ class InvoiceApiController extends BaseAPIController
$item[$key] = $val;
}
}
return $item;
}
public function emailInvoice()
public function emailInvoice(InvoiceRequest $request)
{
$data = Input::all();
$error = null;
$invoice = $request->entity();
$invoice = Invoice::scope($data['id'])->withTrashed()->first();
if(!$invoice)
return $this->errorResponse(['message'=>'Invoice does not exist.'], 400);
$this->mailer->sendInvoice($invoice, false, false);
if($error) {
return $this->errorResponse(['message'=>'There was an error sending the invoice'], 400);
}
else {
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
}
$this->mailer->sendInvoice($invoice);
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, $error ? 400 : 200, $headers);
return Response::make($response, 200, $headers);
}
/**
* @SWG\Put(
* path="/invoices",
@ -349,45 +306,25 @@ class InvoiceApiController extends BaseAPIController
* )
* )
*/
public function update(UpdateInvoiceRequest $request, $publicId)
public function update(UpdateInvoiceAPIRequest $request, $publicId)
{
if ($request->action == ACTION_ARCHIVE) {
$invoice = Invoice::scope($publicId)->firstOrFail();
$this->invoiceRepo->archive($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
else if ($request->action == ACTION_CONVERT) {
$quote = Invoice::scope($publicId)->firstOrFail();
if ($request->action == ACTION_CONVERT) {
$quote = $request->entity();
$invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
}
else if ($request->action == ACTION_RESTORE) {
$invoice = Invoice::scope($publicId)->withTrashed()->firstOrFail();
$this->invoiceRepo->restore($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
return $this->itemResponse($invoice);
} elseif ($request->action) {
return $this->handleAction($request);
}
$data = $request->input();
$data['public_id'] = $publicId;
$this->invoiceService->save($data);
$this->invoiceService->save($data, $request->entity());
$invoice = Invoice::scope($publicId)->with('client', 'invoice_items', 'invitations')->firstOrFail();
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
$invoice = Invoice::scope($publicId)
->with('client', 'invoice_items', 'invitations')
->firstOrFail();
return $this->itemResponse($invoice);
}
/**
@ -412,18 +349,13 @@ class InvoiceApiController extends BaseAPIController
* )
*/
public function destroy($publicId)
public function destroy(UpdateInvoiceAPIRequest $request)
{
$data['public_id'] = $publicId;
$invoice = Invoice::scope($publicId)->firstOrFail();
$invoice = $request->entity();
$this->invoiceRepo->delete($invoice);
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($invoice, $transformer, 'invoice');
return $this->response($data);
return $this->itemResponse($invoice);
}
}

View File

@ -17,26 +17,32 @@ use App\Models\Invoice;
use App\Models\Client;
use App\Models\Account;
use App\Models\Product;
use App\Models\Expense;
use App\Models\TaxRate;
use App\Models\InvoiceDesign;
use App\Models\Activity;
use App\Ninja\Mailers\ContactMailer as Mailer;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Services\InvoiceService;
use App\Services\RecurringInvoiceService;
use App\Http\Requests\SaveInvoiceWithClientRequest;
use App\Http\Requests\InvoiceRequest;
use App\Http\Requests\CreateInvoiceRequest;
use App\Http\Requests\UpdateInvoiceRequest;
class InvoiceController extends BaseController
{
protected $mailer;
protected $invoiceRepo;
protected $clientRepo;
protected $documentRepo;
protected $invoiceService;
protected $recurringInvoiceService;
protected $model = 'App\Models\Invoice';
protected $entityType = ENTITY_INVOICE;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService)
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService)
{
// parent::__construct();
@ -85,20 +91,13 @@ class InvoiceController extends BaseController
return $this->recurringInvoiceService->getDatatable($accountId, $clientPublicId, ENTITY_RECURRING_INVOICE, $search);
}
public function edit($publicId, $clone = false)
public function edit(InvoiceRequest $request, $publicId, $clone = false)
{
$account = Auth::user()->account;
$invoice = Invoice::scope($publicId)
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'payments')
->withTrashed()
->firstOrFail();
if(!$this->checkEditPermission($invoice, $response)){
return $response;
}
$invoice = $request->entity()->load('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'payments');
$entityType = $invoice->getEntityType();
$contactIds = DB::table('invitations')
->join('contacts', 'contacts.id', '=', 'invitations.contact_id')
->where('invitations.invoice_id', '=', $invoice->id)
@ -119,7 +118,7 @@ class InvoiceController extends BaseController
} else {
Utils::trackViewed($invoice->getDisplayName().' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
$method = 'PUT';
$url = "{$entityType}s/{$publicId}";
$url = "{$entityType}s/{$invoice->public_id}";
$clients->whereId($invoice->client_id);
}
@ -129,7 +128,11 @@ class InvoiceController extends BaseController
$invoice->start_date = Utils::fromSqlDate($invoice->start_date);
$invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$invoice->last_sent_date = Utils::fromSqlDate($invoice->last_sent_date);
$invoice->is_pro = Auth::user()->isPro();
$invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$actions = [
['url' => 'javascript:onCloneClick()', 'label' => trans("texts.clone_{$entityType}")],
@ -191,7 +194,7 @@ class InvoiceController extends BaseController
'isRecurring' => $invoice->is_recurring,
'actions' => $actions,
'lastSent' => $lastSent);
$data = array_merge($data, self::getViewModel());
$data = array_merge($data, self::getViewModel($invoice));
if ($clone) {
$data['formIsChanged'] = true;
@ -211,6 +214,7 @@ class InvoiceController extends BaseController
$contact->email_error = $invitation->email_error;
$contact->invitation_link = $invitation->getLink();
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
$contact->invitation_openend = $invitation->opened_date && $invitation->opened_date != '0000-00-00 00:00:00' ? $invitation->opened_date : false;
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
}
}
@ -223,25 +227,27 @@ class InvoiceController extends BaseController
return View::make('invoices.edit', $data);
}
public function create($clientPublicId = 0, $isRecurring = false)
public function create(InvoiceRequest $request, $clientPublicId = 0, $isRecurring = false)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
$account = Auth::user()->account;
$account = Auth::user()->account;
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
$clientId = null;
if ($clientPublicId) {
$clientId = Client::getPrivateId($clientPublicId);
if ($request->client_id) {
$clientId = Client::getPrivateId($request->client_id);
}
$invoice = $account->createInvoice($entityType, $clientId);
$invoice->public_id = 0;
if (Session::get('expenses')) {
$invoice->expenses = Expense::scope(Session::get('expenses'))->with('documents')->get();
}
$clients = Client::scope()->with('contacts', 'country')->orderBy('name');
if(!Auth::user()->hasPermission('view_all')){
if (!Auth::user()->hasPermission('view_all')) {
$clients = $clients->where('clients.user_id', '=', Auth::user()->id);
}
@ -253,17 +259,17 @@ class InvoiceController extends BaseController
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
];
$data = array_merge($data, self::getViewModel());
$data = array_merge($data, self::getViewModel($invoice));
return View::make('invoices.edit', $data);
}
public function createRecurring($clientPublicId = 0)
public function createRecurring(InvoiceRequest $request, $clientPublicId = 0)
{
return self::create($clientPublicId, true);
return self::create($request, $clientPublicId, true);
}
private static function getViewModel()
private static function getViewModel($invoice)
{
$recurringHelp = '';
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
@ -324,11 +330,37 @@ class InvoiceController extends BaseController
}
}
// Tax rate $options
$account = Auth::user()->account;
$rates = TaxRate::scope()->orderBy('name')->get();
$options = [];
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
$defaultTax = $rate;
}
}
// Check for any taxes which have been deleted
if ($invoice->exists) {
foreach ($invoice->getTaxes() as $key => $rate) {
if (isset($options[$key])) {
continue;
}
$options[$key] = $rate['name'] . ' ' . $rate['rate'] . '%';
}
}
return [
'data' => Input::old('data'),
'account' => Auth::user()->account->load('country'),
'products' => Product::scope()->with('default_tax_rate')->orderBy('product_key')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'sizes' => Cache::get('sizes'),
@ -350,7 +382,6 @@ class InvoiceController extends BaseController
'recurringDueDateHelp' => $recurringDueDateHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
];
@ -361,18 +392,15 @@ class InvoiceController extends BaseController
*
* @return Response
*/
public function store(SaveInvoiceWithClientRequest $request)
public function store(CreateInvoiceRequest $request)
{
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$data['documents'] = $request->file('documents');
$action = Input::get('action');
$entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($data, true);
$invoice = $this->invoiceService->save($data);
$entityType = $invoice->getEntityType();
$message = trans("texts.created_{$entityType}");
@ -401,26 +429,23 @@ class InvoiceController extends BaseController
* @param int $id
* @return Response
*/
public function update(SaveInvoiceWithClientRequest $request)
public function update(UpdateInvoiceRequest $request)
{
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$data['documents'] = $request->file('documents');
$action = Input::get('action');
$entityType = Input::get('entityType');
$invoice = $this->invoiceService->save($data, true);
$invoice = $this->invoiceService->save($data, $request->entity());
$entityType = $invoice->getEntityType();
$message = trans("texts.updated_{$entityType}");
Session::flash('message', $message);
if ($action == 'clone') {
return $this->cloneInvoice($invoice->public_id);
return $this->cloneInvoice($request, $invoice->public_id);
} elseif ($action == 'convert') {
return $this->convertQuote($invoice->public_id);
return $this->convertQuote($request, $invoice->public_id);
} elseif ($action == 'email') {
return $this->emailInvoice($invoice, Input::get('pdfupload'));
}
@ -489,7 +514,7 @@ class InvoiceController extends BaseController
{
Session::reflash();
return Redirect::to("invoices/{$publicId}/edit");
return Redirect::to("invoices/$publicId/edit");
}
/**
@ -517,27 +542,31 @@ class InvoiceController extends BaseController
}
}
public function convertQuote($publicId)
public function convertQuote(InvoiceRequest $request)
{
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
$clone = $this->invoiceService->convertQuote($invoice);
$clone = $this->invoiceService->convertQuote($request->entity());
Session::flash('message', trans('texts.converted_to_invoice'));
return Redirect::to('invoices/'.$clone->public_id);
return Redirect::to('invoices/' . $clone->public_id);
}
public function cloneInvoice($publicId)
public function cloneInvoice(InvoiceRequest $request, $publicId)
{
return self::edit($publicId, true);
return self::edit($request, $publicId, true);
}
public function invoiceHistory($publicId)
public function invoiceHistory(InvoiceRequest $request)
{
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail();
$invoice->load('user', 'invoice_items', 'account.country', 'client.contacts', 'client.country');
$invoice = $request->entity();
$invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country');
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = Auth::user()->isPro();
$invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$invoice->is_quote = intval($invoice->is_quote);
$activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
@ -555,7 +584,11 @@ class InvoiceController extends BaseController
$backup = json_decode($activity->json_backup);
$backup->invoice_date = Utils::fromSqlDate($backup->invoice_date);
$backup->due_date = Utils::fromSqlDate($backup->due_date);
$backup->is_pro = Auth::user()->isPro();
$backup->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote);
$backup->account = $invoice->account->toArray();

View File

@ -12,18 +12,21 @@ use App\Ninja\Repositories\PaymentRepository;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\PaymentTransformer;
use App\Ninja\Transformers\InvoiceTransformer;
use App\Http\Requests\UpdatePaymentRequest;
use App\Http\Requests\CreatePaymentAPIRequest;
class PaymentApiController extends BaseAPIController
{
protected $paymentRepo;
protected $entityType = ENTITY_PAYMENT;
public function __construct(PaymentRepository $paymentRepo, ContactMailer $contactMailer)
{
parent::__construct();
$this->paymentRepo = $paymentRepo;
$this->contactMailer = $contactMailer;
}
/**
@ -44,85 +47,49 @@ class PaymentApiController extends BaseAPIController
*/
public function index()
{
$paginator = Payment::scope();
$payments = Payment::scope()
->with('client.contacts', 'invitation', 'user', 'invoice')->withTrashed();
->withTrashed()
->with(['invoice'])
->orderBy('created_at', 'desc');
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')->paginate();
$paginator = $paginator->paginate();
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createCollection($payments, $transformer, 'payments', $paginator);
return $this->response($data);
return $this->listResponse($payments);
}
/**
* @SWG\Put(
* path="/payments/{payment_id",
* summary="Update a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Update payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
/**
* @SWG\Put(
* path="/payments/{payment_id",
* summary="Update a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Update payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function update(Request $request, $publicId)
{
$data = Input::all();
$data['public_id'] = $publicId;
$error = false;
if ($request->action == ACTION_ARCHIVE) {
$payment = Payment::scope($publicId)->withTrashed()->firstOrFail();
$this->paymentRepo->archive($payment);
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
$payment = $this->paymentRepo->save($data);
if ($error) {
return $error;
}
/*
$invoice = Invoice::scope($data['invoice_id'])->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->withTrashed()->first();
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
public function update(UpdatePaymentRequest $request, $publicId)
{
if ($request->action) {
return $this->handleAction($request);
}
$data = $request->input();
$data['public_id'] = $publicId;
$payment = $this->paymentRepo->save($data, $request->entity());
return $this->itemResponse($payment);
}
/**
* @SWG\Post(
@ -145,89 +112,46 @@ class PaymentApiController extends BaseAPIController
* )
* )
*/
public function store()
public function store(CreatePaymentAPIRequest $request)
{
$data = Input::all();
$error = false;
if (isset($data['invoice_id'])) {
$invoice = Invoice::scope($data['invoice_id'])->with('client')->first();
if ($invoice) {
$data['invoice_id'] = $invoice->id;
$data['client_id'] = $invoice->client->id;
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
if (!isset($data['transaction_reference'])) {
$data['transaction_reference'] = '';
}
if ($error) {
return $error;
}
$payment = $this->paymentRepo->save($data);
$payment = $this->paymentRepo->save($request->input());
if (Input::get('email_receipt')) {
$this->contactMailer->sendPaymentConfirmation($payment);
}
/*
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
return $this->itemResponse($payment);
}
/**
* @SWG\Delete(
* path="/payments/{payment_id}",
* summary="Delete a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Delete payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
/**
* @SWG\Delete(
* path="/payments/{payment_id}",
* summary="Delete a payment",
* tags={"payment"},
* @SWG\Parameter(
* in="body",
* name="body",
* @SWG\Schema(ref="#/definitions/Payment")
* ),
* @SWG\Response(
* response=200,
* description="Delete payment",
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function destroy($publicId)
{
public function destroy(UpdatePaymentRequest $request)
{
$payment = $request->entity();
$this->clientRepo->delete($payment);
$payment = Payment::scope($publicId)->withTrashed()->first();
$invoiceId = $payment->invoice->public_id;
return $this->itemResponse($payment);
}
$this->paymentRepo->delete($payment);
/*
$invoice = Invoice::scope($invoiceId)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) {
$query->withTrashed();
}])->first();
*/
$transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($payment, $transformer, 'invoice');
return $this->response($data);
}
}

View File

@ -25,12 +25,13 @@ use App\Ninja\Repositories\AccountRepository;
use App\Ninja\Mailers\ContactMailer;
use App\Services\PaymentService;
use App\Http\Requests\PaymentRequest;
use App\Http\Requests\CreatePaymentRequest;
use App\Http\Requests\UpdatePaymentRequest;
class PaymentController extends BaseController
{
protected $model = 'App\Models\Payment';
protected $entityType = ENTITY_PAYMENT;
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService)
{
@ -67,12 +68,8 @@ class PaymentController extends BaseController
return $this->paymentService->getDatatable($clientPublicId, Input::get('sSearch'));
}
public function create($clientPublicId = 0, $invoicePublicId = 0)
public function create(PaymentRequest $request)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
$invoices = Invoice::scope()
->where('is_recurring', '=', false)
->where('is_quote', '=', false)
@ -81,8 +78,8 @@ class PaymentController extends BaseController
->orderBy('invoice_number')->get();
$data = array(
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : ($request->invoice_id ?: 0),
'invoice' => null,
'invoices' => $invoices,
'payment' => null,
@ -96,14 +93,10 @@ class PaymentController extends BaseController
return View::make('payments.edit', $data);
}
public function edit($publicId)
public function edit(PaymentRequest $request)
{
$payment = Payment::scope($publicId)->firstOrFail();
if(!$this->checkEditPermission($payment, $response)){
return $response;
}
$payment = $request->entity();
$payment->payment_date = Utils::fromSqlDate($payment->payment_date);
$data = array(
@ -113,7 +106,7 @@ class PaymentController extends BaseController
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
'payment' => $payment,
'method' => 'PUT',
'url' => 'payments/'.$publicId,
'url' => 'payments/'.$payment->public_id,
'title' => trans('texts.edit_payment'),
'paymentTypes' => Cache::get('paymentTypes'),
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
@ -191,7 +184,7 @@ class PaymentController extends BaseController
'currencyId' => $client->getCurrencyId(),
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideHeader' => $account->isNinjaAccount(),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
@ -355,9 +348,18 @@ class PaymentController extends BaseController
$license->save();
}
return $productId == PRODUCT_INVOICE_DESIGNS ? file_get_contents(storage_path() . '/invoice_designs.txt') : 'valid';
if ($productId == PRODUCT_INVOICE_DESIGNS) {
return file_get_contents(storage_path() . '/invoice_designs.txt');
} else {
// temporary fix to enable previous version to work
if (Input::get('get_date')) {
return $license->created_at->format('Y-m-d');
} else {
return 'valid';
}
}
} else {
return 'invalid';
return RESULT_FAILURE;
}
}
@ -460,6 +462,8 @@ class PaymentController extends BaseController
$ref = $response->getData()['m_payment_id'];
} elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) {
$ref = $response->getData()['signature'];
} elseif ($accountGateway->gateway_id == GATEWAY_CYBERSOURCE) {
$ref = $response->getData()['transaction_uuid'];
} else {
$ref = $response->getTransactionReference();
}
@ -482,6 +486,7 @@ class PaymentController extends BaseController
if ($account->account_key == NINJA_ACCOUNT_KEY) {
Session::flash('trackEventCategory', '/account');
Session::flash('trackEventAction', '/buy_pro_plan');
Session::flash('trackEventAmount', $payment->amount);
}
return Redirect::to('view/'.$payment->invitation->invitation_key);
@ -551,7 +556,16 @@ class PaymentController extends BaseController
}
try {
if (method_exists($gateway, 'completePurchase')
if ($accountGateway->isGateway(GATEWAY_CYBERSOURCE)) {
if (Input::get('decision') == 'ACCEPT') {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
} else {
$message = Input::get('message') . ': ' . Input::get('invalid_fields');
Session::flash('error', $message);
}
return Redirect::to($invitation->getLink());
} elseif (method_exists($gateway, 'completePurchase')
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)
&& !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) {
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
@ -572,11 +586,9 @@ class PaymentController extends BaseController
} else {
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to($invitation->getLink());
}
} catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to($invitation->getLink());
}
@ -585,11 +597,7 @@ class PaymentController extends BaseController
public function store(CreatePaymentRequest $request)
{
$input = $request->input();
if(!$this->checkUpdatePermission($input, $response)){
return $response;
}
$input['invoice_id'] = Invoice::getPrivateId($input['invoice']);
$input['client_id'] = Client::getPrivateId($input['client']);
$payment = $this->paymentRepo->save($input);
@ -606,13 +614,7 @@ class PaymentController extends BaseController
public function update(UpdatePaymentRequest $request)
{
$input = $request->input();
if(!$this->checkUpdatePermission($input, $response)){
return $response;
}
$payment = $this->paymentRepo->save($input);
$payment = $this->paymentRepo->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_payment'));

View File

@ -1,103 +1,54 @@
<?php namespace App\Http\Controllers;
use App\Ninja\Repositories\ProductRepository;
use App\Ninja\Transformers\ProductTransformer;
use Auth;
use Str;
use DB;
use Datatable;
use Utils;
use URL;
use View;
use Input;
use Session;
use Redirect;
use App\Models\Product;
use App\Models\TaxRate;
use App\Services\ProductService;
use App\Ninja\Repositories\ProductRepository;
use App\Http\Requests\CreateProductRequest;
use App\Http\Requests\UpdateProductRequest;
class ProductApiController extends BaseAPIController
{
protected $productService;
protected $productRepo;
protected $entityType = ENTITY_PRODUCT;
protected $productRepo;
public function __construct(ProductService $productService, ProductRepository $productRepo)
public function __construct(ProductRepository $productRepo)
{
parent::__construct();
$this->productService = $productService;
$this->productRepo = $productRepo;
}
public function index()
{
$products = Product::scope()
->withTrashed()
->orderBy('created_at', 'desc');
$products = Product::scope()->withTrashed();
$products = $products->paginate();
$paginator = Product::scope()->withTrashed()->paginate();
$transformer = new ProductTransformer(\Auth::user()->account, $this->serializer);
$data = $this->createCollection($products, $transformer, 'products', $paginator);
return $this->response($data);
return $this->listResponse($products);
}
public function getDatatable()
public function store(CreateProductRequest $request)
{
return $this->productService->getDatatable(Auth::user()->account_id);
$product = $this->productRepo->save($request->input());
return $this->itemResponse($product);
}
public function store()
public function update(UpdateProductRequest $request, $publicId)
{
return $this->save();
}
public function update(\Illuminate\Http\Request $request, $publicId)
{
if ($request->action == ACTION_ARCHIVE) {
$product = Product::scope($publicId)->withTrashed()->firstOrFail();
$this->productRepo->archive($product);
$transformer = new ProductTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($product, $transformer, 'products');
return $this->response($data);
if ($request->action) {
return $this->handleAction($request);
}
else
return $this->save($publicId);
$data = $request->input();
$data['public_id'] = $publicId;
$product = $this->productRepo->save($data, $request->entity());
return $this->itemResponse($product);
}
public function destroy($publicId)
{
//stub
}
private function save($productPublicId = false)
{
if ($productPublicId) {
$product = Product::scope($productPublicId)->firstOrFail();
} else {
$product = Product::createNew();
}
$product->product_key = trim(Input::get('product_key'));
$product->notes = trim(Input::get('notes'));
$product->cost = trim(Input::get('cost'));
//$product->default_tax_rate_id = Input::get('default_tax_rate_id');
$product->save();
$transformer = new ProductTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($product, $transformer, 'products');
return $this->response($data);
}
}

View File

@ -7,27 +7,33 @@ use URL;
use Input;
use Utils;
use Request;
use Response;
use Session;
use Datatable;
use App\Models\Gateway;
use App\Models\Invitation;
use App\Models\Document;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ActivityRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed;
use App\Services\PaymentService;
use Barracuda\ArchiveStream\ZipArchive;
class PublicClientController extends BaseController
{
private $invoiceRepo;
private $paymentRepo;
private $documentRepo;
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService)
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, DocumentRepository $documentRepo, PaymentService $paymentService)
{
$this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo;
$this->activityRepo = $activityRepo;
$this->documentRepo = $documentRepo;
$this->paymentService = $paymentService;
}
@ -66,7 +72,11 @@ class PublicClientController extends BaseController
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->is_pro = $account->isPro();
$invoice->features = [
'customize_invoice_design' => $account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => $account->hasFeature(FEATURE_REMOVE_CREATED_BY),
'invoice_settings' => $account->hasFeature(FEATURE_INVOICE_SETTINGS),
];
$invoice->invoice_fonts = $account->getFontsData();
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
@ -116,9 +126,10 @@ class PublicClientController extends BaseController
'account' => $account,
'showApprove' => $showApprove,
'showBreadcrumbs' => false,
'hideLogo' => $account->isWhiteLabel(),
'hideHeader' => $account->isNinjaAccount(),
'hideDashboard' => !$account->enable_client_portal,
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal,
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'invoice' => $invoice->hidePrivateFields(),
@ -132,6 +143,15 @@ class PublicClientController extends BaseController
'checkoutComDebug' => $checkoutComDebug,
'phantomjs' => Input::has('phantomjs'),
);
if($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()){
$zipDocs = $this->getInvoiceZipDocuments($invoice, $size);
if(count($zipDocs) > 1){
$data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}");
$data['documentsZipSize'] = $size;
}
}
return View::make('invoices.view', $data);
}
@ -196,7 +216,7 @@ class PublicClientController extends BaseController
$client = $invoice->client;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
if (!$account->enable_client_portal || !$account->enable_client_portal_dashboard) {
return $this->returnError();
}
@ -204,7 +224,8 @@ class PublicClientController extends BaseController
'color' => $color,
'account' => $account,
'client' => $client,
'hideLogo' => $account->isWhiteLabel(),
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
];
@ -245,13 +266,20 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.invoices'),
@ -278,12 +306,17 @@ class PublicClientController extends BaseController
return $this->returnError();
}
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'entityType' => ENTITY_PAYMENT,
@ -315,13 +348,19 @@ class PublicClientController extends BaseController
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'hideDashboard' => !$account->enable_client_portal,
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.quotes'),
@ -342,6 +381,44 @@ class PublicClientController extends BaseController
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
}
public function documentIndex()
{
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$account = $invitation->account;
if (!$account->enable_client_portal) {
return $this->returnError();
}
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
'hideDashboard' => !$account->enable_client_portal_dashboard,
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
'clientViewCSS' => $account->clientViewCSS(),
'clientFontUrl' => $account->getFontsUrl(),
'title' => trans('texts.documents'),
'entityType' => ENTITY_DOCUMENT,
'columns' => Utils::trans(['invoice_number', 'name', 'document_date', 'document_size']),
];
return response()->view('public_list', $data);
}
public function documentDatatable()
{
if (!$invitation = $this->getInvitation()) {
return false;
}
return $this->documentRepo->getClientDatatable($invitation->contact_id, ENTITY_DOCUMENT, Input::get('sSearch'));
}
private function returnError($error = false)
{
return response()->view('error', [
@ -372,5 +449,148 @@ class PublicClientController extends BaseController
return $invitation;
}
public function getDocumentVFSJS($publicId, $name){
if (!$invitation = $this->getInvitation()) {
return $this->returnError();
}
$clientId = $invitation->invoice->client_id;
$document = Document::scope($publicId, $invitation->account_id)->first();
if(!$document->isPDFEmbeddable()){
return Response::view('error', array('error'=>'Image does not exist!'), 404);
}
$authorized = false;
if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){
$authorized = true;
} else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){
$authorized = true;
}
if(!$authorized){
return Response::view('error', array('error'=>'Not authorized'), 403);
}
if(substr($name, -3)=='.js'){
$name = substr($name, 0, -3);
}
$content = $document->preview?$document->getRawPreview():$document->getRaw();
$content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
$response = Response::make($content, 200);
$response->header('content-type', 'text/javascript');
$response->header('cache-control', 'max-age=31536000');
return $response;
}
protected function canCreateZip(){
return function_exists('gmp_init');
}
protected function getInvoiceZipDocuments($invoice, &$size=0){
$documents = $invoice->documents;
foreach($invoice->expenses as $expense){
$documents = $documents->merge($expense->documents);
}
$documents = $documents->sortBy('size');
$size = 0;
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
$toZip = array();
foreach($documents as $document){
if($size + $document->size > $maxSize)break;
if(!empty($toZip[$document->name])){
// This name is taken
if($toZip[$document->name]->hash != $document->hash){
// 2 different files with the same name
$nameInfo = pathinfo($document->name);
for($i = 1;; $i++){
$name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension'];
if(empty($toZip[$name])){
$toZip[$name] = $document;
$size += $document->size;
break;
} else if ($toZip[$name]->hash == $document->hash){
// We're not adding this after all
break;
}
}
}
}
else{
$toZip[$document->name] = $document;
$size += $document->size;
}
}
return $toZip;
}
public function getInvoiceDocumentsZip($invitationKey){
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return $this->returnError();
}
Session::put('invitation_key', $invitationKey); // track current invitation
$invoice = $invitation->invoice;
$toZip = $this->getInvoiceZipDocuments($invoice);
if(!count($toZip)){
return Response::view('error', array('error'=>'No documents small enough'), 404);
}
$zip = new ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip');
return Response::stream(function() use ($toZip, $zip) {
foreach($toZip as $name=>$document){
$fileStream = $document->getStream();
if($fileStream){
$zip->init_file_stream_transfer($name, $document->size, array('time'=>$document->created_at->timestamp));
while ($buffer = fread($fileStream, 256000))$zip->stream_file_part($buffer);
fclose($fileStream);
$zip->complete_file_stream();
}
else{
$zip->add_file($name, $document->getRaw());
}
}
$zip->finish();
}, 200);
}
public function getDocument($invitationKey, $publicId){
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
return $this->returnError();
}
Session::put('invitation_key', $invitationKey); // track current invitation
$clientId = $invitation->invoice->client_id;
$document = Document::scope($publicId, $invitation->account_id)->firstOrFail();
$authorized = false;
if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){
$authorized = true;
} else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){
$authorized = true;
}
if(!$authorized){
return Response::view('error', array('error'=>'Not authorized'), 403);
}
return DocumentController::getDownloadResponse($document);
}
}

View File

@ -1,75 +0,0 @@
<?php namespace App\Http\Controllers;
use Auth;
use Input;
use Utils;
use Response;
use App\Models\Invoice;
use App\Ninja\Repositories\InvoiceRepository;
use App\Http\Controllers\BaseAPIController;
use App\Ninja\Transformers\QuoteTransformer;
class QuoteApiController extends BaseAPIController
{
protected $invoiceRepo;
public function __construct(InvoiceRepository $invoiceRepo)
{
parent::__construct();
$this->invoiceRepo = $invoiceRepo;
}
/**
* @SWG\Get(
* path="/quotes",
* tags={"quote"},
* summary="List of quotes",
* @SWG\Response(
* response=200,
* description="A list with quotes",
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice"))
* ),
* @SWG\Response(
* response="default",
* description="an ""unexpected"" error"
* )
* )
*/
public function index()
{
$paginator = Invoice::scope();
$invoices = Invoice::scope()
->with('client', 'invitations', 'user', 'invoice_items')
->where('invoices.is_quote', '=', true);
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')->paginate();
$transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer'));
$paginator = $paginator->paginate();
$data = $this->createCollection($invoices, $transformer, 'quotes', $paginator);
return $this->response($data);
}
/*
public function store()
{
$data = Input::all();
$invoice = $this->invoiceRepo->save(false, $data, false);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
*/
}

View File

@ -26,6 +26,7 @@ use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\ClientRepository;
use App\Events\QuoteInvitationWasApproved;
use App\Services\InvoiceService;
use App\Http\Requests\InvoiceRequest;
class QuoteController extends BaseController
{
@ -33,7 +34,7 @@ class QuoteController extends BaseController
protected $invoiceRepo;
protected $clientRepo;
protected $invoiceService;
protected $model = 'App\Models\Invoice';
protected $entityType = ENTITY_INVOICE;
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
{
@ -47,7 +48,7 @@ class QuoteController extends BaseController
public function index()
{
if (!Utils::isPro()) {
if (!Utils::hasFeature(FEATURE_QUOTES)) {
return Redirect::to('/invoices/create');
}
@ -78,13 +79,9 @@ class QuoteController extends BaseController
return $this->invoiceService->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search);
}
public function create($clientPublicId = 0)
public function create(InvoiceRequest $request, $clientPublicId = 0)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
if (!Utils::isPro()) {
if (!Utils::hasFeature(FEATURE_QUOTES)) {
return Redirect::to('/invoices/create');
}
@ -111,10 +108,27 @@ class QuoteController extends BaseController
private static function getViewModel()
{
// Tax rate $options
$account = Auth::user()->account;
$rates = TaxRate::scope()->orderBy('name')->get();
$options = [];
$defaultTax = false;
foreach ($rates as $rate) {
$options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%';
// load default invoice tax
if ($rate->id == $account->default_tax_rate_id) {
$defaultTax = $rate;
}
}
return [
'entityType' => ENTITY_QUOTE,
'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
'taxRateOptions' => $options,
'defaultTax' => $defaultTax,
'countries' => Cache::get('countries'),
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
'taxRates' => TaxRate::scope()->orderBy('name')->get(),

View File

@ -21,7 +21,7 @@ class ReportController extends BaseController
$message = '';
$fileName = storage_path().'/dataviz_sample.txt';
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
$account = Account::where('id', '=', Auth::user()->account->id)
->with(['clients.invoices.invoice_items', 'clients.contacts'])
->first();
@ -99,13 +99,13 @@ class ReportController extends BaseController
'title' => trans('texts.charts_and_reports'),
];
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
if ($enableReport) {
$isExport = $action == 'export';
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
if ($isExport) {
self::export($params['displayData'], $params['columns'], $params['reportTotals']);
self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
}
}
if ($enableChart) {
@ -514,11 +514,14 @@ class ReportController extends BaseController
return $data;
}
private function export($data, $columns, $totals)
private function export($reportType, $data, $columns, $totals)
{
$output = fopen('php://output', 'w') or Utils::fatalError();
$reportType = trans("texts.{$reportType}s");
$date = date('Y-m-d');
header('Content-Type:application/csv');
header('Content-Disposition:attachment;filename=ninja-report.csv');
header("Content-Disposition:attachment;filename={$date}_Ninja_{$reportType}.csv");
Utils::exportData($output, $data, Utils::trans($columns));

View File

@ -13,6 +13,8 @@ class TaskApiController extends BaseAPIController
{
protected $taskRepo;
protected $entityType = ENTITY_TASK;
public function __construct(TaskRepository $taskRepo)
{
parent::__construct();
@ -38,25 +40,11 @@ class TaskApiController extends BaseAPIController
*/
public function index()
{
$paginator = Task::scope();
$tasks = Task::scope()
->with($this->getIncluded());
$payments = Task::scope()
->withTrashed()
->orderBy('created_at', 'desc');
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')->paginate();
$paginator = $paginator->paginate();
$transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer'));
$data = $this->createCollection($tasks, $transformer, 'tasks', $paginator);
return $this->response($data);
return $this->listResponse($payments);
}
/**

View File

@ -18,11 +18,15 @@ use App\Ninja\Repositories\TaskRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Services\TaskService;
use App\Http\Requests\TaskRequest;
use App\Http\Requests\CreateTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
class TaskController extends BaseController
{
protected $taskRepo;
protected $taskService;
protected $model = 'App\Models\Task';
protected $entityType = ENTITY_TASK;
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService)
{
@ -66,7 +70,7 @@ class TaskController extends BaseController
*
* @return Response
*/
public function store()
public function store(CreateTaskRequest $request)
{
return $this->save();
}
@ -83,16 +87,13 @@ class TaskController extends BaseController
*
* @return Response
*/
public function create($clientPublicId = 0)
public function create(TaskRequest $request)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
$this->checkTimezone();
$data = [
'task' => null,
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
'clientPublicId' => Input::old('client') ? Input::old('client') : ($request->client_id ?: 0),
'method' => 'POST',
'url' => 'tasks',
'title' => trans('texts.new_task'),
@ -111,15 +112,11 @@ class TaskController extends BaseController
* @param int $id
* @return Response
*/
public function edit($publicId)
public function edit(TaskRequest $request)
{
$this->checkTimezone();
$task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail();
if(!$this->checkEditPermission($task, $response)){
return $response;
}
$task = $request->entity();
$actions = [];
if ($task->invoice) {
@ -147,7 +144,7 @@ class TaskController extends BaseController
'task' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT',
'url' => 'tasks/'.$publicId,
'url' => 'tasks/'.$task->public_id,
'title' => trans('texts.edit_task'),
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions,
@ -167,9 +164,11 @@ class TaskController extends BaseController
* @param int $id
* @return Response
*/
public function update($publicId)
public function update(UpdateTaskRequest $request)
{
return $this->save($publicId);
$task = $request->entity();
return $this->save($task->public_id);
}
private static function getViewModel()
@ -184,22 +183,10 @@ class TaskController extends BaseController
{
$action = Input::get('action');
if(!$this->checkUpdatePermission(array('public_id'=>$publicId)/* Hacky, but works */, $response)){
return $response;
}
if (in_array($action, ['archive', 'delete', 'restore'])) {
return self::bulk();
}
if ($validator = $this->taskRepo->getErrors(Input::all())) {
$url = $publicId ? 'tasks/'.$publicId.'/edit' : 'tasks/create';
Session::flash('error', trans('texts.task_errors'));
return Redirect::to($url)
->withErrors($validator)
->withInput();
}
$task = $this->taskRepo->save($publicId, Input::all());
Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task'));

View File

@ -1,68 +1,54 @@
<?php namespace App\Http\Controllers;
use App\Services\TaxRateService;
use App\Ninja\Repositories\TaxRateRepository;
use App\Ninja\Transformers\TaxRateTransformer;
use Auth;
use App\Models\TaxRate;
use App\Ninja\Repositories\TaxRateRepository;
use App\Http\Requests\CreateTaxRateRequest;
use App\Http\Requests\UpdateTaxRateRequest;
class TaxRateApiController extends BaseAPIController
{
protected $taxRateService;
protected $taxRateRepo;
protected $entityType = ENTITY_TAX_RATE;
public function __construct(TaxRateService $taxRateService, TaxRateRepository $taxRateRepo)
public function __construct(TaxRateRepository $taxRateRepo)
{
parent::__construct();
$this->taxRateService = $taxRateService;
$this->taxRateRepo = $taxRateRepo;
}
public function index()
{
$taxRates = TaxRate::scope()->withTrashed();
$taxRates = $taxRates->paginate();
$taxRates = TaxRate::scope()
->withTrashed()
->orderBy('created_at', 'desc');
$paginator = TaxRate::scope()->withTrashed()->paginate();
$transformer = new TaxRateTransformer(Auth::user()->account, $this->serializer);
$data = $this->createCollection($taxRates, $transformer, 'tax_rates', $paginator);
return $this->response($data);
return $this->listResponse($taxRates);
}
public function store(CreateTaxRateRequest $request)
{
return $this->save($request);
$taxRate = $this->taxRateRepo->save($request->input());
return $this->itemResponse($taxRate);
}
public function update(UpdateTaxRateRequest $request, $taxRatePublicId)
public function update(UpdateTaxRateRequest $request, $publicId)
{
$taxRate = TaxRate::scope($taxRatePublicId)->firstOrFail();
if ($request->action == ACTION_ARCHIVE) {
$this->taxRateRepo->archive($taxRate);
$transformer = new TaxRateTransformer(Auth::user()->account, $request->serializer);
$data = $this->createItem($taxRate, $transformer, 'tax_rates');
return $this->response($data);
} else {
return $this->save($request, $taxRate);
if ($request->action) {
return $this->handleAction($request);
}
$data = $request->input();
$data['public_id'] = $publicId;
$taxRate = $this->taxRateRepo->save($data, $request->entity());
return $this->itemResponse($taxRate);
}
private function save($request, $taxRate = false)
public function destroy($publicId)
{
$taxRate = $this->taxRateRepo->save($request->input(), $taxRate);
$transformer = new TaxRateTransformer(\Auth::user()->account, $request->serializer);
$data = $this->createItem($taxRate, $transformer, 'tax_rates');
return $this->response($data);
//stub
}
}

View File

@ -75,9 +75,7 @@ class TaxRateController extends BaseController
public function update(UpdateTaxRateRequest $request, $publicId)
{
$taxRate = TaxRate::scope($publicId)->firstOrFail();
$this->taxRateRepo->save($request->input(), $taxRate);
$this->taxRateRepo->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_tax_rate'));
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);

View File

@ -32,7 +32,7 @@ class TokenController extends BaseController
public function getDatatable()
{
return $this->tokenService->getDatatable(Auth::user()->account_id);
return $this->tokenService->getDatatable(Auth::user()->id);
}
public function edit($publicId)
@ -93,7 +93,7 @@ class TokenController extends BaseController
*/
public function save($tokenPublicId = false)
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->account->hasFeature(FEATURE_API)) {
$rules = [
'name' => 'required',
];

View File

@ -14,6 +14,8 @@ class UserApiController extends BaseAPIController
protected $userService;
protected $userRepo;
protected $entityType = ENTITY_USER;
public function __construct(UserService $userService, UserRepository $userRepo)
{
parent::__construct();
@ -24,16 +26,11 @@ class UserApiController extends BaseAPIController
public function index()
{
$user = Auth::user();
$users = User::whereAccountId($user->account_id)->withTrashed();
$users = $users->paginate();
$paginator = User::whereAccountId($user->account_id)->withTrashed()->paginate();
$transformer = new UserTransformer(Auth::user()->account, $this->serializer);
$data = $this->createCollection($users, $transformer, 'users', $paginator);
return $this->response($data);
$users = User::whereAccountId(Auth::user()->account_id)
->withTrashed()
->orderBy('created_at', 'desc');
return $this->listResponse($users);
}
/*
@ -45,11 +42,6 @@ class UserApiController extends BaseAPIController
public function update(UpdateUserRequest $request, $userPublicId)
{
/*
// temporary fix for ids starting at 0
$userPublicId -= 1;
$user = User::scope($userPublicId)->firstOrFail();
*/
$user = Auth::user();
if ($request->action == ACTION_ARCHIVE) {

View File

@ -164,7 +164,7 @@ class UserController extends BaseController
*/
public function save($userPublicId = false)
{
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
if (Auth::user()->hasFeature(FEATURE_USERS)) {
$rules = [
'first_name' => 'required',
'last_name' => 'required',
@ -190,8 +190,10 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) {
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
}
} else {
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
->orderBy('public_id', 'DESC')->first();
@ -202,12 +204,14 @@ class UserController extends BaseController
$user->last_name = trim(Input::get('last_name'));
$user->username = trim(Input::get('email'));
$user->email = trim(Input::get('email'));
$user->is_admin = boolval(Input::get('is_admin'));
$user->registered = true;
$user->password = str_random(RANDOM_KEY_LENGTH);
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
$user->public_id = $lastUser->public_id + 1;
$user->permissions = Input::get('permissions');
if (Auth::user()->hasFeature(FEATURE_USER_PERMISSIONS)) {
$user->is_admin = boolval(Input::get('is_admin'));
$user->permissions = Input::get('permissions');
}
}
$user->save();
@ -286,6 +290,9 @@ class UserController extends BaseController
if (!Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
if ($account->company->accounts->count() == 1) {
$account->company->forceDelete();
}
$account->forceDelete();
}
}

View File

@ -14,6 +14,8 @@ class VendorApiController extends BaseAPIController
{
protected $vendorRepo;
protected $entityType = ENTITY_VENDOR;
public function __construct(VendorRepository $vendorRepo)
{
parent::__construct();
@ -46,17 +48,11 @@ class VendorApiController extends BaseAPIController
*/
public function index()
{
$vendors = Vendor::scope()
->with($this->getIncluded())
$vendors = Vendor::scope()
->withTrashed()
->orderBy('created_at', 'desc')
->paginate();
->orderBy('created_at', 'desc');
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer'));
$paginator = Vendor::scope()->paginate();
$data = $this->createCollection($vendors, $transformer, ENTITY_VENDOR, $paginator);
return $this->response($data);
return $this->listResponse($vendors);
}
/**
@ -85,11 +81,9 @@ class VendorApiController extends BaseAPIController
$vendor = $this->vendorRepo->save($request->input());
$vendor = Vendor::scope($vendor->public_id)
->with('country', 'vendorcontacts', 'industry', 'size', 'currency')
->with('country', 'vendor_contacts', 'industry', 'size', 'currency')
->first();
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer'));
$data = $this->createItem($vendor, $transformer, ENTITY_VENDOR);
return $this->response($data);
return $this->itemResponse($vendor);
}
}

View File

@ -23,14 +23,15 @@ use App\Models\Country;
use App\Ninja\Repositories\VendorRepository;
use App\Services\VendorService;
use App\Http\Requests\VendorRequest;
use App\Http\Requests\CreateVendorRequest;
use App\Http\Requests\UpdateVendorRequest;
// vendor
class VendorController extends BaseController
{
protected $vendorService;
protected $vendorRepo;
protected $model = 'App\Models\Vendor';
protected $entityType = ENTITY_VENDOR;
public function __construct(VendorRepository $vendorRepo, VendorService $vendorService)
{
@ -38,8 +39,6 @@ class VendorController extends BaseController
$this->vendorRepo = $vendorRepo;
$this->vendorService = $vendorService;
}
/**
@ -77,13 +76,7 @@ class VendorController extends BaseController
*/
public function store(CreateVendorRequest $request)
{
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$vendor = $this->vendorService->save($data);
$vendor = $this->vendorService->save($request->input());
Session::flash('message', trans('texts.created_vendor'));
@ -96,18 +89,14 @@ class VendorController extends BaseController
* @param int $id
* @return Response
*/
public function show($publicId)
public function show(VendorRequest $request)
{
$vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail();
if(!$this->checkViewPermission($vendor, $response)){
return $response;
}
$vendor = $request->entity();
Utils::trackViewed($vendor->getDisplayName(), 'vendor');
$actionLinks = [
['label' => trans('texts.new_vendor'), 'url' => '/vendors/create/' . $vendor->public_id]
['label' => trans('texts.new_vendor'), 'url' => URL::to('/vendors/create/' . $vendor->public_id)]
];
$data = array(
@ -129,12 +118,8 @@ class VendorController extends BaseController
*
* @return Response
*/
public function create()
public function create(VendorRequest $request)
{
if(!$this->checkCreatePermission($response)){
return $response;
}
if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) {
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]);
}
@ -157,26 +142,22 @@ class VendorController extends BaseController
* @param int $id
* @return Response
*/
public function edit($publicId)
public function edit(VendorRequest $request)
{
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail();
if(!$this->checkEditPermission($vendor, $response)){
return $response;
}
$vendor = $request->entity();
$data = [
'vendor' => $vendor,
'method' => 'PUT',
'url' => 'vendors/'.$publicId,
'url' => 'vendors/'.$vendor->public_id,
'title' => trans('texts.edit_vendor'),
];
$data = array_merge($data, self::getViewModel());
if (Auth::user()->account->isNinjaAccount()) {
if ($account = Account::whereId($vendor->public_id)->first()) {
$data['proPlanPaid'] = $account['pro_plan_paid'];
if ($account = Account::whereId($client->public_id)->first()) {
$data['planDetails'] = $account->getPlanDetails(false, false);
}
}
@ -201,13 +182,7 @@ class VendorController extends BaseController
*/
public function update(UpdateVendorRequest $request)
{
$data = $request->input();
if(!$this->checkUpdatePermission($data, $response)){
return $response;
}
$vendor = $this->vendorService->save($data);
$vendor = $this->vendorService->save($request->input(), $request->entity());
Session::flash('message', trans('texts.updated_vendor'));

View File

@ -34,7 +34,8 @@ class ApiCheck {
// check for a valid token
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
if ($token) {
// check if user is archived
if ($token && $token->user) {
Auth::loginUsingId($token->user_id);
Session::set('token_id', $token->id);
} else {
@ -47,7 +48,7 @@ class ApiCheck {
return $next($request);
}
if (!Utils::isPro() && !$loggingIn) {
if (!Utils::hasFeature(FEATURE_API) && !$loggingIn) {
return Response::json('API requires pro plan', 403, $headers);
} else {
$key = Auth::check() ? Auth::user()->account->id : $request->getClientIp();

View File

@ -42,7 +42,7 @@ class Authenticate {
// Does this account require portal passwords?
$account = Account::whereId($account_id)->first();
if(!$account->enable_portal_password || !$account->isPro()){
if($account && (!$account->enable_portal_password || !$account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD))){
$authenticated = true;
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Middleware;
<?php namespace App\Http\Middleware;
use Closure;

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Middleware;
<?php namespace App\Http\Middleware;
use Request;
use Closure;
@ -124,7 +124,8 @@ class StartupCheck
$licenseKey = Input::get('license_key');
$productId = Input::get('product_id');
$data = trim(file_get_contents((Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL)."/claim_license?license_key={$licenseKey}&product_id={$productId}"));
$url = (Utils::isNinjaDev() ? SITE_URL : NINJA_APP_URL) . "/claim_license?license_key={$licenseKey}&product_id={$productId}&get_date=true";
$data = trim(file_get_contents($url));
if ($productId == PRODUCT_INVOICE_DESIGNS) {
if ($data = json_decode($data)) {
@ -140,10 +141,13 @@ class StartupCheck
Session::flash('message', trans('texts.bought_designs'));
}
} elseif ($productId == PRODUCT_WHITE_LABEL) {
if ($data == 'valid') {
$account = Auth::user()->account;
$account->pro_plan_paid = date_create()->format('Y-m-d');
$account->save();
if ($data && $data != RESULT_FAILURE) {
$company = Auth::user()->account->company;
$company->plan_term = PLAN_TERM_YEARLY;
$company->plan_paid = $data;
$company->plan_expires = date_create($data)->modify('+1 year')->format('Y-m-d');
$company->plan = PLAN_WHITE_LABEL;
$company->save();
Session::flash('message', trans('texts.bought_white_label'));
}

View File

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier {
private $openRoutes = [
'complete',
'signup/register',
'api/v1/*',
'api/v1/login',

View File

@ -0,0 +1,18 @@
<?php namespace App\Http\Requests;
class ClientRequest extends EntityRequest {
protected $entityType = ENTITY_CLIENT;
public function entity()
{
$client = parent::entity();
// eager load the contacts
if ($client && ! $client->relationLoaded('contacts')) {
$client->load('contacts');
}
return $client;
}
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateClientRequest extends Request
class CreateClientRequest extends ClientRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreateClientRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_CLIENT);
}
/**
@ -26,21 +23,4 @@ class CreateClientRequest extends Request
'contacts' => 'valid_contacts',
];
}
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['contact'])) {
$input['contacts'] = [$input['contact']];
unset($input['contact']);
$this->replace($input);
}
return $factory->make(
$this->input(),
$this->container->call([$this, 'rules']),
$this->messages()
);
}
}

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateCreditRequest extends Request
class CreateCreditRequest extends CreditRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreateCreditRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_CREDIT);
}
/**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateExpenseRequest extends Request
class CreateDocumentRequest extends DocumentRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdateExpenseRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_DOCUMENT);
}
/**
@ -23,8 +20,7 @@ class UpdateExpenseRequest extends Request
public function rules()
{
return [
'amount' => 'required|positive',
];
}
}

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateExpenseRequest extends Request
class CreateExpenseRequest extends ExpenseRequest
{
// Expenses
/**
@ -13,7 +10,7 @@ class CreateExpenseRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_EXPENSE);
}
/**

View File

@ -0,0 +1,32 @@
<?php namespace App\Http\Requests;
class CreateInvoiceAPIRequest extends InvoiceRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_INVOICE);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'email' => 'required_without:client_id',
'client_id' => 'required_without:email',
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,,id,account_id,' . $this->user()->account_id,
'discount' => 'positive',
];
return $rules;
}
}

View File

@ -1,11 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use Auth;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class CreateInvoiceRequest extends Request
class CreateInvoiceRequest extends InvoiceRequest
{
/**
* Determine if the user is authorized to make this request.
@ -14,7 +9,7 @@ class CreateInvoiceRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_INVOICE);
}
/**
@ -25,13 +20,18 @@ class CreateInvoiceRequest extends Request
public function rules()
{
$rules = [
'email' => 'required_without:client_id',
'client_id' => 'required_without:email',
'client.contacts' => 'valid_contacts',
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id,
'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,' . $this->user()->account_id,
'discount' => 'positive',
];
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');
}
*/
return $rules;
}
}

View File

@ -0,0 +1,48 @@
<?php namespace App\Http\Requests;
use App\Models\Invoice;
class CreatePaymentAPIRequest extends PaymentRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_PAYMENT);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
if ( ! $this->invoice_id || ! $this->amount) {
return [
'invoice_id' => 'required',
'amount' => 'required',
];
}
$invoice = Invoice::scope($this->invoice_id)->firstOrFail();
$this->merge([
'invoice_id' => $invoice->id,
'client_id' => $invoice->client->id,
]);
$rules = array(
'amount' => "required|less_than:{$invoice->balance}|positive",
);
if ($this->payment_type_id == PAYMENT_TYPE_CREDIT) {
$rules['payment_type_id'] = 'has_credit:' . $invoice->client->public_id . ',' . $this->amount;
}
return $rules;
}
}

View File

@ -1,10 +1,8 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class CreatePaymentRequest extends Request
class CreatePaymentRequest extends PaymentRequest
{
/**
* Determine if the user is authorized to make this request.
@ -13,7 +11,7 @@ class CreatePaymentRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_PAYMENT);
}
/**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreatePaymentTermRequest extends Request
class CreateProductRequest extends ProductRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreatePaymentTermRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_PRODUCT);
}
/**
@ -23,8 +20,7 @@ class CreatePaymentTermRequest extends Request
public function rules()
{
return [
'num_days' => 'required',
'name' => 'required',
'product_key' => 'required',
];
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class CreateTaskRequest extends TaskRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('create', ENTITY_TASK);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'time_log' => 'time_log',
];
}
}

View File

@ -1,9 +1,9 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class CreateTaxRateRequest extends Request
class CreateTaxRateRequest extends TaxRateRequest
{
// Expenses
/**
@ -13,7 +13,7 @@ class CreateTaxRateRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_TAX_RATE);
}
/**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
// vendor
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
<?php namespace App\Http\Requests;
class CreateVendorRequest extends Request
class CreateVendorRequest extends VendorRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class CreateVendorRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('create', ENTITY_VENDOR);
}
/**
@ -26,21 +23,4 @@ class CreateVendorRequest extends Request
'name' => 'required',
];
}
/*
public function validator($factory)
{
// support submiting the form with a single contact record
$input = $this->input();
if (isset($input['vendor_contact'])) {
$input['vendor_contacts'] = [$input['vendor_contact']];
unset($input['vendor_contact']);
$this->replace($input);
}
return $factory->make(
$this->input(), $this->container->call([$this, 'rules']), $this->messages()
);
}
*/
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class CreditRequest extends EntityRequest {
protected $entityType = ENTITY_CREDIT;
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class DocumentRequest extends EntityRequest {
protected $entityType = ENTITY_DOCUMENT;
}

View File

@ -0,0 +1,57 @@
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Input;
use Utils;
class EntityRequest extends Request {
protected $entityType;
private $entity;
public function entity()
{
if ($this->entity) {
return $this->entity;
}
// The entity id can appear as invoices, invoice_id, public_id or id
$publicId = false;
foreach (['_id', 's'] as $suffix) {
$field = $this->entityType . $suffix;
if ($this->$field) {
$publicId= $this->$field;
}
}
if ( ! $publicId) {
$publicId = Input::get('public_id') ?: Input::get('id');
}
if ( ! $publicId) {
return null;
}
$class = Utils::getEntityClass($this->entityType);
if (method_exists($class, 'withTrashed')) {
$this->entity = $class::scope($publicId)->withTrashed()->firstOrFail();
} else {
$this->entity = $class::scope($publicId)->firstOrFail();
}
return $this->entity;
}
public function authorize()
{
if ($this->entity()) {
return $this->user()->can('view', $this->entity());
} else {
return $this->user()->can('create', $this->entityType);
}
}
public function rules()
{
return [];
}
}

View File

@ -0,0 +1,18 @@
<?php namespace App\Http\Requests;
class ExpenseRequest extends EntityRequest {
protected $entityType = ENTITY_EXPENSE;
public function entity()
{
$expense = parent::entity();
// eager load the documents
if ($expense && ! $expense->relationLoaded('documents')) {
$expense->load('documents');
}
return $expense;
}
}

View File

@ -0,0 +1,19 @@
<?php namespace App\Http\Requests;
class InvoiceRequest extends EntityRequest {
protected $entityType = ENTITY_INVOICE;
public function entity()
{
$invoice = parent::entity();
// eager load the invoice items
if ($invoice && ! $invoice->relationLoaded('invoice_items')) {
$invoice->load('invoice_items');
}
return $invoice;
}
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class PaymentRequest extends EntityRequest {
protected $entityType = ENTITY_PAYMENT;
}

View File

@ -0,0 +1,6 @@
<?php namespace App\Http\Requests;
class ProductRequest extends EntityRequest {
protected $entityType = ENTITY_PRODUCT;
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use Auth;
use App\Http\Requests\Request;

View File

@ -1,45 +0,0 @@
<?php namespace app\Http\Requests;
use Auth;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class SaveInvoiceWithClientRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$publicId = Request::get('public_id');
$invoiceId = $publicId ? Invoice::getPrivateId($publicId) : '';
$rules = [
'client.contacts' => 'valid_contacts',
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'required|unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.Auth::user()->account_id,
'discount' => 'positive',
];
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');
}
*/
return $rules;
}
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class TaskRequest extends EntityRequest {
protected $entityType = ENTITY_TASK;
}

View File

@ -0,0 +1,7 @@
<?php namespace App\Http\Requests;
class TaxRateRequest extends EntityRequest {
protected $entityType = ENTITY_TAX_RATE;
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateClientRequest extends Request
class UpdateClientRequest extends ClientRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdateClientRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**

View File

@ -1,10 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateExpenseRequest extends Request
class UpdateExpenseRequest extends ExpenseRequest
{
/**
* Determine if the user is authorized to make this request.
@ -13,7 +9,7 @@ class UpdateExpenseRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**

View File

@ -0,0 +1,36 @@
<?php namespace App\Http\Requests;
class UpdateInvoiceAPIRequest extends InvoiceRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
if ($this->action == ACTION_ARCHIVE) {
return [];
}
$invoiceId = $this->entity()->id;
$rules = [
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . $this->user()->account_id,
'discount' => 'positive',
];
return $rules;
}
}

View File

@ -1,11 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use Auth;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
use App\Models\Invoice;
class UpdateInvoiceRequest extends Request
class UpdateInvoiceRequest extends InvoiceRequest
{
/**
* Determine if the user is authorized to make this request.
@ -14,7 +9,7 @@ class UpdateInvoiceRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**
@ -24,19 +19,21 @@ class UpdateInvoiceRequest extends Request
*/
public function rules()
{
if ($this->action == ACTION_ARCHIVE) {
return [];
}
$publicId = $this->route('invoices');
$invoiceId = Invoice::getPrivateId($publicId);
$invoiceId = $this->entity()->id;
$rules = [
'client.contacts' => 'valid_contacts',
'invoice_items' => 'valid_invoice_items',
'invoice_number' => 'unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.Auth::user()->account_id,
'invoice_number' => 'required|unique:invoices,invoice_number,' . $invoiceId . ',id,account_id,' . $this->user()->account_id,
'discount' => 'positive',
];
/* There's a problem parsing the dates
if (Request::get('is_recurring') && Request::get('start_date') && Request::get('end_date')) {
$rules['end_date'] = 'after' . Request::get('start_date');
}
*/
return $rules;
}
}

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdatePaymentRequest extends Request
class UpdatePaymentRequest extends PaymentRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdatePaymentRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateProductRequest extends ProductRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'product_key' => 'required',
];
}
}

View File

@ -0,0 +1,26 @@
<?php namespace App\Http\Requests;
class UpdateTaskRequest extends TaskRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return $this->user()->can('edit', $this->entity());
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'time_log' => 'time_log',
];
}
}

View File

@ -1,9 +1,9 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
class UpdateTaxRateRequest extends Request
class UpdateTaxRateRequest extends TaxRateRequest
{
// Expenses
/**
@ -13,7 +13,7 @@ class UpdateTaxRateRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**

View File

@ -1,4 +1,4 @@
<?php namespace app\Http\Requests;
<?php namespace App\Http\Requests;
use Auth;
use App\Http\Requests\Request;
@ -14,7 +14,7 @@ class UpdateUserRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**

View File

@ -1,9 +1,6 @@
<?php namespace app\Http\Requests;
// vendor
use App\Http\Requests\Request;
use Illuminate\Validation\Factory;
<?php namespace App\Http\Requests;
class UpdateVendorRequest extends Request
class UpdateVendorRequest extends VendorRequest
{
/**
* Determine if the user is authorized to make this request.
@ -12,7 +9,7 @@ class UpdateVendorRequest extends Request
*/
public function authorize()
{
return true;
return $this->user()->can('edit', $this->entity());
}
/**

View File

@ -0,0 +1,19 @@
<?php namespace App\Http\Requests;
class VendorRequest extends EntityRequest {
protected $entityType = ENTITY_VENDOR;
public function entity()
{
$vendor = parent::entity();
// eager load the contacts
if ($vendor && ! $vendor->relationLoaded('vendor_contacts')) {
$vendor->load('vendor_contacts');
}
return $vendor;
}
}

View File

@ -43,17 +43,23 @@ Route::group(['middleware' => 'auth:client'], function() {
Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment');
Route::match(['GET', 'POST'], 'complete', 'PaymentController@offsite_payment');
Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/documents', 'PublicClientController@documentIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard');
Route::get('client/documents/js/{documents}/{filename}', 'PublicClientController@getDocumentVFSJS');
Route::get('client/documents/{invitation_key}/{documents}/{filename?}', 'PublicClientController@getDocument');
Route::get('client/documents/{invitation_key}/{filename?}', 'PublicClientController@getInvoiceDocumentsZip');
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
});
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
Route::get('license', 'PaymentController@show_license_payment');
Route::post('license', 'PaymentController@do_license_payment');
@ -74,8 +80,8 @@ Route::post('/signup', array('as' => 'signup', 'uses' => 'Auth\AuthController@po
Route::get('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@getLoginWrapper'));
Route::post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWrapper'));
Route::get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogoutWrapper'));
Route::get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail'));
Route::post('/recover_password', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail'));
Route::get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset'));
Route::post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset'));
Route::get('/user/confirm/{code}', 'UserController@confirm');
@ -84,8 +90,8 @@ Route::get('/user/confirm/{code}', 'UserController@confirm');
Route::get('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@getLogin'));
Route::post('/client/login', array('as' => 'login', 'uses' => 'ClientAuth\AuthController@postLogin'));
Route::get('/client/logout', array('as' => 'logout', 'uses' => 'ClientAuth\AuthController@getLogout'));
Route::get('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
Route::post('/client/forgot', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail'));
Route::get('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getEmail'));
Route::post('/client/recover_password', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postEmail'));
Route::get('/client/password/reset/{invitation_key}/{token}', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@getReset'));
Route::post('/client/password/reset', array('as' => 'forgot', 'uses' => 'ClientAuth\PasswordController@postReset'));
@ -105,9 +111,11 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('hide_message', 'HomeController@hideMessage');
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
Route::get('settings/user_details', 'AccountController@showUserDetails');
Route::post('settings/user_details', 'AccountController@saveUserDetails');
Route::post('users/change_password', 'UserController@changePassword');
Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
@ -129,15 +137,20 @@ Route::group(['middleware' => 'auth:user'], function() {
Route::get('invoices/create/{client_id?}', 'InvoiceController@create');
Route::get('recurring_invoices/create/{client_id?}', 'InvoiceController@createRecurring');
Route::get('recurring_invoices', 'RecurringInvoiceController@index');
Route::get('invoices/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::get('invoices/{invoices}/clone', 'InvoiceController@cloneInvoice');
Route::post('invoices/bulk', 'InvoiceController@bulk');
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
Route::get('documents/{documents}/{filename?}', 'DocumentController@get');
Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS');
Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview');
Route::post('document', 'DocumentController@postUpload');
Route::get('quotes/create/{client_id?}', 'QuoteController@create');
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice');
Route::get('quotes/{public_id}/edit', 'InvoiceController@edit');
Route::put('quotes/{public_id}', 'InvoiceController@update');
Route::get('quotes/{public_id}', 'InvoiceController@edit');
Route::get('quotes/{invoices}/clone', 'InvoiceController@cloneInvoice');
Route::get('quotes/{invoices}/edit', 'InvoiceController@edit');
Route::put('quotes/{invoices}', 'InvoiceController@update');
Route::get('quotes/{invoices}', 'InvoiceController@edit');
Route::post('quotes', 'InvoiceController@store');
Route::get('quotes', 'QuoteController@index');
Route::get('api/quotes/{client_id?}', array('as'=>'api.quotes', 'uses'=>'QuoteController@getDatatable'));
@ -178,9 +191,9 @@ Route::group([
Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk');
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('start_trial', 'AccountController@startTrial');
Route::get('start_trial/{plan}', 'AccountController@startTrial')
->where(['plan'=>'pro']);
Route::get('restore_user/{user_id}', 'UserController@restoreUser');
Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
Route::get('/unlink_account/{user_account_id}/{user_id}', 'UserController@unlinkAccount');
Route::get('/manage_companies', 'UserController@manageCompanies');
@ -197,21 +210,18 @@ Route::group([
Route::resource('tax_rates', 'TaxRateController');
Route::post('tax_rates/bulk', 'TaxRateController@bulk');
Route::get('settings/email_preview', 'AccountController@previewEmail');
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
Route::get('settings/data_visualizations', 'ReportController@d3');
Route::get('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/change_plan', 'AccountController@changePlan');
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
Route::post('settings/company_details', 'AccountController@updateDetails');
Route::get('settings/{section?}', 'AccountController@showSection');
Route::post('settings/{section?}', 'AccountController@doSection');
//Route::get('api/payment_terms', array('as'=>'api.payment_terms', 'uses'=>'PaymentTermController@getDatatable'));
//Route::resource('payment_terms', 'PaymentTermController');
//Route::post('payment_terms/bulk', 'PaymentTermController@bulk');
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
Route::post('user/setTheme', 'UserController@setTheme');
Route::post('remove_logo', 'AccountController@removeLogo');
Route::post('account/go_pro', 'AccountController@enableProPlan');
@ -234,15 +244,15 @@ Route::group([
// Route groups for API
Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
{
Route::get('ping', 'ClientApiController@ping');
Route::get('ping', 'AccountApiController@ping');
Route::post('login', 'AccountApiController@login');
Route::post('register', 'AccountApiController@register');
Route::get('static', 'AccountApiController@getStaticData');
Route::get('accounts', 'AccountApiController@show');
Route::put('accounts', 'AccountApiController@update');
Route::resource('clients', 'ClientApiController');
Route::get('quotes', 'QuoteApiController@index');
Route::resource('quotes', 'QuoteApiController');
//Route::get('quotes', 'QuoteApiController@index');
//Route::resource('quotes', 'QuoteApiController');
Route::get('invoices', 'InvoiceApiController@index');
Route::resource('invoices', 'InvoiceApiController');
Route::get('payments', 'PaymentApiController@index');
@ -268,7 +278,6 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function()
});
// Redirects for legacy links
/*
Route::get('/rocksteady', function() {
return Redirect::to(NINJA_WEB_URL, 301);
});
@ -296,7 +305,7 @@ Route::get('/compare-online-invoicing{sites?}', function() {
Route::get('/forgot_password', function() {
return Redirect::to(NINJA_APP_URL.'/forgot', 301);
});
*/
if (!defined('CONTACT_EMAIL')) {
define('CONTACT_EMAIL', Config::get('mail.from.address'));
@ -311,6 +320,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ENTITY_CLIENT', 'client');
define('ENTITY_CONTACT', 'contact');
define('ENTITY_INVOICE', 'invoice');
define('ENTITY_DOCUMENT', 'document');
define('ENTITY_INVOICE_ITEMS', 'invoice_items');
define('ENTITY_INVITATION', 'invitation');
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
@ -344,6 +354,7 @@ if (!defined('CONTACT_EMAIL')) {
define('ACCOUNT_LOCALIZATION', 'localization');
define('ACCOUNT_NOTIFICATIONS', 'notifications');
define('ACCOUNT_IMPORT_EXPORT', 'import_export');
define('ACCOUNT_MANAGEMENT', 'account_management');
define('ACCOUNT_PAYMENTS', 'online_payments');
define('ACCOUNT_BANKS', 'bank_accounts');
define('ACCOUNT_IMPORT_EXPENSES', 'import_expenses');
@ -426,6 +437,10 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_IFRAME_URL_LENGTH', 250);
define('MAX_LOGO_FILE_SIZE', 200); // KB
define('MAX_FAILED_LOGINS', 10);
define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB
define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB
define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed)
define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels
define('DEFAULT_FONT_SIZE', 9);
define('DEFAULT_HEADER_FONT', 1);// Roboto
define('DEFAULT_BODY_FONT', 1);// Roboto
@ -521,6 +536,7 @@ if (!defined('CONTACT_EMAIL')) {
define('GATEWAY_BITPAY', 42);
define('GATEWAY_DWOLLA', 43);
define('GATEWAY_CHECKOUT_COM', 47);
define('GATEWAY_CYBERSOURCE', 49);
define('EVENT_CREATE_CLIENT', 1);
define('EVENT_CREATE_INVOICE', 2);
@ -534,25 +550,26 @@ if (!defined('CONTACT_EMAIL')) {
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG');
define('NINJA_WEB_URL', 'https://www.invoiceninja.com');
define('NINJA_APP_URL', 'https://app.invoiceninja.com');
define('NINJA_VERSION', '2.5.1.3');
define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.invoiceninja.com'));
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
define('NINJA_DATE', '2000-01-01');
define('NINJA_VERSION', '2.5.2' . env('NINJA_VERSION_SUFFIX'));
define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja');
define('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja');
define('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/');
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'));
define('SOCIAL_LINK_GITHUB', env('SOCIAL_LINK_GITHUB', 'https://github.com/invoiceninja/invoiceninja/'));
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja');
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/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/');
define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup');
define('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all');
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
define('PDFMAKE_DOCS', env('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'));
define('PHANTOMJS_CLOUD', env('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/'));
define('PHP_DATE_FORMATS', env('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php'));
define('REFERRAL_PROGRAM_URL', env('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/'));
define('EMAIL_MARKUP_URL', env('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup'));
define('OFX_HOME_URL', env('OFX_HOME_URL', 'http://www.ofxhome.com/index.php/home/directory/all'));
define('GOOGLE_ANALYITCS_URL', env('GOOGLE_ANALYITCS_URL', 'https://www.google-analytics.com/collect'));
define('BLANK_IMAGE', 'data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=');
@ -566,9 +583,12 @@ if (!defined('CONTACT_EMAIL')) {
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PRO_PLAN_PRICE', 50);
define('WHITE_LABEL_PRICE', 20);
define('INVOICE_DESIGNS_PRICE', 10);
define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 5));
define('PLAN_PRICE_PRO_YEARLY', env('PLAN_PRICE_PRO_YEARLY', 50));
define('PLAN_PRICE_ENTERPRISE_MONTHLY', env('PLAN_PRICE_ENTERPRISE_MONTHLY', 10));
define('PLAN_PRICE_ENTERPRISE_YEARLY', env('PLAN_PRICE_ENTERPRISE_YEARLY', 100));
define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 20));
define('INVOICE_DESIGNS_PRICE', env('INVOICE_DESIGNS_PRICE', 10));
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
@ -577,9 +597,11 @@ if (!defined('CONTACT_EMAIL')) {
define('TEST_USERNAME', 'user@example.com');
define('TEST_PASSWORD', 'password');
define('API_SECRET', 'API_SECRET');
define('DEFAULT_API_PAGE_SIZE', 15);
define('MAX_API_PAGE_SIZE', 100);
define('IOS_PRODUCTION_PUSH','ninjaIOS');
define('IOS_DEV_PUSH','devNinjaIOS');
define('IOS_PRODUCTION_PUSH', env('IOS_PRODUCTION_PUSH', 'ninjaIOS'));
define('IOS_DEV_PUSH', env('IOS_DEV_PUSH', 'devNinjaIOS'));
define('TOKEN_BILLING_DISABLED', 1);
define('TOKEN_BILLING_OPT_IN', 2);
@ -629,7 +651,46 @@ if (!defined('CONTACT_EMAIL')) {
define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B');
// These must be lowercase
define('PLAN_FREE', 'free');
define('PLAN_PRO', 'pro');
define('PLAN_ENTERPRISE', 'enterprise');
define('PLAN_WHITE_LABEL', 'white_label');
define('PLAN_TERM_MONTHLY', 'month');
define('PLAN_TERM_YEARLY', 'year');
// Pro
define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design');
define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by');
define('FEATURE_DIFFERENT_DESIGNS', 'different_designs');
define('FEATURE_EMAIL_TEMPLATES_REMINDERS', 'email_templates_reminders');
define('FEATURE_INVOICE_SETTINGS', 'invoice_settings');
define('FEATURE_CUSTOM_EMAILS', 'custom_emails');
define('FEATURE_PDF_ATTACHMENT', 'pdf_attachment');
define('FEATURE_MORE_INVOICE_DESIGNS', 'more_invoice_designs');
define('FEATURE_QUOTES', 'quotes');
define('FEATURE_REPORTS', 'reports');
define('FEATURE_API', 'api');
define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password');
define('FEATURE_CUSTOM_URL', 'custom_url');
define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed
// Whitelabel
define('FEATURE_CLIENT_PORTAL_CSS', 'client_portal_css');
define('FEATURE_WHITE_LABEL', 'feature_white_label');
// Enterprise
define('FEATURE_DOCUMENTS', 'documents');
// No Trial allowed
define('FEATURE_USERS', 'users');// Grandfathered for old Pro users
define('FEATURE_USER_PERMISSIONS', 'user_permissions');
// Pro users who started paying on or before this date will be able to manage users
define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
@ -679,30 +740,6 @@ 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');
// Format binding data for sql insertion
foreach ($bindings as $i => $binding) {
if ($binding instanceof \DateTime) {
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
} elseif (is_string($binding)) {
$bindings[$i] = "'$binding'";
}
}
// Insert bindings into query
$query = str_replace(array('%', '?'), array('%%', '%s'), $query);
$query = vsprintf($query, $bindings);
Log::info($query, $data);
});
}
*/
/*
if (Utils::isNinjaDev())
{

View File

@ -2,6 +2,8 @@
// https://github.com/denvertimothy/OFX
use Utils;
use Log;
use SimpleXMLElement;
class OFX
@ -21,13 +23,19 @@ class OFX
$c = curl_init();
curl_setopt($c, CURLOPT_URL, $this->bank->url);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-Type: application/x-ofx'));
// User-Agent: http://www.ofxhome.com/ofxforum/viewtopic.php?pid=108091#p108091
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-Type: application/x-ofx', 'User-Agent: httpclient'));
curl_setopt($c, CURLOPT_POSTFIELDS, $this->request);
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
$this->response = curl_exec($c);
//print_r($this->response);
//\Log::info(print_r($this->response, true));
if (Utils::isNinjaDev()) {
Log::info(print_r($this->response, true));
}
curl_close($c);
$tmp = explode('<OFX>', $this->response);
$this->responseHeader = $tmp[0];
$this->responseBody = '<OFX>'.$tmp[1];
@ -35,14 +43,15 @@ class OFX
public function xml()
{
$xml = $this->responseBody;
self::closeTags($xml);
$xml = self::closeTags($xml);
$x = new SimpleXMLElement($xml);
return $x;
}
public static function closeTags(&$x)
public static function closeTags($x)
{
$x = preg_replace('/(<([^<\/]+)>)(?!.*?<\/\2>)([^<]+)/', '\1\3</\2>', $x);
$x = preg_replace('/\s+/', '', $x);
return preg_replace('/(<([^<\/]+)>)(?!.*?<\/\2>)([^<]+)/', '\1\3</\2>', $x);
}
}
@ -224,3 +233,4 @@ class Account
}
}
}

View File

@ -51,6 +51,11 @@ class Utils
return php_sapi_name() == 'cli';
}
public static function isTravis()
{
return env('TRAVIS') == 'true';
}
public static function isNinja()
{
return self::isNinjaProd() || self::isNinjaDev();
@ -118,6 +123,11 @@ class Utils
return Auth::check() && Auth::user()->isPro();
}
public static function hasFeature($feature)
{
return Auth::check() && Auth::user()->hasFeature($feature);
}
public static function isAdmin()
{
return Auth::check() && Auth::user()->is_admin;
@ -130,7 +140,7 @@ class Utils
public static function hasAllPermissions($permission)
{
return Auth::check() && Auth::user()->hasPermissions($permission);
return Auth::check() && Auth::user()->hasPermission($permission);
}
public static function isTrial()
@ -331,6 +341,7 @@ class Utils
$currency = self::getFromCache($currencyId, 'currencies');
$thousand = $currency->thousand_separator;
$decimal = $currency->decimal_separator;
$precision = $currency->precision;
$code = $currency->code;
$swapSymbol = false;
@ -345,7 +356,7 @@ class Utils
}
}
$value = number_format($value, $currency->precision, $decimal, $thousand);
$value = number_format($value, $precision, $decimal, $thousand);
$symbol = $currency->symbol;
if ($showCode || !$symbol) {
@ -440,7 +451,12 @@ class Utils
return false;
}
$dateTime = new DateTime($date);
if ($date instanceof DateTime) {
$dateTime = $date;
} else {
$dateTime = new DateTime($date);
}
$timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
@ -659,9 +675,14 @@ class Utils
return $year + $offset;
}
public static function getEntityClass($entityType)
{
return 'App\\Models\\' . static::getEntityName($entityType);
}
public static function getEntityName($entityType)
{
return ucwords(str_replace('_', ' ', $entityType));
return ucwords(Utils::toCamelCase($entityType));
}
public static function getClientDisplayName($model)
@ -961,38 +982,6 @@ class Utils
return $entity1;
}
public static function withinPastYear($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
$interval = $today->diff($datePaid);
return $interval->y == 0;
}
public static function getInterval($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
return $today->diff($datePaid);
}
public static function withinPastTwoWeeks($date)
{
$interval = Utils::getInterval($date);
return $interval && $interval->d <= 14;
}
public static function addHttp($url)
{
if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners;
<?php namespace App\Listeners;
use App\Models\Invoice;
use App\Events\ClientWasCreated;

View File

@ -0,0 +1,54 @@
<?php namespace App\Listeners;
use Log;
use Utils;
use App\Events\PaymentWasCreated;
class AnalyticsListener
{
public function trackRevenue(PaymentWasCreated $event)
{
if ( ! Utils::isNinja() || ! env('ANALYTICS_KEY')) {
return;
}
$payment = $event->payment;
$invoice = $payment->invoice;
$account = $payment->account;
if ($account->account_key != NINJA_ACCOUNT_KEY) {
return;
}
$analyticsId = env('ANALYTICS_KEY');
$client = $payment->client;
$amount = $payment->amount;
$base = "v=1&tid={$analyticsId}&cid{$client->public_id}&cu=USD&ti={$invoice->invoice_number}";
$url = $base . "&t=transaction&ta=ninja&tr={$amount}";
$this->sendAnalytics($url);
//Log::info($url);
$url = $base . "&t=item&in=plan&ip={$amount}&iq=1";
$this->sendAnalytics($url);
//Log::info($url);
}
private function sendAnalytics($data)
{
$data = json_encode($data);
$curl = curl_init();
$opts = [
CURLOPT_URL => GOOGLE_ANALYITCS_URL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => 'POST',
CURLOPT_POSTFIELDS => $data,
];
curl_setopt_array($curl, $opts);
$response = curl_exec($curl);
curl_close($curl);
}
}

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners;
<?php namespace App\Listeners;
use Carbon;
use App\Models\Credit;

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners;
<?php namespace App\Listeners;
use Carbon;
use App\Models\Expense;

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners;
<?php namespace App\Listeners;
use Utils;
use Auth;

View File

@ -1,4 +1,4 @@
<?php namespace app\Listeners;
<?php namespace App\Listeners;
use Utils;
use Auth;
@ -14,10 +14,11 @@ class InvoiceListener
{
public function createdInvoice(InvoiceWasCreated $event)
{
if (Utils::isPro()) {
if (Utils::hasFeature(FEATURE_DIFFERENT_DESIGNS)) {
return;
}
// Make sure the account has the same design set as the invoice does
if (Auth::check()) {
$invoice = $event->invoice;
$account = Auth::user()->account;

Some files were not shown because too many files have changed in this diff Show More