diff --git a/app/Http/Controllers/Common/Search.php b/app/Http/Controllers/Common/Search.php deleted file mode 100644 index 006257016..000000000 --- a/app/Http/Controllers/Common/Search.php +++ /dev/null @@ -1,160 +0,0 @@ -results = []; - $search->keyword = request('keyword'); - - if ($user->can('read-banking-accounts')) { - $accounts = Account::enabled()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($accounts->count()) { - foreach ($accounts as $account) { - $search->results[] = (object) [ - 'id' => $account->id, - 'name' => $account->name, - 'type' => trans_choice('general.accounts', 1), - 'color' => '#55588b', - 'href' => route('accounts.edit', $account->id), - ]; - } - } - } - - if ($user->can('read-common-items')) { - $items = Item::enabled()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($items->count()) { - foreach ($items as $item) { - $search->results[] = (object) [ - 'id' => $item->id, - 'name' => $item->name, - 'type' => trans_choice('general.items', 1), - 'color' => '#efad32', - 'href' => route('items.edit', $item->id), - ]; - } - } - } - - if ($user->can('read-sales-invoices')) { - $invoices = Document::invoice()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($invoices->count()) { - foreach ($invoices as $invoice) { - $search->results[] = (object) [ - 'id' => $invoice->id, - 'name' => $invoice->document_number . ' - ' . $invoice->contact_name, - 'type' => trans_choice('general.invoices', 1), - 'color' => '#6da252', - 'href' => route('invoices.show', $invoice->id), - ]; - } - } - } - - /*if ($user->can('read-sales-revenues')) { - $revenues = Transaction::income()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($revenues->count()) { - foreach ($revenues as $revenue) { - $results[] = (object)[ - 'id' => $revenue->id, - 'name' => $revenue->contact_name, - 'type' => trans_choice('general.revenues', 1), - 'color' => '#00c0ef', - 'href' => route('revenues.edit', $revenue->id), - ]; - } - } - }*/ - - if ($user->can('read-sales-customers')) { - $customers = Contact::customer()->enabled()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($customers->count()) { - foreach ($customers as $customer) { - $search->results[] = (object) [ - 'id' => $customer->id, - 'name' => $customer->name, - 'type' => trans_choice('general.customers', 1), - 'color' => '#328aef', - 'href' => route('customers.show', $customer->id), - ]; - } - } - } - - if ($user->can('read-purchases-bills')) { - $bills = Document::bill()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($bills->count()) { - foreach ($bills as $bill) { - $search->results[] = (object) [ - 'id' => $bill->id, - 'name' => $bill->document_number . ' - ' . $bill->contact_name, - 'type' => trans_choice('general.bills', 1), - 'color' => '#ef3232', - 'href' => route('bills.show', $bill->id), - ]; - } - } - } - - /*if ($user->can('read-purchases-payments')) { - $payments = Transaction::expense()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($payments->count()) { - foreach ($payments as $payment) { - $results[] = (object)[ - 'id' => $payment->id, - 'name' => $payment->contact_name, - 'type' => trans_choice('general.payments', 1), - 'color' => '#00c0ef', - 'href' => route('payments.edit', $payment->id), - ]; - } - } - }*/ - - if ($user->can('read-purchases-vendors')) { - $vendors = Contact::vendor()->enabled()->usingSearchString($search->keyword)->take(setting('default.select_limit'))->get(); - - if ($vendors->count()) { - foreach ($vendors as $vendor) { - $search->results[] = (object) [ - 'id' => $vendor->id, - 'name' => $vendor->name, - 'type' => trans_choice('general.vendors', 1), - 'color' => '#efef32', - 'href' => route('vendors.show', $vendor->id), - ]; - } - } - } - - event(new GlobalSearched($search)); - - return response()->json((object) $search->results); - } -} diff --git a/app/Http/Livewire/Common/Search.php b/app/Http/Livewire/Common/Search.php new file mode 100644 index 000000000..646f3ec8d --- /dev/null +++ b/app/Http/Livewire/Common/Search.php @@ -0,0 +1,273 @@ +user = user(); + + $this->search(); + + return view('livewire.common.search'); + } + + /** + * Searching operation. + * If the keyword is empty searching will be cancelled. + * + * @return void + */ + public function search() + { + $this->results = []; + + if (empty($this->keyword)) { + return; + } + + $this->searchOnAccounts(); + $this->searchOnItems(); + $this->searchOnInvoices(); + $this->searchOnCustomers(); + $this->searchOnBills(); + $this->searchOnVendors(); + + $this->dispatchGlobalSearched(); + } + + /** + * Resets keyword. + * + * @return void + */ + public function resetKeyword() + { + $this->keyword = ''; + } + + /** + * Fires GlobalSearched event with the keyword and results. + * + * @return void + */ + public function dispatchGlobalSearched() + { + $search = new \stdClass(); + $search->results = $this->results; + $search->keyword = $this->keyword; + + event(new GlobalSearched($search)); + + $this->results = $search->results; + } + + /** + * Searching on Banking Accounts with given keyword. + * + * @return void + */ + public function searchOnAccounts() + { + if (!$this->user->can('read-banking-accounts')) { + return; + } + + $accounts = Account::enabled() + ->usingSearchString($this->keyword) + ->take(setting('default.select_limit')) + ->get(); + + if ($accounts->isEmpty()) { + return; + } + + foreach ($accounts as $account) { + $this->results[] = (object) [ + 'id' => $account->id, + 'name' => $account->name, + 'type' => trans_choice('general.accounts', 1), + 'color' => '#55588b', + 'href' => route('accounts.edit', $account->id), + ]; + } + } + + /** + * Searching on Items with given keyword. + * + * @return void + */ + public function searchOnItems() + { + if (!$this->user->can('read-common-items')) { + return; + } + + $items = Item::enabled() + ->usingSearchString($this->keyword) + ->take(setting('default.select_limit')) + ->get(); + + if ($items->isEmpty()) { + return; + } + + foreach ($items as $item) { + $this->results[] = (object) [ + 'id' => $item->id, + 'name' => $item->name, + 'type' => trans_choice('general.items', 1), + 'color' => '#efad32', + 'href' => route('items.edit', $item->id), + ]; + } + } + + /** + * Searching on Invoices with given keyword. + * + * @return void + */ + public function searchOnInvoices() + { + if (!$this->user->can('read-sales-invoices')) { + return; + } + + $invoices = Document::invoice() + ->usingSearchString($this->keyword) + ->take(setting('default.select_limit')) + ->get(); + + if ($invoices->isEmpty()) { + return; + } + + foreach ($invoices as $invoice) { + $this->results[] = (object) [ + 'id' => $invoice->id, + 'name' => $invoice->document_number . ' - ' . $invoice->contact_name, + 'type' => trans_choice('general.invoices', 1), + 'color' => '#6da252', + 'href' => route('invoices.show', $invoice->id), + ]; + } + } + + /** + * Searching on Customers with given keyword. + * + * @return void + */ + public function searchOnCustomers() + { + if (!$this->user->can('read-sales-customers')) { + return; + } + + $customers = Contact::customer() + ->enabled() + ->usingSearchString($this->keyword) + ->take(setting('default.select_limit')) + ->get(); + + if ($customers->isEmpty()) { + return; + } + + foreach ($customers as $customer) { + $this->results[] = (object) [ + 'id' => $customer->id, + 'name' => $customer->name, + 'type' => trans_choice('general.customers', 1), + 'color' => '#328aef', + 'href' => route('customers.show', $customer->id), + ]; + } + } + + /** + * Searching on Bills with given keyword. + * + * @return void + */ + public function searchOnBills() + { + if (!$this->user->can('read-purchases-bills')) { + return; + } + + $bills = Document::bill() + ->usingSearchString($this->keyword) + ->take(setting('default.select_limit')) + ->get(); + + if ($bills->isEmpty()) { + return; + } + + foreach ($bills as $bill) { + $this->results[] = (object) [ + 'id' => $bill->id, + 'name' => $bill->document_number . ' - ' . $bill->contact_name, + 'type' => trans_choice('general.bills', 1), + 'color' => '#ef3232', + 'href' => route('bills.show', $bill->id), + ]; + } + } + + /** + * Searching on Vendors with given keyword. + * + * @return void + */ + public function searchOnVendors() + { + if (!$this->user->can('read-purchases-vendors')) { + return; + } + + $vendors = Contact::vendor() + ->enabled() + ->usingSearchString($this->keyword) + ->take(setting('default.select_limit')) + ->get(); + + if ($vendors->isEmpty()) { + return; + } + + foreach ($vendors as $vendor) { + $this->results[] = (object) [ + 'id' => $vendor->id, + 'name' => $vendor->name, + 'type' => trans_choice('general.vendors', 1), + 'color' => '#efef32', + 'href' => route('vendors.show', $vendor->id), + ]; + } + } +} diff --git a/composer.json b/composer.json index 5485b8b37..23ce74ad3 100644 --- a/composer.json +++ b/composer.json @@ -89,6 +89,7 @@ "Database\\Seeds\\": "database/seeds/", "Illuminate\\Translation\\": "overrides/Illuminate/Translation/", "Illuminate\\View\\Concerns\\": "overrides/Illuminate/View/Concerns/", + "Livewire\\": "overrides/livewire/", "Maatwebsite\\Excel\\": "overrides/maatwebsite/excel/", "Modules\\": "modules/", "Symfony\\Component\\Process\\": "overrides/symfony/process/" @@ -100,6 +101,7 @@ "vendor/akaunting/laravel-module/src/Commands/InstallCommand.php", "vendor/laravel/framework/src/Illuminate/Translation/MessageSelector.php", "vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesStacks.php", + "vendor/livewire/livewire/src/LivewireServiceProvider.php", "vendor/maatwebsite/excel/src/Fakes/ExcelFake.php", "vendor/maatwebsite/excel/src/QueuedWriter.php", "vendor/symfony/process/PhpExecutableFinder.php" diff --git a/overrides/livewire/LivewireServiceProvider.php b/overrides/livewire/LivewireServiceProvider.php new file mode 100644 index 000000000..4fd2f9321 --- /dev/null +++ b/overrides/livewire/LivewireServiceProvider.php @@ -0,0 +1,352 @@ +registerConfig(); + $this->registerTestMacros(); + $this->registerLivewireSingleton(); + $this->registerComponentAutoDiscovery(); + } + + public function boot() + { + $this->registerViews(); + $this->registerRoutes(); + $this->registerCommands(); + $this->registerRenameMes(); + $this->registerViewMacros(); + $this->registerTagCompiler(); + $this->registerPublishables(); + $this->registerBladeDirectives(); + $this->registerViewCompilerEngine(); + $this->registerHydrationMiddleware(); + + // Bypass specific middlewares during Livewire requests. + // These are usually helpful during a typical request, but + // during Livewire requests, they can damage data properties. + $this->bypassTheseMiddlewaresDuringLivewireRequests([ + TrimStrings::class, + ConvertEmptyStringsToNull::class, + // If the app overrode "TrimStrings". + \App\Http\Middleware\TrimStrings::class, + ]); + } + + protected function registerLivewireSingleton() + { + $this->app->singleton('livewire', LivewireManager::class); + } + + protected function registerComponentAutoDiscovery() + { + // Rather than forcing users to register each individual component, + // we will auto-detect the component's class based on its kebab-cased + // alias. For instance: 'examples.foo' => App\Http\Livewire\Examples\Foo + + // We will generate a manifest file so we don't have to do the lookup every time. + $defaultManifestPath = $this->app['livewire']->isOnVapor() + ? '/tmp/storage/bootstrap/cache/livewire-components.php' + : app()->bootstrapPath('cache/livewire-components.php'); + + $this->app->singleton(LivewireComponentsFinder::class, function () use ($defaultManifestPath) { + return new LivewireComponentsFinder( + new Filesystem, + config('livewire.manifest_path') ?: $defaultManifestPath, + ComponentParser::generatePathFromNamespace( + config('livewire.class_namespace') + ) + ); + }); + } + + protected function registerConfig() + { + $this->mergeConfigFrom(__DIR__.'/../../vendor/livewire/livewire/config/livewire.php', 'livewire'); + } + + protected function registerViews() + { + // This is mainly for overriding Laravel's pagination views + // when a user applies the WithPagination trait to a component. + $this->loadViewsFrom( + __DIR__.'/../../vendor/livewire/livewire/src'.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'pagination', + 'livewire' + ); + } + + protected function registerRoutes() + { + RouteFacade::post('/{company_id}/livewire/message/{name}', HttpConnectionHandler::class) + ->name('livewire.message') + ->middleware(config('livewire.middleware_group', '')); + + RouteFacade::post('/{company_id}/livewire/upload-file', [FileUploadHandler::class, 'handle']) + ->name('livewire.upload-file') + ->middleware(config('livewire.middleware_group', '')); + + RouteFacade::get('/{company_id}/livewire/preview-file/{filename}', [FilePreviewHandler::class, 'handle']) + ->name('livewire.preview-file') + ->middleware(config('livewire.middleware_group', '')); + + RouteFacade::get('/livewire/livewire.js', [LivewireJavaScriptAssets::class, 'source']); + RouteFacade::get('/livewire/livewire.js.map', [LivewireJavaScriptAssets::class, 'maps']); + } + + protected function registerCommands() + { + if (! $this->app->runningInConsole()) return; + + $this->commands([ + MakeLivewireCommand::class, // make:livewire + MakeCommand::class, // livewire:make + TouchCommand::class, // livewire:touch + CopyCommand::class, // livewire:copy + CpCommand::class, // livewire:cp + DeleteCommand::class, // livewire:delete + RmCommand::class, // livewire:rm + MoveCommand::class, // livewire:move + MvCommand::class, // livewire:mv + StubsCommand::class, // livewire:stubs + DiscoverCommand::class, // livewire:discover + S3CleanupCommand::class, // livewire:configure-s3-upload-cleanup + PublishCommand::class, // livewire:publish + ]); + } + + protected function registerTestMacros() + { + // Usage: $this->assertSeeLivewire('counter'); + TestResponse::macro('assertSeeLivewire', function ($component) { + $escapedComponentName = trim(htmlspecialchars(json_encode(['name' => $component])), '{}'); + + \PHPUnit\Framework\Assert::assertStringContainsString( + $escapedComponentName, + $this->getContent(), + 'Cannot find Livewire component ['.$component.'] rendered on page.' + ); + + return $this; + }); + + // Usage: $this->assertDontSeeLivewire('counter'); + TestResponse::macro('assertDontSeeLivewire', function ($component) { + $escapedComponentName = trim(htmlspecialchars(json_encode(['name' => $component])), '{}'); + + \PHPUnit\Framework\Assert::assertStringNotContainsString( + $escapedComponentName, + $this->getContent(), + 'Found Livewire component ['.$component.'] rendered on page.' + ); + + return $this; + }); + } + + protected function registerViewMacros() + { + // Early versions of Laravel 7.x don't have this method. + if (method_exists(ComponentAttributeBag::class, 'macro')) { + ComponentAttributeBag::macro('wire', function ($name) { + $entries = head($this->whereStartsWith('wire:'.$name)); + + $directive = head(array_keys($entries)); + $value = head(array_values($entries)); + + return new WireDirective($name, $directive, $value); + }); + } + + View::mixin(new ViewMacros); + } + + protected function registerTagCompiler() + { + if (method_exists($this->app['blade.compiler'], 'precompiler')) { + $this->app['blade.compiler']->precompiler(function ($string) { + return app(LivewireTagCompiler::class)->compile($string); + }); + } + } + + protected function registerPublishables() + { + $this->publishesToGroups([ + __DIR__.'/../../vendor/livewire/livewire/dist' => public_path('vendor/livewire'), + ], ['livewire', 'livewire:assets']); + + $this->publishesToGroups([ + __DIR__.'/../../vendor/livewire/livewire/config/livewire.php' => base_path('config/livewire.php'), + ], ['livewire', 'livewire:config']); + + $this->publishesToGroups([ + __DIR__.'/../../vendor/livewire/livewire/src/views/pagination' => $this->app->resourcePath('views/vendor/livewire'), + ], ['livewire', 'livewire:pagination']); + } + + protected function registerBladeDirectives() + { + Blade::directive('this', [LivewireBladeDirectives::class, 'this']); + Blade::directive('entangle', [LivewireBladeDirectives::class, 'entangle']); + Blade::directive('livewire', [LivewireBladeDirectives::class, 'livewire']); + Blade::directive('livewireStyles', [LivewireBladeDirectives::class, 'livewireStyles']); + Blade::directive('livewireScripts', [LivewireBladeDirectives::class, 'livewireScripts']); + } + + protected function registerViewCompilerEngine() + { + // This is a custom view engine that gets used when rendering + // Livewire views. Things like letting certain exceptions bubble + // to the handler, and registering custom directives like: "@this". + $this->app->make('view.engine.resolver')->register('blade', function () { + + // If the application is using Ignition, make sure Livewire's view compiler + // uses a version that extends Ignition's so it can continue to report errors + // correctly. Don't change this class without first submitting a PR to Ignition. + if (class_exists(\Facade\Ignition\IgnitionServiceProvider::class)) { + return new CompilerEngineForIgnition($this->app['blade.compiler']); + } + + return new LivewireViewCompilerEngine($this->app['blade.compiler']); + }); + } + + protected function registerRenameMes() + { + RenameMe\SupportEvents::init(); + RenameMe\SupportLocales::init(); + RenameMe\SupportChildren::init(); + RenameMe\SupportRedirects::init(); + RenameMe\SupportValidation::init(); + RenameMe\SupportFileUploads::init(); + RenameMe\OptimizeRenderedDom::init(); + RenameMe\SupportFileDownloads::init(); + RenameMe\SupportActionReturns::init(); + RenameMe\SupportBrowserHistory::init(); + RenameMe\SupportComponentTraits::init(); + } + + protected function registerHydrationMiddleware() + { + LifecycleManager::registerHydrationMiddleware([ + + /* This is the core middleware stack of Livewire. It's important */ + /* to understand that the request goes through each class by the */ + /* order it is listed in this array, and is reversed on response */ + /* */ + /* ↓ Incoming Request Outgoing Response ↑ */ + /* ↓ ↑ */ + /* ↓ Secure Stuff ↑ */ + /* ↓ */ SecureHydrationWithChecksum::class, /* --------------- ↑ */ + /* ↓ */ NormalizeServerMemoSansDataForJavaScript::class, /* -- ↑ */ + /* ↓ */ HashDataPropertiesForDirtyDetection::class, /* ------- ↑ */ + /* ↓ ↑ */ + /* ↓ Hydrate Stuff ↑ */ + /* ↓ */ HydratePublicProperties::class, /* ------------------- ↑ */ + /* ↓ */ CallPropertyHydrationHooks::class, /* ---------------- ↑ */ + /* ↓ */ CallHydrationHooks::class, /* ------------------------ ↑ */ + /* ↓ ↑ */ + /* ↓ Update Stuff ↑ */ + /* ↓ */ PerformDataBindingUpdates::class, /* ----------------- ↑ */ + /* ↓ */ PerformActionCalls::class, /* ------------------------ ↑ */ + /* ↓ */ PerformEventEmissions::class, /* --------------------- ↑ */ + /* ↓ ↑ */ + /* ↓ Output Stuff ↑ */ + /* ↓ */ RenderView::class, /* -------------------------------- ↑ */ + /* ↓ */ NormalizeComponentPropertiesForJavaScript::class, /* - ↑ */ + + ]); + + LifecycleManager::registerInitialDehydrationMiddleware([ + + /* Initial Response */ + /* ↑ */ [SecureHydrationWithChecksum::class, 'dehydrate'], + /* ↑ */ [NormalizeServerMemoSansDataForJavaScript::class, 'dehydrate'], + /* ↑ */ [HydratePublicProperties::class, 'dehydrate'], + /* ↑ */ [CallPropertyHydrationHooks::class, 'dehydrate'], + /* ↑ */ [CallHydrationHooks::class, 'initialDehydrate'], + /* ↑ */ [RenderView::class, 'dehydrate'], + /* ↑ */ [NormalizeComponentPropertiesForJavaScript::class, 'dehydrate'], + + ]); + + LifecycleManager::registerInitialHydrationMiddleware([ + + [CallHydrationHooks::class, 'initialHydrate'], + + ]); + } + + protected function bypassTheseMiddlewaresDuringLivewireRequests(array $middlewareToExclude) + { + if (! Livewire::isProbablyLivewireRequest()) return; + + $kernel = $this->app->make(\Illuminate\Contracts\Http\Kernel::class); + + $openKernel = new ObjectPrybar($kernel); + + $middleware = $openKernel->getProperty('middleware'); + + $openKernel->setProperty('middleware', array_diff($middleware, $middlewareToExclude)); + } + + protected function publishesToGroups(array $paths, $groups = null) + { + if (is_null($groups)) { + $this->publishes($paths); + + return; + } + + foreach ((array) $groups as $group) { + $this->publishes($paths, $group); + } + } +} diff --git a/resources/assets/js/views/common/search.js b/resources/assets/js/views/common/search.js deleted file mode 100644 index d9e80c3b5..000000000 --- a/resources/assets/js/views/common/search.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * First we will load all of this project's JavaScript dependencies which - * includes Vue and other libraries. It is a great starting point when - * building robust, powerful web applications using Vue and Laravel. - */ - -require('./../../bootstrap'); - -import Vue from 'vue'; - -import axios from 'axios'; - -import NProgress from 'nprogress'; -import 'nprogress/nprogress.css'; -import NProgressAxios from './../../plugins/nprogress-axios'; - -import clickOutside from './../../directives/click-ouside.js'; - -Vue.directive('click-outside', clickOutside); - -const search = new Vue({ - el: '#global-search', - - data: function () { - return { - show: false, - count:0, - keyword: '', - items: {} - } - }, - - methods:{ - onChange() { - this.show = false; - - if (this.keyword.length) { - axios.get(url + '/common/search', { - params: { - keyword: this.keyword - } - }) - .then(response => { - this.items = response.data; - this.count = Object.keys(this.items).length; - - if (this.count) { - this.show = true; - } - }) - .catch(error => { - }); - } - }, - - closeResult() { - this.show = false; - this.count = 0; - this.items = {}; - } - } -}); diff --git a/resources/views/livewire/common/search.blade.php b/resources/views/livewire/common/search.blade.php new file mode 100644 index 000000000..a1b0e2e1c --- /dev/null +++ b/resources/views/livewire/common/search.blade.php @@ -0,0 +1,42 @@ + + +@push('scripts_end') + +@endpush diff --git a/resources/views/partials/admin/navbar.blade.php b/resources/views/partials/admin/navbar.blade.php index 0aa615ee0..64cc26012 100644 --- a/resources/views/partials/admin/navbar.blade.php +++ b/resources/views/partials/admin/navbar.blade.php @@ -5,40 +5,7 @@ @stack('navbar_search') @can('read-common-search') - + @endcan