For example, you access the URL:
https://example.com/admin/settings/users
File public/index.php is the entry point
It bootstraps the Laravel app (bootstrap/app.php)
Then, Laravel handles middleware, routes, v.v
Laravel finds the matching route in:
packages/Webkul/Admin/src/Routes/web.php
In Magisto, if overridden, this route can be reloaded from Customize/Admin/Providers/AdminServiceProvider. For example:
Route::get('admin/catalog/products', [ProductController::class, 'index']);
Datagrid in Bagisto is a table, for example, this is a datagrid of the users page:
The data and column definition in the class packages\Customize\Admin\src\DataGrids\Settings\UserDataGrid.php
<?php
namespace Customize\Admin\DataGrids\Settings ;
use Illuminate\Support\Facades\DB ;
use Illuminate\Support\Facades\Storage ;
use Webkul\User\Repositories\RoleRepository ;
use Webkul\Admin\DataGrids\Settings\UserDataGrid as BaseUserDataGrid;
use Webkul\Core\Repositories\CountryRepository ;
class UserDataGrid extends BaseUserDataGrid
{
/**
* Index.
*
* @var string
*/
protected $primaryColumn = 'user_id';
/**
* Constructor for the class.
*
* @return void
*/
public function __construct(protected RoleRepository $roleRepository, protected CountryRepository $countryRepository)
{
}
/**
* Prepare query builder.
*
* @return \Illuminate\Database\Query\Builder
*/
public function prepareQueryBuilder()
{
$queryBuilder = DB::table('admins')
->leftJoin('roles', 'admins.role_id', '=', 'roles.id')
->leftJoin('countries', 'admins.country_id', '=', 'countries.id')
->select(
'admins.id as user_id',
'admins.name as user_name',
'admins.image as user_image',
'admins.status',
'admins.email',
'roles.name as role_name',
'countries.name as country_name',
'admins.whatsapp',
);
$this->addFilter('user_id', 'admins.id');
$this->addFilter('user_name', 'admins.name');
$this->addFilter('role_name', 'roles.name');
$this->addFilter('status', 'admins.status');
return $queryBuilder;
}
/**
* Add columns.
*
* @return void
*/
public function prepareColumns()
{
$this->addColumn([
'index' => 'user_id',
'label' => trans('admin::app.settings.users.index.datagrid.id'),
'type' => 'integer',
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'user_name',
'label' => trans('admin::app.settings.users.index.datagrid.name'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'email',
'label' => trans('admin::app.settings.users.index.datagrid.email'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'user_img',
'label' => trans('admin::app.settings.users.index.datagrid.name'),
'type' => 'string',
'closure' => function ($row) {
if ($row->user_image) {
return Storage::url($row->user_image);
}
return null;
},
]);
$this->addColumn([
'index' => 'status',
'label' => trans('admin::app.settings.users.index.datagrid.status'),
'type' => 'boolean',
'searchable' => true,
'filterable' => true,
'filterable_options' => [
[
'label' => trans('admin::app.settings.users.index.datagrid.active'),
'value' => 1,
],
[
'label' => trans('admin::app.settings.users.index.datagrid.inactive'),
'value' => 0,
],
],
'sortable' => true,
'closure' => function ($value) {
if ($value->status) {
return trans('admin::app.settings.users.index.datagrid.active');
}
return trans('admin::app.settings.users.index.datagrid.inactive');
},
]);
$this->addColumn([
'index' => 'role_name',
'label' => trans('admin::app.settings.users.index.datagrid.role'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'filterable_type' => 'dropdown',
'filterable_options' => $this->roleRepository->all(['name as label', 'name as value'])->toArray(),
'sortable' => true,
]);
}
}
You can see the header of the table definition in the prepareColumns function, and the data rows definition in the prepareQueryBuilder function
When write code, you maybe face to face with the issue the class name CSS of tailwind does not apply, you need to do:
cd /var/app/primass/packages/Customize/Admin
npm run build
For example, in Bagisto, there is Order Datagrid in packages/Webkul/Admin/src/DataGrids/Customers/View/OrderDataGrid.php , so I clone it to packages/Customize/Admin/src/DataGrids/Orders/OrderDataGrid.php. Now I want to overwrite it to the default OrderDatagrid of Bagisto. How to do it? Let's go!
In packages/Customize/Admin/src/Providers/AdminServiceProvider.php
namespace Customize\Admin\Providers ;
use Webkul\Admin\DataGrids\Customers\View\OrderDataGrid as WebkulOrderDataGrid;
use Customize\Admin\DataGrids\Orders\OrderDataGrid as CustomizeOrderDataGrid;
class AdminServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
// ...
$this->app->bind(WebkulOrderDataGrid::class, CustomizeOrderDataGrid::class);
}
}
In Bagisto, every module will have a language definition, for example:
📁packages
📁 Webkul/
📁 Admin/
📁 src
📁 Providers
📄 AdminServiceProvider.php
📁 Resources/
📁 lang/
📁 en/
📄 app.php
📄 validation.php
📁 ja/
📄 app.php
📄 validation.php
📁 DataTransfer/
📁 src
📁 Providers
📄 DataTransferServiceProvider.php
📁 Resources/
📁 lang/
📁 en/
📄 app.php
📄 validation.php
📁 ja/
📄 app.php
📄 validation.php
When you customize the language of a module, you need to create the structure like this and register it to the service provider, for example:
📁packages
📁 Customize/
📁 DataTransfer/
📁 src
📁 Providers
📄 DataTransferServiceProvider.php
📁 Resources/
📁 lang/
📁 en/
📄 app.php
📄 validation.php
📁 ja/
📄 app.php
📄 validation.php
In DataTransferServiceProvider.php
class DataTransferServiceProvider extends ServiceProvider
{
//...
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'data_transfer');
}
//...
}
This will overwrite the language of DataTransfer module
Create app/Console/Commands/FindLangKey.php
<?php
namespace App\Console\Commands ;
use Illuminate\Console\Command ;
use File;
class FindLangKey extends Command
{
protected $signature = 'lang:find {key} {locale=en}';
protected $description = 'Find the file containing the key lang (including the module)';
public function handle()
{
$key = $this->argument('key');
$locale = $this->argument('locale');
$segments = explode('::', $key);
if (count($segments) !== 2) {
$this->error('Key is not in correct package format::key.path');
return;
}
[$package, $path] = $segments;
$pathsToSearch = base_path("packages/Customize/" . ucfirst($package) . "/src/Resources/lang/$locale" );
if (!is_dir($pathsToSearch)) {
$this->error("Lang directory not found at: $pathsToSearch" );
return;
}
$files = File::allFiles($pathsToSearch);
foreach ($files as $file) {
$content = File::getRequire($file->getPathname());
$pathPartArray = explode('.', $path);
array_shift($pathPartArray); // bỏ đi phần tử đầu, vì nó là tên file /lang/en/<tên file>.php
$found = $this->searchKey($content, $pathPartArray);
if ($found) {
$fullPath = $file->getPathname();
$relativePath = str_replace(base_path() . '/', '', $fullPath);
// Find a line that contains the key
$lines = file($fullPath);
$searchString = "'" . end($pathPartArray) . "'";
$lineNumber = 1;
foreach ($lines as $index => $line) {
if (strpos($line, $searchString) !== false) {
$lineNumber = $index + 1;
break;
}
}
$this->info("✅ Key in: {$relativePath}:{$lineNumber}" );
return;
}
}
$this->warn("❌ Not found key - " . $pathsToSearch);
}
private function searchKey($array, $keys)
{
$key = array_shift($keys);
if (!array_key_exists($key, $array)) return false;
$value = $array[$key];
if (empty($keys)) return true;
if (!is_array($value)) return false;
return $this->searchKey($value, $keys);
}
}
Usage:
php artisan lang:find "admin::app.customers.customers.index.datagrid.suspended"
For example, the output:
✅ Key in: packages/Customize/Admin/src/Resources/lang/en/app.php:1775
You don't need to join the product table with the attributes table, you can access them directly, for example:
packages/Webkul/Shop/src/Http/Resources/ProductResource.php
public function toArray($request)
{
$productTypeInstance = $this->getTypeInstance();
return [
'id' => $this->id,
'sku' => $this->sku,
'name' => $this->name,
...
...
'mileage' => $this->mileage, // <---
'registration_year' => $this->registration_year, // <---
];
}
In Bagisto, when a user register an account, it will send a welcome email to the user.
packages/Webkul/Shop/src/Resources/views/components/categories/categories.blade.php
<v-shop-category ></v-shop-category >
@pushOnce('scripts')
<script
type="text/x-template"
id="v-shop-category-template"
>
<div >Hello world</div >
</script >
<script type="module">
app.component('v-shop-category', {
template: '#v-shop-category-template'
});
</script >
@endPushOnce
index.blade.php
@include('shop::components.categories.categories')
For example, I want create a global variable it can use in anywhere (child component):
packages/Webkul/Shop/src/Resources/assets/js/app.js
...
import { createApp, reactive } from "vue/dist/vue.esm-bundler";
const pageLoadStatus = reactive({
isPageReady: false
});
/**
* Main root application registry.
*/
window.app = createApp({
...
});
...
[
...
].forEach((plugin) => app.use(plugin));
/**
* Provide pageLoadStatus globally for injection if needed
*/
app.config.globalProperties.$pageLoadStatus = pageLoadStatus;
export default app;
another page/component
<template v-cloak v-if="$pageLoadStatus.isPageReady">
<x-shop::layouts.footer />
</template >
getProducts() {
this.$axios.get(this.src)
.then(response => {
...
this.$pageLoadStatus.isPageReady = true;
});
},
data() {
return {
fromBuyNow: @json(request()->routeIs('shop.checkout.onepage.index'))
}
}
11. Modal Proxy
Ở bagisto có 1 cái gọi là các module proxy, giả sử ở modal
class Cart extends Model implements CartContract
{
public function shipping(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(CartShippingProxy::modelClass());
}
}
packages/Webkul/Checkout/src/Models/CartShippingProxy.php
<?php
namespace Webkul\Checkout\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CartShippingProxy extends ModelProxy {}
packages/Webkul/Checkout/src/Providers/ModuleServiceProvider.php
<?php
namespace Webkul\Checkout\Providers;
use Webkul\Core\Providers\CoreModuleServiceProvider;
class ModuleServiceProvider extends CoreModuleServiceProvider
{
/**
* Models.
*
* @var array
*/
protected $models = [
...
\Webkul\Checkout\Models\CartShipping::class,
];
}