diff --git a/app/Core/Admin/Http/Controllers/Views/IndexView.php b/app/Core/Admin/Http/Controllers/Views/IndexView.php index dc776c8..9682e20 100644 --- a/app/Core/Admin/Http/Controllers/Views/IndexView.php +++ b/app/Core/Admin/Http/Controllers/Views/IndexView.php @@ -80,7 +80,7 @@ protected function buildLineChart() protected function buildWeekArea() { $startDate = new \DateTime('monday this week'); - $endDate = new \DateTime('friday this week'); + $endDate = new \DateTime('sunday this week'); // Запрос к базе данных $payments = rep(PaymentInvoice::class)->select() @@ -113,7 +113,7 @@ protected function buildWeekArea() protected function buildRegistrationsArea() { $startDate = new \DateTime('monday this week'); - $endDate = (new \DateTime('friday this week'))->setTime(23, 59, 59); + $endDate = (new \DateTime('sunday this week'))->setTime(23, 59, 59); // Запрос к базе данных $userRegistrations = rep(User::class)->select() diff --git a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php index 0e74d0b..e0e43b5 100644 --- a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php +++ b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php @@ -198,6 +198,7 @@ protected function getAllDrivers() $gatewayClassShortName = substr($filename, 0, -4); $gatewayClass = $namespace . $gatewayClassShortName; + if (payments()->gatewayExists($gatewayClass) && !Strings::startsWith($gatewayClassShortName, 'Abstract')) { $gatewayInstance = new $gatewayClass(); diff --git a/app/Core/Admin/Http/Views/assets/js/pages/payments/add.js b/app/Core/Admin/Http/Views/assets/js/pages/payments/add.js index 9eaf35f..2b6ffc6 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/payments/add.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/payments/add.js @@ -45,7 +45,7 @@ $(function() { $(document).on('change', '#adapter', function () { var paymentSystem = $(this).val(); var handleUrl = u(`api/lk/handle/${paymentSystem}`); // Example URL format - $('#handleUrl').val(handleUrl); + $('#handleUrl').val(handleUrl).attr('data-copy', handleUrl); updateParameters(); }); diff --git a/app/Core/Admin/Http/Views/assets/js/pages/theme_edit.js b/app/Core/Admin/Http/Views/assets/js/pages/theme_edit.js index b1e33de..ba0d892 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/theme_edit.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/theme_edit.js @@ -1,9 +1,10 @@ +var pickerInstances = {}; + function updateValue($element, id, value) { let unit = id === 'transition' ? 's' : 'px'; $element.find('#' + id + '-value').text(value + unit); $('#theme-editor').css('--' + id, value + unit); - // Обновление --range у родительского элемента с классом range-control let percentage = ((value - parseFloat($element.find('#' + id).attr('min'))) / (parseFloat($element.find('#' + id).attr('max')) - @@ -25,7 +26,6 @@ function parseColors() { if (!editor) return; const editorId = editor.getAttribute('id'); - const pickrInstances = {}; // Initialize Pickr for each color input editor.querySelectorAll('.color-picker').forEach(function (pickerElement) { @@ -73,7 +73,7 @@ function parseColors() { }, }); - pickrInstances[colorVariable] = pickr; + pickerInstances[colorVariable] = pickr; pickr.on('change', (color, source, instance) => { const rgbaColor = color.toRGBA().toString(); @@ -84,8 +84,6 @@ function parseColors() { pickerElement.value = rgbaColor; }); }); - - window.pickrInstances = pickrInstances; } function addUnitsToProperties(settings) { @@ -125,6 +123,7 @@ $(function () { let $this = $(this).find('input[type="range"]'); initRange($(this), $this.attr('id')); }); + parseColors(); }); @@ -151,7 +150,7 @@ $(function () { }); // Get values from Pickr instances - for (const [key, pickr] of Object.entries(window.pickrInstances)) { + for (const [key, pickr] of Object.entries(pickerInstances)) { const color = pickr.getColor().toRGBA().toString(); themeSettings[key] = color; } diff --git a/app/Core/Admin/Http/Views/pages/payments/add.blade.php b/app/Core/Admin/Http/Views/pages/payments/add.blade.php index 8dda85f..a52ff30 100644 --- a/app/Core/Admin/Http/Views/pages/payments/add.blade.php +++ b/app/Core/Admin/Http/Views/pages/payments/add.blade.php @@ -65,7 +65,7 @@
-
+
@@ -83,7 +83,7 @@
-
+
@@ -92,7 +92,7 @@
-
+
diff --git a/app/Core/App.php b/app/Core/App.php index 8311b3b..cd6381c 100644 --- a/app/Core/App.php +++ b/app/Core/App.php @@ -47,7 +47,7 @@ final class App * * @var string */ - public const VERSION = '0.2.0-alpha'; + public const VERSION = '0.2.1-alpha'; /** * Set the base path of the application diff --git a/app/Core/Cache/AbstractCacheDriver.php b/app/Core/Cache/AbstractCacheDriver.php index 4f3418e..367e5bd 100644 --- a/app/Core/Cache/AbstractCacheDriver.php +++ b/app/Core/Cache/AbstractCacheDriver.php @@ -73,17 +73,17 @@ public function set(string $key, $value, $ttl = null): bool { $item = $this->cache->getItem($key); - // Если кэша с таким ключом нет, устанавливаем новое значение - if (!$item->isHit()) { - $item->set($value); - $item->expiresAfter($ttl); - return $this->cache->save($item); + $item->set($value); + $item->expiresAfter($ttl); + $result = $this->cache->save($item); + + if (!$result) { + logs()->error("ERROR SAVE CACHE - $key"); } - return false; + return $result; } - /** * Удаляет значение из кэша по ключу * diff --git a/app/Core/Database/Entities/User.php b/app/Core/Database/Entities/User.php index a4175f8..7bb2884 100644 --- a/app/Core/Database/Entities/User.php +++ b/app/Core/Database/Entities/User.php @@ -89,7 +89,7 @@ class User public $created_at; /** - * @Column(type="timestamp", default="CURRENT_TIMESTAMP") + * @Column(type="timestamp", nullable=true) */ public $last_logged; diff --git a/app/Core/Http/Controllers/Profile/ProfileRedirectController.php b/app/Core/Http/Controllers/Profile/ProfileRedirectController.php index 961e885..bf093bd 100644 --- a/app/Core/Http/Controllers/Profile/ProfileRedirectController.php +++ b/app/Core/Http/Controllers/Profile/ProfileRedirectController.php @@ -12,9 +12,9 @@ public function search(FluteRequest $request, $value) { $redirectUrl = $request->input('else-redirect', null); - $user = rep(UserSocialNetwork::class)->findOne([ + $user = rep(UserSocialNetwork::class)->select()->where([ 'value' => $value - ]); + ])->load('user')->fetchOne(); if (!empty($redirectUrl) && empty($user)) return redirect($redirectUrl); diff --git a/app/Core/Services/EventNotifications.php b/app/Core/Services/EventNotifications.php index 31b5b98..7e88e4c 100644 --- a/app/Core/Services/EventNotifications.php +++ b/app/Core/Services/EventNotifications.php @@ -3,7 +3,6 @@ namespace Flute\Core\Services; use Flute\Core\Database\Entities\EventNotification; -use Flute\Core\Database\Entities\Notification; class EventNotifications { @@ -15,38 +14,99 @@ public function listen() $events = rep(EventNotification::class)->findAll(); foreach ($events as $event) { - events()->addDeferredListener($event->event, function ($eventInstance) use ($event) { - $notification = new Notification; - $notification->content = $this->replaceContent($event->content, $eventInstance); - $notification->url = $event->url; - $notification->user = user()->getCurrentUser(); - $notification->icon = $event->icon; - $notification->title = $event->title; - - notification()->create($notification); - }); + events()->addDeferredListener($event->event, [$this, 'handleEvent']); } } - private function replaceContent(string $content, $event) + public static function handleEvent($eventInstance) { - $content = $this->replaceUserContent($content); - - return preg_replace_callback('/\{(.*?)\}/', function ($matches) use ($event) { - $parts = explode('.', $matches[1]); - if (count($parts) == 2 && method_exists($event, $parts[0])) { - return $event->{$parts[0]}()->{$parts[1]}; - } elseif (count($parts) == 1 && method_exists($event, $parts[0])) { - return $event->{$parts[0]}(); - } elseif (property_exists($event, $matches[1])) { - return $event->{$matches[1]}; - } else { - return $matches[0]; + if ($eventInstance::NAME) { + $events = rep(EventNotification::class)->select()->where('event', $eventInstance::NAME)->fetchAll(); + + foreach ($events as $event) { + $table = db()->table('notifications'); + + $table->insertOne([ + 'content' => self::replaceContent($event->content, $eventInstance), + 'icon' => $event->icon, + 'url' => $event->url, + 'title' => $event->title, + 'user_id' => user()->getCurrentUser()->id, + 'created_at' => new \DateTime() + ]); } + } + } + + private static function replaceContent(string $content, $eventInstance): string + { + $content = self::replaceUserContent($content); + + return preg_replace_callback('/\{(.*?)\}/', function ($matches) use ($eventInstance) { + return self::evaluateExpression($matches[1], $eventInstance); }, $content); } - private function replaceUserContent(string $content) + private static function evaluateExpression($expression, $eventInstance) + { + if (preg_match('/^(\w+)\((.*?)\)$/', $expression, $matches)) { + $func = $matches[1]; + $args = self::parseArguments($matches[2]); + if (function_exists($func)) { + $result = call_user_func_array($func, $args); + return is_object($result) ? self::getNestedProperty($result, array_slice(explode('.', $expression), 1)) : $result; + } + } + + $parts = explode('.', $expression); + return self::getNestedProperty($eventInstance, $parts); + } + + private static function getNestedProperty($object, array $parts) + { + $current = $object; + + foreach ($parts as $part) { + if (preg_match('/(\w+)\((.*?)\)$/', $part, $matches)) { + $func = $matches[1]; + $args = self::parseArguments($matches[2]); + if (is_object($current) && method_exists($current, $func)) { + $current = call_user_func_array([$current, $func], $args); + } elseif (function_exists($func)) { + $current = call_user_func_array($func, $args); + } else { + return '{' . implode('.', $parts) . '}'; + } + } elseif (is_object($current)) { + if (method_exists($current, $part)) { + $current = $current->{$part}(); + } elseif (property_exists($current, $part)) { + $current = $current->{$part}; + } else { + return '{' . implode('.', $parts) . '}'; + } + } else { + return '{' . implode('.', $parts) . '}'; + } + } + + return $current; + } + + private static function parseArguments($argsString) + { + $args = []; + if (!empty($argsString)) { + $parts = explode(',', $argsString); + foreach ($parts as $part) { + $part = trim($part, " \t\n\r\0\x0B'\""); + $args[] = $part; + } + } + return $args; + } + + private static function replaceUserContent(string $content) { return str_replace(['{name}', '{login}', '{email}', '{balance}'], [ user()->getCurrentUser()->name, diff --git a/app/Core/Support/FluteEventDispatcher.php b/app/Core/Support/FluteEventDispatcher.php index acbbb8e..89205cd 100644 --- a/app/Core/Support/FluteEventDispatcher.php +++ b/app/Core/Support/FluteEventDispatcher.php @@ -1,8 +1,8 @@ getListenerId($listener); + // if ($listener instanceof \Closure) { + // $listener = new SerializableClosure($listener); + // } + if (!isset($this->deferredListeners[$eventName])) { $this->deferredListeners[$eventName] = []; } + if (isset($this->deferredListeners[$eventName][$listenerId])) + return; + $this->deferredListeners[$eventName][$listenerId] = ['listener' => $listener, 'priority' => $priority]; - if (is_callable($listener)) + if (is_callable($listener)) { $this->addListener($eventName, $listener, $priority); + $this->saveDeferredListenersToCache(); + } } public function removeDeferredListener($eventName, $listener) @@ -41,7 +50,7 @@ public function removeDeferredListener($eventName, $listener) unset($this->deferredListeners[$eventName]); } - cache()->set($this->deferredListenersKey, $this->deferredListeners); + $this->saveDeferredListenersToCache(); } $this->removeListener($eventName, $listener); @@ -56,13 +65,20 @@ private function initializeDeferredListeners() { $deferredListeners = cache()->get($this->deferredListenersKey, []); - if (!is_array($deferredListeners)) + if (!is_array($deferredListeners)) { $deferredListeners = []; + } foreach ($deferredListeners as $eventName => $listeners) { foreach ($listeners as $listenerData) { - if (is_callable($listenerData['listener'])) - $this->addListener($eventName, $listenerData['listener'], $listenerData['priority']); + $listener = $listenerData['listener']; + if ($listener instanceof SerializableClosure) { + $listener = $listener->getClosure(); + } + + if (is_callable($listener)) { + $this->addListener($eventName, $listener, $listenerData['priority']); + } } } @@ -71,14 +87,34 @@ private function initializeDeferredListeners() private function getListenerId($listener) { - if (is_object($listener)) { - return spl_object_hash($listener); + if ($listener instanceof \Closure) { + return $this->getClosureId($listener); + } + + if ($listener instanceof SerializableClosure) { + return $this->getClosureId($listener->getClosure()); } if (is_array($listener)) { + if (is_object($listener[0])) { + return spl_object_hash($listener[0]) . '::' . $listener[1]; + } + return $listener[0] . '::' . $listener[1]; } + if (is_object($listener)) { + return $this->getClosureId($listener); + } + return $listener; } + + private function getClosureId($closure) + { + $reflection = new ReflectionClosure($closure); + $code = $reflection->getCode(); + + return md5($code); + } } diff --git a/bootstrap/app.php b/bootstrap/app.php index b9db75f..5a8f6fc 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -2,6 +2,14 @@ declare(strict_types=1); +if (version_compare(PHP_VERSION, '7.4.0', '<')) { + exit('Flute requires PHP version 7.4 or higher.'); +} + +if (!file_exists(BASE_PATH . 'vendor/autoload.php')) { + exit('Folder "vendor" wasn\'t found. Please, check your files again'); +} + use Flute\Core\App; use Flute\Core\ServiceProviders\AdminServiceProvider; use Flute\Core\ServiceProviders\AuthServiceProvider; @@ -41,10 +49,6 @@ use Flute\Core\ServiceProviders\WidgetServiceProvider; use Flute\Core\ServiceProviders\EmailServiceProvider; -if (!file_exists(BASE_PATH . 'vendor/autoload.php')) { - exit('Folder "vendor" wasn\'t found. Please, check your files again'); -} - /** * Include the composer autoloader */