add: full multi-tenancy control

This commit is contained in:
Cauê Faleiros
2026-02-02 15:31:15 -03:00
commit c6ec92802b
1711 changed files with 258106 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
{
"name": "krayin/laravel-core",
"license": "MIT",
"authors": [
{
"name": "Jitendra Singh",
"email": "jitendra@webkul.com"
}
],
"require": {
},
"autoload": {
"psr-4": {
"Webkul\\Core\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Webkul\\Core\\Providers\\CoreServiceProvider"
],
"aliases": {}
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Webkul\Core;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Webkul\Core\Acl\AclItem;
class Acl
{
/**
* acl items.
*/
protected array $items = [];
/**
* Add a new acl item.
*/
public function addItem(AclItem $aclItem): void
{
$this->items[] = $aclItem;
}
/**
* Get all acl items.
*/
public function getItems(): Collection
{
if (! $this->items) {
$this->prepareAclItems();
}
return collect($this->items)
->sortBy('sort')
->values();
}
/**
* Acl Config.
*/
private function getAclConfig(): array
{
static $aclConfig;
if ($aclConfig) {
return $aclConfig;
}
$aclConfig = config('acl');
return $aclConfig;
}
/**
* Get all roles.
*/
public function getRoles(): Collection
{
static $roles;
if ($roles) {
return $roles;
}
$roles = collect($this->getAclConfig())
->mapWithKeys(function ($role) {
if (is_array($role['route'])) {
return collect($role['route'])->mapWithKeys(function ($route) use ($role) {
return [$route => $role['key']];
});
} else {
return [$role['route'] => $role['key']];
}
});
return $roles;
}
/**
* Prepare acl items.
*/
private function prepareAclItems(): void
{
$aclWithDotNotation = [];
foreach ($this->getAclConfig() as $item) {
$aclWithDotNotation[$item['key']] = $item;
}
$acl = Arr::undot(Arr::dot($aclWithDotNotation));
foreach ($acl as $aclItemKey => $aclItem) {
$subAclItems = $this->processSubAclItems($aclItem);
$this->addItem(new AclItem(
key: $aclItemKey,
name: trans($aclItem['name']),
route: $aclItem['route'],
sort: $aclItem['sort'],
children: $subAclItems,
));
}
}
/**
* Process sub acl items.
*/
private function processSubAclItems($aclItem): Collection
{
return collect($aclItem)
->sortBy('sort')
->filter(fn ($value, $key) => is_array($value) && $key !== 'route')
->map(function ($subAclItem) {
$subSubAclItems = $this->processSubAclItems($subAclItem);
return new AclItem(
key: $subAclItem['key'],
name: trans($subAclItem['name']),
route: $subAclItem['route'],
sort: $subAclItem['sort'],
children: $subSubAclItems,
);
});
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Webkul\Core\Acl;
use Illuminate\Support\Collection;
class AclItem
{
/**
* Create a new AclItem instance.
*/
public function __construct(
public string $key,
public string $name,
public array|string $route,
public int $sort,
public Collection $children,
) {}
}

View File

@@ -0,0 +1,25 @@
<?php
return [
'modules' => [
\Webkul\Activity\Providers\ModuleServiceProvider::class,
\Webkul\Admin\Providers\ModuleServiceProvider::class,
\Webkul\Attribute\Providers\ModuleServiceProvider::class,
\Webkul\Automation\Providers\ModuleServiceProvider::class,
\Webkul\Contact\Providers\ModuleServiceProvider::class,
\Webkul\Core\Providers\ModuleServiceProvider::class,
\Webkul\DataGrid\Providers\ModuleServiceProvider::class,
\Webkul\EmailTemplate\Providers\ModuleServiceProvider::class,
\Webkul\Email\Providers\ModuleServiceProvider::class,
\Webkul\Lead\Providers\ModuleServiceProvider::class,
\Webkul\Product\Providers\ModuleServiceProvider::class,
\Webkul\Quote\Providers\ModuleServiceProvider::class,
\Webkul\Tag\Providers\ModuleServiceProvider::class,
\Webkul\User\Providers\ModuleServiceProvider::class,
\Webkul\Warehouse\Providers\ModuleServiceProvider::class,
\Webkul\WebForm\Providers\ModuleServiceProvider::class,
\Webkul\DataTransfer\Providers\ModuleServiceProvider::class,
],
'register_route_models' => true,
];

View File

@@ -0,0 +1,62 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Laravel CORS Options
|--------------------------------------------------------------------------
|
| The allowed_methods and allowed_headers options are case-insensitive.
|
| You don't need to provide both allowed_origins and allowed_origins_patterns.
| If one of the strings passed matches, it is considered a valid origin.
|
| If ['*'] is provided to allowed_methods, allowed_origins or allowed_headers
| all methods / origins / headers are allowed.
|
*/
/*
* You can enable CORS for 1 or multiple paths.
* Example: ['api/*']
*/
'paths' => [
'admin/web-forms/forms/*',
],
/*
* Matches the request method. `['*']` allows all methods.
*/
'allowed_methods' => ['*'],
/*
* Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`
*/
'allowed_origins' => ['*'],
/*
* Patterns that can be used with `preg_match` to match the origin.
*/
'allowed_origins_patterns' => [],
/*
* Sets the Access-Control-Allow-Headers response header. `['*']` allows all headers.
*/
'allowed_headers' => ['*'],
/*
* Sets the Access-Control-Expose-Headers response header with these headers.
*/
'exposed_headers' => [],
/*
* Sets the Access-Control-Max-Age response header when > 0.
*/
'max_age' => 0,
/*
* Sets the Access-Control-Allow-Credentials header.
*/
'supports_credentials' => false,
];

View File

@@ -0,0 +1,65 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['user'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
];

View File

@@ -0,0 +1,42 @@
<?php
namespace Webkul\Core\Console\Commands;
use Illuminate\Console\Command;
class Version extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'krayin-crm:version';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Displays current version of Krayin CRM installed';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->comment('v'.core()->version());
}
}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\Core\Contracts;
interface CoreConfig {}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\Core\Contracts;
interface Country {}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\Core\Contracts;
interface CountryState {}

View File

@@ -0,0 +1,30 @@
<?php
namespace Webkul\Core\Contracts\Validations;
use Illuminate\Contracts\Validation\Rule;
class Code implements Rule
{
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return preg_match('/^[a-zA-Z]+[a-zA-Z0-9_]+$/', $value);
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return trans('core::app.validations.code');
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Webkul\Core\Contracts\Validations;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class Decimal implements ValidationRule
{
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (! preg_match('/^\d*(\.\d{1,4})?$/', $value)) {
$fail(trans('admin::app.validations.message.decimal', ['attribute' => $attribute]));
}
}
}

View File

@@ -0,0 +1,243 @@
<?php
namespace Webkul\Core;
use Carbon\Carbon;
use Webkul\Core\Repositories\CoreConfigRepository;
use Webkul\Core\Repositories\CountryRepository;
use Webkul\Core\Repositories\CountryStateRepository;
class Core
{
/**
* The Krayin version.
*
* @var string
*/
const KRAYIN_VERSION = '2.1.5';
/**
* Create a new instance.
*
* @return void
*/
public function __construct(
protected CountryRepository $countryRepository,
protected CoreConfigRepository $coreConfigRepository,
protected CountryStateRepository $countryStateRepository
) {}
/**
* Get the version number of the Krayin.
*
* @return string
*/
public function version()
{
return static::KRAYIN_VERSION;
}
/**
* Retrieve all timezones.
*/
public function timezones(): array
{
$timezones = [];
foreach (timezone_identifiers_list() as $timezone) {
$timezones[$timezone] = $timezone;
}
return $timezones;
}
/**
* Retrieve all locales.
*/
public function locales(): array
{
$options = [];
foreach (config('app.available_locales') as $key => $title) {
$options[] = [
'title' => $title,
'value' => $key,
];
}
return $options;
}
/**
* Retrieve all countries.
*
* @return \Illuminate\Support\Collection
*/
public function countries()
{
return $this->countryRepository->all();
}
/**
* Returns country name by code.
*/
public function country_name(string $code): string
{
$country = $this->countryRepository->findOneByField('code', $code);
return $country ? $country->name : '';
}
/**
* Returns state name by code.
*/
public function state_name(string $code): string
{
$state = $this->countryStateRepository->findOneByField('code', $code);
return $state ? $state->name : $code;
}
/**
* Retrieve all country states.
*
* @return \Illuminate\Support\Collection
*/
public function states(string $countryCode)
{
return $this->countryStateRepository->findByField('country_code', $countryCode);
}
/**
* Retrieve all grouped states by country code.
*
* @return \Illuminate\Support\Collection
*/
public function groupedStatesByCountries()
{
$collection = [];
foreach ($this->countryStateRepository->all() as $state) {
$collection[$state->country_code][] = $state->toArray();
}
return $collection;
}
/**
* Retrieve all grouped states by country code.
*
* @return \Illuminate\Support\Collection
*/
public function findStateByCountryCode($countryCode = null, $stateCode = null)
{
$collection = [];
$collection = $this->countryStateRepository->findByField([
'country_code' => $countryCode,
'code' => $stateCode,
]);
if (count($collection)) {
return $collection->first();
} else {
return false;
}
}
/**
* Create singleton object through single facade.
*
* @param string $className
* @return mixed
*/
public function getSingletonInstance($className)
{
static $instances = [];
if (array_key_exists($className, $instances)) {
return $instances[$className];
}
return $instances[$className] = app($className);
}
/**
* Format date
*
* @return string
*/
public function formatDate($date, $format = 'd M Y h:iA')
{
return Carbon::parse($date)->format($format);
}
/**
* Week range.
*
* @param string $date
* @param int $day
* @return string
*/
public function xWeekRange($date, $day)
{
$ts = strtotime($date);
if (! $day) {
$start = (date('D', $ts) == 'Sun') ? $ts : strtotime('last sunday', $ts);
return date('Y-m-d', $start);
} else {
$end = (date('D', $ts) == 'Sat') ? $ts : strtotime('next saturday', $ts);
return date('Y-m-d', $end);
}
}
/**
* Return currency symbol from currency code.
*
* @param float $price
* @return string
*/
public function currencySymbol($code)
{
$formatter = new \NumberFormatter(app()->getLocale().'@currency='.$code, \NumberFormatter::CURRENCY);
return $formatter->getSymbol(\NumberFormatter::CURRENCY_SYMBOL);
}
/**
* Format price with base currency symbol. This method also give ability to encode
* the base currency symbol and its optional.
*
* @param float $price
* @return string
*/
public function formatBasePrice($price)
{
if (is_null($price)) {
$price = 0;
}
$formatter = new \NumberFormatter(app()->getLocale(), \NumberFormatter::CURRENCY);
return $formatter->formatCurrency($price, config('app.currency'));
}
/**
* Get the config field.
*/
public function getConfigField(string $fieldName): ?array
{
return system_config()->getConfigField($fieldName);
}
/**
* Retrieve information for configuration.
*/
public function getConfigData(string $field): mixed
{
return system_config()->getConfigData($field);
}
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('core_config', function (Blueprint $table) {
$table->increments('id');
$table->string('code');
$table->string('value');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('core_config');
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('countries', function (Blueprint $table) {
$table->increments('id');
$table->string('code');
$table->string('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('countries');
}
};

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('country_states', function (Blueprint $table) {
$table->increments('id');
$table->string('country_code');
$table->string('code');
$table->string('name');
$table->integer('country_id')->unsigned();
$table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('country_states');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('core_config', function (Blueprint $table) {
$table->text('value')->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('core_config', function (Blueprint $table) {
$table->string('value')->change();
});
}
};

View File

@@ -0,0 +1,146 @@
<?php
namespace Webkul\Core\Eloquent;
use Prettus\Repository\Contracts\CacheableInterface;
use Prettus\Repository\Eloquent\BaseRepository;
use Prettus\Repository\Traits\CacheableRepository;
abstract class Repository extends BaseRepository implements CacheableInterface
{
use CacheableRepository;
/**
* Find data by field and value
*
* @param string $field
* @param string $value
* @param array $columns
* @return mixed
*/
public function findOneByField($field, $value = null, $columns = ['*'])
{
$model = $this->findByField($field, $value, $columns = ['*']);
return $model->first();
}
/**
* Find data by field and value
*
* @param string $field
* @param string $value
* @param array $columns
* @return mixed
*/
public function findOneWhere(array $where, $columns = ['*'])
{
$model = $this->findWhere($where, $columns);
return $model->first();
}
/**
* Find data by id
*
* @param int $id
* @param array $columns
* @return mixed
*/
public function find($id, $columns = ['*'])
{
$this->applyCriteria();
$this->applyScope();
$model = $this->model->find($id, $columns);
$this->resetModel();
return $this->parserResult($model);
}
/**
* Find data by id
*
* @param int $id
* @param array $columns
* @return mixed
*/
public function findOrFail($id, $columns = ['*'])
{
$this->applyCriteria();
$this->applyScope();
$model = $this->model->findOrFail($id, $columns);
$this->resetModel();
return $this->parserResult($model);
}
/**
* Count results of repository
*
* @param string $columns
* @return int
*/
public function count(array $where = [], $columns = '*')
{
$this->applyCriteria();
$this->applyScope();
if ($where) {
$this->applyConditions($where);
}
$result = $this->model->count($columns);
$this->resetModel();
$this->resetScope();
return $result;
}
/**
* @param string $columns
* @return mixed
*/
public function sum($columns)
{
$this->applyCriteria();
$this->applyScope();
$sum = $this->model->sum($columns);
$this->resetModel();
return $sum;
}
/**
* @param string $columns
* @return mixed
*/
public function avg($columns)
{
$this->applyCriteria();
$this->applyScope();
$avg = $this->model->avg($columns);
$this->resetModel();
return $avg;
}
/**
* @return mixed
*/
public function getModel($data = [])
{
return $this->model;
}
/**
* @throws RepositoryException
*/
public function resetModel()
{
$this->makeModel();
return $this;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Webkul\Core\Eloquent;
use Astrotomic\Translatable\Translatable;
use Illuminate\Database\Eloquent\Model;
class TranslatableModel extends Model
{
use Translatable;
/**
* @return string
*/
protected function locale()
{
if ($this->defaultLocale) {
return $this->defaultLocale;
}
return config('translatable.locale') ?: app()->make('translator')->getLocale();
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Webkul\Core\Exceptions;
class ViterNotFound extends \Exception
{
/**
* Create an instance.
*
* @param string $theme
* @return void
*/
public function __construct($namespace)
{
parent::__construct("Viter with `$namespace` namespace not found. Please add `$namespace` namespace in the `config/krayin-vite.php` file.", 1);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Webkul\Core\Facades;
use Illuminate\Support\Facades\Facade;
class Acl extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'acl';
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Webkul\Core\Facades;
use Illuminate\Support\Facades\Facade;
class Core extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'core';
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Webkul\Core\Facades;
use Illuminate\Support\Facades\Facade;
class Menu extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'menu';
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Webkul\Core\Facades;
use Illuminate\Support\Facades\Facade;
class SystemConfig extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'system_config';
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Webkul\Core\Helpers;
class Helper
{
/**
* @param string $packageName
* @return array
*/
public function jsonTranslations($packageName)
{
$currentLocale = app()->getLocale();
$path = __DIR__."/../../../$packageName/src/Resources/lang/$currentLocale/app.php";
if (is_string($path) && is_readable($path)) {
return include $path;
} else {
$currentLocale = 'en';
$path = __DIR__."/../../../$packageName/src/Resources/lang/$currentLocale/app.php";
return include $path;
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Webkul\Core\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class CoreController extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
protected $_config;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->_config = request('_config');
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view($this->_config['view'], $this->_config);
}
}

View File

@@ -0,0 +1,74 @@
<?php
use Webkul\Core\Acl;
use Webkul\Core\Core;
use Webkul\Core\Menu;
use Webkul\Core\SystemConfig;
use Webkul\Core\ViewRenderEventManager;
use Webkul\Core\Vite;
if (! function_exists('core')) {
/**
* Core helper.
*/
function core(): Core
{
return app('core');
}
}
if (! function_exists('menu')) {
/**
* Menu helper.
*/
function menu(): Menu
{
return app('menu');
}
}
if (! function_exists('acl')) {
/**
* Acl helper.
*/
function acl(): Acl
{
return app('acl');
}
}
if (! function_exists('system_config')) {
/**
* System Config helper.
*/
function system_config(): SystemConfig
{
return app('system_config');
}
}
if (! function_exists('view_render_event')) {
/**
* View render event helper.
*/
function view_render_event($eventName, $params = null)
{
app()->singleton(ViewRenderEventManager::class);
$viewEventManager = app()->make(ViewRenderEventManager::class);
$viewEventManager->handleRenderEvent($eventName, $params);
return $viewEventManager->render();
}
}
if (! function_exists('vite')) {
/**
* Vite helper.
*/
function vite(): Vite
{
return app(Vite::class);
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace Webkul\Core;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Webkul\Core\Menu\MenuItem;
class Menu
{
/**
* Menu items.
*/
private array $items = [];
/**
* Config menu.
*/
private array $configMenu = [];
/**
* Contains current item key.
*/
private string $currentKey = '';
/**
* Menu area for admin.
*/
const ADMIN = 'admin';
/**
* Menu area for customer.
*/
const CUSTOMER = 'customer';
/**
* Add a new menu item.
*/
public function addItem(MenuItem $menuItem): void
{
$this->items[] = $menuItem;
}
/**
* Get all menu items.
*/
public function getItems(?string $area = null, string $key = ''): Collection
{
if (! $area) {
throw new \Exception('Area must be provided to get menu items.');
}
static $items;
if ($items) {
return $items;
}
$configMenu = collect(config("menu.$area"))->map(function ($item) {
return Arr::except([
...$item,
'url' => route($item['route'], $item['params'] ?? []),
], ['params']);
});
switch ($area) {
case self::ADMIN:
$this->configMenu = $configMenu
->filter(fn ($item) => bouncer()->hasPermission($item['key']))
->toArray();
break;
default:
$this->configMenu = $configMenu->toArray();
break;
}
if (! $this->items) {
$this->prepareMenuItems();
}
$items = collect($this->items)->sortBy(fn ($item) => $item->getPosition());
return $items;
}
/**
* Get admin menu by key or keys.
*/
public function getAdminMenuByKey(array|string $keys): mixed
{
$items = $this->getItems('admin');
$keysArray = (array) $keys;
$filteredItems = $items->filter(fn ($item) => in_array($item->getKey(), $keysArray));
return is_array($keys) ? $filteredItems : $filteredItems->first();
}
/**
* Prepare menu items.
*/
private function prepareMenuItems(): void
{
$menuWithDotNotation = [];
foreach ($this->configMenu as $item) {
if (strpos(request()->url(), route($item['route'])) !== false) {
$this->currentKey = $item['key'];
}
$menuWithDotNotation[$item['key']] = $item;
}
$menu = Arr::undot(Arr::dot($menuWithDotNotation));
foreach ($menu as $menuItemKey => $menuItem) {
$this->addItem(new MenuItem(
key: $menuItemKey,
name: trans($menuItem['name']),
route: $menuItem['route'],
url: $menuItem['url'],
sort: $menuItem['sort'],
icon: $menuItem['icon-class'],
info: trans($menuItem['info'] ?? ''),
children: $this->processSubMenuItems($menuItem),
));
}
}
/**
* Process sub menu items.
*/
private function processSubMenuItems($menuItem): Collection
{
return collect($menuItem)
->sortBy('sort')
->filter(fn ($value) => is_array($value))
->map(function ($subMenuItem) {
$subSubMenuItems = $this->processSubMenuItems($subMenuItem);
return new MenuItem(
key: $subMenuItem['key'],
name: trans($subMenuItem['name']),
route: $subMenuItem['route'],
url: $subMenuItem['url'],
sort: $subMenuItem['sort'],
icon: $subMenuItem['icon-class'],
info: trans($subMenuItem['info'] ?? ''),
children: $subSubMenuItems,
);
});
}
/**
* Get current active menu.
*/
public function getCurrentActiveMenu(?string $area = null): ?MenuItem
{
$currentKey = implode('.', array_slice(explode('.', $this->currentKey), 0, 2));
return $this->findMatchingItem($this->getItems($area), $currentKey);
}
/**
* Finding the matching item.
*/
private function findMatchingItem($items, $currentKey): ?MenuItem
{
foreach ($items as $item) {
if ($item->key == $currentKey) {
return $item;
}
if ($item->haveChildren()) {
$matchingChild = $this->findMatchingItem($item->getChildren(), $currentKey);
if ($matchingChild) {
return $matchingChild;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace Webkul\Core\Menu;
use Illuminate\Support\Collection;
class MenuItem
{
/**
* Create a new MenuItem instance.
*
* @return void
*/
public function __construct(
private string $key,
private string $name,
private string $route,
private string $url,
private int $sort,
private string $icon,
private string $info,
private Collection $children,
) {}
/**
* Set name of menu item.
*/
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Get name of menu item.
*/
public function getName(): string
{
return $this->name;
}
/**
* Set position of menu item.
*/
public function setPosition(int $sort): self
{
$this->sort = $sort;
return $this;
}
/**
* Get position of menu item.
*/
public function getPosition()
{
return $this->sort;
}
/**
* Set icon of menu item.
*/
public function setIcon(string $icon): self
{
$this->icon = $icon;
return $this;
}
/**
* Get the icon of menu item.
*/
public function getIcon(): string
{
return $this->icon;
}
/**
* Set info of menu item.
*/
public function setInfo(string $info): self
{
$this->info = $info;
return $this;
}
/**
* Get info of menu item.
*/
public function getInfo(): string
{
return $this->info;
}
/**
* Set route of menu item.
*/
public function setRoute(string $route): self
{
$this->route = $route;
return $this;
}
/**
* Get current route.
*/
public function getRoute(): string
{
return $this->route;
}
/**
* Set url of menu item.
*/
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
/**
* Get the url of the menu item.
*/
public function getUrl(): string
{
return $this->url;
}
/**
* Set the key of the menu item.
*/
public function setKey(string $key): self
{
$this->key = $key;
return $this;
}
/**
* Get the key of the menu item.
*/
public function getKey(): string
{
return $this->key;
}
/**
* Set children of menu item.
*/
public function setChildren(Collection $children): self
{
$this->children = $children;
return $this;
}
/**
* Check weather menu item have children or not.
*/
public function haveChildren(): bool
{
return $this->children->isNotEmpty();
}
/**
* Get children of menu item.
*/
public function getChildren(): Collection
{
if (! $this->haveChildren()) {
return collect();
}
return $this->children;
}
/**
* Check weather menu item is active or not.
*/
public function isActive(): bool
{
if (request()->fullUrlIs($this->getUrl().'*')) {
return true;
}
if ($this->haveChildren()) {
foreach ($this->getChildren() as $child) {
if ($child->isActive()) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Webkul\Core\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Core\Contracts\CoreConfig as CoreConfigContract;
class CoreConfig extends Model implements CoreConfigContract
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $table = 'core_config';
protected $fillable = [
'code',
'value',
'locale',
];
protected $hidden = ['token'];
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\Core\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CoreConfigProxy extends ModelProxy {}

View File

@@ -0,0 +1,11 @@
<?php
namespace Webkul\Core\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Core\Contracts\Country as CountryContract;
class Country extends Model implements CountryContract
{
public $timestamps = false;
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\Core\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CountryProxy extends ModelProxy {}

View File

@@ -0,0 +1,11 @@
<?php
namespace Webkul\Core\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Core\Contracts\CountryState as CountryStateContract;
class CountryState extends Model implements CountryStateContract
{
public $timestamps = false;
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\Core\Models;
use Konekt\Concord\Proxies\ModelProxy;
class CountryStateProxy extends ModelProxy {}

View File

@@ -0,0 +1,32 @@
<?php
namespace Webkul\Core\Providers;
use Konekt\Concord\BaseModuleServiceProvider as ConcordBaseModuleServiceProvider;
class BaseModuleServiceProvider extends ConcordBaseModuleServiceProvider
{
/**
* {@inheritdoc}
*/
public function boot()
{
if ($this->areMigrationsEnabled()) {
$this->registerMigrations();
}
if ($this->areModelsEnabled()) {
$this->registerModels();
$this->registerEnums();
$this->registerRequestTypes();
}
if ($this->areViewsEnabled()) {
$this->registerViews();
}
if ($routes = $this->config('routes', true)) {
$this->registerRoutes($routes);
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Webkul\Core\Providers;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;
use Webkul\Core\Acl;
use Webkul\Core\Console\Commands\Version;
use Webkul\Core\Core;
use Webkul\Core\Facades\Acl as AclFacade;
use Webkul\Core\Facades\Core as CoreFacade;
use Webkul\Core\Facades\Menu as MenuFacade;
use Webkul\Core\Facades\SystemConfig as SystemConfigFacade;
use Webkul\Core\Menu;
use Webkul\Core\SystemConfig;
class CoreServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function boot()
{
include __DIR__.'/../Http/helpers.php';
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'core');
$this->publishes([
dirname(__DIR__).'/Config/concord.php' => config_path('concord.php'),
dirname(__DIR__).'/Config/cors.php' => config_path('cors.php'),
dirname(__DIR__).'/Config/sanctum.php' => config_path('sanctum.php'),
]);
}
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->registerCommands();
$this->registerFacades();
}
/**
* Register Bouncer as a singleton.
*
* @return void
*/
protected function registerFacades()
{
$loader = AliasLoader::getInstance();
$loader->alias('acl', AclFacade::class);
$loader->alias('core', CoreFacade::class);
$loader->alias('system_config', SystemConfigFacade::class);
$loader->alias('menu', MenuFacade::class);
$this->app->singleton('acl', fn () => app(Acl::class));
$this->app->singleton('core', fn () => app(Core::class));
$this->app->singleton('system_config', fn () => app()->make(SystemConfig::class));
$this->app->singleton('menu', fn () => app()->make(Menu::class));
}
/**
* Register the console commands of this package.
*/
protected function registerCommands(): void
{
if ($this->app->runningInConsole()) {
$this->commands([
Version::class,
]);
}
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Webkul\Core\Providers;
class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
\Webkul\Core\Models\CoreConfig::class,
\Webkul\Core\Models\Country::class,
\Webkul\Core\Models\CountryState::class,
];
}

View File

@@ -0,0 +1,234 @@
<?php
namespace Webkul\Core\Repositories;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Webkul\Core\Contracts\CoreConfig;
use Webkul\Core\Eloquent\Repository;
class CoreConfigRepository extends Repository
{
/**
* Specify model class name.
*/
public function model(): string
{
return CoreConfig::class;
}
/**
* Get the configuration title.
*/
protected function getTranslatedTitle(mixed $configuration): string
{
if (
method_exists($configuration, 'getTitle')
&& ! is_null($configuration->getTitle())
) {
return trans($configuration->getTitle());
}
if (
method_exists($configuration, 'getName')
&& ! is_null($configuration->getName())
) {
return trans($configuration->getName());
}
return '';
}
/**
* Get children and fields.
*/
protected function getChildrenAndFields(mixed $configuration, string $searchTerm, array $path, array &$results): void
{
if (
method_exists($configuration, 'getChildren')
|| method_exists($configuration, 'getFields')
) {
$children = $configuration->haveChildren()
? $configuration->getChildren()
: $configuration->getFields();
$tempPath = array_merge($path, [[
'key' => $configuration->getKey() ?? null,
'title' => $this->getTranslatedTitle($configuration),
]]);
$results = array_merge($results, $this->search($children, $searchTerm, $tempPath));
}
}
/**
* Search configuration.
*
* @param array $items
*/
public function search(Collection $items, string $searchTerm, array $path = []): array
{
$results = [];
foreach ($items as $configuration) {
$title = $this->getTranslatedTitle($configuration);
if (
stripos($title, $searchTerm) !== false
&& count($path)
) {
$queryParam = $path[1]['key'] ?? $configuration->getKey();
$results[] = [
'title' => implode(' > ', [...Arr::pluck($path, 'title'), $title]),
'url' => route('admin.configuration.index', Str::replace('.', '/', $queryParam)),
];
}
$this->getChildrenAndFields($configuration, $searchTerm, $path, $results);
}
return $results;
}
/**
* Create core configuration.
*/
public function create(array $data): void
{
unset($data['_token']);
$preparedData = [];
foreach ($data as $method => $fieldData) {
$recursiveData = $this->recursiveArray($fieldData, $method);
foreach ($recursiveData as $fieldName => $value) {
if (
is_array($value)
&& isset($value['delete'])
) {
$coreConfigValues = $this->model->where('code', $fieldName)->get();
if ($coreConfigValues->isNotEmpty()) {
foreach ($coreConfigValues as $coreConfig) {
if (! empty($coreConfig['value'])) {
Storage::delete($coreConfig['value']);
}
parent::delete($coreConfig['id']);
}
}
continue;
}
}
foreach ($recursiveData as $fieldName => $value) {
if (is_array($value)) {
foreach ($value as $key => $val) {
$fieldNameWithKey = $fieldName.'.'.$key;
$coreConfigValues = $this->model->where('code', $fieldNameWithKey)->get();
if (request()->hasFile($fieldNameWithKey)) {
$val = request()->file($fieldNameWithKey)->store('configuration');
}
if ($coreConfigValues->isNotEmpty()) {
foreach ($coreConfigValues as $coreConfig) {
if (request()->hasFile($fieldNameWithKey)) {
Storage::delete($coreConfig['value']);
}
parent::update(['code' => $fieldNameWithKey, 'value' => $val], $coreConfig->id);
}
} else {
parent::create(['code' => $fieldNameWithKey, 'value' => $val]);
}
}
} else {
if (request()->hasFile($fieldName)) {
$value = request()->file($fieldName)->store('configuration');
}
$preparedData[] = [
'code' => $fieldName,
'value' => $value,
];
}
}
}
if (! empty($preparedData)) {
foreach ($preparedData as $dataItem) {
$coreConfigValues = $this->model->where('code', $dataItem['code'])->get();
if ($coreConfigValues->isNotEmpty()) {
foreach ($coreConfigValues as $coreConfig) {
parent::update($dataItem, $coreConfig->id);
}
} else {
parent::create($dataItem);
}
}
}
Event::dispatch('core.configuration.save.after');
}
/**
* Recursive array.
*/
public function recursiveArray(array $formData, string $method): array
{
static $data = [];
static $recursiveArrayData = [];
foreach ($formData as $form => $formValue) {
$value = $method.'.'.$form;
if (is_array($formValue)) {
$dim = $this->countDim($formValue);
if ($dim > 1) {
$this->recursiveArray($formValue, $value);
} elseif ($dim == 1) {
$data[$value] = $formValue;
}
}
}
foreach ($data as $key => $value) {
$field = core()->getConfigField($key);
if ($field) {
$recursiveArrayData[$key] = $value;
} else {
foreach ($value as $key1 => $val) {
$recursiveArrayData[$key.'.'.$key1] = $val;
}
}
}
return $recursiveArrayData;
}
/**
* Return dimension of the array.
*/
public function countDim(array|string $array): int
{
if (is_array(reset($array))) {
$return = $this->countDim(reset($array)) + 1;
} else {
$return = 1;
}
return $return;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Webkul\Core\Repositories;
use Prettus\Repository\Traits\CacheableRepository;
use Webkul\Core\Eloquent\Repository;
class CountryRepository extends Repository
{
use CacheableRepository;
/**
* Specify Model class name
*
* @return mixed
*/
public function model()
{
return 'Webkul\Core\Contracts\Country';
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Webkul\Core\Repositories;
use Prettus\Repository\Traits\CacheableRepository;
use Webkul\Core\Eloquent\Repository;
class CountryStateRepository extends Repository
{
use CacheableRepository;
/**
* Specify Model class name
*
* @return mixed
*/
public function model()
{
return 'Webkul\Core\Contracts\CountryState';
}
}

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'يجب أن يكون الحقل رمزًا صالحًا.',
'decimal' => 'يجب أن يكون الحقل رقمًا عشريًا.',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'The field must be a valid code.',
'decimal' => 'The field must be a decimal number.',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'El campo debe ser un código válido.',
'decimal' => 'El campo debe ser un número decimal.',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'این فیلد باید یک کد معتبر باشد.',
'decimal' => 'این فیلد باید یک عدد اعشاری باشد.',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'O campo deve ser um código válido.',
'decimal' => 'O campo deve ser um número decimal.',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'Alan geçerli bir kod olmalıdır.',
'decimal' => 'Alan ondalık bir sayı olmalıdır.',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'validations' => [
'code' => 'Trường phải là một mã hợp lệ.',
'decimal' => 'Trường phải là một số thập phân.',
],
];

View File

@@ -0,0 +1,188 @@
<?php
namespace Webkul\Core;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use Webkul\Core\Repositories\CoreConfigRepository;
use Webkul\Core\SystemConfig\Item;
class SystemConfig
{
/**
* Items array.
*/
public array $items = [];
/**
* Create a new class instance.
*
* @return void
*/
public function __construct(protected CoreConfigRepository $coreConfigRepository) {}
/**
* Add Item.
*/
public function addItem(Item $item): void
{
$this->items[] = $item;
}
/**
* Get all configuration items.
*/
public function getItems(): Collection
{
if (! $this->items) {
$this->prepareConfigurationItems();
}
return collect($this->items)
->sortBy('sort');
}
/**
* Retrieve Core Config
*/
private function retrieveCoreConfig(): array
{
static $items;
if ($items) {
return $items;
}
return $items = config('core_config');
}
/**
* Prepare configuration items.
*/
public function prepareConfigurationItems()
{
$configWithDotNotation = [];
foreach ($this->retrieveCoreConfig() as $item) {
$configWithDotNotation[$item['key']] = $item;
}
$configs = Arr::undot(Arr::dot($configWithDotNotation));
foreach ($configs as $configItem) {
$subConfigItems = $this->processSubConfigItems($configItem);
$this->addItem(new Item(
children: $subConfigItems,
fields: $configItem['fields'] ?? null,
icon: $configItem['icon'] ?? null,
key: $configItem['key'],
name: trans($configItem['name']),
route: $configItem['route'] ?? null,
info: trans($configItem['info']) ?? null,
sort: $configItem['sort'],
));
}
}
/**
* Process sub config items.
*/
private function processSubConfigItems($configItem): Collection
{
return collect($configItem)
->sortBy('sort')
->filter(fn ($value) => is_array($value) && isset($value['name']))
->map(function ($subConfigItem) {
$configItemChildren = $this->processSubConfigItems($subConfigItem);
return new Item(
children: $configItemChildren,
fields: $subConfigItem['fields'] ?? null,
icon: $subConfigItem['icon'] ?? null,
key: $subConfigItem['key'],
name: trans($subConfigItem['name']),
info: trans($subConfigItem['info']) ?? null,
route: $subConfigItem['route'] ?? null,
sort: $subConfigItem['sort'] ?? null,
);
});
}
/**
* Get active configuration item.
*/
public function getActiveConfigurationItem(): ?Item
{
if (! $slug = request()->route('slug')) {
return null;
}
$activeItem = $this->getItems()->where('key', $slug)->first() ?? null;
if (! $activeItem) {
return null;
}
if ($slug2 = request()->route('slug2')) {
$activeItem = $activeItem->getChildren()[$slug2];
}
return $activeItem;
}
/**
* Get config field.
*/
public function getConfigField(string $fieldName): ?array
{
foreach ($this->retrieveCoreConfig() as $coreData) {
if (! isset($coreData['fields'])) {
continue;
}
foreach ($coreData['fields'] as $field) {
$name = $coreData['key'].'.'.$field['name'];
if ($name == $fieldName) {
return $field;
}
}
}
return null;
}
/**
* Get default config.
*/
private function getDefaultConfig(string $field): mixed
{
$configFieldInfo = $this->getConfigField($field);
$fields = explode('.', $field);
array_shift($fields);
$field = implode('.', $fields);
return Config::get($field, $configFieldInfo['default'] ?? null);
}
/**
* Retrieve information for configuration
*/
public function getConfigData(string $field): mixed
{
$coreConfigValue = $this->coreConfigRepository->findOneWhere([
'code' => $field,
]);
if (! $coreConfigValue) {
return $this->getDefaultConfig($field);
}
return $coreConfigValue->value;
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace Webkul\Core\SystemConfig;
use Illuminate\Support\Collection;
class Item
{
/**
* Create a new Item instance.
*/
public function __construct(
public Collection $children,
public ?array $fields,
public ?string $icon,
public ?string $info,
public string $key,
public string $name,
public ?string $route = null,
public ?int $sort = null
) {}
/**
* Get name of config item.
*/
public function getName(): string
{
return $this->name ?? '';
}
/**
* Format options.
*/
private function formatOptions($options)
{
return is_array($options) ? $options : (is_string($options) ? $options : []);
}
/**
* Get fields of config item.
*/
public function getFields(): Collection
{
return collect($this->fields)->map(function ($field) {
return new ItemField(
item_key: $this->key,
name: $field['name'],
title: $field['title'],
info: $field['info'] ?? null,
type: $field['type'],
depends: $field['depends'] ?? null,
path: $field['path'] ?? null,
validation: $field['validation'] ?? null,
default: $field['default'] ?? null,
channel_based: $field['channel_based'] ?? null,
locale_based: $field['locale_based'] ?? null,
options: $this->formatOptions($field['options'] ?? null),
tinymce: $field['tinymce'] ?? false,
is_visible: true,
);
});
}
/**
* Get name of config item.
*/
public function getInfo(): ?string
{
return $this->info;
}
/**
* Get current route.
*/
public function getRoute(): string
{
return $this->route;
}
/**
* Get the url of the config item.
*/
public function getUrl(): string
{
return route($this->getRoute());
}
/**
* Get the key of the config item.
*/
public function getKey(): string
{
return $this->key;
}
/**
* Get Icon.
*/
public function getIcon(): ?string
{
return $this->icon;
}
/**
* Check weather config item have children or not.
*/
public function haveChildren(): bool
{
return $this->children->isNotEmpty();
}
/**
* Get children of config item.
*/
public function getChildren(): Collection
{
if (! $this->haveChildren()) {
return collect();
}
return $this->children;
}
}

View File

@@ -0,0 +1,244 @@
<?php
namespace Webkul\Core\SystemConfig;
use Illuminate\Support\Str;
class ItemField
{
/**
* Laravel to Vee Validation mappings.
*
* @var array
*/
protected $veeValidateMappings = [
'min'=> 'min_value',
];
/**
* Create a new ItemField instance.
*/
public function __construct(
public string $item_key,
public string $name,
public string $title,
public ?string $info,
public string $type,
public ?string $path,
public ?string $validation,
public ?string $depends,
public ?string $default,
public ?bool $channel_based,
public ?bool $locale_based,
public array|string $options,
public bool $is_visible = true,
public bool $tinymce = false,
) {
$this->options = $this->getOptions();
}
/**
* Get name of config item.
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Get info of config item.
*/
public function getInfo(): ?string
{
return $this->info ?? '';
}
/**
* Get title of config item.
*/
public function getTitle(): ?string
{
return $this->title ?? '';
}
/**
* Determine if the field should use TinyMCE.
*/
public function getTinymce(): bool
{
return $this->tinymce;
}
/**
* Get type of config item.
*/
public function getType(): string
{
return $this->type;
}
/**
* Get path of config item.
*/
public function getPath(): ?string
{
return $this->path;
}
/**
* Get item key of config item.
*/
public function getItemKey(): string
{
return $this->item_key;
}
/**
* Get validation of config item.
*/
public function getValidations(): ?string
{
if (empty($this->validation)) {
return '';
}
foreach ($this->veeValidateMappings as $laravelRule => $veeValidateRule) {
$this->validation = str_replace($laravelRule, $veeValidateRule, $this->validation);
}
return $this->validation;
}
/**
* Get depends of config item.
*/
public function getDepends(): ?string
{
return $this->depends;
}
/**
* Get default value of config item.
*/
public function getDefault(): ?string
{
return $this->default;
}
/**
* Get channel based of config item.
*/
public function getChannelBased(): ?bool
{
return $this->channel_based;
}
/**
* Get locale based of config item.
*/
public function getLocaleBased(): ?bool
{
return $this->locale_based;
}
/**
* Get name field for forms in configuration page.
*/
public function getNameKey(): string
{
return $this->item_key.'.'.$this->name;
}
/**
* Check if the field is required.
*/
public function isRequired(): string
{
return Str::contains($this->getValidations(), 'required') ? 'required' : '';
}
/**
* Get options of config item.
*/
public function getOptions(): array
{
if (is_array($this->options)) {
return collect($this->options)->map(fn ($option) => [
'title' => trans($option['title']),
'value' => $option['value'],
])->toArray();
}
return collect($this->getFieldOptions($this->options))->map(fn ($option) => [
'title' => trans($option['title']),
'value' => $option['value'],
])->toArray();
}
/**
* Convert the field to an array.
*/
public function toArray()
{
return [
'name' => $this->getName(),
'title' => $this->getTitle(),
'info' => $this->getInfo(),
'type' => $this->getType(),
'path' => $this->getPath(),
'depends' => $this->getDepends(),
'validation' => $this->getValidations(),
'default' => $this->getDefault(),
'channel_based' => $this->getChannelBased(),
'locale_based' => $this->getLocaleBased(),
'options' => $this->getOptions(),
'item_key' => $this->getItemKey(),
'tinymce' => $this->getTinymce(),
];
}
/**
* Get name field for forms in configuration page.
*
* @param string $key
* @return string
*/
public function getNameField($key = null)
{
if (! $key) {
$key = $this->item_key.'.'.$this->name;
}
$nameField = '';
foreach (explode('.', $key) as $key => $field) {
$nameField .= $key === 0 ? $field : '['.$field.']';
}
return $nameField;
}
/**
* Get depend the field name.
*/
public function getDependFieldName(): string
{
if (empty($depends = $this->getDepends())) {
return '';
}
$dependNameKey = $this->getItemKey().'.'.collect(explode(':', $depends))->first();
return $this->getNameField($dependNameKey);
}
/**
* Returns the select options for the field.
*/
protected function getFieldOptions(string $options): array
{
[$class, $method] = Str::parseCallback($options);
return app($class)->$method();
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Webkul\Core\Traits;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Str;
use Mpdf\Mpdf;
trait PDFHandler
{
/**
* Download PDF.
*
* @return \Illuminate\Http\Response
*/
protected function downloadPDF(string $html, ?string $fileName = null)
{
if (is_null($fileName)) {
$fileName = Str::random(32);
}
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
if (in_array($direction = app()->getLocale(), ['ar', 'he'])) {
$mPDF = new Mpdf([
'margin_left' => 0,
'margin_right' => 0,
'margin_top' => 0,
'margin_bottom'=> 0,
]);
$mPDF->SetDirectionality($direction);
$mPDF->SetDisplayMode('fullpage');
$mPDF->WriteHTML($this->adjustArabicAndPersianContent($html));
return response()->streamDownload(fn () => print ($mPDF->Output('', 'S')), $fileName.'.pdf');
}
return PDF::loadHTML($this->adjustArabicAndPersianContent($html))
->setPaper('A4', 'portrait')
->set_option('defaultFont', 'Courier')
->download($fileName.'.pdf');
}
/**
* Adjust arabic and persian content.
*
* @return string
*/
protected function adjustArabicAndPersianContent(string $html)
{
$arabic = new \ArPHP\I18N\Arabic;
$p = $arabic->arIdentify($html);
for ($i = count($p) - 1; $i >= 0; $i -= 2) {
$utf8ar = $arabic->utf8Glyphs(substr($html, $p[$i - 1], $p[$i] - $p[$i - 1]));
$html = substr_replace($html, $utf8ar, $p[$i - 1], $p[$i] - $p[$i - 1]);
}
return $html;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Webkul\Core\Traits;
use enshrined\svgSanitize\data\AllowedAttributes;
use enshrined\svgSanitize\data\AllowedTags;
use enshrined\svgSanitize\Sanitizer as MainSanitizer;
use Exception;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
/**
* Trait for sanitizing SVG uploads to prevent security vulnerabilities.
*/
trait Sanitizer
{
/**
* Sanitize an SVG file to remove potentially malicious content.
*/
public function sanitizeSvg(string $path, UploadedFile $file): void
{
if (! $this->isSvgFile($file)) {
return;
}
try {
$svgContent = Storage::get($path);
if (! $svgContent) {
return;
}
$sanitizer = new MainSanitizer;
$sanitizer->setAllowedAttrs(new AllowedAttributes);
$sanitizer->setAllowedTags(new AllowedTags);
$sanitizer->minify(true);
$sanitizer->removeRemoteReferences(true);
$sanitizer->removeXMLTag(true);
$sanitizer->setXMLOptions(LIBXML_NONET | LIBXML_NOBLANKS);
$sanitizedContent = $sanitizer->sanitize($svgContent);
if ($sanitizedContent === false) {
$patterns = [
'/<script\b[^>]*>(.*?)<\/script>/is',
'/\bon\w+\s*=\s*["\'][^"\']*["\']/i',
'/javascript\s*:/i',
'/data\s*:[^,]*base64/i',
];
$sanitizedContent = $svgContent;
foreach ($patterns as $pattern) {
$sanitizedContent = preg_replace($pattern, '', $sanitizedContent);
}
Storage::put($path, $sanitizedContent);
return;
}
$sanitizedContent = preg_replace('/(<script.*?>.*?<\/script>)|(\son\w+\s*=\s*["\'][^"\']*["\'])/is', '', $sanitizedContent);
Storage::put($path, $sanitizedContent);
} catch (Exception $e) {
report($e->getMessage());
Storage::delete($path);
}
}
/**
* Check if the uploaded file is an SVG based on both extension and mime type.
*/
public function isSvgFile(UploadedFile $file): bool
{
return str_contains(strtolower($file->getClientOriginalExtension()), 'svg');
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Webkul\Core;
use Illuminate\Support\Facades\Event;
class ViewRenderEventManager
{
/**
* Contains all templates
*
* @var array
*/
protected $templates = [];
/**
* Parameters passed with event
*
* @var array
*/
protected $params;
/**
* Fires event for rendering template
*
* @param string $eventName
* @param array|null $params
* @return string
*/
public function handleRenderEvent($eventName, $params = null)
{
$this->params = $params ?? [];
Event::dispatch($eventName, $this);
return $this->templates;
}
/**
* get params
*
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* get param
*
* @return mixed
*/
public function getParam($name)
{
return optional($this->params)[$name];
}
/**
* Add templates for render
*
* @param string $template
* @return void
*/
public function addTemplate($template)
{
array_push($this->templates, $template);
}
/**
* Renders templates
*
* @return string
*/
public function render()
{
$string = '';
foreach ($this->templates as $template) {
if (view()->exists($template)) {
$string .= view($template, $this->params)->render();
} elseif (is_string($template)) {
$string .= $template;
}
}
return $string;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Webkul\Core;
use Illuminate\Support\Facades\Vite as BaseVite;
use Webkul\Core\Exceptions\ViterNotFound;
class Vite
{
/**
* Return the asset URL.
*
* @return string
*/
public function asset(string $filename, string $namespace = 'admin')
{
$viters = config('krayin-vite.viters');
if (empty($viters[$namespace])) {
throw new ViterNotFound($namespace);
}
$url = trim($filename, '/');
$viteUrl = trim($viters[$namespace]['package_assets_directory'], '/').'/'.$url;
return BaseVite::useHotFile($viters[$namespace]['hot_file'])
->useBuildDirectory($viters[$namespace]['build_directory'])
->asset($viteUrl);
}
/**
* Set krayin vite.
*
* @return mixed
*/
public function set(mixed $entryPoints, string $namespace = 'admin')
{
$viters = config('krayin-vite.viters');
if (empty($viters[$namespace])) {
throw new ViterNotFound($namespace);
}
return BaseVite::useHotFile($viters[$namespace]['hot_file'])
->useBuildDirectory($viters[$namespace]['build_directory'])
->withEntryPoints($entryPoints);
}
}