diff --git a/app/Console/Commands/SendReminders.php b/app/Console/Commands/SendReminders.php index 3243ac016eaf..58c38a7d8163 100644 --- a/app/Console/Commands/SendReminders.php +++ b/app/Console/Commands/SendReminders.php @@ -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; } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index ee67700872a4..d5fe853a4c11 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -521,7 +521,7 @@ class AccountController extends BaseController $invoice->client = $client; $invoice->invoice_items = [$invoiceItem]; - //$invoice->documents = $account->isPro() ? [$document] : []; + //$invoice->documents = $account->hasFeature(FEATURE_DOCUMENTS) ? [$document] : []; $invoice->documents = []; $data['account'] = $account; @@ -643,7 +643,7 @@ class AccountController extends BaseController private function saveCustomizeDesign() { - if (Auth::user()->account->isPro()) { + if (Auth::user()->account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) { $account = Auth::user()->account; $account->custom_design = Input::get('custom_design'); $account->invoice_design_id = CUSTOM_DESIGN; @@ -658,7 +658,7 @@ class AccountController extends BaseController private function saveClientPortal() { // Only allowed for pro Invoice Ninja users or white labeled self-hosted users - if ((Utils::isNinja() && Auth::user()->account->isPro()) || Auth::user()->account->isWhiteLabel()) { + if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { $input_css = Input::get('client_view_css'); if (Utils::isNinja()) { // Allow referencing the body element @@ -709,7 +709,7 @@ class AccountController extends BaseController private function saveEmailTemplates() { - if (Auth::user()->account->isPro()) { + if (Auth::user()->account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { $account = Auth::user()->account; foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) { @@ -771,7 +771,7 @@ class AccountController extends BaseController private function saveEmailSettings() { - if (Auth::user()->account->isPro()) { + if (Auth::user()->account->hasFeature(FEATURE_CUSTOM_EMAILS)) { $rules = []; $user = Auth::user(); $iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH)); @@ -814,7 +814,7 @@ class AccountController extends BaseController private function saveInvoiceSettings() { - if (Auth::user()->account->isPro()) { + if (Auth::user()->account->hasFeature(FEATURE_INVOICE_SETTINGS)) { $rules = [ 'invoice_number_pattern' => 'has_counter', 'quote_number_pattern' => 'has_counter', @@ -894,7 +894,7 @@ class AccountController extends BaseController private function saveInvoiceDesign() { - if (Auth::user()->account->isPro()) { + if (Auth::user()->account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) { $account = Auth::user()->account; $account->hide_quantity = Input::get('hide_quantity') ? true : false; $account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false; @@ -1188,6 +1188,9 @@ class AccountController extends BaseController \Log::info("Canceled Account: {$account->name} - {$user->email}"); $this->accountRepo->unlinkAccount($account); + if ($account->company->accounts->count() == 1) { + $account->company->forceDelete(); + } $account->forceDelete(); Auth::logout(); diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index bd8813912d25..e599890c6a90 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -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(); } diff --git a/app/Http/Controllers/ClientAuth/AuthController.php b/app/Http/Controllers/ClientAuth/AuthController.php index c88c8a4b85ea..ed5f199ac255 100644 --- a/app/Http/Controllers/ClientAuth/AuthController.php +++ b/app/Http/Controllers/ClientAuth/AuthController.php @@ -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(); } diff --git a/app/Http/Controllers/ClientAuth/PasswordController.php b/app/Http/Controllers/ClientAuth/PasswordController.php index beefb016123e..35576b47ae39 100644 --- a/app/Http/Controllers/ClientAuth/PasswordController.php +++ b/app/Http/Controllers/ClientAuth/PasswordController.php @@ -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(); } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 7f2fac3eed46..2992eb9a9550 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -114,7 +114,7 @@ class ClientController extends BaseController if(Task::canCreate()){ $actionLinks[] = ['label' => trans('texts.new_task'), 'url' => URL::to('/tasks/create/'.$client->public_id)]; } - if (Utils::isPro() && Invoice::canCreate()) { + if (Utils::hasFeature(FEATURE_QUOTES) && Invoice::canCreate()) { $actionLinks[] = ['label' => trans('texts.new_quote'), 'url' => URL::to('/quotes/create/'.$client->public_id)]; } @@ -201,7 +201,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); } } diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 8af0d560d679..15f3ced57fd2 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -114,7 +114,7 @@ class DocumentController extends BaseController public function postUpload() { - if (!Utils::isPro()) { + if (!Utils::hasFeature(FEATURE_DOCUMENTS)) { return; } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 794f09404a2d..774ed7a7a70c 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -132,7 +132,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}")], @@ -573,7 +577,11 @@ class InvoiceController extends BaseController $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; @@ -591,7 +599,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(); + $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), + ]; $backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote); $backup->account = $invoice->account->toArray(); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 2f2cdd92cc7e..1f53b4a0bc54 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -191,7 +191,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(), diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index b7649471f051..32d6d53c3887 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -72,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) { @@ -122,10 +126,10 @@ class PublicClientController extends BaseController 'account' => $account, 'showApprove' => $showApprove, 'showBreadcrumbs' => false, - 'hideLogo' => $account->isWhiteLabel(), + 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL), 'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal, 'hideDashboard' => !$account->enable_client_portal_dashboard, - 'showDocuments' => $account->isPro(), + 'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS), 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'invoice' => $invoice->hidePrivateFields(), @@ -140,7 +144,7 @@ class PublicClientController extends BaseController 'phantomjs' => Input::has('phantomjs'), ); - if($account->isPro() && $this->canCreateZip()){ + if($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()){ $zipDocs = $this->getInvoiceZipDocuments($invoice, $size); if(count($zipDocs) > 1){ @@ -220,8 +224,8 @@ class PublicClientController extends BaseController 'color' => $color, 'account' => $account, 'client' => $client, - 'hideLogo' => $account->isWhiteLabel(), - 'showDocuments' => $account->isPro(), + 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL), + 'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS), 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), ]; @@ -273,9 +277,9 @@ class PublicClientController extends BaseController $data = [ 'color' => $color, - 'hideLogo' => $account->isWhiteLabel(), + 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL), 'hideDashboard' => !$account->enable_client_portal_dashboard, - 'showDocuments' => $account->isPro(), + 'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS), 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'title' => trans('texts.invoices'), @@ -310,9 +314,9 @@ class PublicClientController extends BaseController $color = $account->primary_color ? $account->primary_color : '#0b4d78'; $data = [ 'color' => $color, - 'hideLogo' => $account->isWhiteLabel(), + 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL), 'hideDashboard' => !$account->enable_client_portal_dashboard, - 'showDocuments' => $account->isPro(), + 'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS), 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'entityType' => ENTITY_PAYMENT, @@ -354,9 +358,9 @@ class PublicClientController extends BaseController $color = $account->primary_color ? $account->primary_color : '#0b4d78'; $data = [ 'color' => $color, - 'hideLogo' => $account->isWhiteLabel(), + 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL), 'hideDashboard' => !$account->enable_client_portal_dashboard, - 'showDocuments' => $account->isPro(), + 'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS), 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'title' => trans('texts.quotes'), @@ -392,9 +396,9 @@ class PublicClientController extends BaseController $color = $account->primary_color ? $account->primary_color : '#0b4d78'; $data = [ 'color' => $color, - 'hideLogo' => $account->isWhiteLabel(), + 'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL), 'hideDashboard' => !$account->enable_client_portal_dashboard, - 'showDocuments' => $account->isPro(), + 'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS), 'clientViewCSS' => $account->clientViewCSS(), 'clientFontUrl' => $account->getFontsUrl(), 'title' => trans('texts.documents'), diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 20fdd484ba80..4aaa504bfd1b 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -47,7 +47,7 @@ class QuoteController extends BaseController public function index() { - if (!Utils::isPro()) { + if (!Utils::hasFeature(FEATURE_QUOTES)) { return Redirect::to('/invoices/create'); } @@ -84,7 +84,7 @@ class QuoteController extends BaseController return $response; } - if (!Utils::isPro()) { + if (!Utils::hasFeature(FEATURE_QUOTES)) { return Redirect::to('/invoices/create'); } diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index cfb9caaddef6..a1ee3ea06688 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -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,7 +99,7 @@ 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)); diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php index 8e255d6057be..aa5434a2694b 100644 --- a/app/Http/Controllers/TokenController.php +++ b/app/Http/Controllers/TokenController.php @@ -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', ]; diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 2e3f675aa53e..6b3ab9ca5b58 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -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(); } } diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index 032f8d423d34..b337c1df29e7 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -175,8 +175,8 @@ class VendorController extends BaseController $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); } } diff --git a/app/Http/Middleware/ApiCheck.php b/app/Http/Middleware/ApiCheck.php index 63e370cf4192..8b38c60fe2c5 100644 --- a/app/Http/Middleware/ApiCheck.php +++ b/app/Http/Middleware/ApiCheck.php @@ -47,7 +47,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(); diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 3a49c762a94f..81fde62439e2 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -42,7 +42,7 @@ class Authenticate { // Does this account require portal passwords? $account = Account::whereId($account_id)->first(); - if($account && (!$account->enable_portal_password || !$account->isPro())){ + if($account && (!$account->enable_portal_password || !$account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD))){ $authenticated = true; } diff --git a/app/Http/Middleware/StartupCheck.php b/app/Http/Middleware/StartupCheck.php index 14344e10e956..be96ade97efe 100644 --- a/app/Http/Middleware/StartupCheck.php +++ b/app/Http/Middleware/StartupCheck.php @@ -141,9 +141,10 @@ class StartupCheck } } elseif ($productId == PRODUCT_WHITE_LABEL) { if ($data == 'valid') { - $account = Auth::user()->account; - $account->pro_plan_paid = date_create()->format('Y-m-d'); - $account->save(); + $company = Auth::user()->account->company; + $company->plan_paid = date_create()->format('Y-m-d'); + $company->plan = PLAN_WHITE_LABEL; + $company->save(); Session::flash('message', trans('texts.bought_white_label')); } diff --git a/app/Http/routes.php b/app/Http/routes.php index 7ebd29ecc759..9df520ede465 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -555,7 +555,6 @@ if (!defined('CONTACT_EMAIL')) { 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_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); define('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja'); @@ -656,9 +655,41 @@ if (!defined('CONTACT_EMAIL')) { 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'], diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 7213a2ef2b7d..e4ad99c6ce45 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -118,6 +118,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; diff --git a/app/Listeners/InvoiceListener.php b/app/Listeners/InvoiceListener.php index 9e40bbbe6d6b..91f3de8cc213 100644 --- a/app/Listeners/InvoiceListener.php +++ b/app/Listeners/InvoiceListener.php @@ -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; diff --git a/app/Models/Account.php b/app/Models/Account.php index a2a2ea90374c..1d280a3d8aea 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -533,7 +533,7 @@ class Account extends Eloquent public function getNumberPrefix($isQuote) { - if ( ! $this->isPro()) { + if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { return ''; } @@ -542,7 +542,7 @@ class Account extends Eloquent public function hasNumberPattern($isQuote) { - if ( ! $this->isPro()) { + if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS)) { return false; } @@ -662,7 +662,7 @@ class Account extends Eloquent $default = $this->invoice_number_counter; $actual = Utils::parseInt($invoice->invoice_number); - if ( ! $this->isPro() && $default != $actual) { + if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $default != $actual) { $this->invoice_number_counter = $actual + 1; } else { $this->invoice_number_counter += 1; @@ -794,6 +794,63 @@ class Account extends Eloquent $this->company->save(); } + public function hasFeature($feature) + { + $planDetails = $this->getPlanDetails(); + $selfHost = !Utils::isNinjaProd(); + + switch ($feature) { + // Pro + case FEATURE_CUSTOMIZE_INVOICE_DESIGN: + case FEATURE_REMOVE_CREATED_BY: + case FEATURE_DIFFERENT_DESIGNS: + case FEATURE_EMAIL_TEMPLATES_REMINDERS: + case FEATURE_INVOICE_SETTINGS: + case FEATURE_CUSTOM_EMAILS: + case FEATURE_PDF_ATTACHMENT: + case FEATURE_MORE_INVOICE_DESIGNS: + case FEATURE_QUOTES: + case FEATURE_REPORTS: + case FEATURE_API: + case FEATURE_CLIENT_PORTAL_PASSWORD: + case FEATURE_CUSTOM_URL: + return $selfHost || !empty($planDetails); + + // Pro; No trial allowed, unless they're trialing enterprise with an active pro plan + case FEATURE_MORE_CLIENTS: + return $selfHost || !empty($planDetails) && (!$planDetails['trial'] || !empty($this->getPlanDetails(false, false))); + + // White Label + case FEATURE_WHITE_LABEL: + if ($this->isNinjaAccount() || (!$selfHost && $planDetails && !$plan_details['expires'])) { + return false; + } + // Fallthrough + case FEATURE_CLIENT_PORTAL_CSS: + return !empty($planDetails);// A plan is required even for self-hosted users + + // Enterprise + case FEATURE_DOCUMENTS: + return $selfHost || !empty($planDetails) && $planDetails['plan'] == PLAN_ENTERPRISE; + + // Enterprise; No Trial allowed; grandfathered for old pro users + case FEATURE_USERS:// Grandfathered for old Pro users + if($planDetails && $planDetails['trial']) { + // Do they have a non-trial plan? + $planDetails = $this->getPlanDetails(false, false); + } + + return $selfHost || !empty($planDetails) && ($planDetails['plan'] == PLAN_ENTERPRISE || $planDetails['started'] <= date_create(PRO_USERS_GRANDFATHER_DEADLINE)); + + // Enterprise; No Trial allowed + case FEATURE_USER_PERMISSIONS: + return $selfHost || !empty($planDetails) && $planDetails['plan'] == PLAN_ENTERPRISE && !$planDetails['trial']; + + default: + return false; + } + } + public function isPro(&$plan_details = null) { if (!Utils::isNinjaProd()) { @@ -850,7 +907,7 @@ class Account extends Eloquent $plan_active = false; if ($plan) { - if ($this->company->plan_expires == null && $this->company->plan_paid == NINJA_DATE) { + if ($this->company->plan_expires == null) { $plan_active = true; $plan_expires = false; } else { @@ -861,7 +918,7 @@ class Account extends Eloquent } } - if (!$include_inactive && !$plan_active && !$trial_plan) { + if (!$include_inactive && !$plan_active && !$trial_active) { return null; } @@ -956,7 +1013,7 @@ class Account extends Eloquent $today = new DateTime('now'); $interval = $today->diff($planDetails['expires']); - return $interval ? 14 - $interval->d : 0; + return $interval ? $interval->d : 0; } public function getRenewalDate() @@ -973,20 +1030,6 @@ class Account extends Eloquent return $date->format('Y-m-d'); } - public function isWhiteLabel() - { - if ($this->isNinjaAccount()) { - return false; - } - - if (Utils::isNinjaProd()) { - return self::isPro($plan_details) && $plan_details['expires']; - } else { - $plan_details = $this->getPlanDetails(); - return $plan_details; - } - } - public function getLogoSize() { if(!$this->hasLogo()){ @@ -1063,7 +1106,7 @@ class Account extends Eloquent public function getEmailSubject($entityType) { - if ($this->isPro()) { + if ($this->hasFeature(FEATURE_CUSTOM_EMAILS)) { $field = "email_subject_{$entityType}"; $value = $this->$field; @@ -1083,7 +1126,7 @@ class Account extends Eloquent $template = "
\$client,

