diff --git a/app/Console/HostedMigrations.php b/app/Console/HostedMigrations.php
new file mode 100644
index 0000000000..0c31313137
--- /dev/null
+++ b/app/Console/HostedMigrations.php
@@ -0,0 +1,131 @@
+buildCache();
+
+ if(!MultiDB::userFindAndSetDb($this->option('email'))){
+ $this->info("Could not find a user with that email address");
+ return;
+ }
+
+ $user = User::where('email', $this->option('email'))->first();
+
+ if(!$user){
+ $this->info("There was a problem getting the user, did you set the right DB?");
+ return;
+ }
+
+ $path = public_path('storage/migrations/import');
+
+ nlog(public_path('storage/migrations/import'));
+
+ $directory = new DirectoryIterator($path);
+
+ foreach ($directory as $file) {
+ if ($file->getExtension() === 'zip') {
+
+ $company = $user->companies()->first();
+
+ $this->info('Started processing: '.$file->getBasename().' at '.now());
+
+ $zip = new ZipArchive();
+ $archive = $zip->open($file->getRealPath());
+
+ try {
+ if (! $archive) {
+ throw new ProcessingMigrationArchiveFailed('Processing migration archive failed. Migration file is possibly corrupted.');
+ }
+
+ $filename = pathinfo($file->getRealPath(), PATHINFO_FILENAME);
+
+ $zip->extractTo(public_path("storage/migrations/{$filename}"));
+ $zip->close();
+
+ $import_file = public_path("storage/migrations/$filename/migration.json");
+
+ Import::dispatch($import_file, $user->companies()->first(), $user);
+
+ unlink(public_path("storage/migrations/$filename/migration.json"));
+ unlink($file->getRealPath());
+
+ } catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
+ \Mail::to($this->user)->send(new MigrationFailed($e, $e->getMessage()));
+
+ if (app()->environment() !== 'production') {
+ info($e->getMessage());
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/app/Jobs/Util/WebhookHandler.php b/app/Jobs/Util/WebhookHandler.php
index 222ad861b8..fc656ff8eb 100644
--- a/app/Jobs/Util/WebhookHandler.php
+++ b/app/Jobs/Util/WebhookHandler.php
@@ -36,7 +36,7 @@ class WebhookHandler implements ShouldQueue
private $company;
- public $tries = 3; //number of retries
+ public $tries = 1; //number of retries
public $backoff = 10; //seconds to wait until retry
diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php
index 1e19ecc1ce..27194763fb 100644
--- a/app/Models/Presenters/CompanyPresenter.php
+++ b/app/Models/Presenters/CompanyPresenter.php
@@ -54,7 +54,7 @@ class CompanyPresenter extends EntityPresenter
$settings = $this->entity->settings;
}
- if(config('ninja.is_docker'))
+ if(config('ninja.is_docker') || config('ninja.local_download'))
return $this->logo($settings);
$context_options =array(
diff --git a/app/Transformers/ActivityTransformer.php b/app/Transformers/ActivityTransformer.php
index a284f04898..bd761bcfd1 100644
--- a/app/Transformers/ActivityTransformer.php
+++ b/app/Transformers/ActivityTransformer.php
@@ -12,6 +12,7 @@
namespace App\Transformers;
use App\Models\Activity;
+use App\Models\Backup;
use App\Utils\Traits\MakesHash;
class ActivityTransformer extends EntityTransformer
@@ -23,7 +24,9 @@ class ActivityTransformer extends EntityTransformer
/**
* @var array
*/
- protected $availableIncludes = [];
+ protected $availableIncludes = [
+ 'history'
+ ];
/**
* @param Activity $activity
@@ -55,4 +58,11 @@ class ActivityTransformer extends EntityTransformer
];
}
+
+ public function includeHistory(Activity $activity)
+ {
+ $transformer = new ActivityTransformer($this->serializer);
+
+ return $this->includeItem($activity->backup, $transformer, Backup::class);
+ }
}
diff --git a/app/Transformers/InvoiceHistoryTransformer.php b/app/Transformers/InvoiceHistoryTransformer.php
index b650c58c3a..2c83cc76b3 100644
--- a/app/Transformers/InvoiceHistoryTransformer.php
+++ b/app/Transformers/InvoiceHistoryTransformer.php
@@ -20,10 +20,12 @@ class InvoiceHistoryTransformer extends EntityTransformer
use MakesHash;
protected $defaultIncludes = [
- 'activity',
+ // 'activity',
];
- protected $availableIncludes = [];
+ protected $availableIncludes = [
+ 'activity',
+ ];
public function transform(Backup $backup)
{
diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php
index 0f6b81bf3b..0914431dc4 100644
--- a/app/Utils/HtmlEngine.php
+++ b/app/Utils/HtmlEngine.php
@@ -132,6 +132,7 @@ class HtmlEngine
$data['$view_link'] = ['value' => ''.ctrans('texts.view_invoice').'', 'label' => ctrans('texts.view_invoice')];
$data['$viewLink'] = &$data['$view_link'];
$data['$viewButton'] = &$data['$view_link'];
+ $data['$paymentButton'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')];
@@ -148,6 +149,8 @@ class HtmlEngine
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => ''.ctrans('texts.view_quote').'', 'label' => ctrans('texts.view_quote')];
$data['$viewLink'] = &$data['$view_link'];
+ $data['$viewButton'] = &$data['$view_link'];
+ $data['$approveButton'] = ['value' => ''.ctrans('texts.view_quote').'', 'label' => ctrans('texts.approve')];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
}
@@ -159,6 +162,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.credit_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => ''.ctrans('texts.view_credit').'', 'label' => ctrans('texts.view_credit')];
+ $data['$viewButton'] = &$data['$view_link'];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
diff --git a/config/ninja.php b/config/ninja.php
index 8db819f73e..c8d0356d3e 100644
--- a/config/ninja.php
+++ b/config/ninja.php
@@ -36,6 +36,7 @@ return [
'phantomjs_pdf_generation' => env('PHANTOMJS_PDF_GENERATION', true),
'trusted_proxies' => env('TRUSTED_PROXIES', false),
'is_docker' => env('IS_DOCKER', false),
+ 'local_download' => env('LOCAL_DOWNLOAD', false),
'sentry_dsn' => env('SENTRY_LARAVEL_DSN', 'https://9b4e15e575214354a7d666489783904a@sentry.invoicing.co/6'),
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'
'preconfigured_install' => env('PRECONFIGURED_INSTALL',false),
diff --git a/config/querydetector.php b/config/querydetector.php
new file mode 100644
index 0000000000..5f8207ef22
--- /dev/null
+++ b/config/querydetector.php
@@ -0,0 +1,70 @@
+ env('QUERY_DETECTOR_ENABLED', false),
+
+ /*
+ * Threshold level for the N+1 query detection. If a relation query will be
+ * executed more then this amount, the detector will notify you about it.
+ */
+ 'threshold' => (int) env('QUERY_DETECTOR_THRESHOLD', 1),
+
+ /*
+ * Here you can whitelist model relations.
+ *
+ * Right now, you need to define the model relation both as the class name and the attribute name on the model.
+ * So if an "Author" model would have a "posts" relation that points to a "Post" class, you need to add both
+ * the "posts" attribute and the "Post::class", since the relation can get resolved in multiple ways.
+ */
+ 'except' => [
+ //Author::class => [
+ // Post::class,
+ // 'posts',
+ //]
+ ],
+
+ /*
+ * Here you can set a specific log channel to write to
+ * in case you are trying to isolate queries or have a lot
+ * going on in the laravel.log. Defaults to laravel.log though.
+ */
+ 'log_channel' => env('QUERY_DETECTOR_LOG_CHANNEL', 'daily'),
+
+ /*
+ * Define the output format that you want to use. Multiple classes are supported.
+ * Available options are:
+ *
+ * Alert:
+ * Displays an alert on the website
+ * \BeyondCode\QueryDetector\Outputs\Alert::class
+ *
+ * Console:
+ * Writes the N+1 queries into your browsers console log
+ * \BeyondCode\QueryDetector\Outputs\Console::class
+ *
+ * Clockwork: (make sure you have the itsgoingd/clockwork package installed)
+ * Writes the N+1 queries warnings to Clockwork log
+ * \BeyondCode\QueryDetector\Outputs\Clockwork::class
+ *
+ * Debugbar: (make sure you have the barryvdh/laravel-debugbar package installed)
+ * Writes the N+1 queries into a custom messages collector of Debugbar
+ * \BeyondCode\QueryDetector\Outputs\Debugbar::class
+ *
+ * JSON:
+ * Writes the N+1 queries into the response body of your JSON responses
+ * \BeyondCode\QueryDetector\Outputs\Json::class
+ *
+ * Log:
+ * Writes the N+1 queries into the Laravel.log file
+ * \BeyondCode\QueryDetector\Outputs\Log::class
+ */
+ 'output' => [
+ //\BeyondCode\QueryDetector\Outputs\Alert::class,
+ \BeyondCode\QueryDetector\Outputs\Log::class,
+ //\BeyondCode\QueryDetector\Outputs\Json::class,
+ ]
+];