From 742e0d5c9b573c181a963a29ab48b38eaefc6c66 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 16 Jun 2022 13:07:53 +1000 Subject: [PATCH 1/6] Clean up for self updater --- app/Http/Controllers/SelfUpdateController.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index 051b4066d1a4..6eda9a83e86d 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -203,15 +203,15 @@ class SelfUpdateController extends BaseController foreach ($iterator as $file) { - if($file->isDir() && !$file->isDot() && ($current_revision_text != $file->getFileName())) - { - - $directoryIterator = new \RecursiveDirectoryIterator(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()), \RecursiveDirectoryIterator::SKIP_DOTS); - - foreach (new \RecursiveIteratorIterator($directoryIterator) as $filex) + if($file->isDir() && !$file->isDot() && ($current_revision_text != $file->getFileName())) { - unlink($filex->getPathName()); - } + + $directoryIterator = new \RecursiveDirectoryIterator(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName()), \RecursiveDirectoryIterator::SKIP_DOTS); + + foreach (new \RecursiveIteratorIterator($directoryIterator) as $filex) + { + unlink($filex->getPathName()); + } } From 2fb1a130c3b122c13ffc2fd334749e9e9fae1005 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 16 Jun 2022 13:36:38 +1000 Subject: [PATCH 2/6] Update for self updater --- app/Http/Controllers/SelfUpdateController.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index 6eda9a83e86d..eed24a5f7c60 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -213,12 +213,26 @@ class SelfUpdateController extends BaseController unlink($filex->getPathName()); } + $this->deleteDirectory(base_path('vendor/beganovich/snappdf/versions/'.$file->getFileName())); } } } + private function deleteDirectory($dir) { + if (!file_exists($dir)) return true; + + if (!is_dir($dir) || is_link($dir)) return unlink($dir); + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') continue; + if (!$this->deleteDirectory($dir . "/" . $item)) { + if (!$this->deleteDirectory($dir . "/" . $item)) return false; + }; + } + return rmdir($dir); + } + private function postHookUpdate() { if(config('ninja.app_version') == '5.3.82') From c8ef8abce89647216d23ad0d772b580e00dae1a9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 16 Jun 2022 13:51:11 +1000 Subject: [PATCH 3/6] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2568217402f3..807279c6ceeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /node_modules /public/hot /public/storage +/public/react /storage/*.key /vendor /.idea From 9032bc6fa70c00babd09a4b8a4c109560c297940 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 16 Jun 2022 14:42:24 +1000 Subject: [PATCH 4/6] Notifications for Purchase orders / Viewed / Accepted --- .../VendorPortal/InvitationController.php | 11 +- .../PurchaseOrderAcceptedNotification.php | 79 ++++++++++++++ .../Admin/PurchaseOrderAcceptedObject.php | 103 ++++++++++++++++++ app/Providers/EventServiceProvider.php | 2 + resources/lang/en/texts.php | 2 + 5 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php create mode 100644 app/Mail/Admin/PurchaseOrderAcceptedObject.php diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index ba6abc9c25b6..0d3d730f4b97 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -75,14 +75,13 @@ class InvitationController extends Controller auth()->guard('vendor')->loginUsingId($vendor_contact->id, true); } - if (auth()->guard('vendor')->user() && ! request()->has('silent') && ! $invitation->viewed_date) { + session()->put('is_silent', request()->has('silent')); - if(!session()->get('is_silent')){ - - $invitation->markViewed(); - event(new InvitationWasViewed($invitation->purchase_order, $invitation, $invitation->company, Ninja::eventVars())); - } + if (auth()->guard('vendor')->user() && ! session()->get('is_silent') && ! $invitation->viewed_date) { + $invitation->markViewed(); + event(new InvitationWasViewed($invitation->purchase_order, $invitation, $invitation->company, Ninja::eventVars())); + } else{ diff --git a/app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php b/app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php new file mode 100644 index 000000000000..b7d499c471f9 --- /dev/null +++ b/app/Listeners/PurchaseOrder/PurchaseOrderAcceptedNotification.php @@ -0,0 +1,79 @@ +company->db); + + $first_notification_sent = true; + + $purchase_order = $event->purchase_order; + + $nmo = new NinjaMailerObject; + $nmo->mailable = new NinjaMailer( (new PurchaseOrderAcceptedObject($purchase_order, $event->company))->build() ); + $nmo->company = $event->company; + $nmo->settings = $event->company->settings; + + /* We loop through each user and determine whether they need to be notified */ + foreach ($event->company->company_users as $company_user) { + + /* The User */ + $user = $company_user->user; + + if(!$user) + continue; + + /* Returns an array of notification methods */ + $methods = $this->findUserNotificationTypes($purchase_order->invitations()->first(), $company_user, 'purchase_order', ['all_notifications', 'purchase_order_accepted', 'purchase_order_accepted_all']); + + /* If one of the methods is email then we fire the EntitySentMailer */ + if (($key = array_search('mail', $methods)) !== false) { + unset($methods[$key]); + + $nmo->to_user = $user; + + NinjaMailerJob::dispatch($nmo); + + /* This prevents more than one notification being sent */ + $first_notification_sent = false; + } + + } + } +} diff --git a/app/Mail/Admin/PurchaseOrderAcceptedObject.php b/app/Mail/Admin/PurchaseOrderAcceptedObject.php new file mode 100644 index 000000000000..431a1da549a9 --- /dev/null +++ b/app/Mail/Admin/PurchaseOrderAcceptedObject.php @@ -0,0 +1,103 @@ +purchase_order = $purchase_order; + $this->company = $company; + } + + public function build() + { + MultiDB::setDb($this->company->db); + + if(!$this->purchase_order) + return; + + App::forgetInstance('translator'); + /* Init a new copy of the translator*/ + $t = app('translator'); + /* Set the locale*/ + App::setLocale($this->company->getLocale()); + /* Set customized translations _NOW_ */ + $t->replace(Ninja::transformTranslations($this->company->settings)); + + $mail_obj = new stdClass; + $mail_obj->amount = $this->getAmount(); + $mail_obj->subject = $this->getSubject(); + $mail_obj->data = $this->getData(); + $mail_obj->markdown = 'email.admin.generic'; + $mail_obj->tag = $this->company->company_key; + + return $mail_obj; + } + + private function getAmount() + { + return Number::formatMoney($this->purchase_order->amount, $this->company); + } + + private function getSubject() + { + return + ctrans( + "texts.notification_purchase_order_accepted_subject", + [ + 'vendor' => $this->purchase_order->vendor->present()->name(), + 'purchase_order' => $this->purchase_order->number, + ] + ); + } + + private function getData() + { + $settings = $this->company->settings; + + $data = [ + 'title' => $this->getSubject(), + 'message' => ctrans( + "texts.notification_purchase_order_accepted", + [ + 'amount' => $this->getAmount(), + 'vendor' => $this->purchase_order->vendor->present()->name(), + 'purchase_order' => $this->purchase_order->number, + ] + ), + 'url' => $this->purchase_order->invitations->first()->getAdminLink(), + 'button' => ctrans("texts.view_purchase_order"), + 'signature' => $settings->email_signature, + 'logo' => $this->company->present()->logo(), + 'settings' => $settings, + 'whitelabel' => $this->company->account->isPaid() ? true : false, + ]; + + return $data; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index e619deb06319..e84aa13d98fe 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -181,6 +181,7 @@ use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity; use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity; +use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedNotification; use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity; use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity; @@ -475,6 +476,7 @@ class EventServiceProvider extends ServiceProvider ], PurchaseOrderWasAccepted::class => [ PurchaseOrderAcceptedActivity::class, + PurchaseOrderAcceptedNotification::class ], CompanyDocumentsDeleted::class => [ DeleteCompanyDocuments::class, diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 9c044f987481..bf7353d75fb9 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4631,6 +4631,8 @@ $LANG = array( 'accepted' => 'Accepted', 'activity_137' => ':contact accepted purchase order :purchase_order', 'vendor_information' => 'Vendor Information', + 'notification_purchase_order_accepted_subject' => 'Purchase Order :purchase_order was accepted by :vendor', + 'notification_purchase_order_accepted' => 'The following vendor :vendor accepted Purchase Order :purchase_order for :amount.', ); return $LANG; From 77d0dd8ae40484002e8cb3d94a079d51a374fa39 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 16 Jun 2022 15:58:11 +1000 Subject: [PATCH 5/6] Allow setting react_ap flag on accounts table --- app/Http/Controllers/AccountController.php | 15 ++++++ .../Requests/Account/UpdateAccountRequest.php | 53 +++++++++++++++++++ app/Models/Account.php | 11 ++++ routes/api.php | 1 + 4 files changed, 80 insertions(+) create mode 100644 app/Http/Requests/Account/UpdateAccountRequest.php diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index d9843fc34963..161356302fd7 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -12,9 +12,11 @@ namespace App\Http\Controllers; use App\Http\Requests\Account\CreateAccountRequest; +use App\Http\Requests\Account\UpdateAccountRequest; use App\Jobs\Account\CreateAccount; use App\Models\Account; use App\Models\CompanyUser; +use App\Transformers\AccountTransformer; use App\Transformers\CompanyUserTransformer; use App\Utils\TruthSource; use Illuminate\Foundation\Bus\DispatchesJobs; @@ -157,4 +159,17 @@ class AccountController extends BaseController return $this->listResponse($ct); } + + public function update(UpdateAccountRequest $request, Account $account) + { + + $account->fill($request->all()); + $account->save(); + + $this->entity_type = Account::class; + + $this->entity_transformer = AccountTransformer::class; + + return $this->itemResponse($account); + } } diff --git a/app/Http/Requests/Account/UpdateAccountRequest.php b/app/Http/Requests/Account/UpdateAccountRequest.php new file mode 100644 index 000000000000..9267bcefae2a --- /dev/null +++ b/app/Http/Requests/Account/UpdateAccountRequest.php @@ -0,0 +1,53 @@ +user()->isAdmin() || auth()->user()->isOwner(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'set_react_as_default_ap' => 'required|bail|bool' + ]; + } + + protected function prepareForValidation() + { + $input = $this->all(); + + $cleaned_input = array_intersect_key( $input, array_flip(['set_react_as_default_ap'])); + + $this->replace($cleaned_input); + + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 1692ac7d6ea4..2aef2603d7c1 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -11,6 +11,7 @@ namespace App\Models; +use App\Exceptions\ModelNotFoundException; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; use App\Mail\Ninja\EmailQuotaExceeded; @@ -472,4 +473,14 @@ class Account extends BaseModel } + public function resolveRouteBinding($value, $field = null) + { + if (is_numeric($value)) { + throw new ModelNotFoundException("Record with value {$value} not found"); + } + + return $this + ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + } + } diff --git a/routes/api.php b/routes/api.php index 74d277181c70..e37eabb84bfa 100644 --- a/routes/api.php +++ b/routes/api.php @@ -24,6 +24,7 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], }); Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { + Route::put('accounts/{account}', 'AccountController@update')->name('account.update'); Route::post('check_subdomain', 'SubdomainController@index')->name('check_subdomain'); Route::get('ping', 'PingController@index')->name('ping'); Route::get('health_check', 'PingController@health')->name('health_check'); From ce1aea51466605e3bc8f001cdc9aad5059af24a8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 16 Jun 2022 15:59:36 +1000 Subject: [PATCH 6/6] Docs --- app/Http/Requests/Account/UpdateAccountRequest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/Account/UpdateAccountRequest.php b/app/Http/Requests/Account/UpdateAccountRequest.php index 9267bcefae2a..a3197f51ee63 100644 --- a/app/Http/Requests/Account/UpdateAccountRequest.php +++ b/app/Http/Requests/Account/UpdateAccountRequest.php @@ -26,7 +26,7 @@ class UpdateAccountRequest extends Request */ public function authorize() { - return auth()->user()->isAdmin() || auth()->user()->isOwner(); + return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && (int)$this->account->id === auth()->user()->account_id; } /** @@ -41,6 +41,7 @@ class UpdateAccountRequest extends Request ]; } + /* Only allow single field to update account table */ protected function prepareForValidation() { $input = $this->all();