"; - if ($this->isPro() && $this->email_design_id != EMAIL_DESIGN_PLAIN) { + if ($this->hasFeature(FEATURE_CUSTOM_EMAILS) && $this->email_design_id != EMAIL_DESIGN_PLAIN) { $template .= "
" . trans("texts.{$entityType}_message_button", ['amount' => '$amount']) . "

" . "
\$viewButton

"; } else { @@ -1102,7 +1145,7 @@ class Account extends Eloquent { $template = false; - if ($this->isPro()) { + if ($this->hasFeature(FEATURE_CUSTOM_EMAILS)) { $field = "email_template_{$entityType}"; $template = $this->$field; } @@ -1198,7 +1241,7 @@ class Account extends Eloquent public function showCustomField($field, $entity = false) { - if ($this->isPro()) { + if ($this->hasFeature(FEATURE_INVOICE_SETTINGS)) { return $this->$field ? true : false; } @@ -1214,18 +1257,18 @@ class Account extends Eloquent public function attatchPDF() { - return $this->isPro() && $this->pdf_email_attachment; + return $this->hasFeaure(FEATURE_PDF_ATTACHMENT) && $this->pdf_email_attachment; } public function getEmailDesignId() { - return $this->isPro() ? $this->email_design_id : EMAIL_DESIGN_PLAIN; + return $this->hasFeature(FEATURE_CUSTOM_EMAILS) ? $this->email_design_id : EMAIL_DESIGN_PLAIN; } public function clientViewCSS(){ - $css = null; + $css = ''; - if ($this->isPro()) { + if ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) { $bodyFont = $this->getBodyFontCss(); $headerFont = $this->getHeaderFontCss(); @@ -1233,11 +1276,10 @@ class Account extends Eloquent if ($headerFont != $bodyFont) { $css .= 'h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{'.$headerFont.'}'; } - - if ((Utils::isNinja() && $this->isPro()) || $this->isWhiteLabel()) { - // For self-hosted users, a white-label license is required for custom CSS - $css .= $this->client_view_css; - } + } + if ($this->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { + // For self-hosted users, a white-label license is required for custom CSS + $css .= $this->client_view_css; } return $css; @@ -1270,11 +1312,11 @@ class Account extends Eloquent } public function getHeaderFontId() { - return ($this->isPro() && $this->header_font_id) ? $this->header_font_id : DEFAULT_HEADER_FONT; + return ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) && $this->header_font_id) ? $this->header_font_id : DEFAULT_HEADER_FONT; } public function getBodyFontId() { - return ($this->isPro() && $this->body_font_id) ? $this->body_font_id : DEFAULT_BODY_FONT; + return ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) && $this->body_font_id) ? $this->body_font_id : DEFAULT_BODY_FONT; } public function getHeaderFontName(){ diff --git a/app/Models/Client.php b/app/Models/Client.php index 92232647117c..10eca4781d88 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -155,7 +155,7 @@ class Client extends EntityModel $contact->send_invoice = true; } - if (!Utils::isPro() || $this->account->enable_portal_password){ + if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $this->account->enable_portal_password){ if(!empty($data['password']) && $data['password']!='-%unchanged%-'){ $contact->password = bcrypt($data['password']); } else if(empty($data['password'])){ diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 687e9873457d..becd0197c93f 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -40,7 +40,7 @@ class Invitation extends EntityModel $url = SITE_URL; $iframe_url = $this->account->iframe_url; - if ($this->account->isPro()) { + if ($this->account->hasFeature(FEATURE_CUSTOM_URL)) { if ($iframe_url && !$forceOnsite) { return "{$iframe_url}?{$this->invitation_key}"; } elseif ($this->account->subdomain) { diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 4d9b82c79416..f158a3c83830 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -409,7 +409,7 @@ class Invoice extends EntityModel implements BalanceAffecting 'invoice_design', 'invoice_design_id', 'invoice_fonts', - 'is_pro', + 'features', 'is_quote', 'custom_value1', 'custom_value2', diff --git a/app/Models/User.php b/app/Models/User.php index c431dbb34484..32a4960a8482 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -112,9 +112,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return $this->account->isPro(); } + public function hasFeature($feature) + { + return $this->account->hasFeature($feature); + } + public function isPaidPro() { - return $this->isPro() && ! $this->isTrial(); + return $this->isPro($accountDetails) && !$accountDetails['trial']; } public function isTrial() @@ -129,7 +134,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon public function maxInvoiceDesignId() { - return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST); + return $this->hasFeature(FEATURE_MORE_INVOICE_DESIGNS) ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST); } public function getDisplayName() @@ -173,7 +178,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon public function getMaxNumClients() { - if ($this->isPro() && ! $this->isTrial()) { + if ($this->hasFeature(FEATURE_MORE_CLIENTS)) { return MAX_NUM_CLIENTS_PRO; } @@ -186,7 +191,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon public function getMaxNumVendors() { - if ($this->isPro() && ! $this->isTrial()) { + if ($this->hasFeature(FEATURE_MORE_CLIENTS)) { return MAX_NUM_VENDORS_PRO; } diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 229895067e42..4b176628ebf0 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -136,7 +136,7 @@ class ContactMailer extends Mailer 'amount' => $invoice->getRequestedAmount() ]; - if (empty($invitation->contact->password) && $account->isPro() && $account->enable_portal_password && $account->send_portal_password) { + if (empty($invitation->contact->password) && $account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password && $account->send_portal_password) { // The contact needs a password $variables['password'] = $password = $this->generatePassword(); $invitation->contact->password = bcrypt($password); @@ -291,7 +291,7 @@ class ContactMailer extends Mailer $passwordHTML = isset($data['password'])?'

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

