319 lines
8.9 KiB
PHP
319 lines
8.9 KiB
PHP
<?php
|
|
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
|
use Leantime\Core\Language;
|
|
use Leantime\Core\Support\Build;
|
|
use Leantime\Core\Support\Cast;
|
|
use Leantime\Core\Support\DateTimeHelper;
|
|
use Leantime\Core\Support\Format;
|
|
use Leantime\Core\Support\FromFormat;
|
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
|
|
|
if (! function_exists('__')) {
|
|
/**
|
|
* Translate a string.
|
|
*
|
|
* @throws BindingResolutionException
|
|
*/
|
|
function __(string $index, string $default = ''): string
|
|
{
|
|
return app()->make(Language::class)->__(index: $index, default: $default);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('array_sort')) {
|
|
/**
|
|
* sort array of arrqays by value
|
|
*
|
|
* @param string|array<int, array{0: string, 1: string}> $sortyBy Column name (string) or multi-sort spec (array)
|
|
*/
|
|
function array_sort(array $array, mixed $sortyBy): array
|
|
{
|
|
|
|
if (is_string($sortyBy)) {
|
|
$collection = collect($array);
|
|
|
|
$sorted = $collection->sortBy($sortyBy, SORT_NATURAL);
|
|
|
|
return $sorted->values()->all();
|
|
} else {
|
|
return \Illuminate\Support\Collection::make($array)->sortBy($sortyBy)->all();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! function_exists('do_once')) {
|
|
/**
|
|
* Execute a callback only once.
|
|
*/
|
|
function do_once(string $key, Closure $callback, bool $across_requests = false): void
|
|
{
|
|
$key = "do_once_{$key}";
|
|
|
|
if ($across_requests) {
|
|
if (session()->exists('do_once') === false) {
|
|
session(['do_once' => []]);
|
|
}
|
|
|
|
if (session('do_once.'.$key) ?? false) {
|
|
return;
|
|
}
|
|
|
|
session(['do_once.'.$key => true]);
|
|
} else {
|
|
static $do_once;
|
|
$do_once ??= [];
|
|
|
|
if ($do_once[$key] ?? false) {
|
|
return;
|
|
}
|
|
|
|
$do_once[$key] = true;
|
|
}
|
|
|
|
$callback();
|
|
}
|
|
}
|
|
|
|
if (! function_exists('build')) {
|
|
/**
|
|
* Turns any object into a builder object
|
|
*
|
|
**/
|
|
function build(object $object): Build
|
|
{
|
|
return new Build($object);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('format')) {
|
|
/**
|
|
* Returns a format object to format string values
|
|
*
|
|
* @param string|int|float|DateTime|Carbon|null $value
|
|
* @param string|int|float|DateTime|null $value2
|
|
*/
|
|
function format(string|int|float|null|\DateTime|\Carbon\CarbonInterface $value, string|int|float|null|\DateTime|\Carbon\CarbonInterface $value2 = null, ?FromFormat $fromFormat = FromFormat::DbDate): Format
|
|
{
|
|
return new Format($value, $value2, $fromFormat);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('cast')) {
|
|
/**
|
|
* Casts a variable to a different type if possible.
|
|
*
|
|
* @param mixed $source The object to be cast.
|
|
* @param string $classOrType The class to which the object should be cast.
|
|
* @param array $constructParams Optional parameters to pass to the constructor.
|
|
* @param array $mappings Make sure certain sub properties are casted to specific types.
|
|
* @return mixed The casted object, or throws an exception on failure.
|
|
*
|
|
* @throws \InvalidArgumentException If the class does not exist.
|
|
* @throws \RuntimeException|ReflectionException On serialization errors.
|
|
*/
|
|
function cast(mixed $source, string $classOrType, array $constructParams = [], array $mappings = []): mixed
|
|
{
|
|
if (in_array($classOrType, ['int', 'integer', 'float', 'string', 'str', 'bool', 'boolean', 'object', 'stdClass', 'array'])) {
|
|
return Cast::castSimple($source, $classOrType);
|
|
}
|
|
|
|
if (enum_exists($classOrType)) {
|
|
return Cast::castEnum($source, $classOrType);
|
|
}
|
|
|
|
// Convert string to date if required.
|
|
if (is_string($source) && is_a($classOrType, CarbonInterface::class, true)) {
|
|
return Cast::castDateTime($source);
|
|
}
|
|
|
|
return (new Cast($source))->castTo($classOrType, $constructParams, $mappings);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('dtHelper')) {
|
|
/**
|
|
* Get a singleton instance of the DateTimeHelper class.
|
|
*
|
|
* @throws BindingResolutionException
|
|
*/
|
|
function dtHelper(): DateTimeHelper
|
|
{
|
|
if (! app()->bound(DateTimeHelper::class)) {
|
|
app()->singleton(DateTimeHelper::class);
|
|
}
|
|
|
|
return app()->make(DateTimeHelper::class);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('redirect')) {
|
|
/**
|
|
* Get an instance of the redirector.
|
|
*
|
|
* @param string|null $url
|
|
* @param int $http_response_code
|
|
* @param array $headers
|
|
* @param bool|null $secure
|
|
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
|
*/
|
|
function redirect($url = null, $http_response_code = 302, $headers = [], $secure = null)
|
|
{
|
|
return new RedirectResponse(
|
|
trim(preg_replace('/\s\s+/', '', strip_tags($url))),
|
|
$http_response_code
|
|
);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('currentRoute')) {
|
|
/**
|
|
* Get an instance of the redirector.
|
|
*/
|
|
function currentRoute()
|
|
{
|
|
|
|
return app('request')->getCurrentRoute();
|
|
|
|
}
|
|
}
|
|
|
|
if (! function_exists('get_domain_key')) {
|
|
|
|
/**
|
|
* Gets a unique instance key determined by domain
|
|
*/
|
|
function get_domain_key()
|
|
{
|
|
|
|
// Now that we know where the instance is bing called from
|
|
// Let's add a domain level cache.
|
|
|
|
$host = app('request')->host();
|
|
|
|
$url = config('app.url');
|
|
if (is_string($url) && $url !== '') {
|
|
$host = $url;
|
|
}
|
|
|
|
$domainKeyParts = config('app.url').config('app.key');
|
|
$slug = \Illuminate\Support\Str::slug($domainKeyParts);
|
|
$domainCacheName = md5($slug);
|
|
|
|
return $domainCacheName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (! function_exists('mix')) {
|
|
/**
|
|
* Get the path to a versioned Mix file. Customized for Leantime.
|
|
*
|
|
*
|
|
* @throws BindingResolutionException
|
|
*/
|
|
function mix(string $path = '', string $manifestDirectory = ''): \Leantime\Core\Support\Mix|string
|
|
{
|
|
if (! ($app = app())->bound(\Leantime\Core\Support\Mix::class)) {
|
|
$app->instance(\Leantime\Core\Support\Mix::class, new \Leantime\Core\Support\Mix);
|
|
}
|
|
|
|
$mix = $app->make(\Leantime\Core\Support\Mix::class);
|
|
|
|
if (empty($path)) {
|
|
return $mix;
|
|
}
|
|
|
|
return $mix($path, $manifestDirectory);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('base_path')) {
|
|
/**
|
|
* Get the path to the base of the install.
|
|
*
|
|
* @param string $path
|
|
* @return string
|
|
*/
|
|
function base_path($path = '')
|
|
{
|
|
return app()->basePath($path);
|
|
}
|
|
}
|
|
|
|
if (! function_exists('safe_unserialize')) {
|
|
/**
|
|
* Safely unserialize data without allowing object instantiation.
|
|
*
|
|
* Tries json_decode first (forward-compatible with future JSON storage).
|
|
* Falls back to unserialize with allowed_classes=false to prevent
|
|
* PHP object instantiation, which mitigates deserialization RCE attacks.
|
|
*
|
|
* @param string|null $data The serialized/encoded data
|
|
* @param mixed $default Default value if data is empty or unparseable
|
|
* @return mixed The decoded data
|
|
*/
|
|
function safe_unserialize(?string $data, mixed $default = []): mixed
|
|
{
|
|
if ($data === null || $data === '') {
|
|
return $default;
|
|
}
|
|
|
|
// Try JSON first (forward-compatible)
|
|
$jsonResult = json_decode($data, true);
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
return $jsonResult;
|
|
}
|
|
|
|
// Fall back to PHP unserialize with no class instantiation
|
|
$result = @unserialize($data, ['allowed_classes' => false]);
|
|
if ($result !== false) {
|
|
return $result;
|
|
}
|
|
|
|
// Handle the edge case where the serialized value is literally boolean false
|
|
if ($data === 'b:0;') {
|
|
return false;
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
}
|
|
|
|
if (! function_exists('get_release_version')) {
|
|
|
|
/**
|
|
* Gets a unique instance key determined by domain
|
|
*/
|
|
function get_release_version()
|
|
{
|
|
|
|
$appSettings = app()->make(\Leantime\Core\Configuration\AppSettings::class);
|
|
|
|
return $appSettings->appVersion;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (! function_exists('can')) {
|
|
|
|
/**
|
|
* Whether the current user holds a `domain.action` permission, optionally scoped to a
|
|
* project. The permission-era replacement for `$login::userIsAtLeast(...)` in templates.
|
|
*
|
|
* In Blade prefer the @can directive (it routes through the same Gate bridge); use this
|
|
* helper in legacy .tpl.php templates and anywhere a plain boolean is handy:
|
|
* <?php if (can('tickets.edit', $projectId)): ?> ... <?php endif; ?>
|
|
*/
|
|
function can(string $permission, ?int $projectId = null): bool
|
|
{
|
|
return app()->make(\Leantime\Core\Auth\Permissions\PermissionService::class)
|
|
->currentUserCan($permission, $projectId);
|
|
}
|
|
|
|
}
|