From b35bff3cde1443c20d2c21cd505a41d5f73328a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Wed, 16 Oct 2019 22:12:38 +0200 Subject: [PATCH] Forward support messages to contact@invoiceninja.com with system-level info (#2993) * Sending support messages via API * Sending log files only if self-hosted * Remove legacy code --- .../Support/Messages/SendingController.php | 26 ++++++++ app/Mail/SupportMessageSent.php | 59 +++++++++++++++++++ app/Utils/Ninja.php | 31 +++++++--- config/ninja.php | 1 + .../views/email/support/message.blade.php | 39 ++++++++++++ routes/api.php | 27 +++++---- tests/Browser/ClientPortalTest.php | 3 +- 7 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 app/Http/Controllers/Support/Messages/SendingController.php create mode 100644 app/Mail/SupportMessageSent.php create mode 100644 resources/views/email/support/message.blade.php diff --git a/app/Http/Controllers/Support/Messages/SendingController.php b/app/Http/Controllers/Support/Messages/SendingController.php new file mode 100644 index 0000000000..a321470df5 --- /dev/null +++ b/app/Http/Controllers/Support/Messages/SendingController.php @@ -0,0 +1,26 @@ +validate([ + 'message' => ['required'], + ]); + + Mail::to(config('ninja.contact.ninja_official_contact')) + ->send(new SupportMessageSent($request->message)); + + return response()->json([ + 'success' => true + ]); + } + +} diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php new file mode 100644 index 0000000000..b4b4b89d3d --- /dev/null +++ b/app/Mail/SupportMessageSent.php @@ -0,0 +1,59 @@ +message = $message; + + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $system_info = null; + $log_lines = []; + + /** + * With self-hosted version of Ninja, + * we are going to bundle system-level info + * and last 10 lines of laravel.log file. + */ + if(Ninja::isSelfHost()) { + $system_info = Ninja::getDebugInfo(); + + $log_file = new \SplFileObject(sprintf('%s/laravel.log', base_path('storage/logs'))); + + $log_file->seek(PHP_INT_MAX); + $last_line = $log_file->key(); + $lines = new \LimitIterator($log_file, $last_line - 10, $last_line); + + $log_lines = iterator_to_array($lines); + } + + return $this->from(config('mail.from.address')) + ->subject(ctrans('texts.new_support_message')) + ->markdown('email.support.message', [ + 'message' => $this->message, + 'system_info' => $system_info, + 'laravel_log' => $log_lines + ]); + } +} + diff --git a/app/Utils/Ninja.php b/app/Utils/Ninja.php index 57ddc3e382..83c2bac797 100644 --- a/app/Utils/Ninja.php +++ b/app/Utils/Ninja.php @@ -11,18 +11,33 @@ namespace App\Utils; +use Illuminate\Support\Facades\DB; + /** * Class Ninja. */ class Ninja { - public static function isSelfHost() - { - return config('ninja.environment') === 'selfhost'; - } + public static function isSelfHost() + { + return config('ninja.environment') === 'selfhost'; + } - public static function isHosted() - { - return config('ninja.environment') === 'hosted'; - } + public static function isHosted() + { + return config('ninja.environment') === 'hosted'; + } + + public static function getDebugInfo() + { + $mysql_version = DB::select(DB::raw("select version() as version"))[0]->version; + + $info = "App Version: v" . config('ninja.app_version') . "\\n" . + "White Label: " . "\\n" . // TODO: Implement white label with hasFeature. + "Server OS: " . php_uname('s') . ' ' . php_uname('r') . "\\n" . + "PHP Version: " . phpversion() . "\\n" . + "MySQL Version: " . $mysql_version; + + return $info; + } } diff --git a/config/ninja.php b/config/ninja.php index 811bbf174c..b4c51725b9 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -64,6 +64,7 @@ return [ 'contact' => [ 'email' => env('MAIL_FROM_ADDRESS'), 'from_name' => env('MAIL_FROM_NAME'), + 'ninja_official_contact' => env('NINJA_OFFICIAL_CONTACT', 'contact@invoiceninja.com'), ], 'cached_tables' => [ 'banks' => 'App\Models\Bank', diff --git a/resources/views/email/support/message.blade.php b/resources/views/email/support/message.blade.php new file mode 100644 index 0000000000..e4bf02b2a6 --- /dev/null +++ b/resources/views/email/support/message.blade.php @@ -0,0 +1,39 @@ +@component('mail::layout') + +{{-- Header --}} +@slot('header') +@component('mail::header', ['url' => config('app.url')]) +Header Title +@endcomponent +@endslot + +{{-- Body --}} +{{ $message }} + +{!! str_replace('\n', '
', $system_info) !!} + +
+ {{ ctrans('texts.display_log') }} + @foreach($laravel_log as $log_line) + {{ $log_line }}
+ @endforeach +
+ +{{-- Subcopy --}} +@isset($subcopy) +@slot('subcopy') +@component('mail::subcopy') +{{ $subcopy }} +@endcomponent +@endslot +@endisset + + +{{-- Footer --}} +@slot('footer') +@component('mail::footer') +© {{ date('Y') }} {{ config('ninja.app_name') }}. +@endcomponent +@endslot + +@endcomponent diff --git a/routes/api.php b/routes/api.php index 32af9eb495..cc642c25fe 100644 --- a/routes/api.php +++ b/routes/api.php @@ -22,20 +22,20 @@ Route::group(['middleware' => ['api_secret_check']], function () { Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit'); Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin'); - + }); Route::group(['api_secret_check','domain_db'], function () { Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit'); Route::post('api/v1/reset_password', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.reset'); - + }); Route::group(['middleware' => ['api_db','api_secret_check','token_auth'], 'prefix' =>'api/v1', 'as' => 'api.'], function () { Route::resource('activities', 'ActivityController'); // name = (clients. index / create / show / update / destroy / edit - + Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk'); @@ -65,7 +65,7 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth'], 'prefi Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit - + Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk'); Route::resource('users', 'UserController'); // name = (users. index / create / show / update / destroy / edit @@ -81,22 +81,23 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth'], 'prefi Route::post('refresh', 'Auth\LoginController@refresh'); /* Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit - + Route::post('tasks/bulk', 'TaskController@bulk')->name('tasks.bulk'); - - + + Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit - + Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk'); - + Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit - + Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk'); - - + + Route::get('settings', 'SettingsController@index')->name('user.settings'); */ + Route::post('support/messages/send', 'Support\Messages\SendingController'); }); -Route::fallback('BaseController@notFound'); \ No newline at end of file +Route::fallback('BaseController@notFound'); diff --git a/tests/Browser/ClientPortalTest.php b/tests/Browser/ClientPortalTest.php index 1dfc5989a1..ecf1071316 100644 --- a/tests/Browser/ClientPortalTest.php +++ b/tests/Browser/ClientPortalTest.php @@ -180,6 +180,7 @@ class ClientPortalTest extends DuskTestCase $this->browse(function ($browser) { $browser->visit('/client/login') + ->assertPathIs('/client/login') ->type('email', 'user@example.com') ->type('password', config('ninja.testvars.password')) ->press('Login') @@ -319,4 +320,4 @@ class ClientPortalTest extends DuskTestCase }); } -} \ No newline at end of file +}