':false; $documentsHTML = ''; - if($account->isPro() && $invoice->hasDocuments()){ + if($account->hasFeature(FEATURE_DOCUMENTS) && $invoice->hasDocuments()){ $documentsHTML .= trans('texts.email_documents_header').'

@@ -330,7 +330,7 @@ ') !!} - @if ($account->isPro()) + @if ($account->hasFeature(FEATURE_DOCUMENTS))
@@ -499,7 +499,7 @@ @endif - @if (!Utils::isPro() || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST) + @if (!Utils::hasFeature(FEATURE_MORE_INVOICE_DESIGNS) || \App\Models\InvoiceDesign::count() == COUNT_FREE_DESIGNS_SELF_HOST) {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id")->addOption(trans('texts.more_designs') . '...', '-1') !!} @else {!! Former::select('invoice_design_id')->style('display:inline;width:150px;background-color:white !important')->raw()->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") !!} @@ -571,7 +571,7 @@ - @if (Auth::user()->isPro()) + @if (Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS)) @if ($account->custom_client_label1) {!! Former::text('client[custom_value1]') ->label($account->custom_client_label1) @@ -626,7 +626,7 @@ ->addClass('client-email') !!} {!! Former::text('phone')->data_bind("value: phone, valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][phone]'}") !!} - @if ($account->isPro() && $account->enable_portal_password) + @if ($account->hasFeature(FEATURE_CLIENT_PORTAL_PASSWORD) && $account->enable_portal_password) {!! Former::password('password')->data_bind("value: (typeof password=='function'?password():null)?'-%unchanged%-':'', valueUpdate: 'afterkeydown', attr: {name: 'client[contacts][' + \$index() + '][password]'}") !!} @endif @@ -966,7 +966,7 @@ applyComboboxListeners(); - @if (Auth::user()->account->isPro()) + @if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS)) $('.main-form').submit(function(){ if($('#document-upload .dropzone .fallback input').val())$(this).attr('enctype', 'multipart/form-data') else $(this).removeAttr('enctype') @@ -1062,7 +1062,11 @@ var model = ko.toJS(window.model); if(!model)return; var invoice = model.invoice; - invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }}; + invoice.features = { + customize_invoice_design:{{ Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) ? 'true' : 'false' }}, + remove_created_by:{{ Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY) ? 'true' : 'false' }}, + invoice_settings:{{ Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS) ? 'true' : 'false' }} + }; invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }}; invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true}); @@ -1375,7 +1379,7 @@ model.invoice().invoice_number(number); } - @if ($account->isPro()) + @if ($account->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ if(file.mock)return; file.index = model.invoice().documents().length; @@ -1399,7 +1403,7 @@ @endif - @if ($account->isPro() && $account->invoice_embed_documents) + @if ($account->hasFeature(FEATURE_DOCUMENTS) && $account->invoice_embed_documents) @foreach ($invoice->documents as $document) @if($document->isPDFEmbeddable()) diff --git a/resources/views/invoices/history.blade.php b/resources/views/invoices/history.blade.php index baa5c925aadb..18fedecc6b27 100644 --- a/resources/views/invoices/history.blade.php +++ b/resources/views/invoices/history.blade.php @@ -57,7 +57,7 @@ @include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800]) - @if (Utils::isPro() && $invoice->account->invoice_embed_documents) + @if (Utils::hasFeature(FEATURE_DOCUMENTS) && $invoice->account->invoice_embed_documents) @foreach ($invoice->documents as $document) @if($document->isPDFEmbeddable()) diff --git a/resources/views/invoices/pdf.blade.php b/resources/views/invoices/pdf.blade.php index 1c8389affeba..81349390ad39 100644 --- a/resources/views/invoices/pdf.blade.php +++ b/resources/views/invoices/pdf.blade.php @@ -84,7 +84,7 @@ @endif var NINJA = NINJA || {}; - @if ($account->isPro()) + @if ($account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) NINJA.primaryColor = "{{ $account->primary_color }}"; NINJA.secondaryColor = "{{ $account->secondary_color }}"; NINJA.fontSize = {{ $account->font_size }}; diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index 08f5cd9827cd..730094e659af 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -65,7 +65,7 @@
@endif - @if ($account->isPro() && $account->invoice_embed_documents) + @if ($account->hasFeature(FEATURE_DOCUMENTS) && $account->invoice_embed_documents) @foreach ($invoice->documents as $document) @if($document->isPDFEmbeddable()) @@ -82,7 +82,11 @@ @endif