mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Minors Fixes (#3405)
* Tests for client contact passwords * test for client API * Client Tests for password quality * Final tests for client contact password * Implement feature permissions * Minor fixes
This commit is contained in:
parent
e2ed1fad8b
commit
0ff14c97fd
@ -146,10 +146,12 @@ class CompanySettings extends BaseSettings {
|
||||
public $email_subject_invoice = '';
|
||||
public $email_subject_quote = '';
|
||||
public $email_subject_payment = '';
|
||||
public $email_subject_payment_partial = '';
|
||||
public $email_subject_statement = '';
|
||||
public $email_template_invoice = '';
|
||||
public $email_template_quote = '';
|
||||
public $email_template_payment = '';
|
||||
public $email_template_payment_partial = '';
|
||||
public $email_template_statement = '';
|
||||
public $email_subject_reminder1 = '';
|
||||
public $email_subject_reminder2 = '';
|
||||
@ -311,9 +313,11 @@ class CompanySettings extends BaseSettings {
|
||||
'email_subject_invoice' => 'string',
|
||||
'email_subject_quote' => 'string',
|
||||
'email_subject_payment' => 'string',
|
||||
'email_subject_payment_partial' => 'string',
|
||||
'email_template_invoice' => 'string',
|
||||
'email_template_quote' => 'string',
|
||||
'email_template_payment' => 'string',
|
||||
'email_template_payment_partial' => 'string',
|
||||
'email_subject_reminder1' => 'string',
|
||||
'email_subject_reminder2' => 'string',
|
||||
'email_subject_reminder3' => 'string',
|
||||
|
@ -142,6 +142,7 @@ class CompanyUserController extends BaseController
|
||||
}
|
||||
else {
|
||||
$company_user->fill($request->input('company_user')['settings']);
|
||||
$company_user->fill($request->input('company_user')['notifications']);
|
||||
}
|
||||
|
||||
$company_user->save();
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\Language;
|
||||
use App\Utils\CurlUtils;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
@ -39,14 +41,15 @@ class StartupCheck
|
||||
// $start = microtime(true);
|
||||
// Log::error('start up check');
|
||||
|
||||
$cached_tables = config('ninja.cached_tables');
|
||||
|
||||
if (Input::has('clear_cache')) {
|
||||
if ($request->has('clear_cache')) {
|
||||
Session::flash('message', 'Cache cleared');
|
||||
}
|
||||
|
||||
/* Make sure our cache is built */
|
||||
$cached_tables = config('ninja.cached_tables');
|
||||
|
||||
foreach ($cached_tables as $name => $class) {
|
||||
if (Input::has('clear_cache') || ! Cache::has($name)) {
|
||||
if ($request->has('clear_cache') || ! Cache::has($name)) {
|
||||
// check that the table exists in case the migration is pending
|
||||
if (! Schema::hasTable((new $class())->getTable())) {
|
||||
continue;
|
||||
@ -67,6 +70,69 @@ class StartupCheck
|
||||
}
|
||||
}
|
||||
|
||||
/* Catch claim license requests */
|
||||
if(config('ninja.environment') == 'selfhost' && $request->has('license_key') && $request->has('product_id') && $request->segment(3) == 'claim_license')
|
||||
{
|
||||
|
||||
$license_key = $request->input('license_key');
|
||||
$product_id = $request->input('product_id');
|
||||
|
||||
$url = config('ninja.license_url') . "/claim_license?license_key={$license_key}&product_id={$product_id}&get_date=true";
|
||||
$data = trim(CurlUtils::get($url));
|
||||
|
||||
if ($data == Account::RESULT_FAILURE) {
|
||||
|
||||
$error = [
|
||||
'message' => trans('texts.invalid_white_label_license'),
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
return response()->json($error, 400);
|
||||
|
||||
} elseif ($data) {
|
||||
|
||||
$date = date_create($data)->modify('+1 year');
|
||||
|
||||
if ($date < date_create()) {
|
||||
|
||||
$error = [
|
||||
'message' => trans('texts.invalid_white_label_license'),
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
return response()->json($error, 400);
|
||||
|
||||
} else {
|
||||
|
||||
$account = auth()->user()->company()->account;
|
||||
|
||||
$account->plan_term = Account::PLAN_TERM_YEARLY;
|
||||
$account->plan_paid = $data;
|
||||
$account->plan_expires = $date->format('Y-m-d');
|
||||
$account->plan = Account::PLAN_WHITE_LABEL;
|
||||
$account->save();
|
||||
|
||||
$error = [
|
||||
'message' => trans('texts.bought_white_label'),
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
return response()->json($error, 200);
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
$error = [
|
||||
'message' => trans('texts.white_label_license_error'),
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
return response()->json($error, 400);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
return $response;
|
||||
|
@ -68,8 +68,11 @@ class StoreClientRequest extends Request
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
//@todo implement feature permissions for > 100 clients
|
||||
|
||||
if (!isset($input['settings'])) {
|
||||
$input['settings'] = ClientSettings::defaults();
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
@ -52,6 +53,37 @@ class Account extends BaseModel
|
||||
'discount_expires',
|
||||
];
|
||||
|
||||
const PLAN_FREE = 'free';
|
||||
const PLAN_PRO = 'pro';
|
||||
const PLAN_ENTERPRISE = 'enterprise';
|
||||
const PLAN_WHITE_LABEL = 'white_label';
|
||||
const PLAN_TERM_MONTHLY = 'month';
|
||||
const PLAN_TERM_YEARLY = 'year';
|
||||
|
||||
const FEATURE_TASKS = 'tasks';
|
||||
const FEATURE_EXPENSES = 'expenses';
|
||||
const FEATURE_QUOTES = 'quotes';
|
||||
const FEATURE_CUSTOMIZE_INVOICE_DESIGN = 'custom_designs';
|
||||
const FEATURE_DIFFERENT_DESIGNS = 'different_designs';
|
||||
const FEATURE_EMAIL_TEMPLATES_REMINDERS = 'template_reminders';
|
||||
const FEATURE_INVOICE_SETTINGS = 'invoice_settings';
|
||||
const FEATURE_CUSTOM_EMAILS = 'custom_emails';
|
||||
const FEATURE_PDF_ATTACHMENT = 'pdf_attachments';
|
||||
const FEATURE_MORE_INVOICE_DESIGNS = 'more_invoice_designs';
|
||||
const FEATURE_REPORTS = 'reports';
|
||||
const FEATURE_BUY_NOW_BUTTONS = 'buy_now_buttons';
|
||||
const FEATURE_API = 'api';
|
||||
const FEATURE_CLIENT_PORTAL_PASSWORD = 'client_portal_password';
|
||||
const FEATURE_CUSTOM_URL = 'custom_url';
|
||||
const FEATURE_MORE_CLIENTS = 'more_clients';
|
||||
const FEATURE_WHITE_LABEL = 'white_label';
|
||||
const FEATURE_REMOVE_CREATED_BY = 'remove_created_by';
|
||||
const FEATURE_USERS = 'users'; // Grandfathered for old Pro users
|
||||
const FEATURE_DOCUMENTS = 'documents';
|
||||
const FEATURE_USER_PERMISSIONS = 'permissions';
|
||||
|
||||
const RESULT_FAILURE = 'failure';
|
||||
const RESULT_SUCCESS = 'success';
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
@ -82,4 +114,180 @@ class Account extends BaseModel
|
||||
{
|
||||
return $this->plan ?: '';
|
||||
}
|
||||
|
||||
|
||||
public function hasFeature($feature)
|
||||
{
|
||||
$plan_details = $this->getPlanDetails();
|
||||
$self_host = ! Ninja::isNinja();
|
||||
|
||||
switch ($feature) {
|
||||
|
||||
case self::FEATURE_TASKS:
|
||||
case self::FEATURE_EXPENSES:
|
||||
case self::FEATURE_QUOTES:
|
||||
return true;
|
||||
|
||||
case self::FEATURE_CUSTOMIZE_INVOICE_DESIGN:
|
||||
case self::FEATURE_DIFFERENT_DESIGNS:
|
||||
case self::FEATURE_EMAIL_TEMPLATES_REMINDERS:
|
||||
case self::FEATURE_INVOICE_SETTINGS:
|
||||
case self::FEATURE_CUSTOM_EMAILS:
|
||||
case self::FEATURE_PDF_ATTACHMENT:
|
||||
case self::FEATURE_MORE_INVOICE_DESIGNS:
|
||||
case self::FEATURE_REPORTS:
|
||||
case self::FEATURE_BUY_NOW_BUTTONS:
|
||||
case self::FEATURE_API:
|
||||
case self::FEATURE_CLIENT_PORTAL_PASSWORD:
|
||||
case self::FEATURE_CUSTOM_URL:
|
||||
return $self_host || ! empty($plan_details);
|
||||
|
||||
// Pro; No trial allowed, unless they're trialing enterprise with an active pro plan
|
||||
case FEATURE_MORE_CLIENTS:
|
||||
return $self_host || ! empty($plan_details) && (! $plan_details['trial'] || ! empty($this->getPlanDetails(false, false)));
|
||||
|
||||
// White Label
|
||||
case FEATURE_WHITE_LABEL:
|
||||
if (! $self_host && $plan_details && ! $plan_details['expires']) {
|
||||
return false;
|
||||
}
|
||||
// Fallthrough
|
||||
case FEATURE_REMOVE_CREATED_BY:
|
||||
return ! empty($plan_details); // A plan is required even for self-hosted users
|
||||
|
||||
// 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_DOCUMENTS:
|
||||
case FEATURE_USER_PERMISSIONS:
|
||||
return $selfHost || ! empty($planDetails) && $planDetails['plan'] == PLAN_ENTERPRISE && ! $planDetails['trial'];
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function isPaid()
|
||||
{
|
||||
return Ninja::isNinja() ? ($this->isPaidHostedClient() && ! $this->isTrial()) : $this->hasFeature(self::FEATURE_WHITE_LABEL);
|
||||
}
|
||||
|
||||
public function isPaidHostedClient()
|
||||
{
|
||||
return $this->plan == 'pro' || $this->plan == 'enterprise';
|
||||
}
|
||||
|
||||
public function isTrial()
|
||||
{
|
||||
if (! Ninja::isNinja()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$plan_details = $this->getPlanDetails();
|
||||
|
||||
return $plan_details && $plan_details['trial'];
|
||||
}
|
||||
|
||||
public function getPlanDetails($include_inactive = false, $include_trial = true)
|
||||
{
|
||||
if (!$this) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$plan = $this->plan;
|
||||
$price = $this->plan_price;
|
||||
$trial_plan = $this->trial_plan;
|
||||
|
||||
if ((! $plan || $plan == self::PLAN_FREE) && (! $trial_plan || ! $include_trial)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trial_active = false;
|
||||
if ($trial_plan && $include_trial) {
|
||||
$trial_started = \DateTime::createFromFormat('Y-m-d', $this->trial_started);
|
||||
$trial_expires = clone $trial_started;
|
||||
$trial_expires->modify('+2 weeks');
|
||||
|
||||
if ($trial_expires >= date_create()) {
|
||||
$trial_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
$plan_active = false;
|
||||
if ($plan) {
|
||||
if ($this->plan_expires == null) {
|
||||
$plan_active = true;
|
||||
$plan_expires = false;
|
||||
} else {
|
||||
$plan_expires = \DateTime::createFromFormat('Y-m-d', $this->plan_expires);
|
||||
if ($plan_expires >= date_create()) {
|
||||
$plan_active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $include_inactive && ! $plan_active && ! $trial_active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Should we show plan details or trial details?
|
||||
if (($plan && ! $trial_plan) || ! $include_trial) {
|
||||
$use_plan = true;
|
||||
} elseif (! $plan && $trial_plan) {
|
||||
$use_plan = false;
|
||||
} else {
|
||||
// There is both a plan and a trial
|
||||
if (! empty($plan_active) && empty($trial_active)) {
|
||||
$use_plan = true;
|
||||
} elseif (empty($plan_active) && ! empty($trial_active)) {
|
||||
$use_plan = false;
|
||||
} elseif (! empty($plan_active) && ! empty($trial_active)) {
|
||||
// Both are active; use whichever is a better plan
|
||||
if ($plan == self::PLAN_ENTERPRISE) {
|
||||
$use_plan = true;
|
||||
} elseif ($trial_plan == self::PLAN_ENTERPRISE) {
|
||||
$use_plan = false;
|
||||
} else {
|
||||
// They're both the same; show the plan
|
||||
$use_plan = true;
|
||||
}
|
||||
} else {
|
||||
// Neither are active; use whichever expired most recently
|
||||
$use_plan = $plan_expires >= $trial_expires;
|
||||
}
|
||||
}
|
||||
|
||||
if ($use_plan) {
|
||||
return [
|
||||
'account_id' => $this->id,
|
||||
'num_users' => $this->num_users,
|
||||
'plan_price' => $price,
|
||||
'trial' => false,
|
||||
'plan' => $plan,
|
||||
'started' => \DateTime::createFromFormat('Y-m-d', $this->plan_started),
|
||||
'expires' => $plan_expires,
|
||||
'paid' => \DateTime::createFromFormat('Y-m-d', $this->plan_paid),
|
||||
'term' => $this->plan_term,
|
||||
'active' => $plan_active,
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'account_id' => $this->id,
|
||||
'num_users' => 1,
|
||||
'plan_price' => 0,
|
||||
'trial' => true,
|
||||
'plan' => $trial_plan,
|
||||
'started' => $trial_started,
|
||||
'expires' => $trial_expires,
|
||||
'active' => $trial_active,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,17 +12,18 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class CompanyUser extends Pivot
|
||||
{
|
||||
|
||||
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
|
||||
use SoftDeletes;
|
||||
|
||||
// protected $guarded = ['id'];
|
||||
|
||||
protected $dateFormat = 'Y-m-d H:i:s.u';
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
@ -38,6 +39,7 @@ class CompanyUser extends Pivot
|
||||
protected $fillable = [
|
||||
'account_id',
|
||||
'permissions',
|
||||
'notifications',
|
||||
'settings',
|
||||
'is_admin',
|
||||
'is_owner',
|
||||
|
@ -47,7 +47,7 @@ class ClientContactTransformer extends EntityTransformer
|
||||
'contact_key' => $contact->contact_key ?: '',
|
||||
'send_email' => (bool) $contact->send_email,
|
||||
'last_login' => (int)$contact->last_login,
|
||||
'password' => '',
|
||||
'password' => isset($contact->password) ? '*****' : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
53
app/Utils/CurlUtils.php
Normal file
53
app/Utils/CurlUtils.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
class CurlUtils
|
||||
{
|
||||
public static function post($url, $data, $headers = false)
|
||||
{
|
||||
return self::exec('POST', $url, $data, $headers);
|
||||
}
|
||||
|
||||
public static function get($url, $headers = false)
|
||||
{
|
||||
return self::exec('GET', $url, null, $headers);
|
||||
}
|
||||
|
||||
public static function exec($method, $url, $data, $headers = false)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
$opts = [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => $method,
|
||||
CURLOPT_HTTPHEADER => $headers ?: [],
|
||||
];
|
||||
|
||||
if ($data) {
|
||||
$opts[CURLOPT_POSTFIELDS] = $data;
|
||||
}
|
||||
|
||||
curl_setopt_array($curl, $opts);
|
||||
$response = curl_exec($curl);
|
||||
|
||||
if ($error = curl_error($curl)) {
|
||||
\Log::error('CURL Error #' . curl_errno($curl) . ': ' . $error);
|
||||
}
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,11 @@ class Ninja
|
||||
return config('ninja.environment') === 'hosted';
|
||||
}
|
||||
|
||||
public static function isNinja()
|
||||
{
|
||||
return config('ninja.production');
|
||||
}
|
||||
|
||||
public static function getDebugInfo()
|
||||
{
|
||||
$mysql_version = DB::select(DB::raw("select version() as version"))[0]->version;
|
||||
|
@ -3,6 +3,8 @@
|
||||
return [
|
||||
|
||||
'web_url' => 'https://www.invoiceninja.com',
|
||||
'license_url' => 'https://app.invoiceninja.com',
|
||||
'production' => env('NINJA_PROD', false),
|
||||
'app_name' => env('APP_NAME'),
|
||||
'site_url' => env('APP_URL', ''),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
|
@ -198,10 +198,13 @@ class CreateUsersTable extends Migration
|
||||
$table->unsignedInteger('account_id');
|
||||
$table->unsignedInteger('user_id')->index();
|
||||
$table->mediumText('permissions')->nullable();
|
||||
$table->mediumText('notifications')->nullable();
|
||||
$table->mediumText('settings')->nullable();
|
||||
$table->boolean('is_owner')->default(false);
|
||||
$table->boolean('is_admin')->default(false);
|
||||
$table->boolean('is_locked')->default(false); // locks user out of account
|
||||
|
||||
$table->softDeletes('deleted_at', 6);
|
||||
$table->timestamps(6);
|
||||
|
||||
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
|
||||
|
@ -436,7 +436,8 @@ class ClientTest extends TestCase
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
\Log::error($arr);
|
||||
//\Log::error($arr);
|
||||
|
||||
|
||||
}
|
||||
/** @test */
|
||||
|
Loading…
x
Reference in New Issue
Block a user